From 3ec5298efc547b03cf0c18a29553eed8f765472c Mon Sep 17 00:00:00 2001 From: Pasquale Congiusti Date: Sat, 12 Oct 2024 09:04:36 +0200 Subject: [PATCH] feat(trait): mount volume from storage class Closes #2994 --- .../ROOT/partials/apis/camel-k-crds.adoc | 4 +- docs/modules/traits/pages/mount.adoc | 15 ++- helm/camel-k/crds/camel-k-crds.yaml | 48 +++++--- pkg/apis/camel/v1/trait/mount.go | 4 +- ...camel.apache.org_integrationplatforms.yaml | 12 +- .../camel.apache.org_integrationprofiles.yaml | 12 +- .../bases/camel.apache.org_integrations.yaml | 12 +- .../camel.apache.org_kameletbindings.yaml | 6 +- .../crd/bases/camel.apache.org_pipes.yaml | 6 +- pkg/trait/mount.go | 95 +++++++++++++++- pkg/trait/mount_test.go | 105 +++++++++++++++++- pkg/util/kubernetes/factory.go | 5 +- pkg/util/kubernetes/lookup.go | 26 ++++- 13 files changed, 305 insertions(+), 45 deletions(-) diff --git a/docs/modules/ROOT/partials/apis/camel-k-crds.adoc b/docs/modules/ROOT/partials/apis/camel-k-crds.adoc index 98771dac8d..cae7017d45 100644 --- a/docs/modules/ROOT/partials/apis/camel-k-crds.adoc +++ b/docs/modules/ROOT/partials/apis/camel-k-crds.adoc @@ -8008,7 +8008,9 @@ Syntax: [configmap{vbar}secret]:name[/key][@path], where name represents the res | -A list of Persistent Volume Claims to be mounted. Syntax: [pvcname:/container/path] +A list of Persistent Volume Claims to be mounted. Syntax: [pvcname:/container/path]. If the PVC is not found, the Integration fails. +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. |`emptyDirs` + []string diff --git a/docs/modules/traits/pages/mount.adoc b/docs/modules/traits/pages/mount.adoc index 64cbadf8ef..0d5d6b4763 100644 --- a/docs/modules/traits/pages/mount.adoc +++ b/docs/modules/traits/pages/mount.adoc @@ -45,7 +45,9 @@ Syntax: [configmap\|secret]:name[/key][@path], where name represents the resourc | mount.volumes | []string -| A list of Persistent Volume Claims to be mounted. Syntax: [pvcname:/container/path] +| A list of Persistent Volume Claims to be mounted. Syntax: [pvcname:/container/path]. If the PVC is not found, the Integration fails. +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. | mount.empty-dirs | []string @@ -64,3 +66,14 @@ changes in metadata. |=== // End of autogenerated code - DO NOT EDIT! (configuration) + +== Dynamic creation of PersistentVolumeClaims + +If your cluster has some StorageClass defined, you can configure `mount.volume` to create one PersistentVolume on your behalf. This is going to be mounted to the application Pod, according to the configuration given. See Kubernetes documentation about StorageClass to learn more. + +[source,console] +$ kamel run test.yaml -t mount.volumes=my-pvc:/tmp/my-pvc:10Mi:ReadOnlyMany + +The above command would create a new volume on the fly if it does not exists using "default" StorageClass. You can specify any StorageClass as configuration. + +NOTE: if the volume exists, then it would reuse it. diff --git a/helm/camel-k/crds/camel-k-crds.yaml b/helm/camel-k/crds/camel-k-crds.yaml index 6c17ef7035..12e238d8aa 100644 --- a/helm/camel-k/crds/camel-k-crds.yaml +++ b/helm/camel-k/crds/camel-k-crds.yaml @@ -4831,8 +4831,10 @@ spec: 2.5.' type: boolean volumes: - description: 'A list of Persistent Volume Claims to be mounted. - Syntax: [pvcname:/container/path]' + description: |- + A list of Persistent Volume Claims to be mounted. Syntax: [pvcname:/container/path]. If the PVC is not found, the Integration fails. + 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. items: type: string type: array @@ -6969,8 +6971,10 @@ spec: 2.5.' type: boolean volumes: - description: 'A list of Persistent Volume Claims to be mounted. - Syntax: [pvcname:/container/path]' + description: |- + A list of Persistent Volume Claims to be mounted. Syntax: [pvcname:/container/path]. If the PVC is not found, the Integration fails. + 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. items: type: string type: array @@ -9010,8 +9014,10 @@ spec: 2.5.' type: boolean volumes: - description: 'A list of Persistent Volume Claims to be mounted. - Syntax: [pvcname:/container/path]' + description: |- + A list of Persistent Volume Claims to be mounted. Syntax: [pvcname:/container/path]. If the PVC is not found, the Integration fails. + 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. items: type: string type: array @@ -11027,8 +11033,10 @@ spec: 2.5.' type: boolean volumes: - description: 'A list of Persistent Volume Claims to be mounted. - Syntax: [pvcname:/container/path]' + description: |- + A list of Persistent Volume Claims to be mounted. Syntax: [pvcname:/container/path]. If the PVC is not found, the Integration fails. + 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. items: type: string type: array @@ -19085,8 +19093,10 @@ spec: 2.5.' type: boolean volumes: - description: 'A list of Persistent Volume Claims to be mounted. - Syntax: [pvcname:/container/path]' + description: |- + A list of Persistent Volume Claims to be mounted. Syntax: [pvcname:/container/path]. If the PVC is not found, the Integration fails. + 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. items: type: string type: array @@ -21064,8 +21074,10 @@ spec: 2.5.' type: boolean volumes: - description: 'A list of Persistent Volume Claims to be mounted. - Syntax: [pvcname:/container/path]' + description: |- + A list of Persistent Volume Claims to be mounted. Syntax: [pvcname:/container/path]. If the PVC is not found, the Integration fails. + 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. items: type: string type: array @@ -29197,8 +29209,10 @@ spec: 2.5.' type: boolean volumes: - description: 'A list of Persistent Volume Claims to be - mounted. Syntax: [pvcname:/container/path]' + description: |- + A list of Persistent Volume Claims to be mounted. Syntax: [pvcname:/container/path]. If the PVC is not found, the Integration fails. + 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. items: type: string type: array @@ -40121,8 +40135,10 @@ spec: 2.5.' type: boolean volumes: - description: 'A list of Persistent Volume Claims to be - mounted. Syntax: [pvcname:/container/path]' + description: |- + A list of Persistent Volume Claims to be mounted. Syntax: [pvcname:/container/path]. If the PVC is not found, the Integration fails. + 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. items: type: string type: array diff --git a/pkg/apis/camel/v1/trait/mount.go b/pkg/apis/camel/v1/trait/mount.go index 048b95f30f..6fa83b745e 100644 --- a/pkg/apis/camel/v1/trait/mount.go +++ b/pkg/apis/camel/v1/trait/mount.go @@ -32,7 +32,9 @@ type MountTrait struct { // The destination path can be either a default location or any path specified by the user. // Syntax: [configmap|secret]:name[/key][@path], where name represents the resource name, key optionally represents the resource key to be filtered and path represents the destination path Resources []string `property:"resources" json:"resources,omitempty"` - // A list of Persistent Volume Claims to be mounted. Syntax: [pvcname:/container/path] + // A list of Persistent Volume Claims to be mounted. Syntax: [pvcname:/container/path]. If the PVC is not found, the Integration fails. + // 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] EmptyDirs []string `property:"empty-dirs" json:"emptyDirs,omitempty"` diff --git a/pkg/resources/config/crd/bases/camel.apache.org_integrationplatforms.yaml b/pkg/resources/config/crd/bases/camel.apache.org_integrationplatforms.yaml index d4d043e9e3..163232d424 100644 --- a/pkg/resources/config/crd/bases/camel.apache.org_integrationplatforms.yaml +++ b/pkg/resources/config/crd/bases/camel.apache.org_integrationplatforms.yaml @@ -1673,8 +1673,10 @@ spec: 2.5.' type: boolean volumes: - description: 'A list of Persistent Volume Claims to be mounted. - Syntax: [pvcname:/container/path]' + description: |- + A list of Persistent Volume Claims to be mounted. Syntax: [pvcname:/container/path]. If the PVC is not found, the Integration fails. + 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. items: type: string type: array @@ -3811,8 +3813,10 @@ spec: 2.5.' type: boolean volumes: - description: 'A list of Persistent Volume Claims to be mounted. - Syntax: [pvcname:/container/path]' + description: |- + A list of Persistent Volume Claims to be mounted. Syntax: [pvcname:/container/path]. If the PVC is not found, the Integration fails. + 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. items: type: string type: array diff --git a/pkg/resources/config/crd/bases/camel.apache.org_integrationprofiles.yaml b/pkg/resources/config/crd/bases/camel.apache.org_integrationprofiles.yaml index b4178e2760..fd964cb8ea 100644 --- a/pkg/resources/config/crd/bases/camel.apache.org_integrationprofiles.yaml +++ b/pkg/resources/config/crd/bases/camel.apache.org_integrationprofiles.yaml @@ -1542,8 +1542,10 @@ spec: 2.5.' type: boolean volumes: - description: 'A list of Persistent Volume Claims to be mounted. - Syntax: [pvcname:/container/path]' + description: |- + A list of Persistent Volume Claims to be mounted. Syntax: [pvcname:/container/path]. If the PVC is not found, the Integration fails. + 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. items: type: string type: array @@ -3559,8 +3561,10 @@ spec: 2.5.' type: boolean volumes: - description: 'A list of Persistent Volume Claims to be mounted. - Syntax: [pvcname:/container/path]' + description: |- + A list of Persistent Volume Claims to be mounted. Syntax: [pvcname:/container/path]. If the PVC is not found, the Integration fails. + 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. items: type: string type: array diff --git a/pkg/resources/config/crd/bases/camel.apache.org_integrations.yaml b/pkg/resources/config/crd/bases/camel.apache.org_integrations.yaml index 406bd06eef..520f496bcc 100644 --- a/pkg/resources/config/crd/bases/camel.apache.org_integrations.yaml +++ b/pkg/resources/config/crd/bases/camel.apache.org_integrations.yaml @@ -7562,8 +7562,10 @@ spec: 2.5.' type: boolean volumes: - description: 'A list of Persistent Volume Claims to be mounted. - Syntax: [pvcname:/container/path]' + description: |- + A list of Persistent Volume Claims to be mounted. Syntax: [pvcname:/container/path]. If the PVC is not found, the Integration fails. + 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. items: type: string type: array @@ -9541,8 +9543,10 @@ spec: 2.5.' type: boolean volumes: - description: 'A list of Persistent Volume Claims to be mounted. - Syntax: [pvcname:/container/path]' + description: |- + A list of Persistent Volume Claims to be mounted. Syntax: [pvcname:/container/path]. If the PVC is not found, the Integration fails. + 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. items: type: string type: array diff --git a/pkg/resources/config/crd/bases/camel.apache.org_kameletbindings.yaml b/pkg/resources/config/crd/bases/camel.apache.org_kameletbindings.yaml index c6db8e8c2a..93ead8554e 100644 --- a/pkg/resources/config/crd/bases/camel.apache.org_kameletbindings.yaml +++ b/pkg/resources/config/crd/bases/camel.apache.org_kameletbindings.yaml @@ -7630,8 +7630,10 @@ spec: 2.5.' type: boolean volumes: - description: 'A list of Persistent Volume Claims to be - mounted. Syntax: [pvcname:/container/path]' + description: |- + A list of Persistent Volume Claims to be mounted. Syntax: [pvcname:/container/path]. If the PVC is not found, the Integration fails. + 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. items: type: string type: array diff --git a/pkg/resources/config/crd/bases/camel.apache.org_pipes.yaml b/pkg/resources/config/crd/bases/camel.apache.org_pipes.yaml index 2600c40803..91af8ae443 100644 --- a/pkg/resources/config/crd/bases/camel.apache.org_pipes.yaml +++ b/pkg/resources/config/crd/bases/camel.apache.org_pipes.yaml @@ -7628,8 +7628,10 @@ spec: 2.5.' type: boolean volumes: - description: 'A list of Persistent Volume Claims to be - mounted. Syntax: [pvcname:/container/path]' + description: |- + A list of Persistent Volume Claims to be mounted. Syntax: [pvcname:/container/path]. If the PVC is not found, the Integration fails. + 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. items: type: string type: array diff --git a/pkg/trait/mount.go b/pkg/trait/mount.go index 5fe56da65d..33a9b85fc4 100644 --- a/pkg/trait/mount.go +++ b/pkg/trait/mount.go @@ -25,6 +25,8 @@ import ( appsv1 "k8s.io/api/apps/v1" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" + storagev1 "k8s.io/api/storage/v1" + "k8s.io/apimachinery/pkg/api/resource" serving "knative.dev/serving/pkg/apis/serving/v1" @@ -113,7 +115,7 @@ func (t *mountTrait) Apply(e *Environment) error { // Volumes declared in the Integration resources e.configureVolumesAndMounts(volumes, &container.VolumeMounts) // Volumes declared in the trait config/resource options - err := t.configureVolumesAndMounts(volumes, &container.VolumeMounts) + err := t.configureVolumesAndMounts(e, volumes, &container.VolumeMounts) if err != nil { return err } @@ -122,7 +124,7 @@ func (t *mountTrait) Apply(e *Environment) error { return nil } -func (t *mountTrait) configureVolumesAndMounts(vols *[]corev1.Volume, mnts *[]corev1.VolumeMount) error { +func (t *mountTrait) configureVolumesAndMounts(e *Environment, vols *[]corev1.Volume, mnts *[]corev1.VolumeMount) error { for _, c := range t.Configs { if conf, parseErr := utilResource.ParseConfig(c); parseErr == nil { t.mountResource(vols, mnts, conf) @@ -138,11 +140,12 @@ func (t *mountTrait) configureVolumesAndMounts(vols *[]corev1.Volume, mnts *[]co } } for _, v := range t.Volumes { - if vol, parseErr := utilResource.ParseVolume(v); parseErr == nil { - t.mountResource(vols, mnts, vol) - } else { + volume, volumeMount, parseErr := ParseAndCreateVolume(e, v) + if parseErr != nil { return parseErr } + *vols = append(*vols, *volume) + *mnts = append(*mnts, *volumeMount) } for _, v := range t.EmptyDirs { if vol, parseErr := utilResource.ParseEmptyDirVolume(v); parseErr == nil { @@ -186,3 +189,85 @@ func (t *mountTrait) addServiceBindingSecret(e *Environment) { } }) } + +// 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>>. +func ParseAndCreateVolume(e *Environment, item string) (*corev1.Volume, *corev1.VolumeMount, error) { + volumeParts := strings.Split(item, ":") + volumeName := volumeParts[0] + pvc, err := kubernetes.LookupPersistentVolumeClaim(e.Ctx, e.Client, e.Integration.Namespace, volumeName) + if err != nil { + return nil, nil, err + } + var volume *corev1.Volume + if pvc == nil { + if len(volumeParts) == 2 { + return nil, nil, fmt.Errorf("volume %s does not exist. "+ + "Make sure to provide one or configure a dynamic PVC as trait volume configuration pvcName:path/to/mount:size:accessMode<:storageClassName>", + volumeName, + ) + } + if err = createPVC(e, volumeParts); err != nil { + return nil, nil, err + } + } + + volume = &corev1.Volume{ + Name: kubernetes.SanitizeLabel(volumeName), + VolumeSource: corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: volumeName, + }, + }, + } + + volumeMount := getMount(volumeName, volumeParts[1], "", false) + return volume, volumeMount, nil +} + +// createPVC is in charge to create a PersistentVolumeClaim based on the configuration provided. Or it fail within the intent. +// volumeParts is expected to be as: name, path/to/mount, size, accessMode, . +func createPVC(e *Environment, volumeParts []string) error { + if len(volumeParts) < 4 || len(volumeParts) > 5 { + return fmt.Errorf( + "volume mount syntax error, must be name:path/to/mount:size:accessMode<:storageClassName> was %s", + strings.Join(volumeParts, ":"), + ) + } + volumeName := volumeParts[0] + size := volumeParts[2] + accessMode := volumeParts[3] + sizeQty, err := resource.ParseQuantity(size) + if err != nil { + return fmt.Errorf("could not parse size %s, %s", size, err.Error()) + } + + var sc *storagev1.StorageClass + //nolint: nestif + if len(volumeParts) == 5 { + scName := volumeParts[4] + sc, err = kubernetes.LookupStorageClass(e.Ctx, e.Client, e.Integration.Namespace, scName) + if err != nil { + return fmt.Errorf("error looking up for StorageClass %s, %w", scName, err) + } + if sc == nil { + return fmt.Errorf("could not find any %s StorageClass", scName) + } + } else { + sc, err = kubernetes.LookupDefaultStorageClass(e.Ctx, e.Client) + if err != nil { + return fmt.Errorf("error looking up for default StorageClass, %w", err) + } + if sc == nil { + return fmt.Errorf("could not find any default StorageClass") + } + } + + pvc := kubernetes.NewPersistentVolumeClaim(e.Integration.Namespace, volumeName, sc.Name, sizeQty, corev1.PersistentVolumeAccessMode(accessMode)) + if err := e.Client.Create(e.Ctx, pvc); err != nil { + return err + } + + return nil +} diff --git a/pkg/trait/mount_test.go b/pkg/trait/mount_test.go index 0a480e3147..df551dbf5b 100644 --- a/pkg/trait/mount_test.go +++ b/pkg/trait/mount_test.go @@ -26,6 +26,7 @@ import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + storagev1 "k8s.io/api/storage/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" @@ -63,7 +64,6 @@ func TestMountVolumesEmpty(t *testing.T) { func TestMountVolumesIntegrationPhaseDeploying(t *testing.T) { traitCatalog := NewCatalog(nil) - environment := getNominalEnv(t, traitCatalog) environment.Platform.ResyncStatusFullConfig() @@ -180,7 +180,17 @@ func TestMountVolumesIntegrationPhaseInitialization(t *testing.T) { func getNominalEnv(t *testing.T, traitCatalog *Catalog) *Environment { t.Helper() - fakeClient, _ := test.NewFakeClient() + pvc := corev1.PersistentVolumeClaim{ + TypeMeta: metav1.TypeMeta{ + Kind: "PersistentVolumeClaim", + APIVersion: corev1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "my-pvc", + }, + } + fakeClient, _ := test.NewFakeClient(&pvc) catalog, _ := camel.DefaultCatalog() compressedRoute, _ := gzip.CompressBase64([]byte(`from("platform-http:test").log("hello")`)) @@ -240,3 +250,94 @@ func getNominalEnv(t *testing.T, traitCatalog *Catalog) *Environment { Resources: kubernetes.NewCollection(), } } + +func TestMountVolumesExist(t *testing.T) { + traitCatalog := NewCatalog(nil) + e := getNominalEnv(t, traitCatalog) + vol, vm, err := ParseAndCreateVolume(e, "my-pvc:/tmp/my-pvc") + assert.NoError(t, err) + assert.Equal(t, "my-pvc", vol.PersistentVolumeClaim.ClaimName) + assert.Equal(t, "my-pvc", vm.Name) + assert.Equal(t, "/tmp/my-pvc", vm.MountPath) +} + +func TestMountVolumesNotExistAndFail(t *testing.T) { + traitCatalog := NewCatalog(nil) + e := getNominalEnv(t, traitCatalog) + _, _, err := ParseAndCreateVolume(e, "my-pvc-2:/tmp/my-pvc") + assert.Error(t, err) + assert.Equal(t, + "volume my-pvc-2 does not exist. Make sure to provide one or configure a dynamic PVC as trait volume configuration "+ + "pvcName:path/to/mount:size:accessMode<:storageClassName>", err.Error()) + // Wrong configuration + _, _, err = ParseAndCreateVolume(e, "my-pvc-2:/tmp/my-pvc:fail") + assert.Error(t, err) + assert.Equal(t, "volume mount syntax error, must be name:path/to/mount:size:accessMode<:storageClassName> was my-pvc-2:/tmp/my-pvc:fail", err.Error()) + // Wrong size parsing + _, _, err = ParseAndCreateVolume(e, "my-pvc-2:/tmp/my-pvc:10MM:ReadOnly") + assert.Error(t, err) + assert.Equal(t, "could not parse size 10MM, unable to parse quantity's suffix", err.Error()) + // No default storage class + _, _, err = ParseAndCreateVolume(e, "my-pvc-2:/tmp/my-pvc:10Mi:ReadOnly") + assert.Error(t, err) + assert.Equal(t, "could not find any default StorageClass", err.Error()) + // No given storage class + _, _, err = ParseAndCreateVolume(e, "my-pvc-2:/tmp/my-pvc:10Mi:ReadOnly:my-storage-class") + assert.Error(t, err) + assert.Equal(t, "could not find any my-storage-class StorageClass", err.Error()) +} + +func TestMountVolumesCreateDefaultStorageClass(t *testing.T) { + traitCatalog := NewCatalog(nil) + e := getNominalEnv(t, traitCatalog) + sc := storagev1.StorageClass{ + TypeMeta: metav1.TypeMeta{ + Kind: "StorageClass", + APIVersion: storagev1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "default-sc", + Annotations: map[string]string{ + "storageclass.kubernetes.io/is-default-class": "true", + }, + }, + } + fakeClient, _ := test.NewFakeClient(&sc) + e.Client = fakeClient + // Default storage class + vol, vm, err := ParseAndCreateVolume(e, "my-pvc:/tmp/my-pvc:10Mi:ReadOnly") + assert.NoError(t, err) + assert.Equal(t, "my-pvc", vol.PersistentVolumeClaim.ClaimName) + assert.Equal(t, "my-pvc", vm.Name) + assert.Equal(t, "/tmp/my-pvc", vm.MountPath) + pvc, err := kubernetes.LookupPersistentVolumeClaim(e.Ctx, e.Client, e.Integration.Namespace, "my-pvc") + assert.NoError(t, err) + assert.NotNil(t, pvc) +} + +func TestMountVolumesCreateUserStorageClass(t *testing.T) { + traitCatalog := NewCatalog(nil) + e := getNominalEnv(t, traitCatalog) + sc := storagev1.StorageClass{ + TypeMeta: metav1.TypeMeta{ + Kind: "StorageClass", + APIVersion: storagev1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "my-sc", + }, + } + fakeClient, _ := test.NewFakeClient(&sc) + e.Client = fakeClient + // Default storage class + vol, vm, err := ParseAndCreateVolume(e, "my-pvc:/tmp/my-pvc:10Mi:ReadOnly:my-sc") + assert.NoError(t, err) + assert.Equal(t, "my-pvc", vol.PersistentVolumeClaim.ClaimName) + assert.Equal(t, "my-pvc", vm.Name) + assert.Equal(t, "/tmp/my-pvc", vm.MountPath) + pvc, err := kubernetes.LookupPersistentVolumeClaim(e.Ctx, e.Client, e.Integration.Namespace, "my-pvc") + assert.NoError(t, err) + assert.NotNil(t, pvc) +} diff --git a/pkg/util/kubernetes/factory.go b/pkg/util/kubernetes/factory.go index 595f75e97c..9c0e504975 100644 --- a/pkg/util/kubernetes/factory.go +++ b/pkg/util/kubernetes/factory.go @@ -158,7 +158,8 @@ func NewConfigMap(namespace, cmName, originalFilename string, generatedKey strin } // NewPersistentVolumeClaim will create a NewPersistentVolumeClaim based on a StorageClass. -func NewPersistentVolumeClaim(ns, name, storageClassName, capacityStorage string, accessMode corev1.PersistentVolumeAccessMode) *corev1.PersistentVolumeClaim { +func NewPersistentVolumeClaim( + ns, name, storageClassName string, capacityStorage resource.Quantity, accessMode corev1.PersistentVolumeAccessMode) *corev1.PersistentVolumeClaim { pvc := corev1.PersistentVolumeClaim{ TypeMeta: metav1.TypeMeta{ Kind: "PersistentVolumeClaim", @@ -175,7 +176,7 @@ func NewPersistentVolumeClaim(ns, name, storageClassName, capacityStorage string }, Resources: corev1.VolumeResourceRequirements{ Requests: corev1.ResourceList{ - "storage": resource.MustParse(capacityStorage), + "storage": capacityStorage, }, }, }, diff --git a/pkg/util/kubernetes/lookup.go b/pkg/util/kubernetes/lookup.go index 7b69880dab..2ce961d5b1 100644 --- a/pkg/util/kubernetes/lookup.go +++ b/pkg/util/kubernetes/lookup.go @@ -109,7 +109,31 @@ func LookupPersistentVolumeClaim(ctx context.Context, c client.Client, ns string return &pvc, nil } -// LookupDefaultStorageClass will look for the default k8s StorageClass in a given namespace. +// LookupStorageClass will look for any k8s StorageClass with a given name in a given namespace. +func LookupStorageClass(ctx context.Context, c client.Client, ns string, name string) (*storagev1.StorageClass, error) { + sc := storagev1.StorageClass{ + TypeMeta: metav1.TypeMeta{ + Kind: "StorageClass", + APIVersion: storagev1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns, + Name: name, + }, + } + key := ctrl.ObjectKey{ + Namespace: ns, + Name: name, + } + if err := c.Get(ctx, key, &sc); err != nil && k8serrors.IsNotFound(err) { + return nil, nil + } else if err != nil { + return nil, err + } + return &sc, nil +} + +// LookupDefaultStorageClass will look for the default k8s StorageClass in the cluster. func LookupDefaultStorageClass(ctx context.Context, c client.Client) (*storagev1.StorageClass, error) { storageClasses, err := c.StorageV1().StorageClasses().List(ctx, metav1.ListOptions{}) if err != nil {