Skip to content

Commit

Permalink
feat(#5864): Added configuration for sizeLimit on empty-dirs in mount…
Browse files Browse the repository at this point in the history
… trait (#5865)

* Added configuration for sizeLimit on empty-dirs in mount trait

* refactored emptydir logic to within mount trait

* Adding documetation

* changing method signature

* chore(ci): lint

---------

Co-authored-by: hernan-abi <[email protected]>
Co-authored-by: Pasquale Congiusti <[email protected]>
  • Loading branch information
3 people authored Oct 24, 2024
1 parent 0fdfcaa commit f5e245a
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 34 deletions.
3 changes: 2 additions & 1 deletion pkg/apis/camel/v1/trait/mount.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ type MountTrait struct {
// You can use the syntax [pvcname:/container/path:size:accessMode<:storageClass>] to create a dynamic PVC based on the Storage Class provided
// or the default cluster Storage Class. However, if the PVC exists, the operator would mount it.
Volumes []string `property:"volumes" json:"volumes,omitempty"`
// A list of EmptyDir volumes to be mounted. Syntax: [name:/container/path]
// A list of EmptyDir volumes to be mounted. An optional size limit may be configured (default 500Mi).
// Syntax: name:/container/path[:sizeLimit]
EmptyDirs []string `property:"empty-dirs" json:"emptyDirs,omitempty"`
// Enable "hot reload" when a secret/configmap mounted is edited (default `false`). The configmap/secret must be
// marked with `camel.apache.org/integration` label to be taken in account. The resource will be watched for any kind change, also for
Expand Down
44 changes: 38 additions & 6 deletions pkg/trait/mount.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import (
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
storagev1 "k8s.io/api/storage/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

serving "knative.dev/serving/pkg/apis/serving/v1"
Expand All @@ -40,6 +39,7 @@ import (
"github.com/apache/camel-k/v2/pkg/util/log"
"github.com/apache/camel-k/v2/pkg/util/property"
utilResource "github.com/apache/camel-k/v2/pkg/util/resource"
"k8s.io/apimachinery/pkg/api/resource"
)

const (
Expand Down Expand Up @@ -166,11 +166,12 @@ func (t *mountTrait) configureVolumesAndMounts(e *Environment, vols *[]corev1.Vo
*mnts = append(*mnts, *volumeMount)
}
for _, v := range t.EmptyDirs {
if vol, parseErr := utilResource.ParseEmptyDirVolume(v); parseErr == nil {
t.mountResource(vols, mnts, vol)
} else {
volume, volumeMount, parseErr := ParseEmptyDirVolume(v)
if parseErr != nil {
return parseErr
}
*vols = append(*vols, *volume)
*mnts = append(*mnts, *volumeMount)
}

return nil
Expand Down Expand Up @@ -261,8 +262,7 @@ func (t *mountTrait) mountResource(vols *[]corev1.Volume, mnts *[]corev1.VolumeM
vol := getVolume(refName, string(conf.StorageType()), conf.Name(), conf.Key(), dstFile)
mntPath := getMountPoint(conf.Name(), dstDir, string(conf.StorageType()), string(conf.ContentType()))
readOnly := true
if conf.StorageType() == utilResource.StorageTypePVC ||
conf.StorageType() == utilResource.StorageTypeEmptyDir {
if conf.StorageType() == utilResource.StorageTypePVC {
readOnly = false
}
mnt := getMount(refName, mntPath, dstFile, readOnly)
Expand All @@ -281,6 +281,38 @@ func (t *mountTrait) addServiceBindingSecret(e *Environment) {
})
}

// ParseEmptyDirVolume will parse and return an empty-dir volume.
func ParseEmptyDirVolume(item string) (*corev1.Volume, *corev1.VolumeMount, error) {
volumeParts := strings.Split(item, ":")

if len(volumeParts) != 2 && len(volumeParts) != 3 {
return nil, nil, fmt.Errorf("could not match emptyDir volume as %s", item)
}

refName := kubernetes.SanitizeLabel(volumeParts[0])
sizeLimit := "500Mi"
if len(volumeParts) == 3 {
sizeLimit = volumeParts[2]
}

parsed, err := resource.ParseQuantity(sizeLimit)
if err != nil {
return nil, nil, fmt.Errorf("could not parse sizeLimit from emptyDir volume: %s", volumeParts[2])
}

volume := &corev1.Volume{
Name: refName,
VolumeSource: corev1.VolumeSource{
EmptyDir: &corev1.EmptyDirVolumeSource{
SizeLimit: &parsed,
},
},
}

volumeMount := getMount(refName, volumeParts[1], "", false)
return volume, volumeMount, nil
}

// ParseAndCreateVolume will parse a volume configuration. If the volume does not exist it tries to create one based on the storage
// class configuration provided or default.
// item is expected to be as: name:path/to/mount<:size:accessMode<:storageClassName>>.
Expand Down
59 changes: 56 additions & 3 deletions pkg/trait/mount_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,14 +136,67 @@ func TestEmptyDirVolumeIntegrationPhaseDeploying(t *testing.T) {
assert.Len(t, spec.Containers[0].VolumeMounts, 3)
assert.Len(t, spec.Volumes, 3)

var emptyDirVolume *corev1.Volume
for _, v := range spec.Volumes {
if v.Name == "my-empty-dir" {
emptyDirVolume = &v
break
}
}

assert.NotNil(t, emptyDirVolume)
// Default applied by operator
assert.Equal(t, "500Mi", emptyDirVolume.EmptyDir.SizeLimit.String())

assert.Condition(t, func() bool {
for _, v := range spec.Volumes {
if v.Name == "my-empty-dir" {
return true
for _, container := range spec.Containers {
if container.Name == "integration" {
for _, volumeMount := range container.VolumeMounts {
if volumeMount.Name == "my-empty-dir" {
return true
}
}
}
}
return false
})
}

func TestEmptyDirVolumeWithSizeLimitIntegrationPhaseDeploying(t *testing.T) {
traitCatalog := NewCatalog(nil)

environment := getNominalEnv(t, traitCatalog)
environment.Integration.Spec.Traits.Mount = &traitv1.MountTrait{
EmptyDirs: []string{"my-empty-dir:/some/path:450Mi"},
}
environment.Platform.ResyncStatusFullConfig()
conditions, traits, err := traitCatalog.apply(environment)

require.NoError(t, err)
assert.NotEmpty(t, traits)
assert.NotEmpty(t, conditions)
assert.NotEmpty(t, environment.ExecutedTraits)
assert.NotNil(t, environment.GetTrait("mount"))

deployment := environment.Resources.GetDeployment(func(service *appsv1.Deployment) bool {
return service.Name == "hello"
})
assert.NotNil(t, deployment)
spec := deployment.Spec.Template.Spec

assert.Len(t, spec.Containers[0].VolumeMounts, 3)
assert.Len(t, spec.Volumes, 3)

var emptyDirVolume *corev1.Volume
for _, v := range spec.Volumes {
if v.Name == "my-empty-dir" {
emptyDirVolume = &v
break
}
}
assert.NotNil(t, emptyDirVolume)
assert.Equal(t, "450Mi", emptyDirVolume.EmptyDir.SizeLimit.String())

assert.Condition(t, func() bool {
for _, container := range spec.Containers {
if container.Name == "integration" {
Expand Down
9 changes: 0 additions & 9 deletions pkg/trait/trait_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import (
appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"

serving "knative.dev/serving/pkg/apis/serving/v1"

Expand Down Expand Up @@ -440,14 +439,6 @@ func getVolume(volName, storageType, storageName, filterKey, filterValue string)
volume.VolumeSource.PersistentVolumeClaim = &corev1.PersistentVolumeClaimVolumeSource{
ClaimName: storageName,
}
case emptyDirStorageType:
size, err := resource.ParseQuantity("1Gi")
if err != nil {
log.WithValues("Function", "trait.getVolume").Errorf(err, "could not parse empty dir quantity, skipping")
}
volume.VolumeSource.EmptyDir = &corev1.EmptyDirVolumeSource{
SizeLimit: &size,
}
}

return &volume
Expand Down
15 changes: 0 additions & 15 deletions pkg/util/resource/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,21 +136,6 @@ func ParseResource(item string) (*Config, error) {
return parse(item, ContentTypeData)
}

// ParseEmptyDirVolume will parse an empty dir volume and return a Config.
func ParseEmptyDirVolume(item string) (*Config, error) {
configParts := strings.Split(item, ":")

if len(configParts) != 2 {
return nil, fmt.Errorf("could not match emptyDir volume as %s", item)
}

return &Config{
storageType: StorageTypeEmptyDir,
resourceName: configParts[0],
destinationPath: configParts[1],
}, nil
}

// ParseVolume will parse a volume and return a Config.
func ParseVolume(item string) (*Config, error) {
configParts := strings.Split(item, ":")
Expand Down

0 comments on commit f5e245a

Please sign in to comment.