From e24fd060220eb9467f0bec6f8e73fea8fb0f74f0 Mon Sep 17 00:00:00 2001 From: George Kechagias Date: Fri, 28 Nov 2025 18:13:27 +0200 Subject: [PATCH 01/12] K8SPXC-1688 support adding existing pvcs to pxc instances --- ...pxc.percona.com_perconaxtradbclusters.yaml | 57 ++++++++ deploy/bundle.yaml | 57 ++++++++ deploy/cr.yaml | 5 + deploy/crd.yaml | 57 ++++++++ deploy/cw-bundle.yaml | 57 ++++++++ pkg/apis/pxc/v1/pxc_types.go | 131 ++++++++++++++++++ pkg/apis/pxc/v1/zz_generated.deepcopy.go | 20 +++ pkg/pxc/app/statefulset/node.go | 5 +- pkg/pxc/statefulset.go | 5 + 9 files changed, 393 insertions(+), 1 deletion(-) diff --git a/config/crd/bases/pxc.percona.com_perconaxtradbclusters.yaml b/config/crd/bases/pxc.percona.com_perconaxtradbclusters.yaml index c8bf4ccee9..a014a672e0 100644 --- a/config/crd/bases/pxc.percona.com_perconaxtradbclusters.yaml +++ b/config/crd/bases/pxc.percona.com_perconaxtradbclusters.yaml @@ -1746,6 +1746,25 @@ spec: rule: '!(has(self.loadBalancerClass)) || self.type == ''LoadBalancer''' externalTrafficPolicy: type: string + extraPVCs: + items: + properties: + claimName: + type: string + mountPath: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + required: + - claimName + - mountPath + - name + type: object + type: array gracePeriod: format: int64 type: integer @@ -5311,6 +5330,25 @@ spec: rule: '!(has(self.loadBalancerClass)) || self.type == ''LoadBalancer''' externalTrafficPolicy: type: string + extraPVCs: + items: + properties: + claimName: + type: string + mountPath: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + required: + - claimName + - mountPath + - name + type: object + type: array gracePeriod: format: int64 type: integer @@ -8332,6 +8370,25 @@ spec: rule: '!(has(self.loadBalancerClass)) || self.type == ''LoadBalancer''' externalTrafficPolicy: type: string + extraPVCs: + items: + properties: + claimName: + type: string + mountPath: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + required: + - claimName + - mountPath + - name + type: object + type: array gracePeriod: format: int64 type: integer diff --git a/deploy/bundle.yaml b/deploy/bundle.yaml index 6d0e6ee75d..9fd3435406 100644 --- a/deploy/bundle.yaml +++ b/deploy/bundle.yaml @@ -3053,6 +3053,25 @@ spec: rule: '!(has(self.loadBalancerClass)) || self.type == ''LoadBalancer''' externalTrafficPolicy: type: string + extraPVCs: + items: + properties: + claimName: + type: string + mountPath: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + required: + - claimName + - mountPath + - name + type: object + type: array gracePeriod: format: int64 type: integer @@ -6618,6 +6637,25 @@ spec: rule: '!(has(self.loadBalancerClass)) || self.type == ''LoadBalancer''' externalTrafficPolicy: type: string + extraPVCs: + items: + properties: + claimName: + type: string + mountPath: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + required: + - claimName + - mountPath + - name + type: object + type: array gracePeriod: format: int64 type: integer @@ -9639,6 +9677,25 @@ spec: rule: '!(has(self.loadBalancerClass)) || self.type == ''LoadBalancer''' externalTrafficPolicy: type: string + extraPVCs: + items: + properties: + claimName: + type: string + mountPath: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + required: + - claimName + - mountPath + - name + type: object + type: array gracePeriod: format: int64 type: integer diff --git a/deploy/cr.yaml b/deploy/cr.yaml index 42c7fd226e..4bd1f4d257 100644 --- a/deploy/cr.yaml +++ b/deploy/cr.yaml @@ -63,6 +63,11 @@ spec: size: 3 image: perconalab/percona-xtradb-cluster-operator:main-pxc8.0 autoRecovery: true +# extraPVCs: +# - name: extra-data-volume +# claimName: kech-extra-storage +# mountPath: /var/lib/mysql-extra +# readOnly: false # mysqlAllocator: jemalloc # expose: # enabled: true diff --git a/deploy/crd.yaml b/deploy/crd.yaml index bece937fe6..162400cf45 100644 --- a/deploy/crd.yaml +++ b/deploy/crd.yaml @@ -3053,6 +3053,25 @@ spec: rule: '!(has(self.loadBalancerClass)) || self.type == ''LoadBalancer''' externalTrafficPolicy: type: string + extraPVCs: + items: + properties: + claimName: + type: string + mountPath: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + required: + - claimName + - mountPath + - name + type: object + type: array gracePeriod: format: int64 type: integer @@ -6618,6 +6637,25 @@ spec: rule: '!(has(self.loadBalancerClass)) || self.type == ''LoadBalancer''' externalTrafficPolicy: type: string + extraPVCs: + items: + properties: + claimName: + type: string + mountPath: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + required: + - claimName + - mountPath + - name + type: object + type: array gracePeriod: format: int64 type: integer @@ -9639,6 +9677,25 @@ spec: rule: '!(has(self.loadBalancerClass)) || self.type == ''LoadBalancer''' externalTrafficPolicy: type: string + extraPVCs: + items: + properties: + claimName: + type: string + mountPath: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + required: + - claimName + - mountPath + - name + type: object + type: array gracePeriod: format: int64 type: integer diff --git a/deploy/cw-bundle.yaml b/deploy/cw-bundle.yaml index 7246088a7b..21d1de360b 100644 --- a/deploy/cw-bundle.yaml +++ b/deploy/cw-bundle.yaml @@ -3053,6 +3053,25 @@ spec: rule: '!(has(self.loadBalancerClass)) || self.type == ''LoadBalancer''' externalTrafficPolicy: type: string + extraPVCs: + items: + properties: + claimName: + type: string + mountPath: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + required: + - claimName + - mountPath + - name + type: object + type: array gracePeriod: format: int64 type: integer @@ -6618,6 +6637,25 @@ spec: rule: '!(has(self.loadBalancerClass)) || self.type == ''LoadBalancer''' externalTrafficPolicy: type: string + extraPVCs: + items: + properties: + claimName: + type: string + mountPath: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + required: + - claimName + - mountPath + - name + type: object + type: array gracePeriod: format: int64 type: integer @@ -9639,6 +9677,25 @@ spec: rule: '!(has(self.loadBalancerClass)) || self.type == ''LoadBalancer''' externalTrafficPolicy: type: string + extraPVCs: + items: + properties: + claimName: + type: string + mountPath: + type: string + name: + type: string + readOnly: + type: boolean + subPath: + type: string + required: + - claimName + - mountPath + - name + type: object + type: array gracePeriod: format: int64 type: integer diff --git a/pkg/apis/pxc/v1/pxc_types.go b/pkg/apis/pxc/v1/pxc_types.go index 75c3c1c8bd..2ac147a82f 100644 --- a/pkg/apis/pxc/v1/pxc_types.go +++ b/pkg/apis/pxc/v1/pxc_types.go @@ -22,6 +22,7 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" "github.com/percona/percona-xtradb-cluster-operator/pkg/pxc/users" "github.com/percona/percona-xtradb-cluster-operator/pkg/pxctls" @@ -530,6 +531,12 @@ func (cr *PerconaXtraDBCluster) Validate() error { } } + if c.PXC != nil && len(c.PXC.ExtraPVCs) > 0 { + if err := ValidateExtraPVCs(c.PXC.ExtraPVCs); err != nil { + return errors.Wrap(err, "PXC: validate extraPVCs") + } + } + return nil } @@ -607,6 +614,7 @@ type PodSpec struct { Sidecars []corev1.Container `json:"sidecars,omitempty"` SidecarVolumes []corev1.Volume `json:"sidecarVolumes,omitempty"` SidecarPVCs []corev1.PersistentVolumeClaim `json:"sidecarPVCs,omitempty"` + ExtraPVCs []ExtraPVC `json:"extraPVCs,omitempty"` RuntimeClassName *string `json:"runtimeClassName,omitempty"` HookScript string `json:"hookScript,omitempty"` Lifecycle corev1.Lifecycle `json:"lifecycle,omitempty"` @@ -928,6 +936,29 @@ type VolumeSpec struct { PersistentVolumeClaim *corev1.PersistentVolumeClaimSpec `json:"persistentVolumeClaim,omitempty"` } +// ExtraPVC allows mounting an existing PersistentVolumeClaim to the PXC container. +type ExtraPVC struct { + // Name of the volume as it will be referenced in the pod + // +kubebuilder:validation:Required + Name string `json:"name"` + + // ClaimName is the name of the existing PersistentVolumeClaim to mount + // +kubebuilder:validation:Required + ClaimName string `json:"claimName"` + + // MountPath is the path inside the container where the volume will be mounted + // +kubebuilder:validation:Required + MountPath string `json:"mountPath"` + + // SubPath within the volume from which the container's volume should be mounted + // +optional + SubPath string `json:"subPath,omitempty"` + + // ReadOnly specifies whether the volume should be mounted as read-only + // +optional + ReadOnly bool `json:"readOnly,omitempty"` +} + type Volume struct { PVCs []corev1.PersistentVolumeClaim Volumes []corev1.Volume @@ -1687,6 +1718,106 @@ func AddSidecarPVCs(log logr.Logger, existing, sidecarPVCs []corev1.PersistentVo return existing } +// ExtraPVCVolumes generates Kubernetes volumes from ExtraPVC configurations. +// Each ExtraPVC references an existing PersistentVolumeClaim by name. +func ExtraPVCVolumes(log logr.Logger, extraPVCs []ExtraPVC) []corev1.Volume { + if len(extraPVCs) == 0 { + return nil + } + + volumes := make([]corev1.Volume, 0, len(extraPVCs)) + names := make(map[string]struct{}, len(extraPVCs)) + + for _, epvc := range extraPVCs { + if _, ok := names[epvc.Name]; ok { + log.Info("Duplicate extra PVC volume name, skipping", "volumeName", epvc.Name) + continue + } + names[epvc.Name] = struct{}{} + + volumes = append(volumes, corev1.Volume{ + Name: epvc.Name, + VolumeSource: corev1.VolumeSource{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: epvc.ClaimName, + ReadOnly: epvc.ReadOnly, + }, + }, + }) + } + + return volumes +} + +// ExtraPVCVolumeMounts generates volume mounts from ExtraPVC configurations. +func ExtraPVCVolumeMounts(ctx context.Context, extraPVCs []ExtraPVC) []corev1.VolumeMount { + logger := log.FromContext(ctx) + + if len(extraPVCs) == 0 { + return nil + } + + mounts := make([]corev1.VolumeMount, 0, len(extraPVCs)) + names := make(map[string]struct{}, len(extraPVCs)) + + for _, epvc := range extraPVCs { + if _, ok := names[epvc.Name]; ok { + logger.Info("Duplicate extra PVC volume mount name, skipping", "volumeName", epvc.Name) + continue + } + names[epvc.Name] = struct{}{} + + mounts = append(mounts, corev1.VolumeMount{ + Name: epvc.Name, + MountPath: epvc.MountPath, + SubPath: epvc.SubPath, + ReadOnly: epvc.ReadOnly, + }) + } + + return mounts +} + +// ValidateExtraPVCs validates the extraPVCs configuration. +func ValidateExtraPVCs(extraPVCs []ExtraPVC) error { + if len(extraPVCs) == 0 { + return nil + } + + names := make(map[string]struct{}, len(extraPVCs)) + claimNames := make(map[string]struct{}, len(extraPVCs)) + mountPaths := make(map[string]struct{}, len(extraPVCs)) + + for _, epvc := range extraPVCs { + if epvc.Name == "" { + return errors.New("extraPVC: name cannot be empty") + } + if epvc.ClaimName == "" { + return errors.Errorf("extraPVC %s: claimName cannot be empty", epvc.Name) + } + if epvc.MountPath == "" { + return errors.Errorf("extraPVC %s: mountPath cannot be empty", epvc.Name) + } + + if _, ok := names[epvc.Name]; ok { + return errors.Errorf("extraPVC: duplicate volume name %s", epvc.Name) + } + names[epvc.Name] = struct{}{} + + if _, ok := mountPaths[epvc.MountPath]; ok { + return errors.Errorf("extraPVC %s: duplicate mount path %s", epvc.Name, epvc.MountPath) + } + mountPaths[epvc.MountPath] = struct{}{} + + if _, ok := claimNames[epvc.ClaimName]; ok { + return errors.Errorf("extraPVC %s: duplicate claim name %s", epvc.Name, epvc.ClaimName) + } + claimNames[epvc.ClaimName] = struct{}{} + } + + return nil +} + func (cr *PerconaXtraDBCluster) ProxySQLUnreadyServiceNamespacedName() types.NamespacedName { return types.NamespacedName{ Name: cr.Name + "-proxysql-unready", diff --git a/pkg/apis/pxc/v1/zz_generated.deepcopy.go b/pkg/apis/pxc/v1/zz_generated.deepcopy.go index ff13629659..d4d0fb2274 100644 --- a/pkg/apis/pxc/v1/zz_generated.deepcopy.go +++ b/pkg/apis/pxc/v1/zz_generated.deepcopy.go @@ -243,6 +243,21 @@ func (in *ComponentStatus) DeepCopy() *ComponentStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ExtraPVC) DeepCopyInto(out *ExtraPVC) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExtraPVC. +func (in *ExtraPVC) DeepCopy() *ExtraPVC { + if in == nil { + return nil + } + out := new(ExtraPVC) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HAProxyHealthCheckSpec) DeepCopyInto(out *HAProxyHealthCheckSpec) { *out = *in @@ -1225,6 +1240,11 @@ func (in *PodSpec) DeepCopyInto(out *PodSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.ExtraPVCs != nil { + in, out := &in.ExtraPVCs, &out.ExtraPVCs + *out = make([]ExtraPVC, len(*in)) + copy(*out, *in) + } if in.RuntimeClassName != nil { in, out := &in.RuntimeClassName, &out.RuntimeClassName *out = new(string) diff --git a/pkg/pxc/app/statefulset/node.go b/pkg/pxc/app/statefulset/node.go index 37596b02e2..82be2ec62e 100644 --- a/pkg/pxc/app/statefulset/node.go +++ b/pkg/pxc/app/statefulset/node.go @@ -16,7 +16,7 @@ import ( api "github.com/percona/percona-xtradb-cluster-operator/pkg/apis/pxc/v1" "github.com/percona/percona-xtradb-cluster-operator/pkg/naming" - app "github.com/percona/percona-xtradb-cluster-operator/pkg/pxc/app" + "github.com/percona/percona-xtradb-cluster-operator/pkg/pxc/app" "github.com/percona/percona-xtradb-cluster-operator/pkg/pxc/app/config" "github.com/percona/percona-xtradb-cluster-operator/pkg/pxc/users" ) @@ -270,6 +270,9 @@ func (c *Node) AppContainer(ctx context.Context, cl client.Client, spec *api.Pod if cr.CompareVersionWith("1.19.0") >= 0 { setLDPreloadEnv(ctx, cl, cr, &appc) + + extraMounts := api.ExtraPVCVolumeMounts(ctx, spec.ExtraPVCs) + appc.VolumeMounts = append(appc.VolumeMounts, extraMounts...) } return appc, nil diff --git a/pkg/pxc/statefulset.go b/pkg/pxc/statefulset.go index 639ac56fd4..af33b3251c 100644 --- a/pkg/pxc/statefulset.go +++ b/pkg/pxc/statefulset.go @@ -96,6 +96,11 @@ func StatefulSet( pod.Containers = api.AddSidecarContainers(log, pod.Containers, podSpec.Sidecars) pod.Volumes = api.AddSidecarVolumes(log, pod.Volumes, podSpec.SidecarVolumes) + if cr.CompareVersionWith("1.19.0") >= 0 { + extraVolumes := api.ExtraPVCVolumes(log, podSpec.ExtraPVCs) + pod.Volumes = append(pod.Volumes, extraVolumes...) + } + ls := sfs.Labels() customLabels := make(map[string]string, len(ls)) From cd0e5b3f60d71741ecf738a3175f9802bffe2874 Mon Sep 17 00:00:00 2001 From: George Kechagias Date: Mon, 1 Dec 2025 17:13:58 +0200 Subject: [PATCH 02/12] add e2e test --- e2e-tests/extra-pvc/compare/select-1.sql | 1 + e2e-tests/extra-pvc/conf/extra-pvc.yml | 69 ++++++ .../conf/some-name-with-extra-pvcs.yml | 64 ++++++ e2e-tests/extra-pvc/conf/some-name.yml | 59 +++++ e2e-tests/extra-pvc/run | 201 ++++++++++++++++++ 5 files changed, 394 insertions(+) create mode 100644 e2e-tests/extra-pvc/compare/select-1.sql create mode 100644 e2e-tests/extra-pvc/conf/extra-pvc.yml create mode 100644 e2e-tests/extra-pvc/conf/some-name-with-extra-pvcs.yml create mode 100644 e2e-tests/extra-pvc/conf/some-name.yml create mode 100755 e2e-tests/extra-pvc/run diff --git a/e2e-tests/extra-pvc/compare/select-1.sql b/e2e-tests/extra-pvc/compare/select-1.sql new file mode 100644 index 0000000000..8e738f4cf2 --- /dev/null +++ b/e2e-tests/extra-pvc/compare/select-1.sql @@ -0,0 +1 @@ +100500 diff --git a/e2e-tests/extra-pvc/conf/extra-pvc.yml b/e2e-tests/extra-pvc/conf/extra-pvc.yml new file mode 100644 index 0000000000..1096849566 --- /dev/null +++ b/e2e-tests/extra-pvc/conf/extra-pvc.yml @@ -0,0 +1,69 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: extra-storage-source +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi +--- +apiVersion: v1 +kind: Pod +metadata: + name: pvc-populator +spec: + containers: + - name: populator + image: busybox + command: ['sh', '-c', 'echo "Initial data" > /var/lib/mysql-extra/test-data.csv'] + volumeMounts: + - name: data + mountPath: /data + volumes: + - name: data + persistentVolumeClaim: + claimName: extra-storage-source +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: extra-storage-0 +spec: + accessModes: + - ReadOnlyMany + dataSource: + name: extra-storage-source + kind: PersistentVolumeClaim + resources: + requests: + storage: 1Gi +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: extra-storage-1 +spec: + accessModes: + - ReadOnlyMany + dataSource: + name: extra-storage-source + kind: PersistentVolumeClaim + resources: + requests: + storage: 1Gi +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: extra-storage-2 +spec: + accessModes: + - ReadOnlyMany + dataSource: + name: extra-storage-source + kind: PersistentVolumeClaim + resources: + requests: + storage: 1Gi diff --git a/e2e-tests/extra-pvc/conf/some-name-with-extra-pvcs.yml b/e2e-tests/extra-pvc/conf/some-name-with-extra-pvcs.yml new file mode 100644 index 0000000000..970f6534fc --- /dev/null +++ b/e2e-tests/extra-pvc/conf/some-name-with-extra-pvcs.yml @@ -0,0 +1,64 @@ +apiVersion: pxc.percona.com/v1 +kind: PerconaXtraDBCluster +metadata: + name: some-name + finalizers: + - percona.com/delete-pxc-pods-in-order +spec: + secretsName: my-cluster-secrets + vaultSecretName: some-name-vault + pause: false + pxc: + size: 3 + image: -pxc + resources: + requests: + memory: 0.1G + cpu: 100m + limits: + memory: "2G" + cpu: "1" + volumeSpec: + persistentVolumeClaim: + resources: + requests: + storage: 2Gi + extraPVCs: + - name: extra-data-volume + claimName: extra-storage-0 + mountPath: /var/lib/mysql-extra + readOnly: false + affinity: + antiAffinityTopologyKey: "kubernetes.io/hostname" + haproxy: + enabled: true + size: 2 + image: -haproxy + resources: + requests: + memory: 0.1G + cpu: 100m + limits: + memory: 1G + cpu: 700m + affinity: + antiAffinityTopologyKey: "kubernetes.io/hostname" + logcollector: + enabled: false + image: -logcollector + pmm: + enabled: false + image: perconalab/pmm-client:1.17.1 + serverHost: monitoring-service + serverUser: pmm + backup: + image: -backup + storages: + pvc: + type: filesystem + volume: + persistentVolumeClaim: + accessModes: [ "ReadWriteOnce" ] + resources: + requests: + storage: 1G \ No newline at end of file diff --git a/e2e-tests/extra-pvc/conf/some-name.yml b/e2e-tests/extra-pvc/conf/some-name.yml new file mode 100644 index 0000000000..9b6ab5419f --- /dev/null +++ b/e2e-tests/extra-pvc/conf/some-name.yml @@ -0,0 +1,59 @@ +apiVersion: pxc.percona.com/v1 +kind: PerconaXtraDBCluster +metadata: + name: some-name + finalizers: + - percona.com/delete-pxc-pods-in-order +spec: + secretsName: my-cluster-secrets + vaultSecretName: some-name-vault + pause: false + pxc: + size: 3 + image: -pxc + resources: + requests: + memory: 0.1G + cpu: 100m + limits: + memory: "2G" + cpu: "1" + volumeSpec: + persistentVolumeClaim: + resources: + requests: + storage: 2Gi + affinity: + antiAffinityTopologyKey: "kubernetes.io/hostname" + haproxy: + enabled: true + size: 2 + image: -haproxy + resources: + requests: + memory: 0.1G + cpu: 100m + limits: + memory: 1G + cpu: 700m + affinity: + antiAffinityTopologyKey: "kubernetes.io/hostname" + logcollector: + enabled: false + image: -logcollector + pmm: + enabled: false + image: perconalab/pmm-client:1.17.1 + serverHost: monitoring-service + serverUser: pmm + backup: + image: -backup + storages: + pvc: + type: filesystem + volume: + persistentVolumeClaim: + accessModes: [ "ReadWriteOnce" ] + resources: + requests: + storage: 1G \ No newline at end of file diff --git a/e2e-tests/extra-pvc/run b/e2e-tests/extra-pvc/run new file mode 100755 index 0000000000..c70b8509f0 --- /dev/null +++ b/e2e-tests/extra-pvc/run @@ -0,0 +1,201 @@ +#!/bin/bash + +set -o errexit + +test_dir=$(realpath $(dirname $0)) +. ${test_dir}/../functions + + function create_extra_pvcs() { + local ns=$1 + + echo "Creating extra PVCs in namespace ${ns}" + kubectl_bin apply -f ${test_dir}/conf/extra-pvc.yml -n ${ns} + + echo "Waiting for source PVC to be bound" + kubectl_bin wait --for=jsonpath='{.status.phase}'=Bound pvc/extra-storage-source -n ${ns} --timeout=60s + + echo "Waiting for populator pod to be ready" + kubectl_bin wait --for=condition=Ready pod/pvc-populator -n ${ns} --timeout=60s + } + +function verify_extra_pvc_mounted() { + local pod=$1 + local mount_path=$2 + + echo "Verifying extra PVC is mounted in pod ${pod} at ${mount_path}" + + if ! kubectl_bin exec ${pod} -c pxc -- test -d ${mount_path}; then + echo "Mount path ${mount_path} does not exist in pod ${pod}" + exit 1 + fi + + echo "Mount path ${mount_path} exists in pod ${pod}" +} + +function verify_extra_pvc_in_statefulset() { + local cluster=$1 + local expected_claim_name=$2 + local expected_mount_path=$3 + + echo "Verifying extra PVC configuration in StatefulSet ${cluster}-pxc" + + local volume_exists=$(kubectl_bin get statefulset ${cluster}-pxc -o json | \ + jq --arg claim "${expected_claim_name}" '.spec.template.spec.volumes[] | select(.persistentVolumeClaim.claimName == $claim) | .name' -r) + + if [[ -z "$volume_exists" ]]; then + echo "Volume with claimName ${expected_claim_name} not found in StatefulSet" + kubectl_bin get statefulset ${cluster}-pxc -o yaml + exit 1 + fi + + echo "Volume ${volume_exists} with claimName ${expected_claim_name} found in StatefulSet" + + local mount_exists=$(kubectl_bin get statefulset ${cluster}-pxc -o json | \ + jq --arg path "${expected_mount_path}" '.spec.template.spec.containers[] | select(.name == "pxc") | .volumeMounts[] | select(.mountPath == $path) | .name' -r) + + if [[ -z "$mount_exists" ]]; then + echo "Volume mount with mountPath ${expected_mount_path} not found in pxc container" + kubectl_bin get statefulset ${cluster}-pxc -o yaml + exit 1 + fi + + echo "Volume mount ${mount_exists} with mountPath ${expected_mount_path} found in pxc container" +} + +function patch_cluster_add_extra_pvcs() { + local cluster=$1 + + echo "Patching cluster ${cluster} to add extraPVCs" + + kubectl_bin patch pxc ${cluster} --type=json -p='[ + { + "op": "add", + "path": "/spec/pxc/extraPVCs", + "value": [ + { + "name": "extra-data-volume", + "claimName": "extra-storage-0", + "mountPath": "/var/lib/mysql-extra", + "readOnly": true + } + ] + } + ]' +} + +function patch_cluster_remove_extra_pvcs() { + local cluster=$1 + + echo "Patching cluster ${cluster} to remove extraPVCs" + + kubectl_bin patch pxc ${cluster} --type=json -p='[ + { + "op": "remove", + "path": "/spec/pxc/extraPVCs" + } + ]' +} + +function patch_cluster_add_multiple_extra_pvcs() { + local cluster=$1 + + echo "Patching cluster ${cluster} to add multiple extraPVCs" + + kubectl_bin patch pxc ${cluster} --type=json -p='[ + { + "op": "replace", + "path": "/spec/pxc/extraPVCs", + "value": [ + { + "name": "extra-data-volume-1", + "claimName": "extra-storage-1", + "mountPath": "/var/lib/mysql-extra-1", + "readOnly": true + }, + { + "name": "extra-data-volume-2", + "claimName": "extra-storage-2", + "mountPath": "/var/lib/mysql-extra-2", + "readOnly": true + } + ] + } + ]' +} + +set_debug + +create_infra ${namespace} + +desc 'create extra PVCs' +create_extra_pvcs ${namespace} + +desc 'create PXC cluster without extra PVCs' +cluster="some-name" +spinup_pxc "${cluster}" "$test_dir/conf/$cluster.yml" "3" "10" "${conf_dir}/secrets.yml" + +desc 'verify cluster is running without extra PVCs' +wait_cluster_consistency "$cluster" 3 2 + +desc 'test adding extra PVC to running cluster' +patch_cluster_add_extra_pvcs "${cluster}" +wait_cluster_consistency "$cluster" 3 2 +sleep 10 + +desc 'verify extra PVC is configured in StatefulSet' +verify_extra_pvc_in_statefulset "${cluster}" "extra-storage-0" "/var/lib/mysql-extra" + +desc 'verify extra PVC is mounted in all PXC pods' +for i in 0 1 2; do + verify_extra_pvc_mounted "${cluster}-pxc-${i}" "/var/lib/mysql-extra" +done + +desc 'test adding multiple extra PVCs' +patch_cluster_add_multiple_extra_pvcs "${cluster}" +wait_cluster_consistency "$cluster" 3 2 +sleep 10 + +desc 'verify multiple extra PVCs are mounted' +for i in 0 1 2; do + verify_extra_pvc_mounted "${cluster}-pxc-${i}" "/var/lib/mysql-extra-1" + if kubectl_bin exec ${cluster}-pxc-${i} -c pxc -- touch /var/lib/mysql-extra-2/test-write 2>/dev/null; then + echo "Read-only mount /var/lib/mysql-extra-2 is writable in pod ${cluster}-pxc-${i}" + exit 1 + fi + echo "Read-only mount /var/lib/mysql-extra-2 is properly read-only in pod ${cluster}-pxc-${i}" +done + +desc 'test removing extra PVCs from cluster' +patch_cluster_remove_extra_pvcs "${cluster}" +wait_cluster_consistency "$cluster" 3 2 +sleep 10 + +desc 'verify extra PVCs are removed from StatefulSet' +for claim in extra-storage-1 extra-storage-2; do + volume_exists=$(kubectl_bin get statefulset ${cluster}-pxc -o json | \ + jq --arg claim "${claim}" '.spec.template.spec.volumes[] | select(.persistentVolumeClaim.claimName == $claim) | .name' -r) + + if [[ -n "$volume_exists" ]]; then + echo "Volume with claimName ${claim} still exists in StatefulSet after removal" + exit 1 + fi +done +echo "Extra PVCs successfully removed from StatefulSet" + +desc 'test creating cluster with extra PVCs from start' +cluster2="some-name-2" +kubectl_bin apply -f ${test_dir}/conf/some-name-with-extra-pvcs.yml +wait_cluster_consistency "$cluster2" 3 2 + +desc 'verify extra PVC is configured in new cluster' +verify_extra_pvc_in_statefulset "${cluster2}" "extra-storage-0" "/var/lib/mysql-extra" + +for i in 0 1 2; do + verify_extra_pvc_mounted "${cluster2}-pxc-${i}" "/var/lib/mysql-extra" +done + +desc 'cleanup second cluster' +kubectl_bin delete pxc ${cluster2} + +destroy "${namespace}" +desc "test passed" \ No newline at end of file From c912e14728d599b19004d659a5af5cbde4079ff0 Mon Sep 17 00:00:00 2001 From: George Kechagias Date: Tue, 2 Dec 2025 10:51:13 +0200 Subject: [PATCH 03/12] fix linting --- e2e-tests/extra-pvc/run | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/e2e-tests/extra-pvc/run b/e2e-tests/extra-pvc/run index c70b8509f0..225889ad40 100755 --- a/e2e-tests/extra-pvc/run +++ b/e2e-tests/extra-pvc/run @@ -5,18 +5,18 @@ set -o errexit test_dir=$(realpath $(dirname $0)) . ${test_dir}/../functions - function create_extra_pvcs() { - local ns=$1 +function create_extra_pvcs() { + local ns=$1 - echo "Creating extra PVCs in namespace ${ns}" - kubectl_bin apply -f ${test_dir}/conf/extra-pvc.yml -n ${ns} + echo "Creating extra PVCs in namespace ${ns}" + kubectl_bin apply -f ${test_dir}/conf/extra-pvc.yml -n ${ns} - echo "Waiting for source PVC to be bound" - kubectl_bin wait --for=jsonpath='{.status.phase}'=Bound pvc/extra-storage-source -n ${ns} --timeout=60s + echo "Waiting for source PVC to be bound" + kubectl_bin wait --for=jsonpath='{.status.phase}'=Bound pvc/extra-storage-source -n ${ns} --timeout=60s - echo "Waiting for populator pod to be ready" - kubectl_bin wait --for=condition=Ready pod/pvc-populator -n ${ns} --timeout=60s - } + echo "Waiting for populator pod to be ready" + kubectl_bin wait --for=condition=Ready pod/pvc-populator -n ${ns} --timeout=60s +} function verify_extra_pvc_mounted() { local pod=$1 @@ -39,10 +39,10 @@ function verify_extra_pvc_in_statefulset() { echo "Verifying extra PVC configuration in StatefulSet ${cluster}-pxc" - local volume_exists=$(kubectl_bin get statefulset ${cluster}-pxc -o json | \ - jq --arg claim "${expected_claim_name}" '.spec.template.spec.volumes[] | select(.persistentVolumeClaim.claimName == $claim) | .name' -r) + local volume_exists=$(kubectl_bin get statefulset ${cluster}-pxc -o json \ + | jq --arg claim "${expected_claim_name}" '.spec.template.spec.volumes[] | select(.persistentVolumeClaim.claimName == $claim) | .name' -r) - if [[ -z "$volume_exists" ]]; then + if [[ -z $volume_exists ]]; then echo "Volume with claimName ${expected_claim_name} not found in StatefulSet" kubectl_bin get statefulset ${cluster}-pxc -o yaml exit 1 @@ -50,10 +50,10 @@ function verify_extra_pvc_in_statefulset() { echo "Volume ${volume_exists} with claimName ${expected_claim_name} found in StatefulSet" - local mount_exists=$(kubectl_bin get statefulset ${cluster}-pxc -o json | \ - jq --arg path "${expected_mount_path}" '.spec.template.spec.containers[] | select(.name == "pxc") | .volumeMounts[] | select(.mountPath == $path) | .name' -r) + local mount_exists=$(kubectl_bin get statefulset ${cluster}-pxc -o json \ + | jq --arg path "${expected_mount_path}" '.spec.template.spec.containers[] | select(.name == "pxc") | .volumeMounts[] | select(.mountPath == $path) | .name' -r) - if [[ -z "$mount_exists" ]]; then + if [[ -z $mount_exists ]]; then echo "Volume mount with mountPath ${expected_mount_path} not found in pxc container" kubectl_bin get statefulset ${cluster}-pxc -o yaml exit 1 @@ -172,10 +172,10 @@ sleep 10 desc 'verify extra PVCs are removed from StatefulSet' for claim in extra-storage-1 extra-storage-2; do - volume_exists=$(kubectl_bin get statefulset ${cluster}-pxc -o json | \ - jq --arg claim "${claim}" '.spec.template.spec.volumes[] | select(.persistentVolumeClaim.claimName == $claim) | .name' -r) + volume_exists=$(kubectl_bin get statefulset ${cluster}-pxc -o json \ + | jq --arg claim "${claim}" '.spec.template.spec.volumes[] | select(.persistentVolumeClaim.claimName == $claim) | .name' -r) - if [[ -n "$volume_exists" ]]; then + if [[ -n $volume_exists ]]; then echo "Volume with claimName ${claim} still exists in StatefulSet after removal" exit 1 fi @@ -198,4 +198,4 @@ desc 'cleanup second cluster' kubectl_bin delete pxc ${cluster2} destroy "${namespace}" -desc "test passed" \ No newline at end of file +desc "test passed" From 8fd181b941a45a562a187d5a9c65e5690c7614c8 Mon Sep 17 00:00:00 2001 From: George Kechagias Date: Tue, 2 Dec 2025 11:20:24 +0200 Subject: [PATCH 04/12] add unit tests --- pkg/apis/pxc/v1/pxc_types.go | 6 +- pkg/apis/pxc/v1/pxc_types_test.go | 237 +++++++++++++++++++++++++++ pkg/pxc/app/statefulset/node_test.go | 46 ++++++ 3 files changed, 286 insertions(+), 3 deletions(-) diff --git a/pkg/apis/pxc/v1/pxc_types.go b/pkg/apis/pxc/v1/pxc_types.go index 2ac147a82f..51dd3ba00b 100644 --- a/pkg/apis/pxc/v1/pxc_types.go +++ b/pkg/apis/pxc/v1/pxc_types.go @@ -532,7 +532,7 @@ func (cr *PerconaXtraDBCluster) Validate() error { } if c.PXC != nil && len(c.PXC.ExtraPVCs) > 0 { - if err := ValidateExtraPVCs(c.PXC.ExtraPVCs); err != nil { + if err := validateExtraPVCs(c.PXC.ExtraPVCs); err != nil { return errors.Wrap(err, "PXC: validate extraPVCs") } } @@ -1778,8 +1778,8 @@ func ExtraPVCVolumeMounts(ctx context.Context, extraPVCs []ExtraPVC) []corev1.Vo return mounts } -// ValidateExtraPVCs validates the extraPVCs configuration. -func ValidateExtraPVCs(extraPVCs []ExtraPVC) error { +// validateExtraPVCs validates the extraPVCs configuration. +func validateExtraPVCs(extraPVCs []ExtraPVC) error { if len(extraPVCs) == 0 { return nil } diff --git a/pkg/apis/pxc/v1/pxc_types_test.go b/pkg/apis/pxc/v1/pxc_types_test.go index 99ce7a9ba4..c68c0671b3 100644 --- a/pkg/apis/pxc/v1/pxc_types_test.go +++ b/pkg/apis/pxc/v1/pxc_types_test.go @@ -261,3 +261,240 @@ func TestCheckNSetDefaults(t *testing.T) { assert.EqualError(t, cr.CheckNSetDefaults(nil, logf.FromContext(ctx)), ".spec.tls.certValidityDuration shouldn't be smaller than 1 hours") }) } + +func TestExtraPVCVolumeMounts(t *testing.T) { + ctx := t.Context() + + tests := map[string]struct { + extraPVCs []ExtraPVC + expected []corev1.VolumeMount + }{ + "nil extra PVCs": { + extraPVCs: nil, + expected: nil, + }, + "empty extra PVCs": { + extraPVCs: []ExtraPVC{}, + expected: nil, + }, + "single extra PVC without subPath and readOnly": { + extraPVCs: []ExtraPVC{ + { + Name: "extra-data", + ClaimName: "extra-storage-0", + MountPath: "/var/lib/mysql-extra", + }, + }, + expected: []corev1.VolumeMount{ + { + Name: "extra-data", + MountPath: "/var/lib/mysql-extra", + SubPath: "", + ReadOnly: false, + }, + }, + }, + "single extra PVC with subPath and readOnly": { + extraPVCs: []ExtraPVC{ + { + Name: "backup-volume", + ClaimName: "backup-storage-0", + MountPath: "/backups", + SubPath: "mysql", + ReadOnly: true, + }, + }, + expected: []corev1.VolumeMount{ + { + Name: "backup-volume", + MountPath: "/backups", + SubPath: "mysql", + ReadOnly: true, + }, + }, + }, + "multiple extra PVCs": { + extraPVCs: []ExtraPVC{ + { + Name: "extra-data", + ClaimName: "extra-storage-0", + MountPath: "/var/lib/mysql-extra", + }, + { + Name: "backup-volume", + ClaimName: "backup-storage-0", + MountPath: "/backups", + SubPath: "mysql", + ReadOnly: true, + }, + }, + expected: []corev1.VolumeMount{ + { + Name: "extra-data", + MountPath: "/var/lib/mysql-extra", + SubPath: "", + ReadOnly: false, + }, + { + Name: "backup-volume", + MountPath: "/backups", + SubPath: "mysql", + ReadOnly: true, + }, + }, + }, + "duplicate names - only first should be included": { + extraPVCs: []ExtraPVC{ + { + Name: "extra-data", + ClaimName: "extra-storage-0", + MountPath: "/var/lib/mysql-extra", + }, + { + Name: "extra-data", + ClaimName: "extra-storage-1", + MountPath: "/var/lib/mysql-extra2", + }, + }, + expected: []corev1.VolumeMount{ + { + Name: "extra-data", + MountPath: "/var/lib/mysql-extra", + SubPath: "", + ReadOnly: false, + }, + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + actual := ExtraPVCVolumeMounts(ctx, tc.extraPVCs) + assert.Equal(t, tc.expected, actual) + }) + } +} + +func TestValidateExtraPVCs(t *testing.T) { + tests := map[string]struct { + extraPVCs []ExtraPVC + errMsg string + }{ + "nil extra PVCs": { + extraPVCs: nil, + }, + "empty extra PVCs": { + extraPVCs: []ExtraPVC{}, + }, + "valid single extra PVC": { + extraPVCs: []ExtraPVC{ + { + Name: "extra-data", + ClaimName: "extra-storage-0", + MountPath: "/var/lib/mysql-extra", + }, + }, + }, + "valid multiple extra PVCs": { + extraPVCs: []ExtraPVC{ + { + Name: "extra-data", + ClaimName: "extra-storage-0", + MountPath: "/var/lib/mysql-extra", + }, + { + Name: "backup-volume", + ClaimName: "backup-storage-0", + MountPath: "/backups", + }, + }, + }, + "empty name": { + extraPVCs: []ExtraPVC{ + { + Name: "", + ClaimName: "extra-storage-0", + MountPath: "/var/lib/mysql-extra", + }, + }, + errMsg: "extraPVC: name cannot be empty", + }, + "empty claim name": { + extraPVCs: []ExtraPVC{ + { + Name: "extra-data", + ClaimName: "", + MountPath: "/var/lib/mysql-extra", + }, + }, + errMsg: "extraPVC extra-data: claimName cannot be empty", + }, + "empty mount path": { + extraPVCs: []ExtraPVC{ + { + Name: "extra-data", + ClaimName: "extra-storage-0", + MountPath: "", + }, + }, + errMsg: "extraPVC extra-data: mountPath cannot be empty", + }, + "duplicate volume name": { + extraPVCs: []ExtraPVC{ + { + Name: "extra-data", + ClaimName: "extra-storage-0", + MountPath: "/var/lib/mysql-extra", + }, + { + Name: "extra-data", + ClaimName: "extra-storage-1", + MountPath: "/var/lib/mysql-extra2", + }, + }, + errMsg: "extraPVC: duplicate volume name extra-data", + }, + "duplicate mount path": { + extraPVCs: []ExtraPVC{ + { + Name: "extra-data", + ClaimName: "extra-storage-0", + MountPath: "/var/lib/mysql-extra", + }, + { + Name: "backup-volume", + ClaimName: "backup-storage-0", + MountPath: "/var/lib/mysql-extra", + }, + }, + errMsg: "extraPVC backup-volume: duplicate mount path /var/lib/mysql-extra", + }, + "duplicate claim name": { + extraPVCs: []ExtraPVC{ + { + Name: "extra-data", + ClaimName: "extra-storage-0", + MountPath: "/var/lib/mysql-extra", + }, + { + Name: "backup-volume", + ClaimName: "extra-storage-0", + MountPath: "/backups", + }, + }, + errMsg: "extraPVC backup-volume: duplicate claim name extra-storage-0", + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + err := validateExtraPVCs(tc.extraPVCs) + if tc.errMsg != "" { + assert.Error(t, err) + assert.Contains(t, err.Error(), tc.errMsg) + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/pkg/pxc/app/statefulset/node_test.go b/pkg/pxc/app/statefulset/node_test.go index 08ad5c09ac..8ba331f883 100644 --- a/pkg/pxc/app/statefulset/node_test.go +++ b/pkg/pxc/app/statefulset/node_test.go @@ -165,6 +165,52 @@ func TestAppContainer(t *testing.T) { return c }, }, + "container construction with extra pvcs": { + spec: api.PerconaXtraDBClusterSpec{ + CRVersion: version.Version(), + PXC: &api.PXCSpec{ + PodSpec: &api.PodSpec{ + Image: "test-image", + ImagePullPolicy: corev1.PullIfNotPresent, + LivenessProbes: corev1.Probe{ + TimeoutSeconds: 5, + }, + ReadinessProbes: corev1.Probe{ + TimeoutSeconds: 15, + }, + EnvVarsSecretName: "test-secret", + ExtraPVCs: []api.ExtraPVC{ + { + Name: "extra-data-volume", + ClaimName: "extra-storage-0", + MountPath: "/var/lib/mysql-extra", + }, + { + Name: "backup-volume", + ClaimName: "backup-storage-0", + MountPath: "/backups", + SubPath: "mysql", + }, + }, + }, + }, + }, + expectedContainer: func() corev1.Container { + c := defaultExpectedContainer() + c.VolumeMounts = append(c.VolumeMounts, + corev1.VolumeMount{ + Name: "extra-data-volume", + MountPath: "/var/lib/mysql-extra", + }, + corev1.VolumeMount{ + Name: "backup-volume", + MountPath: "/backups", + SubPath: "mysql", + }, + ) + return c + }, + }, } for name, tt := range tests { From b967e24ec0b3f4df7ec8603927fb9f2f1bbd73f3 Mon Sep 17 00:00:00 2001 From: George Kechagias Date: Tue, 2 Dec 2025 12:38:56 +0200 Subject: [PATCH 05/12] update e2e test --- e2e-tests/extra-pvc/conf/extra-pvc.yml | 2 +- e2e-tests/run-distro.csv | 1 + e2e-tests/run-pr.csv | 1 + e2e-tests/run-release.csv | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/e2e-tests/extra-pvc/conf/extra-pvc.yml b/e2e-tests/extra-pvc/conf/extra-pvc.yml index 1096849566..d478082ff5 100644 --- a/e2e-tests/extra-pvc/conf/extra-pvc.yml +++ b/e2e-tests/extra-pvc/conf/extra-pvc.yml @@ -17,7 +17,7 @@ spec: containers: - name: populator image: busybox - command: ['sh', '-c', 'echo "Initial data" > /var/lib/mysql-extra/test-data.csv'] + command: ['sh', '-c', 'echo "Initial data" > /data/test-data.txt && sleep infinity'] volumeMounts: - name: data mountPath: /data diff --git a/e2e-tests/run-distro.csv b/e2e-tests/run-distro.csv index 1bc0a0dec5..471022002e 100644 --- a/e2e-tests/run-distro.csv +++ b/e2e-tests/run-distro.csv @@ -3,6 +3,7 @@ auto-tuning default-cr demand-backup-encrypted-with-tls custom-users +extra-pvc haproxy init-deploy one-pod diff --git a/e2e-tests/run-pr.csv b/e2e-tests/run-pr.csv index 59d3d08a83..9b17ae1927 100644 --- a/e2e-tests/run-pr.csv +++ b/e2e-tests/run-pr.csv @@ -9,6 +9,7 @@ demand-backup,8.0 demand-backup-flow-control,8.0 demand-backup-parallel,8.0 demand-backup-without-passwords,8.0 +extra-pvc haproxy,5.7 haproxy,8.0 init-deploy,5.7 diff --git a/e2e-tests/run-release.csv b/e2e-tests/run-release.csv index 14b265aa1a..0cbb1f74a9 100644 --- a/e2e-tests/run-release.csv +++ b/e2e-tests/run-release.csv @@ -11,6 +11,7 @@ demand-backup-parallel demand-backup-cloud demand-backup-encrypted-with-tls demand-backup-without-passwords +extra-pvc haproxy init-deploy limits From 6a9242f0a42904ad1e1686c839dde7f7f026b5ab Mon Sep 17 00:00:00 2001 From: Viacheslav Sarzhan Date: Tue, 2 Dec 2025 16:51:21 +0200 Subject: [PATCH 06/12] fix tests --- e2e-tests/run-pr.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e-tests/run-pr.csv b/e2e-tests/run-pr.csv index 9b17ae1927..073908cafe 100644 --- a/e2e-tests/run-pr.csv +++ b/e2e-tests/run-pr.csv @@ -9,7 +9,7 @@ demand-backup,8.0 demand-backup-flow-control,8.0 demand-backup-parallel,8.0 demand-backup-without-passwords,8.0 -extra-pvc +extra-pv,8.0 haproxy,5.7 haproxy,8.0 init-deploy,5.7 From c1106f6eb85ffcd758336659ce2db25a1f9f3a0c Mon Sep 17 00:00:00 2001 From: Viacheslav Sarzhan Date: Tue, 2 Dec 2025 17:27:47 +0200 Subject: [PATCH 07/12] Update run-pr.csv --- e2e-tests/run-pr.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e-tests/run-pr.csv b/e2e-tests/run-pr.csv index 073908cafe..c87dad18cd 100644 --- a/e2e-tests/run-pr.csv +++ b/e2e-tests/run-pr.csv @@ -9,7 +9,7 @@ demand-backup,8.0 demand-backup-flow-control,8.0 demand-backup-parallel,8.0 demand-backup-without-passwords,8.0 -extra-pv,8.0 +extra-pvc,8.0 haproxy,5.7 haproxy,8.0 init-deploy,5.7 From 56c95ac004be7dac20ac5a59fa4f4ed84cbe5d4c Mon Sep 17 00:00:00 2001 From: Viacheslav Sarzhan Date: Wed, 3 Dec 2025 22:44:09 +0200 Subject: [PATCH 08/12] K8SPXC-1688 fix extra-pvc test --- e2e-tests/extra-pvc/conf/some-name-with-extra-pvcs.yml | 5 +++-- e2e-tests/extra-pvc/conf/some-name.yml | 3 ++- e2e-tests/extra-pvc/run | 7 +++++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/e2e-tests/extra-pvc/conf/some-name-with-extra-pvcs.yml b/e2e-tests/extra-pvc/conf/some-name-with-extra-pvcs.yml index 970f6534fc..e18ef7109b 100644 --- a/e2e-tests/extra-pvc/conf/some-name-with-extra-pvcs.yml +++ b/e2e-tests/extra-pvc/conf/some-name-with-extra-pvcs.yml @@ -1,9 +1,10 @@ apiVersion: pxc.percona.com/v1 kind: PerconaXtraDBCluster metadata: - name: some-name + name: some-name-2 finalizers: - percona.com/delete-pxc-pods-in-order + - percona.com/delete-pxc-pvc spec: secretsName: my-cluster-secrets vaultSecretName: some-name-vault @@ -61,4 +62,4 @@ spec: accessModes: [ "ReadWriteOnce" ] resources: requests: - storage: 1G \ No newline at end of file + storage: 1G diff --git a/e2e-tests/extra-pvc/conf/some-name.yml b/e2e-tests/extra-pvc/conf/some-name.yml index 9b6ab5419f..c46e4de06f 100644 --- a/e2e-tests/extra-pvc/conf/some-name.yml +++ b/e2e-tests/extra-pvc/conf/some-name.yml @@ -4,6 +4,7 @@ metadata: name: some-name finalizers: - percona.com/delete-pxc-pods-in-order + - percona.com/delete-pxc-pvc spec: secretsName: my-cluster-secrets vaultSecretName: some-name-vault @@ -56,4 +57,4 @@ spec: accessModes: [ "ReadWriteOnce" ] resources: requests: - storage: 1G \ No newline at end of file + storage: 1G diff --git a/e2e-tests/extra-pvc/run b/e2e-tests/extra-pvc/run index 225889ad40..4f5e745e1b 100755 --- a/e2e-tests/extra-pvc/run +++ b/e2e-tests/extra-pvc/run @@ -182,9 +182,12 @@ for claim in extra-storage-1 extra-storage-2; do done echo "Extra PVCs successfully removed from StatefulSet" -desc 'test creating cluster with extra PVCs from start' cluster2="some-name-2" -kubectl_bin apply -f ${test_dir}/conf/some-name-with-extra-pvcs.yml +desc "Deleting ${cluster} and recreating PXC cluster ${cluster1}" +kubectl_bin delete pxc ${cluster} + +desc 'test creating cluster with extra PVCs from start' +spinup_pxc "${cluster2}" "$test_dir/conf/some-name-with-extra-pvcs.yml" "3" "10" wait_cluster_consistency "$cluster2" 3 2 desc 'verify extra PVC is configured in new cluster' From 3dbb4355bb6de3ed37f86b3d5238f8cab8595abc Mon Sep 17 00:00:00 2001 From: George Kechagias Date: Thu, 4 Dec 2025 10:28:14 +0100 Subject: [PATCH 09/12] fix wiring for proxysql and haproxy for extra pvc, update unit test --- pkg/pxc/app/statefulset/haproxy.go | 7 +- pkg/pxc/app/statefulset/haproxy_test.go | 164 +++++++++++++++++++++++ pkg/pxc/app/statefulset/proxysql.go | 5 +- pkg/pxc/app/statefulset/proxysql_test.go | 43 ++++++ 4 files changed, 217 insertions(+), 2 deletions(-) diff --git a/pkg/pxc/app/statefulset/haproxy.go b/pkg/pxc/app/statefulset/haproxy.go index c6d4c444c2..966e1803d7 100644 --- a/pkg/pxc/app/statefulset/haproxy.go +++ b/pkg/pxc/app/statefulset/haproxy.go @@ -46,7 +46,7 @@ func (c *HAProxy) InitContainers(cr *api.PerconaXtraDBCluster, initImageName str return inits } -func (c *HAProxy) AppContainer(_ context.Context, _ client.Client, spec *api.PodSpec, secrets string, cr *api.PerconaXtraDBCluster, +func (c *HAProxy) AppContainer(ctx context.Context, _ client.Client, spec *api.PodSpec, secrets string, cr *api.PerconaXtraDBCluster, _ []corev1.Volume, ) (corev1.Container, error) { appc := corev1.Container{ @@ -201,6 +201,11 @@ func (c *HAProxy) AppContainer(_ context.Context, _ client.Client, spec *api.Pod appc.Lifecycle = &cr.Spec.HAProxy.Lifecycle } + if cr.CompareVersionWith("1.19.0") >= 0 { + extraMounts := api.ExtraPVCVolumeMounts(ctx, spec.ExtraPVCs) + appc.VolumeMounts = append(appc.VolumeMounts, extraMounts...) + } + return appc, nil } diff --git a/pkg/pxc/app/statefulset/haproxy_test.go b/pkg/pxc/app/statefulset/haproxy_test.go index 6d52d06005..26ad6258df 100644 --- a/pkg/pxc/app/statefulset/haproxy_test.go +++ b/pkg/pxc/app/statefulset/haproxy_test.go @@ -4,12 +4,176 @@ import ( "reflect" "testing" + "github.com/stretchr/testify/assert" + api "github.com/percona/percona-xtradb-cluster-operator/pkg/apis/pxc/v1" + "github.com/percona/percona-xtradb-cluster-operator/pkg/pxc/app" + "github.com/percona/percona-xtradb-cluster-operator/pkg/test" "github.com/percona/percona-xtradb-cluster-operator/pkg/version" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +func TestAppContainer_HAProxy(t *testing.T) { + secretName := "my-secret" + + tests := map[string]struct { + spec api.PerconaXtraDBClusterSpec + expectedContainer func() corev1.Container + }{ + "latest cr container construction": { + spec: api.PerconaXtraDBClusterSpec{ + CRVersion: version.Version(), + HAProxy: &api.HAProxySpec{ + PodSpec: api.PodSpec{ + Image: "test-image", + ImagePullPolicy: corev1.PullIfNotPresent, + EnvVarsSecretName: "test-secret", + LivenessProbes: corev1.Probe{ + TimeoutSeconds: 5, + }, + ReadinessProbes: corev1.Probe{ + TimeoutSeconds: 15, + }, + }, + }, + PXC: &api.PXCSpec{ + PodSpec: &api.PodSpec{}, + }, + }, + expectedContainer: func() corev1.Container { + return defaultExpectedHAProxyContainer() + }, + }, + "container construction with extra pvcs": { + spec: api.PerconaXtraDBClusterSpec{ + CRVersion: version.Version(), + HAProxy: &api.HAProxySpec{ + PodSpec: api.PodSpec{ + Image: "test-image", + ImagePullPolicy: corev1.PullIfNotPresent, + EnvVarsSecretName: "test-secret", + LivenessProbes: corev1.Probe{ + TimeoutSeconds: 5, + }, + ReadinessProbes: corev1.Probe{ + TimeoutSeconds: 15, + }, + ExtraPVCs: []api.ExtraPVC{ + { + Name: "extra-data-volume", + ClaimName: "extra-storage-0", + MountPath: "/var/lib/haproxy-extra", + }, + { + Name: "backup-volume", + ClaimName: "backup-storage-0", + MountPath: "/backups", + SubPath: "haproxy", + }, + }, + }, + }, + PXC: &api.PXCSpec{ + PodSpec: &api.PodSpec{}, + }, + }, + expectedContainer: func() corev1.Container { + c := defaultExpectedHAProxyContainer() + c.VolumeMounts = append(c.VolumeMounts, + corev1.VolumeMount{ + Name: "extra-data-volume", + MountPath: "/var/lib/haproxy-extra", + }, + corev1.VolumeMount{ + Name: "backup-volume", + MountPath: "/backups", + SubPath: "haproxy", + }, + ) + return c + }, + }, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + cr := &api.PerconaXtraDBCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster", + Namespace: "test-ns", + UID: "test-uid", + }, + Spec: tt.spec, + } + + client := test.BuildFakeClient() + haproxy := &HAProxy{cr: cr} + + c, err := haproxy.AppContainer(t.Context(), client, &tt.spec.HAProxy.PodSpec, secretName, cr, nil) + assert.Equal(t, tt.expectedContainer(), c) + assert.NoError(t, err) + }) + } +} + +func defaultExpectedHAProxyContainer() corev1.Container { + fvar := true + return corev1.Container{ + Name: "haproxy", + Image: "test-image", + ImagePullPolicy: corev1.PullIfNotPresent, + Command: []string{"/opt/percona/haproxy-entrypoint.sh"}, + Args: []string{"haproxy"}, + Ports: []corev1.ContainerPort{ + {ContainerPort: 3306, Name: "mysql"}, + {ContainerPort: 3307, Name: "mysql-replicas"}, + {ContainerPort: 3309, Name: "proxy-protocol"}, + {ContainerPort: 33062, Name: "mysql-admin"}, + {ContainerPort: 33060, Name: "mysqlx"}, + {ContainerPort: 8404, Name: "stats"}, + }, + VolumeMounts: []corev1.VolumeMount{ + {Name: "haproxy-custom", MountPath: "/etc/haproxy-custom/"}, + {Name: "haproxy-auto", MountPath: "/etc/haproxy/pxc"}, + {Name: app.BinVolumeName, MountPath: app.BinVolumeMountPath}, + {Name: "mysql-users-secret-file", MountPath: "/etc/mysql/mysql-users-secret"}, + {Name: "test-secret", MountPath: "/etc/mysql/haproxy-env-secret"}, + }, + Env: []corev1.EnvVar{ + {Name: "PXC_SERVICE", Value: "test-cluster-pxc"}, + {Name: "LIVENESS_CHECK_TIMEOUT", Value: "5"}, + {Name: "READINESS_CHECK_TIMEOUT", Value: "15"}, + }, + EnvFrom: []corev1.EnvFromSource{ + { + SecretRef: &corev1.SecretEnvSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "test-secret", + }, + Optional: &fvar, + }, + }, + }, + ReadinessProbe: &corev1.Probe{ + TimeoutSeconds: 15, + ProbeHandler: corev1.ProbeHandler{ + Exec: &corev1.ExecAction{ + Command: []string{"/opt/percona/haproxy_readiness_check.sh"}, + }, + }, + }, + LivenessProbe: &corev1.Probe{ + TimeoutSeconds: 5, + ProbeHandler: corev1.ProbeHandler{ + Exec: &corev1.ExecAction{ + Command: []string{"/opt/percona/haproxy_liveness_check.sh"}, + }, + }, + }, + } +} + func TestSidecarContainers_HAProxy(t *testing.T) { tests := map[string]struct { spec api.PodSpec diff --git a/pkg/pxc/app/statefulset/proxysql.go b/pkg/pxc/app/statefulset/proxysql.go index 366283c1f8..dc3fac9993 100644 --- a/pkg/pxc/app/statefulset/proxysql.go +++ b/pkg/pxc/app/statefulset/proxysql.go @@ -57,7 +57,7 @@ func proxyInitContainers(cr *api.PerconaXtraDBCluster, initImageName string) []c return inits } -func (c *Proxy) AppContainer(_ context.Context, _ client.Client, spec *api.PodSpec, secrets string, cr *api.PerconaXtraDBCluster, +func (c *Proxy) AppContainer(ctx context.Context, _ client.Client, spec *api.PodSpec, secrets string, cr *api.PerconaXtraDBCluster, availableVolumes []corev1.Volume, ) (corev1.Container, error) { appc := corev1.Container{ @@ -220,6 +220,9 @@ func (c *Proxy) AppContainer(_ context.Context, _ client.Client, spec *api.PodSp Value: "true", }) } + + extraMounts := api.ExtraPVCVolumeMounts(ctx, spec.ExtraPVCs) + appc.VolumeMounts = append(appc.VolumeMounts, extraMounts...) } return appc, nil diff --git a/pkg/pxc/app/statefulset/proxysql_test.go b/pkg/pxc/app/statefulset/proxysql_test.go index 4b0a3a2778..4ad7b8b23a 100644 --- a/pkg/pxc/app/statefulset/proxysql_test.go +++ b/pkg/pxc/app/statefulset/proxysql_test.go @@ -100,6 +100,49 @@ func TestAppContainer_ProxySQL(t *testing.T) { return c }, }, + "container construction with extra pvcs": { + spec: api.PerconaXtraDBClusterSpec{ + CRVersion: version.Version(), + ProxySQL: &api.ProxySQLSpec{ + PodSpec: api.PodSpec{ + Image: "test-image", + ImagePullPolicy: corev1.PullIfNotPresent, + EnvVarsSecretName: "test-secret", + ExtraPVCs: []api.ExtraPVC{ + { + Name: "extra-data-volume", + ClaimName: "extra-storage-0", + MountPath: "/var/lib/proxysql-extra", + }, + { + Name: "backup-volume", + ClaimName: "backup-storage-0", + MountPath: "/backups", + SubPath: "proxysql", + }, + }, + }, + }, + PXC: &api.PXCSpec{ + PodSpec: &api.PodSpec{}, + }, + }, + expectedContainer: func() corev1.Container { + c := defaultExpectedProxySQLContainer() + c.VolumeMounts = append(c.VolumeMounts, + corev1.VolumeMount{ + Name: "extra-data-volume", + MountPath: "/var/lib/proxysql-extra", + }, + corev1.VolumeMount{ + Name: "backup-volume", + MountPath: "/backups", + SubPath: "proxysql", + }, + ) + return c + }, + }, } for name, tt := range tests { From 991eabeff39f8781dbb7f6bddb8f578d7fc5c644 Mon Sep 17 00:00:00 2001 From: George Kechagias Date: Thu, 4 Dec 2025 10:35:05 +0100 Subject: [PATCH 10/12] move validations to cr level --- ...pxc.percona.com_perconaxtradbclusters.yaml | 9 ++++++ deploy/bundle.yaml | 9 ++++++ deploy/crd.yaml | 9 ++++++ deploy/cw-bundle.yaml | 9 ++++++ pkg/apis/pxc/v1/pxc_types.go | 13 ++------ pkg/apis/pxc/v1/pxc_types_test.go | 30 ------------------- 6 files changed, 39 insertions(+), 40 deletions(-) diff --git a/config/crd/bases/pxc.percona.com_perconaxtradbclusters.yaml b/config/crd/bases/pxc.percona.com_perconaxtradbclusters.yaml index 819a478f37..568d29de2c 100644 --- a/config/crd/bases/pxc.percona.com_perconaxtradbclusters.yaml +++ b/config/crd/bases/pxc.percona.com_perconaxtradbclusters.yaml @@ -1753,10 +1753,13 @@ spec: items: properties: claimName: + minLength: 1 type: string mountPath: + minLength: 1 type: string name: + minLength: 1 type: string readOnly: type: boolean @@ -5337,10 +5340,13 @@ spec: items: properties: claimName: + minLength: 1 type: string mountPath: + minLength: 1 type: string name: + minLength: 1 type: string readOnly: type: boolean @@ -8377,10 +8383,13 @@ spec: items: properties: claimName: + minLength: 1 type: string mountPath: + minLength: 1 type: string name: + minLength: 1 type: string readOnly: type: boolean diff --git a/deploy/bundle.yaml b/deploy/bundle.yaml index 22febcdb23..1b32aac8cf 100644 --- a/deploy/bundle.yaml +++ b/deploy/bundle.yaml @@ -3060,10 +3060,13 @@ spec: items: properties: claimName: + minLength: 1 type: string mountPath: + minLength: 1 type: string name: + minLength: 1 type: string readOnly: type: boolean @@ -6644,10 +6647,13 @@ spec: items: properties: claimName: + minLength: 1 type: string mountPath: + minLength: 1 type: string name: + minLength: 1 type: string readOnly: type: boolean @@ -9684,10 +9690,13 @@ spec: items: properties: claimName: + minLength: 1 type: string mountPath: + minLength: 1 type: string name: + minLength: 1 type: string readOnly: type: boolean diff --git a/deploy/crd.yaml b/deploy/crd.yaml index c20a69c047..f8dfb4e373 100644 --- a/deploy/crd.yaml +++ b/deploy/crd.yaml @@ -3060,10 +3060,13 @@ spec: items: properties: claimName: + minLength: 1 type: string mountPath: + minLength: 1 type: string name: + minLength: 1 type: string readOnly: type: boolean @@ -6644,10 +6647,13 @@ spec: items: properties: claimName: + minLength: 1 type: string mountPath: + minLength: 1 type: string name: + minLength: 1 type: string readOnly: type: boolean @@ -9684,10 +9690,13 @@ spec: items: properties: claimName: + minLength: 1 type: string mountPath: + minLength: 1 type: string name: + minLength: 1 type: string readOnly: type: boolean diff --git a/deploy/cw-bundle.yaml b/deploy/cw-bundle.yaml index 0c7dfc6095..af159c40f1 100644 --- a/deploy/cw-bundle.yaml +++ b/deploy/cw-bundle.yaml @@ -3060,10 +3060,13 @@ spec: items: properties: claimName: + minLength: 1 type: string mountPath: + minLength: 1 type: string name: + minLength: 1 type: string readOnly: type: boolean @@ -6644,10 +6647,13 @@ spec: items: properties: claimName: + minLength: 1 type: string mountPath: + minLength: 1 type: string name: + minLength: 1 type: string readOnly: type: boolean @@ -9684,10 +9690,13 @@ spec: items: properties: claimName: + minLength: 1 type: string mountPath: + minLength: 1 type: string name: + minLength: 1 type: string readOnly: type: boolean diff --git a/pkg/apis/pxc/v1/pxc_types.go b/pkg/apis/pxc/v1/pxc_types.go index 6af4754a12..da290ab486 100644 --- a/pkg/apis/pxc/v1/pxc_types.go +++ b/pkg/apis/pxc/v1/pxc_types.go @@ -941,14 +941,17 @@ type VolumeSpec struct { type ExtraPVC struct { // Name of the volume as it will be referenced in the pod // +kubebuilder:validation:Required + // +kubebuilder:validation:MinLength=1 Name string `json:"name"` // ClaimName is the name of the existing PersistentVolumeClaim to mount // +kubebuilder:validation:Required + // +kubebuilder:validation:MinLength=1 ClaimName string `json:"claimName"` // MountPath is the path inside the container where the volume will be mounted // +kubebuilder:validation:Required + // +kubebuilder:validation:MinLength=1 MountPath string `json:"mountPath"` // SubPath within the volume from which the container's volume should be mounted @@ -1790,16 +1793,6 @@ func validateExtraPVCs(extraPVCs []ExtraPVC) error { mountPaths := make(map[string]struct{}, len(extraPVCs)) for _, epvc := range extraPVCs { - if epvc.Name == "" { - return errors.New("extraPVC: name cannot be empty") - } - if epvc.ClaimName == "" { - return errors.Errorf("extraPVC %s: claimName cannot be empty", epvc.Name) - } - if epvc.MountPath == "" { - return errors.Errorf("extraPVC %s: mountPath cannot be empty", epvc.Name) - } - if _, ok := names[epvc.Name]; ok { return errors.Errorf("extraPVC: duplicate volume name %s", epvc.Name) } diff --git a/pkg/apis/pxc/v1/pxc_types_test.go b/pkg/apis/pxc/v1/pxc_types_test.go index c68c0671b3..d28293d233 100644 --- a/pkg/apis/pxc/v1/pxc_types_test.go +++ b/pkg/apis/pxc/v1/pxc_types_test.go @@ -409,36 +409,6 @@ func TestValidateExtraPVCs(t *testing.T) { }, }, }, - "empty name": { - extraPVCs: []ExtraPVC{ - { - Name: "", - ClaimName: "extra-storage-0", - MountPath: "/var/lib/mysql-extra", - }, - }, - errMsg: "extraPVC: name cannot be empty", - }, - "empty claim name": { - extraPVCs: []ExtraPVC{ - { - Name: "extra-data", - ClaimName: "", - MountPath: "/var/lib/mysql-extra", - }, - }, - errMsg: "extraPVC extra-data: claimName cannot be empty", - }, - "empty mount path": { - extraPVCs: []ExtraPVC{ - { - Name: "extra-data", - ClaimName: "extra-storage-0", - MountPath: "", - }, - }, - errMsg: "extraPVC extra-data: mountPath cannot be empty", - }, "duplicate volume name": { extraPVCs: []ExtraPVC{ { From 62a77289edd023307c0b7055ba70464be688de55 Mon Sep 17 00:00:00 2001 From: George Kechagias Date: Thu, 4 Dec 2025 11:13:55 +0100 Subject: [PATCH 11/12] cr changes --- e2e-tests/extra-pvc/run | 36 ++++++++++++++++++------------------ pkg/apis/pxc/v1/pxc_types.go | 7 +++++-- pkg/pxc/statefulset.go | 2 +- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/e2e-tests/extra-pvc/run b/e2e-tests/extra-pvc/run index 4f5e745e1b..c076647010 100755 --- a/e2e-tests/extra-pvc/run +++ b/e2e-tests/extra-pvc/run @@ -8,13 +8,13 @@ test_dir=$(realpath $(dirname $0)) function create_extra_pvcs() { local ns=$1 - echo "Creating extra PVCs in namespace ${ns}" + log "Creating extra PVCs in namespace ${ns}" kubectl_bin apply -f ${test_dir}/conf/extra-pvc.yml -n ${ns} - echo "Waiting for source PVC to be bound" + log "Waiting for source PVC to be bound" kubectl_bin wait --for=jsonpath='{.status.phase}'=Bound pvc/extra-storage-source -n ${ns} --timeout=60s - echo "Waiting for populator pod to be ready" + log "Waiting for populator pod to be ready" kubectl_bin wait --for=condition=Ready pod/pvc-populator -n ${ns} --timeout=60s } @@ -22,14 +22,14 @@ function verify_extra_pvc_mounted() { local pod=$1 local mount_path=$2 - echo "Verifying extra PVC is mounted in pod ${pod} at ${mount_path}" + log "Verifying extra PVC is mounted in pod ${pod} at ${mount_path}" if ! kubectl_bin exec ${pod} -c pxc -- test -d ${mount_path}; then - echo "Mount path ${mount_path} does not exist in pod ${pod}" + log "Mount path ${mount_path} does not exist in pod ${pod}" exit 1 fi - echo "Mount path ${mount_path} exists in pod ${pod}" + log "Mount path ${mount_path} exists in pod ${pod}" } function verify_extra_pvc_in_statefulset() { @@ -37,35 +37,35 @@ function verify_extra_pvc_in_statefulset() { local expected_claim_name=$2 local expected_mount_path=$3 - echo "Verifying extra PVC configuration in StatefulSet ${cluster}-pxc" + log "Verifying extra PVC configuration in StatefulSet ${cluster}-pxc" local volume_exists=$(kubectl_bin get statefulset ${cluster}-pxc -o json \ | jq --arg claim "${expected_claim_name}" '.spec.template.spec.volumes[] | select(.persistentVolumeClaim.claimName == $claim) | .name' -r) if [[ -z $volume_exists ]]; then - echo "Volume with claimName ${expected_claim_name} not found in StatefulSet" + log "Volume with claimName ${expected_claim_name} not found in StatefulSet" kubectl_bin get statefulset ${cluster}-pxc -o yaml exit 1 fi - echo "Volume ${volume_exists} with claimName ${expected_claim_name} found in StatefulSet" + log "Volume ${volume_exists} with claimName ${expected_claim_name} found in StatefulSet" local mount_exists=$(kubectl_bin get statefulset ${cluster}-pxc -o json \ | jq --arg path "${expected_mount_path}" '.spec.template.spec.containers[] | select(.name == "pxc") | .volumeMounts[] | select(.mountPath == $path) | .name' -r) if [[ -z $mount_exists ]]; then - echo "Volume mount with mountPath ${expected_mount_path} not found in pxc container" + log "Volume mount with mountPath ${expected_mount_path} not found in pxc container" kubectl_bin get statefulset ${cluster}-pxc -o yaml exit 1 fi - echo "Volume mount ${mount_exists} with mountPath ${expected_mount_path} found in pxc container" + log "Volume mount ${mount_exists} with mountPath ${expected_mount_path} found in pxc container" } function patch_cluster_add_extra_pvcs() { local cluster=$1 - echo "Patching cluster ${cluster} to add extraPVCs" + log "Patching cluster ${cluster} to add extraPVCs" kubectl_bin patch pxc ${cluster} --type=json -p='[ { @@ -86,7 +86,7 @@ function patch_cluster_add_extra_pvcs() { function patch_cluster_remove_extra_pvcs() { local cluster=$1 - echo "Patching cluster ${cluster} to remove extraPVCs" + log "Patching cluster ${cluster} to remove extraPVCs" kubectl_bin patch pxc ${cluster} --type=json -p='[ { @@ -99,7 +99,7 @@ function patch_cluster_remove_extra_pvcs() { function patch_cluster_add_multiple_extra_pvcs() { local cluster=$1 - echo "Patching cluster ${cluster} to add multiple extraPVCs" + log "Patching cluster ${cluster} to add multiple extraPVCs" kubectl_bin patch pxc ${cluster} --type=json -p='[ { @@ -159,10 +159,10 @@ desc 'verify multiple extra PVCs are mounted' for i in 0 1 2; do verify_extra_pvc_mounted "${cluster}-pxc-${i}" "/var/lib/mysql-extra-1" if kubectl_bin exec ${cluster}-pxc-${i} -c pxc -- touch /var/lib/mysql-extra-2/test-write 2>/dev/null; then - echo "Read-only mount /var/lib/mysql-extra-2 is writable in pod ${cluster}-pxc-${i}" + log "Read-only mount /var/lib/mysql-extra-2 is writable in pod ${cluster}-pxc-${i}" exit 1 fi - echo "Read-only mount /var/lib/mysql-extra-2 is properly read-only in pod ${cluster}-pxc-${i}" + log "Read-only mount /var/lib/mysql-extra-2 is properly read-only in pod ${cluster}-pxc-${i}" done desc 'test removing extra PVCs from cluster' @@ -176,11 +176,11 @@ for claim in extra-storage-1 extra-storage-2; do | jq --arg claim "${claim}" '.spec.template.spec.volumes[] | select(.persistentVolumeClaim.claimName == $claim) | .name' -r) if [[ -n $volume_exists ]]; then - echo "Volume with claimName ${claim} still exists in StatefulSet after removal" + log "Volume with claimName ${claim} still exists in StatefulSet after removal" exit 1 fi done -echo "Extra PVCs successfully removed from StatefulSet" +log "Extra PVCs successfully removed from StatefulSet" cluster2="some-name-2" desc "Deleting ${cluster} and recreating PXC cluster ${cluster1}" diff --git a/pkg/apis/pxc/v1/pxc_types.go b/pkg/apis/pxc/v1/pxc_types.go index da290ab486..f6c1001fa4 100644 --- a/pkg/apis/pxc/v1/pxc_types.go +++ b/pkg/apis/pxc/v1/pxc_types.go @@ -21,6 +21,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" + ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" @@ -1724,7 +1725,9 @@ func AddSidecarPVCs(log logr.Logger, existing, sidecarPVCs []corev1.PersistentVo // ExtraPVCVolumes generates Kubernetes volumes from ExtraPVC configurations. // Each ExtraPVC references an existing PersistentVolumeClaim by name. -func ExtraPVCVolumes(log logr.Logger, extraPVCs []ExtraPVC) []corev1.Volume { +func ExtraPVCVolumes(ctx context.Context, extraPVCs []ExtraPVC) []corev1.Volume { + logger := ctrl.LoggerFrom(ctx) + if len(extraPVCs) == 0 { return nil } @@ -1734,7 +1737,7 @@ func ExtraPVCVolumes(log logr.Logger, extraPVCs []ExtraPVC) []corev1.Volume { for _, epvc := range extraPVCs { if _, ok := names[epvc.Name]; ok { - log.Info("Duplicate extra PVC volume name, skipping", "volumeName", epvc.Name) + logger.Info("Duplicate extra PVC volume name, skipping", "volumeName", epvc.Name) continue } names[epvc.Name] = struct{}{} diff --git a/pkg/pxc/statefulset.go b/pkg/pxc/statefulset.go index af33b3251c..2f0905ffdc 100644 --- a/pkg/pxc/statefulset.go +++ b/pkg/pxc/statefulset.go @@ -97,7 +97,7 @@ func StatefulSet( pod.Volumes = api.AddSidecarVolumes(log, pod.Volumes, podSpec.SidecarVolumes) if cr.CompareVersionWith("1.19.0") >= 0 { - extraVolumes := api.ExtraPVCVolumes(log, podSpec.ExtraPVCs) + extraVolumes := api.ExtraPVCVolumes(ctx, podSpec.ExtraPVCs) pod.Volumes = append(pod.Volumes, extraVolumes...) } From ee0ffc971bd5f584147eb638a5bbf8ad706998db Mon Sep 17 00:00:00 2001 From: George Kechagias Date: Thu, 4 Dec 2025 11:42:49 +0100 Subject: [PATCH 12/12] fix logger --- pkg/apis/pxc/v1/pxc_types.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/apis/pxc/v1/pxc_types.go b/pkg/apis/pxc/v1/pxc_types.go index f6c1001fa4..0537b492f7 100644 --- a/pkg/apis/pxc/v1/pxc_types.go +++ b/pkg/apis/pxc/v1/pxc_types.go @@ -21,7 +21,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" - ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" @@ -1726,7 +1725,7 @@ func AddSidecarPVCs(log logr.Logger, existing, sidecarPVCs []corev1.PersistentVo // ExtraPVCVolumes generates Kubernetes volumes from ExtraPVC configurations. // Each ExtraPVC references an existing PersistentVolumeClaim by name. func ExtraPVCVolumes(ctx context.Context, extraPVCs []ExtraPVC) []corev1.Volume { - logger := ctrl.LoggerFrom(ctx) + logger := log.FromContext(ctx) if len(extraPVCs) == 0 { return nil