This is an automated email from the ASF dual-hosted git repository.
pcongiusti pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-k.git
The following commit(s) were added to refs/heads/main by this push:
new 3ec5298ef feat(trait): mount volume from storage class
3ec5298ef is described below
commit 3ec5298efc547b03cf0c18a29553eed8f765472c
Author: Pasquale Congiusti <[email protected]>
AuthorDate: Sat Oct 12 09:04:36 2024 +0200
feat(trait): mount volume from storage class
Closes #2994
---
docs/modules/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 ++-
.../crd/bases/camel.apache.org_integrations.yaml | 12 ++-
.../bases/camel.apache.org_kameletbindings.yaml | 6 +-
.../config/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 98771dac8..cae7017d4 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 64cbadf8e..0d5d6b476 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 6c17ef703..12e238d8a 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 048b95f30..6fa83b745 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 d4d043e9e..163232d42 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 b4178e276..fd964cb8e 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 406bd06ee..520f496bc 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 c6db8e8c2..93ead8554 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 2600c4080..91af8ae44 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 5fe56da65..33a9b85fc 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,
<storageClassName>.
+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 0a480e314..df551dbf5 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 595f75e97..9c0e50497 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 7b69880da..2ce961d5b 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 {