From c3b6a72e66be944822ac93988832dae9ba6fda1e Mon Sep 17 00:00:00 2001 From: Andrii Chubatiuk Date: Sat, 26 Jul 2025 21:59:10 +0300 Subject: [PATCH] vlsingle, vmsingle: do not mount emptyDir if data volume is present in --- api/operator/v1/vlagent_types.go | 4 +- config/crd/overlay/crd.yaml | 4 +- docs/CHANGELOG.md | 1 + docs/api.md | 4 +- .../operator/factory/build/container.go | 44 +++ .../operator/factory/build/container_test.go | 367 ++++++++++++++---- .../operator/factory/vlsingle/vlsingle.go | 43 +- .../operator/factory/vmsingle/vmsingle.go | 76 +--- test/e2e/vlsingle_test.go | 5 +- test/e2e/vmsingle_test.go | 36 +- 10 files changed, 387 insertions(+), 197 deletions(-) diff --git a/api/operator/v1/vlagent_types.go b/api/operator/v1/vlagent_types.go index 5e6160440..45b757b96 100644 --- a/api/operator/v1/vlagent_types.go +++ b/api/operator/v1/vlagent_types.go @@ -55,10 +55,10 @@ type VLAgentSpec struct { // PodDisruptionBudget created by operator // +optional PodDisruptionBudget *vmv1beta1.EmbeddedPodDisruptionBudgetSpec `json:"podDisruptionBudget,omitempty"` - // StatefulStorage configures storage for StatefulSet + // Storage configures storage for StatefulSet // +optional Storage *vmv1beta1.StorageSpec `json:"storage,omitempty"` - // StatefulRollingUpdateStrategy allows configuration for strategyType + // RollingUpdateStrategy allows configuration for strategyType // set it to RollingUpdate for disabling operator statefulSet rollingUpdate // +optional RollingUpdateStrategy appsv1.StatefulSetUpdateStrategyType `json:"rollingUpdateStrategy,omitempty"` diff --git a/config/crd/overlay/crd.yaml b/config/crd/overlay/crd.yaml index d29373e27..f31cffc83 100644 --- a/config/crd/overlay/crd.yaml +++ b/config/crd/overlay/crd.yaml @@ -1267,7 +1267,7 @@ spec: type: integer rollingUpdateStrategy: description: |- - StatefulRollingUpdateStrategy allows configuration for strategyType + RollingUpdateStrategy allows configuration for strategyType set it to RollingUpdate for disabling operator statefulSet rollingUpdate type: string runtimeClassName: @@ -1357,7 +1357,7 @@ spec: type: object x-kubernetes-preserve-unknown-fields: true storage: - description: StatefulStorage configures storage for StatefulSet + description: Storage configures storage for StatefulSet properties: disableMountSubPath: description: |- diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 0552f5341..b7d7bc0ee 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -29,6 +29,7 @@ aliases: * FEATURE: [vmuser](https://docs.victoriametrics.com/operator/resources/vmuser/): introduce `spec.managedMetadata` for custom labels and annotations that should be attached to a Secret. * FEATURE: [vmuser](https://docs.victoriametrics.com/operator/resources/vmuser/): introduce `query_args` parameter that allows to append query arguments for backend url generation +* BUGFIX: [vmsingle](https://docs.victoriametrics.com/operator/resources/vmsingle/), [vlsingle](https://docs.victoriametrics.com/operator/resources/vlsingle/) and [vmalertmanager](https://docs.victoriametrics.com/operator/resources/vmalertmanager): do not mount emptydir if storage data volume is already present in volumes list. Now it's impossible to mount external PVC without overriding default storageDataPath using `spec.extraArgs` and without having unneeded emptydir listed among pod volumes. Related issues [#1477](https://github.com/VictoriaMetrics/operator/issues/1477). * BUGFIX: [vmalertmanager](https://docs.victoriametrics.com/operator/resources/vmalertmanager/): check `mute_time_intervals` in subroutes: See [#1618](https://github.com/VictoriaMetrics/operator/issues/1618). * BUGFIX: [vmoperator](https://docs.victoriametrics.com/operator/): remove incorrect key argument in structured log for when the actual PVC storage size is larger than the currently configured size and properly indicate which is the new and which is the existing size: See PR [#1636](https://github.com/VictoriaMetrics/operator/pull/1636) for details. * BUGFIX: [converter](https://docs.victoriametrics.com/operator/integrations/prometheus/#objects-conversion): properly convert Prometheus ScrapeConfig `scrapeInterval` into VM ScrapeConfig `scrape_interval`. Before it was ignored. See [#1645](https://github.com/VictoriaMetrics/operator/issues/1645). diff --git a/docs/api.md b/docs/api.md index b2154c9f0..7a1381b5c 100644 --- a/docs/api.md +++ b/docs/api.md @@ -253,7 +253,7 @@ Appears in: [VLAgent](#vlagent) | replicaCount#
_integer_ | _(Optional)_
ReplicaCount is the expected size of the Application. | | resources#
_[ResourceRequirements](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#resourcerequirements-v1-core)_ | _(Optional)_
Resources container resource request and limits, https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
if not defined default resources from operator config will be used | | revisionHistoryLimitCount#
_integer_ | _(Optional)_
The number of old ReplicaSets to retain to allow rollback in deployment or
maximum number of revisions that will be maintained in the Deployment revision history.
Has no effect at StatefulSets
Defaults to 10. | -| rollingUpdateStrategy#
_[StatefulSetUpdateStrategyType](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#statefulsetupdatestrategytype-v1-apps)_ | _(Optional)_
StatefulRollingUpdateStrategy allows configuration for strategyType
set it to RollingUpdate for disabling operator statefulSet rollingUpdate | +| rollingUpdateStrategy#
_[StatefulSetUpdateStrategyType](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#statefulsetupdatestrategytype-v1-apps)_ | _(Optional)_
RollingUpdateStrategy allows configuration for strategyType
set it to RollingUpdate for disabling operator statefulSet rollingUpdate | | runtimeClassName#
_string_ | _(Optional)_
RuntimeClassName - defines runtime class for kubernetes pod.
https://kubernetes.io/docs/concepts/containers/runtime-class/ | | schedulerName#
_string_ | _(Optional)_
SchedulerName - defines kubernetes scheduler name | | secrets#
_string array_ | _(Optional)_
Secrets is a list of Secrets in the same namespace as the Application
object, which shall be mounted into the Application container
at /etc/vm/secrets/SECRET_NAME folder | @@ -261,7 +261,7 @@ Appears in: [VLAgent](#vlagent) | serviceAccountName#
_string_ | _(Optional)_
ServiceAccountName is the name of the ServiceAccount to use to run the pods | | serviceScrapeSpec#
_[VMServiceScrapeSpec](#vmservicescrapespec)_ | _(Optional)_
ServiceScrapeSpec that will be added to vlagent VMServiceScrape spec | | serviceSpec#
_[AdditionalServiceSpec](#additionalservicespec)_ | _(Optional)_
ServiceSpec that will be added to vlagent service spec | -| storage#
_[StorageSpec](#storagespec)_ | _(Optional)_
StatefulStorage configures storage for StatefulSet | +| storage#
_[StorageSpec](#storagespec)_ | _(Optional)_
Storage configures storage for StatefulSet | | syslogSpec#
_[SyslogServerSpec](#syslogserverspec)_ | _(Optional)_
SyslogSpec defines syslog listener configuration | | terminationGracePeriodSeconds#
_integer_ | _(Optional)_
TerminationGracePeriodSeconds period for container graceful termination | | tolerations#
_[Toleration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.30/#toleration-v1-core) array_ | _(Optional)_
Tolerations If specified, the pod's tolerations. | diff --git a/internal/controller/operator/factory/build/container.go b/internal/controller/operator/factory/build/container.go index edc7e4da5..93cdc326b 100644 --- a/internal/controller/operator/factory/build/container.go +++ b/internal/controller/operator/factory/build/container.go @@ -535,3 +535,47 @@ func AddSyslogTLSConfigToVolumes(dstVolumes []corev1.Volume, dstMounts []corev1. } return dstVolumes, dstMounts } + +func StorageVolumeMountsTo(volumes []corev1.Volume, mounts []corev1.VolumeMount, pvcSrc *corev1.PersistentVolumeClaimVolumeSource, volumeName, storagePath string) ([]corev1.Volume, []corev1.VolumeMount) { + var alreadyMounted bool + for _, volumeMount := range mounts { + if volumeMount.Name == volumeName { + alreadyMounted = true + break + } + } + if !alreadyMounted { + mounts = append(mounts, corev1.VolumeMount{ + Name: volumeName, + MountPath: storagePath, + }) + } + if pvcSrc != nil { + volumes = append(volumes, corev1.Volume{ + Name: volumeName, + VolumeSource: corev1.VolumeSource{ + PersistentVolumeClaim: pvcSrc, + }, + }) + return volumes, mounts + } + + var volumePresent bool + for _, volume := range volumes { + if volume.Name == volumeName { + volumePresent = true + break + } + } + if volumePresent { + return volumes, mounts + } + + volumes = append(volumes, corev1.Volume{ + Name: volumeName, + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }) + return volumes, mounts +} diff --git a/internal/controller/operator/factory/build/container_test.go b/internal/controller/operator/factory/build/container_test.go index 9f26d8c12..a037bf575 100644 --- a/internal/controller/operator/factory/build/container_test.go +++ b/internal/controller/operator/factory/build/container_test.go @@ -45,7 +45,6 @@ func Test_buildProbe(t *testing.T) { cr testBuildProbeCR validate func(corev1.Container) error } - f := func(o opts) { t.Helper() got := Probe(o.container, o.cr) @@ -226,128 +225,324 @@ func TestFormatContainerImage(t *testing.T) { } func TestAddSyslogArgsTo(t *testing.T) { - f := func(syslogSpec *vmv1.SyslogServerSpec, wantArgs []string) { + type opts struct { + spec *vmv1.SyslogServerSpec + expected []string + } + f := func(opts opts) { t.Helper() - args := AddSyslogArgsTo(nil, syslogSpec, "/etc/vm/tls-server-secrets") + args := AddSyslogArgsTo(nil, opts.spec, "/etc/vm/tls-server-secrets") sort.Strings(args) - sort.Strings(wantArgs) - assert.Equal(t, wantArgs, args) + sort.Strings(opts.expected) + assert.Equal(t, opts.expected, args) } - f(nil, nil) + + // empty + o := opts{} + f(o) + // multiple tcp listeners - spec := vmv1.SyslogServerSpec{ - TCPListeners: []*vmv1.SyslogTCPListener{ - { - ListenPort: 3001, - }, - { - ListenPort: 3002, - StreamFields: vmv1.FieldsListString(`["msg_1","msg_2"]`), + o = opts{ + spec: &vmv1.SyslogServerSpec{ + TCPListeners: []*vmv1.SyslogTCPListener{ + { + ListenPort: 3001, + }, + { + ListenPort: 3002, + StreamFields: vmv1.FieldsListString(`["msg_1","msg_2"]`), + }, }, }, + expected: []string{ + "-syslog.listenAddr.tcp=:3001,:3002", + `-syslog.streamFields.tcp='','["msg_1","msg_2"]'`, + }, } - expected := []string{ - "-syslog.listenAddr.tcp=:3001,:3002", - `-syslog.streamFields.tcp='','["msg_1","msg_2"]'`, + f(o) + + // multiple udp listeners + o = opts{ + spec: &vmv1.SyslogServerSpec{ + UDPListeners: []*vmv1.SyslogUDPListener{ + { + ListenPort: 3001, + IgnoreFields: vmv1.FieldsListString(`["ignore_1"]`), + }, + { + ListenPort: 3002, + StreamFields: vmv1.FieldsListString(`["msg_1","msg_2"]`), + }, + { + ListenPort: 3005, + }, + }, + }, + expected: []string{ + "-syslog.listenAddr.udp=:3001,:3002,:3005", + `-syslog.streamFields.udp='','["msg_1","msg_2"]',''`, + `-syslog.ignoreFields.udp='["ignore_1"]','',''`, + }, } - f(&spec, expected) + f(o) + // mixed udp and tcp // multiple udp listeners - spec = vmv1.SyslogServerSpec{ - UDPListeners: []*vmv1.SyslogUDPListener{ - { - ListenPort: 3001, - IgnoreFields: vmv1.FieldsListString(`["ignore_1"]`), + o = opts{ + spec: &vmv1.SyslogServerSpec{ + TCPListeners: []*vmv1.SyslogTCPListener{ + { + ListenPort: 3001, + }, + { + ListenPort: 3002, + StreamFields: vmv1.FieldsListString(`["msg_1","msg_2"]`), + }, }, - { - ListenPort: 3002, - StreamFields: vmv1.FieldsListString(`["msg_1","msg_2"]`), + UDPListeners: []*vmv1.SyslogUDPListener{ + { + ListenPort: 3001, + IgnoreFields: vmv1.FieldsListString(`["ignore_1"]`), + }, + { + ListenPort: 3002, + StreamFields: vmv1.FieldsListString(`["msg_1","msg_2"]`), + }, + { + ListenPort: 3005, + }, }, - { - ListenPort: 3005, + }, + expected: []string{ + "-syslog.listenAddr.tcp=:3001,:3002", + `-syslog.streamFields.tcp='','["msg_1","msg_2"]'`, + "-syslog.listenAddr.udp=:3001,:3002,:3005", + `-syslog.streamFields.udp='','["msg_1","msg_2"]',''`, + `-syslog.ignoreFields.udp='["ignore_1"]','',''`, + }, + } + f(o) + + // with tls + o = opts{ + spec: &vmv1.SyslogServerSpec{ + TCPListeners: []*vmv1.SyslogTCPListener{ + { + ListenPort: 3001, + TenantID: "10:25", + }, + { + ListenPort: 3002, + StreamFields: vmv1.FieldsListString(`["msg_1","msg_2"]`), + TLSConfig: &vmv1.TLSServerConfig{ + CertSecret: &corev1.SecretKeySelector{ + Key: "CERT", + LocalObjectReference: corev1.LocalObjectReference{ + Name: "tls", + }, + }, + KeyFile: "/etc/vm/secrets/tls/key", + }, + }, + }, + UDPListeners: []*vmv1.SyslogUDPListener{ + { + ListenPort: 3001, + CompressMethod: "zstd", + }, }, }, + expected: []string{ + "-syslog.listenAddr.tcp=:3001,:3002", + `-syslog.streamFields.tcp='','["msg_1","msg_2"]'`, + `-syslog.tenantID.tcp=10:25,`, + "-syslog.tls=,true", + "-syslog.tlsCertFile=,/etc/vm/tls-server-secrets/tls/CERT", + "-syslog.tlsKeyFile=,/etc/vm/secrets/tls/key", + "-syslog.listenAddr.udp=:3001", + "-syslog.compressMethod.udp=zstd", + }, + } + f(o) +} + +func TestStorageVolumeMountsTo(t *testing.T) { + type opts struct { + pvcSrc *corev1.PersistentVolumeClaimVolumeSource + volumeName string + storagePath string + volumes []corev1.Volume + expectedVolumes []corev1.Volume + mounts []corev1.VolumeMount + expectedMounts []corev1.VolumeMount } - expected = []string{ - "-syslog.listenAddr.udp=:3001,:3002,:3005", - `-syslog.streamFields.udp='','["msg_1","msg_2"]',''`, - `-syslog.ignoreFields.udp='["ignore_1"]','',''`, + f := func(opts opts) { + t.Helper() + gotVolumes, gotMounts := StorageVolumeMountsTo(opts.volumes, opts.mounts, opts.pvcSrc, opts.volumeName, opts.storagePath) + assert.Equal(t, opts.expectedMounts, gotMounts) + assert.Equal(t, opts.expectedVolumes, gotVolumes) } - f(&spec, expected) - // mixed udp and tcp - // multiple udp listeners - spec = vmv1.SyslogServerSpec{ - TCPListeners: []*vmv1.SyslogTCPListener{ + // no PVC spec and no volumes and mounts + o := opts{ + volumeName: "test", + storagePath: "/test", + expectedVolumes: []corev1.Volume{{ + Name: "test", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }}, + expectedMounts: []corev1.VolumeMount{{ + Name: "test", + MountPath: "/test", + }}, + } + f(o) + + // with PVC spec and no volumes and mounts + o = opts{ + volumeName: "test", + storagePath: "/test", + pvcSrc: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: "test-claim", + }, + expectedVolumes: []corev1.Volume{{ + Name: "test", + VolumeSource: corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: "test-claim", + }, + }, + }}, + expectedMounts: []corev1.VolumeMount{{ + Name: "test", + MountPath: "/test", + }}, + } + f(o) + + // with PVC spec and matching data volume + o = opts{ + volumes: []corev1.Volume{{ + Name: "test", + VolumeSource: corev1.VolumeSource{ + AWSElasticBlockStore: &corev1.AWSElasticBlockStoreVolumeSource{ + VolumeID: "aws-volume", + }, + }, + }}, + volumeName: "test", + storagePath: "/test", + pvcSrc: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: "test-claim", + }, + expectedVolumes: []corev1.Volume{ { - ListenPort: 3001, + Name: "test", + VolumeSource: corev1.VolumeSource{ + AWSElasticBlockStore: &corev1.AWSElasticBlockStoreVolumeSource{ + VolumeID: "aws-volume", + }, + }, }, { - ListenPort: 3002, - StreamFields: vmv1.FieldsListString(`["msg_1","msg_2"]`), + Name: "test", + VolumeSource: corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: "test-claim", + }, + }, }, }, - UDPListeners: []*vmv1.SyslogUDPListener{ - { - ListenPort: 3001, - IgnoreFields: vmv1.FieldsListString(`["ignore_1"]`), + expectedMounts: []corev1.VolumeMount{{ + Name: "test", + MountPath: "/test", + }}, + } + f(o) + + // with PVC spec and not matching data volume + o = opts{ + volumes: []corev1.Volume{{ + Name: "extra", + VolumeSource: corev1.VolumeSource{ + AWSElasticBlockStore: &corev1.AWSElasticBlockStoreVolumeSource{ + VolumeID: "aws-volume", + }, }, + }}, + volumeName: "test", + storagePath: "/test", + pvcSrc: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: "test-claim", + }, + expectedVolumes: []corev1.Volume{ { - ListenPort: 3002, - StreamFields: vmv1.FieldsListString(`["msg_1","msg_2"]`), + Name: "extra", + VolumeSource: corev1.VolumeSource{ + AWSElasticBlockStore: &corev1.AWSElasticBlockStoreVolumeSource{ + VolumeID: "aws-volume", + }, + }, }, { - ListenPort: 3005, + Name: "test", + VolumeSource: corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: "test-claim", + }, + }, }, }, + expectedMounts: []corev1.VolumeMount{{ + Name: "test", + MountPath: "/test", + }}, } - expected = []string{ - "-syslog.listenAddr.tcp=:3001,:3002", - `-syslog.streamFields.tcp='','["msg_1","msg_2"]'`, - "-syslog.listenAddr.udp=:3001,:3002,:3005", - `-syslog.streamFields.udp='','["msg_1","msg_2"]',''`, - `-syslog.ignoreFields.udp='["ignore_1"]','',''`, - } - f(&spec, expected) + f(o) - // with tls - spec = vmv1.SyslogServerSpec{ - TCPListeners: []*vmv1.SyslogTCPListener{ - { - ListenPort: 3001, - TenantID: "10:25", + // with PVC spec and existing data volume mount + o = opts{ + volumes: []corev1.Volume{{ + Name: "extra", + VolumeSource: corev1.VolumeSource{ + AWSElasticBlockStore: &corev1.AWSElasticBlockStoreVolumeSource{ + VolumeID: "aws-volume", + }, }, + }}, + mounts: []corev1.VolumeMount{{ + Name: "test", + MountPath: "/other-path", + }}, + volumeName: "test", + storagePath: "/test", + pvcSrc: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: "test-claim", + }, + expectedVolumes: []corev1.Volume{ { - ListenPort: 3002, - StreamFields: vmv1.FieldsListString(`["msg_1","msg_2"]`), - TLSConfig: &vmv1.TLSServerConfig{ - CertSecret: &corev1.SecretKeySelector{ - Key: "CERT", - LocalObjectReference: corev1.LocalObjectReference{ - Name: "tls", - }, + Name: "extra", + VolumeSource: corev1.VolumeSource{ + AWSElasticBlockStore: &corev1.AWSElasticBlockStoreVolumeSource{ + VolumeID: "aws-volume", }, - KeyFile: "/etc/vm/secrets/tls/key", }, }, - }, - UDPListeners: []*vmv1.SyslogUDPListener{ { - ListenPort: 3001, - CompressMethod: "zstd", + Name: "test", + VolumeSource: corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: "test-claim", + }, + }, }, }, + expectedMounts: []corev1.VolumeMount{{ + Name: "test", + MountPath: "/other-path", + }}, } - expected = []string{ - "-syslog.listenAddr.tcp=:3001,:3002", - `-syslog.streamFields.tcp='','["msg_1","msg_2"]'`, - `-syslog.tenantID.tcp=10:25,`, - "-syslog.tls=,true", - "-syslog.tlsCertFile=,/etc/vm/tls-server-secrets/tls/CERT", - "-syslog.tlsKeyFile=,/etc/vm/secrets/tls/key", - "-syslog.listenAddr.udp=:3001", - "-syslog.compressMethod.udp=zstd", - } - f(&spec, expected) - + f(o) } diff --git a/internal/controller/operator/factory/vlsingle/vlsingle.go b/internal/controller/operator/factory/vlsingle/vlsingle.go index 13d1c0a0e..69eba1069 100644 --- a/internal/controller/operator/factory/vlsingle/vlsingle.go +++ b/internal/controller/operator/factory/vlsingle/vlsingle.go @@ -69,7 +69,7 @@ func CreateOrUpdate(ctx context.Context, rclient client.Client, cr *vmv1.VLSingl return err } } - if cr.Spec.Storage != nil && cr.Spec.StorageDataPath == "" { + if cr.Spec.Storage != nil { if err := createOrUpdatePVC(ctx, rclient, cr, prevCR); err != nil { return err } @@ -156,9 +156,6 @@ func makePodSpec(r *vmv1.VLSingle) (*corev1.PodTemplateSpec, error) { args = append(args, fmt.Sprintf("-retention.maxDiskSpaceUsageBytes=%s", r.Spec.RetentionMaxDiskSpaceUsageBytes)) } - // if customStorageDataPath is not empty, do not add pvc. - shouldAddPVC := r.Spec.StorageDataPath == "" - storagePath := vlsingleDataDir if r.Spec.StorageDataPath != "" { storagePath = r.Spec.StorageDataPath @@ -193,37 +190,19 @@ func makePodSpec(r *vmv1.VLSingle) (*corev1.PodTemplateSpec, error) { var ports []corev1.ContainerPort ports = append(ports, corev1.ContainerPort{Name: "http", Protocol: "TCP", ContainerPort: intstr.Parse(r.Spec.Port).IntVal}) - volumes := []corev1.Volume{} - - storageSpec := r.Spec.Storage - - if storageSpec == nil { - volumes = append(volumes, corev1.Volume{ - Name: vlsingleDataVolumeName, - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{}, - }, - }) - } else if shouldAddPVC { - volumes = append(volumes, corev1.Volume{ - Name: vlsingleDataVolumeName, - VolumeSource: corev1.VolumeSource{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ - ClaimName: r.PrefixedName(), - }, - }, - }) - } + var volumes []corev1.Volume + var vmMounts []corev1.VolumeMount volumes = append(volumes, r.Spec.Volumes...) - vmMounts := []corev1.VolumeMount{ - { - Name: vlsingleDataVolumeName, - MountPath: storagePath, - }, - } - vmMounts = append(vmMounts, r.Spec.VolumeMounts...) + var pvcSrc *corev1.PersistentVolumeClaimVolumeSource + if r.Spec.Storage != nil { + pvcSrc = &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: r.PrefixedName(), + } + } + volumes, vmMounts = build.StorageVolumeMountsTo(volumes, vmMounts, pvcSrc, vlsingleDataVolumeName, storagePath) + for _, s := range r.Spec.Secrets { volumes = append(volumes, corev1.Volume{ Name: k8stools.SanitizeVolumeName("secret-" + s), diff --git a/internal/controller/operator/factory/vmsingle/vmsingle.go b/internal/controller/operator/factory/vmsingle/vmsingle.go index 79d5e473c..67b5a5102 100644 --- a/internal/controller/operator/factory/vmsingle/vmsingle.go +++ b/internal/controller/operator/factory/vmsingle/vmsingle.go @@ -83,7 +83,7 @@ func CreateOrUpdate(ctx context.Context, cr *vmv1beta1.VMSingle, rclient client. } } - if cr.Spec.Storage != nil && cr.Spec.StorageDataPath == "" { + if cr.Spec.Storage != nil { if err := createStorage(ctx, rclient, cr, prevCR); err != nil { return fmt.Errorf("cannot create storage: %w", err) } @@ -154,11 +154,6 @@ func makeSpec(ctx context.Context, cr *vmv1beta1.VMSingle) (*corev1.PodTemplateS args = append(args, fmt.Sprintf("-retentionPeriod=%s", cr.Spec.RetentionPeriod)) } - // if customStorageDataPath is not empty, do not add volumes - // and volumeMounts - // it's user responsibility to provide correct values - mustAddVolumeMounts := cr.Spec.StorageDataPath == "" - storagePath := vmSingleDataDir if cr.Spec.StorageDataPath != "" { storagePath = cr.Spec.StorageDataPath @@ -191,8 +186,17 @@ func makeSpec(ctx context.Context, cr *vmv1beta1.VMSingle) (*corev1.PodTemplateS var volumes []corev1.Volume var vmMounts []corev1.VolumeMount - volumes, vmMounts = addVolumeMountsTo(volumes, vmMounts, cr, mustAddVolumeMounts, storagePath) + volumes = append(volumes, cr.Spec.Volumes...) + vmMounts = append(vmMounts, cr.Spec.VolumeMounts...) + var pvcSpec *corev1.PersistentVolumeClaimVolumeSource + if cr.Spec.Storage != nil { + pvcSpec = &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: cr.PrefixedName(), + } + } + + volumes, vmMounts = build.StorageVolumeMountsTo(volumes, vmMounts, pvcSpec, vmDataVolumeName, storagePath) if cr.Spec.VMBackup != nil && cr.Spec.VMBackup.CredentialsSecret != nil { volumes = append(volumes, corev1.Volume{ Name: k8stools.SanitizeVolumeName("secret-" + cr.Spec.VMBackup.CredentialsSecret.Name), @@ -204,9 +208,6 @@ func makeSpec(ctx context.Context, cr *vmv1beta1.VMSingle) (*corev1.PodTemplateS }) } - volumes = append(volumes, cr.Spec.Volumes...) - vmMounts = append(vmMounts, cr.Spec.VolumeMounts...) - for _, s := range cr.Spec.Secrets { volumes = append(volumes, corev1.Volume{ Name: k8stools.SanitizeVolumeName("secret-" + s), @@ -451,58 +452,3 @@ func deleteOrphaned(ctx context.Context, rclient client.Client, cr *vmv1beta1.VM return nil } - -func addVolumeMountsTo(volumes []corev1.Volume, vmMounts []corev1.VolumeMount, cr *vmv1beta1.VMSingle, mustAddVolumeMounts bool, storagePath string) ([]corev1.Volume, []corev1.VolumeMount) { - - switch { - case mustAddVolumeMounts: - // add volume and mount point by operator directly - vmMounts = append(vmMounts, corev1.VolumeMount{ - Name: vmDataVolumeName, - MountPath: storagePath}, - ) - - vlSource := corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{}, - } - if cr.Spec.Storage != nil { - vlSource = corev1.VolumeSource{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ - ClaimName: cr.PrefixedName(), - }, - } - } - volumes = append(volumes, corev1.Volume{ - Name: vmDataVolumeName, - VolumeSource: vlSource}) - - case len(cr.Spec.Volumes) > 0: - // add missing volumeMount point for backward compatibility - // it simplifies management of external PVCs - var volumeNamePresent bool - for _, volume := range cr.Spec.Volumes { - if volume.Name == vmDataVolumeName { - volumeNamePresent = true - break - } - } - if volumeNamePresent { - var mustSkipVolumeAdd bool - for _, volumeMount := range cr.Spec.VolumeMounts { - if volumeMount.Name == vmDataVolumeName { - mustSkipVolumeAdd = true - break - } - } - if !mustSkipVolumeAdd { - vmMounts = append(vmMounts, corev1.VolumeMount{ - Name: vmDataVolumeName, - MountPath: storagePath, - }) - } - } - - } - - return volumes, vmMounts -} diff --git a/test/e2e/vlsingle_test.go b/test/e2e/vlsingle_test.go index bc8cf17bf..fbc84c971 100644 --- a/test/e2e/vlsingle_test.go +++ b/test/e2e/vlsingle_test.go @@ -145,7 +145,6 @@ var _ = Describe("test vlsingle Controller", Label("vl", "single", "vlsingle"), }, RetentionPeriod: "1", StorageDataPath: "/custom-path/internal/dir", - Storage: &corev1.PersistentVolumeClaimSpec{}, }, }, func(cr *vmv1.VLSingle) { @@ -156,8 +155,8 @@ var _ = Describe("test vlsingle Controller", Label("vl", "single", "vlsingle"), Expect(ts.Containers).To(HaveLen(1)) Expect(ts.Volumes).To(HaveLen(2)) Expect(ts.Containers[0].VolumeMounts).To(HaveLen(2)) - Expect(ts.Containers[0].VolumeMounts[0].Name).To(Equal("data")) - Expect(ts.Containers[0].VolumeMounts[1].Name).To(Equal("unused")) + Expect(ts.Containers[0].VolumeMounts[0].Name).To(Equal("unused")) + Expect(ts.Containers[0].VolumeMounts[1].Name).To(Equal("data")) }), ) diff --git a/test/e2e/vmsingle_test.go b/test/e2e/vmsingle_test.go index a50828cf2..9a102f52f 100644 --- a/test/e2e/vmsingle_test.go +++ b/test/e2e/vmsingle_test.go @@ -215,7 +215,7 @@ var _ = Describe("test vmsingle Controller", Label("vm", "single"), func() { Expect(*createdDeploy.Spec.Template.Spec.Containers[0].SecurityContext.RunAsNonRoot).To(BeTrue()) }), - Entry("with data emptyDir", "emptydir", false, + Entry("with storage", "storage", false, &vmv1beta1.VMSingle{ ObjectMeta: metav1.ObjectMeta{ Namespace: namespace, @@ -245,8 +245,34 @@ var _ = Describe("test vmsingle Controller", Label("vm", "single"), func() { Expect(k8sClient.Get(ctx, createdChildObjects, &createdDeploy)).To(Succeed()) ts := createdDeploy.Spec.Template.Spec Expect(ts.Containers).To(HaveLen(1)) - Expect(ts.Volumes).To(BeEmpty()) - Expect(ts.Containers[0].VolumeMounts).To(BeEmpty()) + Expect(ts.Volumes).To(HaveLen(1)) + Expect(ts.Containers[0].VolumeMounts).To(HaveLen(1)) + }), + Entry("with empty dir", "emptydir", false, + &vmv1beta1.VMSingle{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + }, + Spec: vmv1beta1.VMSingleSpec{ + CommonApplicationDeploymentParams: vmv1beta1.CommonApplicationDeploymentParams{ + ReplicaCount: ptr.To[int32](1), + }, + CommonDefaultableParams: vmv1beta1.CommonDefaultableParams{ + UseStrictSecurity: ptr.To(false), + }, + RetentionPeriod: "1", + RemovePvcAfterDelete: true, + StorageDataPath: "/tmp/", + }, + }, + func(cr *vmv1beta1.VMSingle) { + createdChildObjects := types.NamespacedName{Namespace: namespace, Name: cr.PrefixedName()} + var createdDeploy appsv1.Deployment + Expect(k8sClient.Get(ctx, createdChildObjects, &createdDeploy)).To(Succeed()) + ts := createdDeploy.Spec.Template.Spec + Expect(ts.Containers).To(HaveLen(1)) + Expect(ts.Volumes).To(HaveLen(1)) + Expect(ts.Containers[0].VolumeMounts).To(HaveLen(1)) }), Entry("with external volume", "externalvolume", true, &vmv1beta1.VMSingle{ @@ -289,7 +315,6 @@ var _ = Describe("test vmsingle Controller", Label("vm", "single"), func() { RetentionPeriod: "1", RemovePvcAfterDelete: true, StorageDataPath: "/custom-path/internal/dir", - Storage: &corev1.PersistentVolumeClaimSpec{}, VMBackup: &vmv1beta1.VMBackup{ Destination: "fs:///opt/backup", VolumeMounts: []corev1.VolumeMount{{Name: "backup", MountPath: "/opt/backup"}}, @@ -304,7 +329,8 @@ var _ = Describe("test vmsingle Controller", Label("vm", "single"), func() { Expect(ts.Containers).To(HaveLen(2)) Expect(ts.Volumes).To(HaveLen(4)) Expect(ts.Containers[0].VolumeMounts).To(HaveLen(3)) - Expect(ts.Containers[0].VolumeMounts[0].Name).To(Equal("data")) + Expect(ts.Containers[0].VolumeMounts[0].Name).To(Equal("unused")) + Expect(ts.Containers[0].VolumeMounts[1].Name).To(Equal("data")) Expect(ts.Containers[1].VolumeMounts).To(HaveLen(3)) Expect(ts.Containers[1].VolumeMounts[0].Name).To(Equal("data")) Expect(ts.Containers[1].VolumeMounts[1].Name).To(Equal("backup"))