From dfd743fe22e97ddf2a8443125ba94a425aeec43f Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Thu, 6 Mar 2025 13:13:40 +0530 Subject: [PATCH 01/23] add field for disabling volume expansion Signed-off-by: Mayank Shah --- api/v1alpha1/databasecluster_types.go | 21 +++++++++++++++++++ .../everest.percona.com_databaseclusters.yaml | 7 +++++++ 2 files changed, 28 insertions(+) diff --git a/api/v1alpha1/databasecluster_types.go b/api/v1alpha1/databasecluster_types.go index 7ea3e2bfc..9d6db0266 100644 --- a/api/v1alpha1/databasecluster_types.go +++ b/api/v1alpha1/databasecluster_types.go @@ -122,6 +122,11 @@ type Storage struct { Size resource.Quantity `json:"size"` // Class is the storage class to use for the persistent volume claim Class *string `json:"class,omitempty"` + // DisableVolumeExpansion controls whether the storage size of an existing cluster can be increased. + // When set to true, the storage size cannot be modified after creation. + // By default, this is false, allowing the storage size to be increased by specifying a new size value. + // Note: Volume expansion requires support from the underlying storage class. + DisableVolumeExpansion bool `json:"disableVolumeExpansion,omitempty"` } // Resources are the resource requirements. @@ -354,6 +359,20 @@ type DatabaseClusterSpec struct { Sharding *Sharding `json:"sharding,omitempty"` } +const ( + // ConditionTypeCannotExpandStorage is a condition type that indicates that the storage cannot be expanded. + ConditionTypeCannotExpandStorage = "CannotExpandStorage" +) + +const ( + // ReasonStorageClassDoesNotSupportExpansion is a reason for condition ConditionTypeCannotExpandStorage + // when the storage class does not support volume expansion. + ReasonStorageClassDoesNotSupportExpansion = "StorageClassDoesNotSupportExpansion" + // ReasonStorageExpasionDisabled is a reason for condition ConditionTypeCannotExpandStorage + // when the storage expansion is disabled for the database cluster. + ReasonStorageExpasionDisabled = "StorageExpasionDisabled" +) + // DatabaseClusterStatus defines the observed state of DatabaseCluster. type DatabaseClusterStatus struct { // ObservedGeneration is the most recent generation observed for this DatabaseCluster. @@ -380,6 +399,8 @@ type DatabaseClusterStatus struct { RecommendedCRVersion *string `json:"recommendedCRVersion,omitempty"` // Details provides full status of the upstream cluster as a plain text. Details string `json:"details,omitempty"` + // Conditions contains the observed conditions of the DatabaseCluster. + Conditions []metav1.Condition `json:"conditions,omitempty"` } //+kubebuilder:object:root=true diff --git a/config/crd/bases/everest.percona.com_databaseclusters.yaml b/config/crd/bases/everest.percona.com_databaseclusters.yaml index 86e799609..9eb74e2b9 100644 --- a/config/crd/bases/everest.percona.com_databaseclusters.yaml +++ b/config/crd/bases/everest.percona.com_databaseclusters.yaml @@ -209,6 +209,13 @@ spec: description: Class is the storage class to use for the persistent volume claim type: string + disableVolumeExpansion: + description: |- + DisableVolumeExpansion controls whether the storage size of an existing cluster can be increased. + When set to true, the storage size cannot be modified after creation. + By default, this is false, allowing the storage size to be increased by specifying a new size value. + Note: Volume expansion requires support from the underlying storage class. + type: boolean size: anyOf: - type: integer From 95f93e3340b3e49a2c3f986cfe3b48ec317637f3 Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Thu, 6 Mar 2025 13:13:54 +0530 Subject: [PATCH 02/23] implement storage scaling Signed-off-by: Mayank Shah --- internal/controller/common/helper.go | 34 +++++++++ internal/controller/providers/pxc/applier.go | 74 +++++++++++++++++--- 2 files changed, 99 insertions(+), 9 deletions(-) diff --git a/internal/controller/common/helper.go b/internal/controller/common/helper.go index 6189c80e2..71f9ff4ac 100644 --- a/internal/controller/common/helper.go +++ b/internal/controller/common/helper.go @@ -793,3 +793,37 @@ func toUnstructured(obj runtime.Object) (*unstructured.Unstructured, error) { } return ud, nil } + +const ( + storageClassDefaultAnnotation = "storageclass.kubernetes.io/is-default-class" +) + +// StorageClassSupportsVolumeExpansion returns true if the storage class supports volume expansion. +// If className is unspecified, uses the default storage class +func StorageClassSupportsVolumeExpansion(c client.Client, ctx context.Context, className *string) (bool, error) { + storageClass, err := getStorageClassOrDefault(c, ctx, className) + if err != nil { + return false, fmt.Errorf("getStorageClassOrDefault failed: %w", err) + } + return *storageClass.AllowVolumeExpansion, nil +} + +func getStorageClassOrDefault(c client.Client, ctx context.Context, scName *string) (*storagev1.StorageClass, error) { + storageClass := &storagev1.StorageClass{} + if scName == nil { + storageClasses := &storagev1.StorageClassList{} + if err := c.List(ctx, storageClasses); err != nil { + return nil, err + } + for _, sc := range storageClasses.Items { + if sc.Annotations[storageClassDefaultAnnotation] == "true" { + return &sc, nil + } + } + return nil, fmt.Errorf("no default storage class found") + } + if err := c.Get(ctx, types.NamespacedName{Name: *scName}, storageClass); err != nil { + return nil, err + } + return storageClass, nil +} diff --git a/internal/controller/providers/pxc/applier.go b/internal/controller/providers/pxc/applier.go index 99d66d024..dcfc79533 100644 --- a/internal/controller/providers/pxc/applier.go +++ b/internal/controller/providers/pxc/applier.go @@ -25,10 +25,12 @@ import ( pxcv1 "github.com/percona/percona-xtradb-cluster-operator/pkg/apis/pxc/v1" corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" everestv1alpha1 "github.com/percona/everest-operator/api/v1alpha1" @@ -73,6 +75,63 @@ func (p *applier) AllowUnsafeConfig() { } } +func configureStorage( + c client.Client, + ctx context.Context, + desired *pxcv1.PerconaXtraDBClusterSpec, + current *pxcv1.PerconaXtraDBClusterSpec, + db *everestv1alpha1.DatabaseCluster, +) error { + meta.RemoveStatusCondition(&db.Status.Conditions, everestv1alpha1.ConditionTypeCannotExpandStorage) + + var currentSize resource.Quantity + desiredSize := db.Spec.Engine.Storage.Size + + if db.Status.Status != everestv1alpha1.AppStateUnknown { + currentSize = current.PXC.PodSpec.VolumeSpec.PersistentVolumeClaim.Resources.Requests[corev1.ResourceStorage] + } + + hasStorageExpanded := currentSize.Cmp(desiredSize) < 0 && !currentSize.IsZero() + allowedByStorageClass, err := common.StorageClassSupportsVolumeExpansion(c, ctx, db.Spec.Engine.Storage.Class) + if err != nil { + return fmt.Errorf("failed to check if storage class supports volume expansion: %w", err) + } + + if hasStorageExpanded && db.Spec.Engine.Storage.DisableVolumeExpansion { + meta.SetStatusCondition(&db.Status.Conditions, metav1.Condition{ + Type: everestv1alpha1.ConditionTypeCannotExpandStorage, + Status: metav1.ConditionTrue, + Reason: everestv1alpha1.ReasonStorageExpasionDisabled, + LastTransitionTime: metav1.Now(), + ObservedGeneration: db.GetGeneration(), + }) + desiredSize = currentSize + } + + if hasStorageExpanded && !allowedByStorageClass { + meta.SetStatusCondition(&db.Status.Conditions, metav1.Condition{ + Type: everestv1alpha1.ConditionTypeCannotExpandStorage, + Status: metav1.ConditionTrue, + Reason: everestv1alpha1.ReasonStorageClassDoesNotSupportExpansion, + LastTransitionTime: metav1.Now(), + ObservedGeneration: db.GetGeneration(), + }) + desiredSize = currentSize + } + + desired.PXC.PodSpec.VolumeSpec = &pxcv1.VolumeSpec{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimSpec{ + StorageClassName: db.Spec.Engine.Storage.Class, + Resources: corev1.VolumeResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceStorage: desiredSize, + }, + }, + }, + } + return nil +} + func (p *applier) Engine() error { engine := p.DBEngine if p.DB.Spec.Engine.Version == "" { @@ -97,15 +156,12 @@ func (p *applier) Engine() error { } pxc.Spec.PXC.Image = pxcEngineVersion.ImagePath - pxc.Spec.PXC.PodSpec.VolumeSpec = &pxcv1.VolumeSpec{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimSpec{ - StorageClassName: p.DB.Spec.Engine.Storage.Class, - Resources: corev1.VolumeResourceRequirements{ - Requests: corev1.ResourceList{ - corev1.ResourceStorage: p.DB.Spec.Engine.Storage.Size, - }, - }, - }, + if !p.DB.Spec.Engine.Storage.DisableVolumeExpansion { + pxc.Spec.VolumeExpansionEnabled = true + } + + if err := configureStorage(p.C, p.ctx, &pxc.Spec, &p.currentPerconaXtraDBClusterSpec, p.DB); err != nil { + return err } if !p.DB.Spec.Engine.Resources.CPU.IsZero() { From 97ef5ee626bfff89957142c40f9169b4557b9fd0 Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Thu, 6 Mar 2025 14:10:47 +0530 Subject: [PATCH 03/23] run code gen Signed-off-by: Mayank Shah --- api/v1alpha1/zz_generated.deepcopy.go | 8 +++ .../everest.percona.com_databaseclusters.yaml | 57 +++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 01571074c..dd94c9f9a 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -20,6 +20,7 @@ package v1alpha1 import ( + "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -585,6 +586,13 @@ func (in *DatabaseClusterStatus) DeepCopyInto(out *DatabaseClusterStatus) { *out = new(string) **out = **in } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatabaseClusterStatus. diff --git a/config/crd/bases/everest.percona.com_databaseclusters.yaml b/config/crd/bases/everest.percona.com_databaseclusters.yaml index 9eb74e2b9..1ff807951 100644 --- a/config/crd/bases/everest.percona.com_databaseclusters.yaml +++ b/config/crd/bases/everest.percona.com_databaseclusters.yaml @@ -416,6 +416,63 @@ spec: activeStorage: description: ActiveStorage is the storage used in cluster (psmdb only) type: string + conditions: + description: Conditions contains the observed conditions of the DatabaseCluster. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array crVersion: description: CRVersion is the observed version of the CR used with the underlying operator. From daf3155275216cc25a9c02c988699d3c1f90babb Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Thu, 6 Mar 2025 14:10:55 +0530 Subject: [PATCH 04/23] fix check Signed-off-by: Mayank Shah --- internal/controller/providers/pxc/applier.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/controller/providers/pxc/applier.go b/internal/controller/providers/pxc/applier.go index dcfc79533..bec384a24 100644 --- a/internal/controller/providers/pxc/applier.go +++ b/internal/controller/providers/pxc/applier.go @@ -87,7 +87,7 @@ func configureStorage( var currentSize resource.Quantity desiredSize := db.Spec.Engine.Storage.Size - if db.Status.Status != everestv1alpha1.AppStateUnknown { + if db.Status.Status != everestv1alpha1.AppStateNew { currentSize = current.PXC.PodSpec.VolumeSpec.PersistentVolumeClaim.Resources.Requests[corev1.ResourceStorage] } From f7f9c3255c88190c390492f0b2fc629469a3fa6c Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Thu, 6 Mar 2025 14:37:03 +0530 Subject: [PATCH 05/23] add status type Signed-off-by: Mayank Shah --- api/v1alpha1/databasecluster_types.go | 2 ++ internal/controller/providers/pxc/provider.go | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/api/v1alpha1/databasecluster_types.go b/api/v1alpha1/databasecluster_types.go index 9d6db0266..d5f88e1ed 100644 --- a/api/v1alpha1/databasecluster_types.go +++ b/api/v1alpha1/databasecluster_types.go @@ -49,6 +49,8 @@ const ( AppStateRestoring AppState = "restoring" // AppStateDeleting is a deleting state. AppStateDeleting AppState = "deleting" + // AppStateResizingVolumes is the state when PVCs are being resized. + AppStateResizingVolumes = "resizingVolumes" // AppStateNew represents a newly created cluster that has not yet been reconciled. AppStateNew AppState = "" diff --git a/internal/controller/providers/pxc/provider.go b/internal/controller/providers/pxc/provider.go index d505889f0..682fca703 100644 --- a/internal/controller/providers/pxc/provider.go +++ b/internal/controller/providers/pxc/provider.go @@ -220,6 +220,12 @@ func (p *Provider) Status(ctx context.Context) (everestv1alpha1.DatabaseClusterS return status, err } status.RecommendedCRVersion = recCRVer + + annotations := pxc.GetAnnotations() + _, pvcResizing := annotations[pxcv1.AnnotationPVCResizeInProgress] + if pvcResizing { + status.Status = everestv1alpha1.AppStateResizingVolumes + } return status, nil } From 7b93834ef09200898236e2044f50837d78cf7a6b Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Thu, 6 Mar 2025 14:37:11 +0530 Subject: [PATCH 06/23] bug fix with status Signed-off-by: Mayank Shah --- internal/controller/databasecluster_controller.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/controller/databasecluster_controller.go b/internal/controller/databasecluster_controller.go index 66a5e4d70..aeea01c3b 100644 --- a/internal/controller/databasecluster_controller.go +++ b/internal/controller/databasecluster_controller.go @@ -180,10 +180,10 @@ func (r *DatabaseClusterReconciler) reconcileDB( // Running the applier can possibly also mutate the DatabaseCluster, // so we should make sure we push those changes to the API server. - updatedDB := db.DeepCopy() - if _, err := controllerutil.CreateOrUpdate(ctx, r.Client, db, func() error { - db.ObjectMeta = updatedDB.ObjectMeta - db.Spec = updatedDB.Spec + dbCopy := db.DeepCopy() + if _, err := controllerutil.CreateOrUpdate(ctx, r.Client, dbCopy, func() error { + dbCopy.ObjectMeta = db.ObjectMeta + dbCopy.Spec = db.Spec return nil }); err != nil { return ctrl.Result{}, err From c3d5a1c2eb7670f0813b49674e838f1665151881 Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Thu, 6 Mar 2025 15:42:36 +0530 Subject: [PATCH 07/23] formatting Signed-off-by: Mayank Shah --- api/v1alpha1/zz_generated.deepcopy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index dd94c9f9a..4bfec509e 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -20,7 +20,7 @@ package v1alpha1 import ( - "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) From 156b814fffb9ba69ac50e1ad2f6515a6a371f459 Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Fri, 7 Mar 2025 12:30:44 +0530 Subject: [PATCH 08/23] typos Signed-off-by: Mayank Shah --- api/v1alpha1/databasecluster_types.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/v1alpha1/databasecluster_types.go b/api/v1alpha1/databasecluster_types.go index d5f88e1ed..bea94cd04 100644 --- a/api/v1alpha1/databasecluster_types.go +++ b/api/v1alpha1/databasecluster_types.go @@ -370,9 +370,9 @@ const ( // ReasonStorageClassDoesNotSupportExpansion is a reason for condition ConditionTypeCannotExpandStorage // when the storage class does not support volume expansion. ReasonStorageClassDoesNotSupportExpansion = "StorageClassDoesNotSupportExpansion" - // ReasonStorageExpasionDisabled is a reason for condition ConditionTypeCannotExpandStorage + // ReasonStorageExpansionDisabled is a reason for condition ConditionTypeCannotExpandStorage // when the storage expansion is disabled for the database cluster. - ReasonStorageExpasionDisabled = "StorageExpasionDisabled" + ReasonStorageExpansionDisabled = "StorageExpansionDisabled" ) // DatabaseClusterStatus defines the observed state of DatabaseCluster. From 09abef32babe8fc85eebaac47190870eadf1228d Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Fri, 7 Mar 2025 12:30:53 +0530 Subject: [PATCH 09/23] Typos Signed-off-by: Mayank Shah --- internal/controller/providers/pxc/applier.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/controller/providers/pxc/applier.go b/internal/controller/providers/pxc/applier.go index bec384a24..1363d35c7 100644 --- a/internal/controller/providers/pxc/applier.go +++ b/internal/controller/providers/pxc/applier.go @@ -101,7 +101,7 @@ func configureStorage( meta.SetStatusCondition(&db.Status.Conditions, metav1.Condition{ Type: everestv1alpha1.ConditionTypeCannotExpandStorage, Status: metav1.ConditionTrue, - Reason: everestv1alpha1.ReasonStorageExpasionDisabled, + Reason: everestv1alpha1.ReasonStorageExpansionDisabled, LastTransitionTime: metav1.Now(), ObservedGeneration: db.GetGeneration(), }) From 9c7ceb8a1d07b68ca1da051726e6e6fe6f8c544c Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Fri, 7 Mar 2025 12:31:15 +0530 Subject: [PATCH 10/23] add storage scaling tests Signed-off-by: Mayank Shah --- tests/e2e/core/pxc/62-assert.yaml | 27 ++++++++++++++++++ .../pxc/62-create-cluster-no-pvc-resize.yaml | 28 +++++++++++++++++++ tests/e2e/core/pxc/63-assert.yaml | 13 +++++++++ tests/e2e/core/pxc/63-expand-storage.yaml | 5 ++++ tests/e2e/core/pxc/64-assert.yaml | 24 ++++++++++++++++ .../core/pxc/64-enable-storage-expansion.yaml | 5 ++++ tests/e2e/core/pxc/65-assert.yaml | 10 +++++++ 7 files changed, 112 insertions(+) create mode 100644 tests/e2e/core/pxc/62-assert.yaml create mode 100644 tests/e2e/core/pxc/62-create-cluster-no-pvc-resize.yaml create mode 100644 tests/e2e/core/pxc/63-assert.yaml create mode 100644 tests/e2e/core/pxc/63-expand-storage.yaml create mode 100644 tests/e2e/core/pxc/64-assert.yaml create mode 100644 tests/e2e/core/pxc/64-enable-storage-expansion.yaml create mode 100644 tests/e2e/core/pxc/65-assert.yaml diff --git a/tests/e2e/core/pxc/62-assert.yaml b/tests/e2e/core/pxc/62-assert.yaml new file mode 100644 index 000000000..283b43478 --- /dev/null +++ b/tests/e2e/core/pxc/62-assert.yaml @@ -0,0 +1,27 @@ +apiVersion: kuttl.dev/v1 +kind: TestAssert +timeout: 600 +--- +apiVersion: everest.percona.com/v1alpha1 +kind: DatabaseCluster +metadata: + name: test-pxc-cluster +status: + status: ready +--- +apiVersion: pxc.percona.com/v1 +kind: PerconaXtraDBCluster +metadata: + name: test-pxc-cluster +status: + haproxy: + ready: 1 + size: 1 + status: ready + pxc: + ready: 1 + size: 1 + status: ready + ready: 2 + size: 2 + state: ready diff --git a/tests/e2e/core/pxc/62-create-cluster-no-pvc-resize.yaml b/tests/e2e/core/pxc/62-create-cluster-no-pvc-resize.yaml new file mode 100644 index 000000000..66136c7dc --- /dev/null +++ b/tests/e2e/core/pxc/62-create-cluster-no-pvc-resize.yaml @@ -0,0 +1,28 @@ +apiVersion: kuttl.dev/v1 +kind: TestStep +timeout: 10 +--- +apiVersion: everest.percona.com/v1alpha1 +kind: DatabaseCluster +metadata: + name: test-pxc-cluster +spec: + engine: + type: pxc + userSecretsName: pxc-sample-secrets + config: | + [mysqld] + wsrep_provider_options="debug=1;gcache.size=1G" + wsrep_debug=1 + wsrep_trx_fragment_unit='bytes' + wsrep_trx_fragment_size=3670016 + replicas: 1 + storage: + size: 15G + disableVolumeExpansion: true + resources: + cpu: 600m + memory: 1G + proxy: + replicas: 1 + type: haproxy diff --git a/tests/e2e/core/pxc/63-assert.yaml b/tests/e2e/core/pxc/63-assert.yaml new file mode 100644 index 000000000..08502337a --- /dev/null +++ b/tests/e2e/core/pxc/63-assert.yaml @@ -0,0 +1,13 @@ +apiVersion: kuttl.dev/v1 +kind: TestAssert +timeout: 600 +--- +apiVersion: everest.percona.com/v1alpha1 +kind: DatabaseCluster +metadata: + name: test-pxc-cluster +status: + conditions: + - reason: StorageExpansionDisabled + status: "True" + type: CannotExpandStorage diff --git a/tests/e2e/core/pxc/63-expand-storage.yaml b/tests/e2e/core/pxc/63-expand-storage.yaml new file mode 100644 index 000000000..773364e03 --- /dev/null +++ b/tests/e2e/core/pxc/63-expand-storage.yaml @@ -0,0 +1,5 @@ +apiVersion: kuttl.dev/v1 +kind: TestStep +timeout: 10 +commands: + - command: kubectl patch db test-pxc-cluster -n $NAMESPACE -p '{"spec":{"engine":{"storage":{"size":"20G"}}}}' --type merge diff --git a/tests/e2e/core/pxc/64-assert.yaml b/tests/e2e/core/pxc/64-assert.yaml new file mode 100644 index 000000000..4bbed75fe --- /dev/null +++ b/tests/e2e/core/pxc/64-assert.yaml @@ -0,0 +1,24 @@ +apiVersion: kuttl.dev/v1 +kind: TestAssert +timeout: 600 +--- +apiVersion: everest.percona.com/v1alpha1 +kind: DatabaseCluster +metadata: + name: test-pxc-cluster +status: + status: resizingVolumes +--- +apiVersion: pxc.percona.com/v1 +kind: PerconaXtraDBCluster +metadata: + name: test-pxc-cluster +spec: + enableVolumeExpansion: true + pxc: + size: 1 + volumeSpec: + persistentVolumeClaim: + resources: + requests: + storage: 20G diff --git a/tests/e2e/core/pxc/64-enable-storage-expansion.yaml b/tests/e2e/core/pxc/64-enable-storage-expansion.yaml new file mode 100644 index 000000000..b39c20ce7 --- /dev/null +++ b/tests/e2e/core/pxc/64-enable-storage-expansion.yaml @@ -0,0 +1,5 @@ +apiVersion: kuttl.dev/v1 +kind: TestStep +timeout: 10 +commands: + - script: kubectl patch db test-pxc-cluster -n $NAMESPACE -p '{"spec":{"engine":{"storage":{"disableVolumeExpansion":false}}}}' --type merge diff --git a/tests/e2e/core/pxc/65-assert.yaml b/tests/e2e/core/pxc/65-assert.yaml new file mode 100644 index 000000000..3a7b481da --- /dev/null +++ b/tests/e2e/core/pxc/65-assert.yaml @@ -0,0 +1,10 @@ +apiVersion: kuttl.dev/v1 +kind: TestAssert +timeout: 600 +--- +apiVersion: everest.percona.com/v1alpha1 +kind: DatabaseCluster +metadata: + name: test-pxc-cluster +status: + status: ready From 1345506da18ee677a7555077f8cc2bc203975113 Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Fri, 7 Mar 2025 14:41:08 +0530 Subject: [PATCH 11/23] refactor Signed-off-by: Mayank Shah --- internal/controller/common/helper.go | 58 ++++++++++++++++++- internal/controller/providers/pxc/applier.go | 59 ++++++-------------- 2 files changed, 73 insertions(+), 44 deletions(-) diff --git a/internal/controller/common/helper.go b/internal/controller/common/helper.go index 71f9ff4ac..d9dff3e39 100644 --- a/internal/controller/common/helper.go +++ b/internal/controller/common/helper.go @@ -35,6 +35,7 @@ import ( storagev1 "k8s.io/api/storage/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/fields" @@ -798,9 +799,7 @@ const ( storageClassDefaultAnnotation = "storageclass.kubernetes.io/is-default-class" ) -// StorageClassSupportsVolumeExpansion returns true if the storage class supports volume expansion. -// If className is unspecified, uses the default storage class -func StorageClassSupportsVolumeExpansion(c client.Client, ctx context.Context, className *string) (bool, error) { +func storageClassSupportsVolumeExpansion(c client.Client, ctx context.Context, className *string) (bool, error) { storageClass, err := getStorageClassOrDefault(c, ctx, className) if err != nil { return false, fmt.Errorf("getStorageClassOrDefault failed: %w", err) @@ -827,3 +826,56 @@ func getStorageClassOrDefault(c client.Client, ctx context.Context, scName *stri } return storageClass, nil } + +// ConfigureStorageParams ... +type ConfigureStorageParams struct { + DesiredSize resource.Quantity + StorageClass *string + DisableVolumeExpansion bool + CurrentSize resource.Quantity + DB *everestv1alpha1.DatabaseCluster + SetStorageSizeFunc func(resource.Quantity) +} + +// ConfigureStorage handles storage configuration and volume expansion checks for database clusters +func ConfigureStorage(c client.Client, ctx context.Context, params ConfigureStorageParams) error { + meta.RemoveStatusCondition(¶ms.DB.Status.Conditions, everestv1alpha1.ConditionTypeCannotExpandStorage) + + hasStorageExpanded := params.CurrentSize.Cmp(params.DesiredSize) < 0 && !params.CurrentSize.IsZero() + if !hasStorageExpanded { + params.SetStorageSizeFunc(params.DesiredSize) + return nil + } + + allowedByStorageClass, err := storageClassSupportsVolumeExpansion(c, ctx, params.StorageClass) + if err != nil { + return fmt.Errorf("failed to check if storage class supports volume expansion: %w", err) + } + + if params.DisableVolumeExpansion { + meta.SetStatusCondition(¶ms.DB.Status.Conditions, metav1.Condition{ + Type: everestv1alpha1.ConditionTypeCannotExpandStorage, + Status: metav1.ConditionTrue, + Reason: everestv1alpha1.ReasonStorageExpansionDisabled, + LastTransitionTime: metav1.Now(), + ObservedGeneration: params.DB.GetGeneration(), + }) + params.SetStorageSizeFunc(params.CurrentSize) + return nil + } + + if !allowedByStorageClass { + meta.SetStatusCondition(¶ms.DB.Status.Conditions, metav1.Condition{ + Type: everestv1alpha1.ConditionTypeCannotExpandStorage, + Status: metav1.ConditionTrue, + Reason: everestv1alpha1.ReasonStorageClassDoesNotSupportExpansion, + LastTransitionTime: metav1.Now(), + ObservedGeneration: params.DB.GetGeneration(), + }) + params.SetStorageSizeFunc(params.CurrentSize) + return nil + } + + params.SetStorageSizeFunc(params.DesiredSize) + return nil +} diff --git a/internal/controller/providers/pxc/applier.go b/internal/controller/providers/pxc/applier.go index 1363d35c7..853ce6736 100644 --- a/internal/controller/providers/pxc/applier.go +++ b/internal/controller/providers/pxc/applier.go @@ -25,7 +25,6 @@ import ( pxcv1 "github.com/percona/percona-xtradb-cluster-operator/pkg/apis/pxc/v1" corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -82,54 +81,32 @@ func configureStorage( current *pxcv1.PerconaXtraDBClusterSpec, db *everestv1alpha1.DatabaseCluster, ) error { - meta.RemoveStatusCondition(&db.Status.Conditions, everestv1alpha1.ConditionTypeCannotExpandStorage) - var currentSize resource.Quantity - desiredSize := db.Spec.Engine.Storage.Size - if db.Status.Status != everestv1alpha1.AppStateNew { currentSize = current.PXC.PodSpec.VolumeSpec.PersistentVolumeClaim.Resources.Requests[corev1.ResourceStorage] } - hasStorageExpanded := currentSize.Cmp(desiredSize) < 0 && !currentSize.IsZero() - allowedByStorageClass, err := common.StorageClassSupportsVolumeExpansion(c, ctx, db.Spec.Engine.Storage.Class) - if err != nil { - return fmt.Errorf("failed to check if storage class supports volume expansion: %w", err) - } - - if hasStorageExpanded && db.Spec.Engine.Storage.DisableVolumeExpansion { - meta.SetStatusCondition(&db.Status.Conditions, metav1.Condition{ - Type: everestv1alpha1.ConditionTypeCannotExpandStorage, - Status: metav1.ConditionTrue, - Reason: everestv1alpha1.ReasonStorageExpansionDisabled, - LastTransitionTime: metav1.Now(), - ObservedGeneration: db.GetGeneration(), - }) - desiredSize = currentSize - } - - if hasStorageExpanded && !allowedByStorageClass { - meta.SetStatusCondition(&db.Status.Conditions, metav1.Condition{ - Type: everestv1alpha1.ConditionTypeCannotExpandStorage, - Status: metav1.ConditionTrue, - Reason: everestv1alpha1.ReasonStorageClassDoesNotSupportExpansion, - LastTransitionTime: metav1.Now(), - ObservedGeneration: db.GetGeneration(), - }) - desiredSize = currentSize - } - - desired.PXC.PodSpec.VolumeSpec = &pxcv1.VolumeSpec{ - PersistentVolumeClaim: &corev1.PersistentVolumeClaimSpec{ - StorageClassName: db.Spec.Engine.Storage.Class, - Resources: corev1.VolumeResourceRequirements{ - Requests: corev1.ResourceList{ - corev1.ResourceStorage: desiredSize, + setStorageSize := func(size resource.Quantity) { + desired.PXC.PodSpec.VolumeSpec = &pxcv1.VolumeSpec{ + PersistentVolumeClaim: &corev1.PersistentVolumeClaimSpec{ + StorageClassName: db.Spec.Engine.Storage.Class, + Resources: corev1.VolumeResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceStorage: size, + }, }, }, - }, + } } - return nil + + return common.ConfigureStorage(c, ctx, common.ConfigureStorageParams{ + DesiredSize: db.Spec.Engine.Storage.Size, + StorageClass: db.Spec.Engine.Storage.Class, + DisableVolumeExpansion: db.Spec.Engine.Storage.DisableVolumeExpansion, + CurrentSize: currentSize, + DB: db, + SetStorageSizeFunc: setStorageSize, + }) } func (p *applier) Engine() error { From 2729e1dbb60d1a96f7e682c18c87dffe0b913e1f Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Fri, 7 Mar 2025 14:46:57 +0530 Subject: [PATCH 12/23] fix args order Signed-off-by: Mayank Shah --- internal/controller/common/helper.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/controller/common/helper.go b/internal/controller/common/helper.go index d9dff3e39..1824f39d3 100644 --- a/internal/controller/common/helper.go +++ b/internal/controller/common/helper.go @@ -799,7 +799,7 @@ const ( storageClassDefaultAnnotation = "storageclass.kubernetes.io/is-default-class" ) -func storageClassSupportsVolumeExpansion(c client.Client, ctx context.Context, className *string) (bool, error) { +func storageClassSupportsVolumeExpansion(ctx context.Context, c client.Client, className *string) (bool, error) { storageClass, err := getStorageClassOrDefault(c, ctx, className) if err != nil { return false, fmt.Errorf("getStorageClassOrDefault failed: %w", err) @@ -847,7 +847,7 @@ func ConfigureStorage(c client.Client, ctx context.Context, params ConfigureStor return nil } - allowedByStorageClass, err := storageClassSupportsVolumeExpansion(c, ctx, params.StorageClass) + allowedByStorageClass, err := storageClassSupportsVolumeExpansion(ctx, c, params.StorageClass) if err != nil { return fmt.Errorf("failed to check if storage class supports volume expansion: %w", err) } From e656546f27b06d82892651d2bfb8f8110a0df89b Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Fri, 7 Mar 2025 14:49:41 +0530 Subject: [PATCH 13/23] fix ordering Signed-off-by: Mayank Shah --- internal/controller/common/helper.go | 2 +- internal/controller/providers/pxc/applier.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/controller/common/helper.go b/internal/controller/common/helper.go index 1824f39d3..1a9da1ca1 100644 --- a/internal/controller/common/helper.go +++ b/internal/controller/common/helper.go @@ -838,7 +838,7 @@ type ConfigureStorageParams struct { } // ConfigureStorage handles storage configuration and volume expansion checks for database clusters -func ConfigureStorage(c client.Client, ctx context.Context, params ConfigureStorageParams) error { +func ConfigureStorage(ctx context.Context, c client.Client, params ConfigureStorageParams) error { meta.RemoveStatusCondition(¶ms.DB.Status.Conditions, everestv1alpha1.ConditionTypeCannotExpandStorage) hasStorageExpanded := params.CurrentSize.Cmp(params.DesiredSize) < 0 && !params.CurrentSize.IsZero() diff --git a/internal/controller/providers/pxc/applier.go b/internal/controller/providers/pxc/applier.go index 853ce6736..a90e5a90a 100644 --- a/internal/controller/providers/pxc/applier.go +++ b/internal/controller/providers/pxc/applier.go @@ -99,7 +99,7 @@ func configureStorage( } } - return common.ConfigureStorage(c, ctx, common.ConfigureStorageParams{ + return common.ConfigureStorage(ctx, c, common.ConfigureStorageParams{ DesiredSize: db.Spec.Engine.Storage.Size, StorageClass: db.Spec.Engine.Storage.Class, DisableVolumeExpansion: db.Spec.Engine.Storage.DisableVolumeExpansion, From 7980af9e7a74b76ca27d2bcd8f658fa6d080bddb Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Fri, 7 Mar 2025 14:51:48 +0530 Subject: [PATCH 14/23] more improvements Signed-off-by: Mayank Shah --- internal/controller/common/helper.go | 17 ++++++++--------- internal/controller/providers/pxc/applier.go | 3 +-- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/internal/controller/common/helper.go b/internal/controller/common/helper.go index 1a9da1ca1..8e99fa1a5 100644 --- a/internal/controller/common/helper.go +++ b/internal/controller/common/helper.go @@ -819,7 +819,7 @@ func getStorageClassOrDefault(c client.Client, ctx context.Context, scName *stri return &sc, nil } } - return nil, fmt.Errorf("no default storage class found") + return nil, errors.New("no default storage class found") } if err := c.Get(ctx, types.NamespacedName{Name: *scName}, storageClass); err != nil { return nil, err @@ -833,13 +833,12 @@ type ConfigureStorageParams struct { StorageClass *string DisableVolumeExpansion bool CurrentSize resource.Quantity - DB *everestv1alpha1.DatabaseCluster SetStorageSizeFunc func(resource.Quantity) } -// ConfigureStorage handles storage configuration and volume expansion checks for database clusters -func ConfigureStorage(ctx context.Context, c client.Client, params ConfigureStorageParams) error { - meta.RemoveStatusCondition(¶ms.DB.Status.Conditions, everestv1alpha1.ConditionTypeCannotExpandStorage) +// ConfigureStorage handles storage configuration and volume expansion checks for the given database cluster. +func ConfigureStorage(ctx context.Context, c client.Client, db *everestv1alpha1.DatabaseCluster, params ConfigureStorageParams) error { + meta.RemoveStatusCondition(&db.Status.Conditions, everestv1alpha1.ConditionTypeCannotExpandStorage) hasStorageExpanded := params.CurrentSize.Cmp(params.DesiredSize) < 0 && !params.CurrentSize.IsZero() if !hasStorageExpanded { @@ -853,24 +852,24 @@ func ConfigureStorage(ctx context.Context, c client.Client, params ConfigureStor } if params.DisableVolumeExpansion { - meta.SetStatusCondition(¶ms.DB.Status.Conditions, metav1.Condition{ + meta.SetStatusCondition(&db.Status.Conditions, metav1.Condition{ Type: everestv1alpha1.ConditionTypeCannotExpandStorage, Status: metav1.ConditionTrue, Reason: everestv1alpha1.ReasonStorageExpansionDisabled, LastTransitionTime: metav1.Now(), - ObservedGeneration: params.DB.GetGeneration(), + ObservedGeneration: db.GetGeneration(), }) params.SetStorageSizeFunc(params.CurrentSize) return nil } if !allowedByStorageClass { - meta.SetStatusCondition(¶ms.DB.Status.Conditions, metav1.Condition{ + meta.SetStatusCondition(&db.Status.Conditions, metav1.Condition{ Type: everestv1alpha1.ConditionTypeCannotExpandStorage, Status: metav1.ConditionTrue, Reason: everestv1alpha1.ReasonStorageClassDoesNotSupportExpansion, LastTransitionTime: metav1.Now(), - ObservedGeneration: params.DB.GetGeneration(), + ObservedGeneration: db.GetGeneration(), }) params.SetStorageSizeFunc(params.CurrentSize) return nil diff --git a/internal/controller/providers/pxc/applier.go b/internal/controller/providers/pxc/applier.go index a90e5a90a..9072a6167 100644 --- a/internal/controller/providers/pxc/applier.go +++ b/internal/controller/providers/pxc/applier.go @@ -99,12 +99,11 @@ func configureStorage( } } - return common.ConfigureStorage(ctx, c, common.ConfigureStorageParams{ + return common.ConfigureStorage(ctx, c, db, common.ConfigureStorageParams{ DesiredSize: db.Spec.Engine.Storage.Size, StorageClass: db.Spec.Engine.Storage.Class, DisableVolumeExpansion: db.Spec.Engine.Storage.DisableVolumeExpansion, CurrentSize: currentSize, - DB: db, SetStorageSizeFunc: setStorageSize, }) } From 6b563179ef4bdd56463a44388106c32e2238687f Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Fri, 7 Mar 2025 15:05:46 +0530 Subject: [PATCH 15/23] linting Signed-off-by: Mayank Shah --- internal/controller/common/helper.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/controller/common/helper.go b/internal/controller/common/helper.go index 8e99fa1a5..398e8a1ea 100644 --- a/internal/controller/common/helper.go +++ b/internal/controller/common/helper.go @@ -800,14 +800,14 @@ const ( ) func storageClassSupportsVolumeExpansion(ctx context.Context, c client.Client, className *string) (bool, error) { - storageClass, err := getStorageClassOrDefault(c, ctx, className) + storageClass, err := getStorageClassOrDefault(ctx, c, className) if err != nil { return false, fmt.Errorf("getStorageClassOrDefault failed: %w", err) } return *storageClass.AllowVolumeExpansion, nil } -func getStorageClassOrDefault(c client.Client, ctx context.Context, scName *string) (*storagev1.StorageClass, error) { +func getStorageClassOrDefault(ctx context.Context, c client.Client, scName *string) (*storagev1.StorageClass, error) { storageClass := &storagev1.StorageClass{} if scName == nil { storageClasses := &storagev1.StorageClassList{} From 47a148367ac5bb26678bd53029029505a00f9e2f Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Fri, 7 Mar 2025 15:13:38 +0530 Subject: [PATCH 16/23] linting Signed-off-by: Mayank Shah --- internal/controller/providers/pxc/applier.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/controller/providers/pxc/applier.go b/internal/controller/providers/pxc/applier.go index 9072a6167..380098384 100644 --- a/internal/controller/providers/pxc/applier.go +++ b/internal/controller/providers/pxc/applier.go @@ -75,8 +75,8 @@ func (p *applier) AllowUnsafeConfig() { } func configureStorage( - c client.Client, ctx context.Context, + c client.Client, desired *pxcv1.PerconaXtraDBClusterSpec, current *pxcv1.PerconaXtraDBClusterSpec, db *everestv1alpha1.DatabaseCluster, @@ -136,7 +136,7 @@ func (p *applier) Engine() error { pxc.Spec.VolumeExpansionEnabled = true } - if err := configureStorage(p.C, p.ctx, &pxc.Spec, &p.currentPerconaXtraDBClusterSpec, p.DB); err != nil { + if err := configureStorage(p.ctx, p.C, &pxc.Spec, &p.currentPerconaXtraDBClusterSpec, p.DB); err != nil { return err } From a104f4931910adb04cd189d9031cf61a96791a8b Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Fri, 7 Mar 2025 15:46:47 +0530 Subject: [PATCH 17/23] generate manifests Signed-off-by: Mayank Shah --- ...verest-operator.clusterserviceversion.yaml | 2 +- .../everest.percona.com_databaseclusters.yaml | 64 +++++++++++++++++++ deploy/bundle.yaml | 63 ++++++++++++++++++ 3 files changed, 128 insertions(+), 1 deletion(-) diff --git a/bundle/manifests/everest-operator.clusterserviceversion.yaml b/bundle/manifests/everest-operator.clusterserviceversion.yaml index b189eab8c..0e404f37a 100644 --- a/bundle/manifests/everest-operator.clusterserviceversion.yaml +++ b/bundle/manifests/everest-operator.clusterserviceversion.yaml @@ -78,7 +78,7 @@ metadata: } ] capabilities: Basic Install - createdAt: "2025-01-21T11:40:01Z" + createdAt: "2025-03-07T10:16:24Z" operators.operatorframework.io/builder: operator-sdk-v1.38.0 operators.operatorframework.io/project_layout: go.kubebuilder.io/v4 name: everest-operator.v0.0.0 diff --git a/bundle/manifests/everest.percona.com_databaseclusters.yaml b/bundle/manifests/everest.percona.com_databaseclusters.yaml index 9a32724bd..02dd614d2 100644 --- a/bundle/manifests/everest.percona.com_databaseclusters.yaml +++ b/bundle/manifests/everest.percona.com_databaseclusters.yaml @@ -209,6 +209,13 @@ spec: description: Class is the storage class to use for the persistent volume claim type: string + disableVolumeExpansion: + description: |- + DisableVolumeExpansion controls whether the storage size of an existing cluster can be increased. + When set to true, the storage size cannot be modified after creation. + By default, this is false, allowing the storage size to be increased by specifying a new size value. + Note: Volume expansion requires support from the underlying storage class. + type: boolean size: anyOf: - type: integer @@ -409,6 +416,63 @@ spec: activeStorage: description: ActiveStorage is the storage used in cluster (psmdb only) type: string + conditions: + description: Conditions contains the observed conditions of the DatabaseCluster. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array crVersion: description: CRVersion is the observed version of the CR used with the underlying operator. diff --git a/deploy/bundle.yaml b/deploy/bundle.yaml index f82c76e43..54aac82a5 100644 --- a/deploy/bundle.yaml +++ b/deploy/bundle.yaml @@ -555,6 +555,13 @@ spec: class: description: Class is the storage class to use for the persistent volume claim type: string + disableVolumeExpansion: + description: |- + DisableVolumeExpansion controls whether the storage size of an existing cluster can be increased. + When set to true, the storage size cannot be modified after creation. + By default, this is false, allowing the storage size to be increased by specifying a new size value. + Note: Volume expansion requires support from the underlying storage class. + type: boolean size: anyOf: - type: integer @@ -752,6 +759,62 @@ spec: activeStorage: description: ActiveStorage is the storage used in cluster (psmdb only) type: string + conditions: + description: Conditions contains the observed conditions of the DatabaseCluster. + items: + description: Condition contains details for one aspect of the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array crVersion: description: CRVersion is the observed version of the CR used with the underlying operator. type: string From aca2ac45a2c460e8f1f76c0a10dfec22b63de2bf Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Tue, 11 Mar 2025 12:52:54 +0530 Subject: [PATCH 18/23] add condition for volume shrinking Signed-off-by: Mayank Shah --- api/v1alpha1/databasecluster_types.go | 7 ++++-- internal/controller/common/helper.go | 31 ++++++++++++++++++++------- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/api/v1alpha1/databasecluster_types.go b/api/v1alpha1/databasecluster_types.go index 4bc02de69..6d4143fe2 100644 --- a/api/v1alpha1/databasecluster_types.go +++ b/api/v1alpha1/databasecluster_types.go @@ -363,8 +363,8 @@ type DatabaseClusterSpec struct { } const ( - // ConditionTypeCannotExpandStorage is a condition type that indicates that the storage cannot be expanded. - ConditionTypeCannotExpandStorage = "CannotExpandStorage" + // ConditionTypeCannotResizeVolume is a condition type that indicates that the volume cannot be resized. + ConditionTypeCannotResizeVolume = "CannotResizeVolume" ) const ( @@ -374,6 +374,9 @@ const ( // ReasonStorageExpansionDisabled is a reason for condition ConditionTypeCannotExpandStorage // when the storage expansion is disabled for the database cluster. ReasonStorageExpansionDisabled = "StorageExpansionDisabled" + // ReasonCannotShrinkVolume is a reason for condition ConditionTypeCannotResizeVolume + // when the volume cannot be shrunk. + ReasonCannotShrinkVolume = "CannotShrinkVolume" ) // DatabaseClusterStatus defines the observed state of DatabaseCluster. diff --git a/internal/controller/common/helper.go b/internal/controller/common/helper.go index 398e8a1ea..7b119668f 100644 --- a/internal/controller/common/helper.go +++ b/internal/controller/common/helper.go @@ -838,22 +838,32 @@ type ConfigureStorageParams struct { // ConfigureStorage handles storage configuration and volume expansion checks for the given database cluster. func ConfigureStorage(ctx context.Context, c client.Client, db *everestv1alpha1.DatabaseCluster, params ConfigureStorageParams) error { - meta.RemoveStatusCondition(&db.Status.Conditions, everestv1alpha1.ConditionTypeCannotExpandStorage) + meta.RemoveStatusCondition(&db.Status.Conditions, everestv1alpha1.ConditionTypeCannotResizeVolume) + // We cannot shrink the volume size. + hasStorageShrunk := params.CurrentSize.Cmp(params.DesiredSize) > 0 && !params.CurrentSize.IsZero() + if hasStorageShrunk { + meta.SetStatusCondition(&db.Status.Conditions, metav1.Condition{ + Type: everestv1alpha1.ConditionTypeCannotResizeVolume, + Status: metav1.ConditionTrue, + Reason: everestv1alpha1.ReasonCannotShrinkVolume, + LastTransitionTime: metav1.Now(), + ObservedGeneration: db.GetGeneration(), + }) + params.SetStorageSizeFunc(params.CurrentSize) + return nil + } + + // Check if storage size is being expanded. If not, set the desired size and return early. hasStorageExpanded := params.CurrentSize.Cmp(params.DesiredSize) < 0 && !params.CurrentSize.IsZero() if !hasStorageExpanded { params.SetStorageSizeFunc(params.DesiredSize) return nil } - allowedByStorageClass, err := storageClassSupportsVolumeExpansion(ctx, c, params.StorageClass) - if err != nil { - return fmt.Errorf("failed to check if storage class supports volume expansion: %w", err) - } - if params.DisableVolumeExpansion { meta.SetStatusCondition(&db.Status.Conditions, metav1.Condition{ - Type: everestv1alpha1.ConditionTypeCannotExpandStorage, + Type: everestv1alpha1.ConditionTypeCannotResizeVolume, Status: metav1.ConditionTrue, Reason: everestv1alpha1.ReasonStorageExpansionDisabled, LastTransitionTime: metav1.Now(), @@ -863,9 +873,14 @@ func ConfigureStorage(ctx context.Context, c client.Client, db *everestv1alpha1. return nil } + allowedByStorageClass, err := storageClassSupportsVolumeExpansion(ctx, c, params.StorageClass) + if err != nil { + return fmt.Errorf("failed to check if storage class supports volume expansion: %w", err) + } + if !allowedByStorageClass { meta.SetStatusCondition(&db.Status.Conditions, metav1.Condition{ - Type: everestv1alpha1.ConditionTypeCannotExpandStorage, + Type: everestv1alpha1.ConditionTypeCannotResizeVolume, Status: metav1.ConditionTrue, Reason: everestv1alpha1.ReasonStorageClassDoesNotSupportExpansion, LastTransitionTime: metav1.Now(), From 0f29d35ac8d0b30efbc8e6176592dce3c2c4a11e Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Tue, 11 Mar 2025 12:53:57 +0530 Subject: [PATCH 19/23] update tests Signed-off-by: Mayank Shah --- tests/e2e/core/pxc/63-assert.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/core/pxc/63-assert.yaml b/tests/e2e/core/pxc/63-assert.yaml index 08502337a..23f1ef191 100644 --- a/tests/e2e/core/pxc/63-assert.yaml +++ b/tests/e2e/core/pxc/63-assert.yaml @@ -10,4 +10,4 @@ status: conditions: - reason: StorageExpansionDisabled status: "True" - type: CannotExpandStorage + type: CannotResizeVolume From de741df2540b075f5b1b509c849110d7b9e736c3 Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Tue, 11 Mar 2025 13:17:28 +0530 Subject: [PATCH 20/23] refactor and add unit test Signed-off-by: Mayank Shah --- internal/controller/common/helper.go | 39 ++-- internal/controller/common/helper_test.go | 209 +++++++++++++++++++ internal/controller/providers/pxc/applier.go | 8 +- 3 files changed, 230 insertions(+), 26 deletions(-) diff --git a/internal/controller/common/helper.go b/internal/controller/common/helper.go index 7b119668f..58c010215 100644 --- a/internal/controller/common/helper.go +++ b/internal/controller/common/helper.go @@ -827,21 +827,22 @@ func getStorageClassOrDefault(ctx context.Context, c client.Client, scName *stri return storageClass, nil } -// ConfigureStorageParams ... -type ConfigureStorageParams struct { - DesiredSize resource.Quantity - StorageClass *string - DisableVolumeExpansion bool - CurrentSize resource.Quantity - SetStorageSizeFunc func(resource.Quantity) -} - // ConfigureStorage handles storage configuration and volume expansion checks for the given database cluster. -func ConfigureStorage(ctx context.Context, c client.Client, db *everestv1alpha1.DatabaseCluster, params ConfigureStorageParams) error { +func ConfigureStorage( + ctx context.Context, + c client.Client, + db *everestv1alpha1.DatabaseCluster, + currentSize resource.Quantity, + setStorageSizeFunc func(resource.Quantity), +) error { meta.RemoveStatusCondition(&db.Status.Conditions, everestv1alpha1.ConditionTypeCannotResizeVolume) + desiredSize := db.Spec.Engine.Storage.Size + disableVolumeExpansion := db.Spec.Engine.Storage.DisableVolumeExpansion + storageClass := db.Spec.Engine.Storage.Class + // We cannot shrink the volume size. - hasStorageShrunk := params.CurrentSize.Cmp(params.DesiredSize) > 0 && !params.CurrentSize.IsZero() + hasStorageShrunk := currentSize.Cmp(desiredSize) > 0 && !currentSize.IsZero() if hasStorageShrunk { meta.SetStatusCondition(&db.Status.Conditions, metav1.Condition{ Type: everestv1alpha1.ConditionTypeCannotResizeVolume, @@ -850,18 +851,18 @@ func ConfigureStorage(ctx context.Context, c client.Client, db *everestv1alpha1. LastTransitionTime: metav1.Now(), ObservedGeneration: db.GetGeneration(), }) - params.SetStorageSizeFunc(params.CurrentSize) + setStorageSizeFunc(currentSize) return nil } // Check if storage size is being expanded. If not, set the desired size and return early. - hasStorageExpanded := params.CurrentSize.Cmp(params.DesiredSize) < 0 && !params.CurrentSize.IsZero() + hasStorageExpanded := currentSize.Cmp(desiredSize) < 0 && !currentSize.IsZero() if !hasStorageExpanded { - params.SetStorageSizeFunc(params.DesiredSize) + setStorageSizeFunc(desiredSize) return nil } - if params.DisableVolumeExpansion { + if disableVolumeExpansion { meta.SetStatusCondition(&db.Status.Conditions, metav1.Condition{ Type: everestv1alpha1.ConditionTypeCannotResizeVolume, Status: metav1.ConditionTrue, @@ -869,11 +870,11 @@ func ConfigureStorage(ctx context.Context, c client.Client, db *everestv1alpha1. LastTransitionTime: metav1.Now(), ObservedGeneration: db.GetGeneration(), }) - params.SetStorageSizeFunc(params.CurrentSize) + setStorageSizeFunc(currentSize) return nil } - allowedByStorageClass, err := storageClassSupportsVolumeExpansion(ctx, c, params.StorageClass) + allowedByStorageClass, err := storageClassSupportsVolumeExpansion(ctx, c, storageClass) if err != nil { return fmt.Errorf("failed to check if storage class supports volume expansion: %w", err) } @@ -886,10 +887,10 @@ func ConfigureStorage(ctx context.Context, c client.Client, db *everestv1alpha1. LastTransitionTime: metav1.Now(), ObservedGeneration: db.GetGeneration(), }) - params.SetStorageSizeFunc(params.CurrentSize) + setStorageSizeFunc(currentSize) return nil } - params.SetStorageSizeFunc(params.DesiredSize) + setStorageSizeFunc(desiredSize) return nil } diff --git a/internal/controller/common/helper_test.go b/internal/controller/common/helper_test.go index 3d43cacbf..546dbdb22 100644 --- a/internal/controller/common/helper_test.go +++ b/internal/controller/common/helper_test.go @@ -16,15 +16,20 @@ package common import ( + "context" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + storagev1 "k8s.io/api/storage/v1" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes/scheme" + "k8s.io/utils/pointer" "sigs.k8s.io/controller-runtime/pkg/client/fake" everestv1alpha1 "github.com/percona/everest-operator/api/v1alpha1" @@ -153,3 +158,207 @@ func TestMergeMapError(t *testing.T) { err := mergeMap(testDst, src) require.Error(t, err) } + +func TestConfigureStorage(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + db *everestv1alpha1.DatabaseCluster + currentSize resource.Quantity + storageClassExists bool + storageClassAllowExpansion bool + wantSize resource.Quantity + expectFailureCond bool + wantFailureCondReason string + expectErr bool + }{ + { + name: "initial storage setup", + db: &everestv1alpha1.DatabaseCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-db", + Namespace: "default", + Generation: 1, + }, + Spec: everestv1alpha1.DatabaseClusterSpec{ + Engine: everestv1alpha1.Engine{ + Storage: everestv1alpha1.Storage{ + Size: resource.MustParse("10Gi"), + Class: pointer.String("standard"), + }, + }, + }, + }, + currentSize: resource.MustParse("0"), + storageClassExists: true, + storageClassAllowExpansion: true, + wantSize: resource.MustParse("10Gi"), + }, + { + name: "successful volume expansion", + db: &everestv1alpha1.DatabaseCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-db", + Namespace: "default", + Generation: 1, + }, + Spec: everestv1alpha1.DatabaseClusterSpec{ + Engine: everestv1alpha1.Engine{ + Storage: everestv1alpha1.Storage{ + Size: resource.MustParse("20Gi"), + Class: pointer.String("standard"), + }, + }, + }, + }, + currentSize: resource.MustParse("10Gi"), + storageClassExists: true, + storageClassAllowExpansion: true, + wantSize: resource.MustParse("20Gi"), + }, + { + name: "volume shrink not allowed", + db: &everestv1alpha1.DatabaseCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-db", + Namespace: "default", + Generation: 1, + }, + Spec: everestv1alpha1.DatabaseClusterSpec{ + Engine: everestv1alpha1.Engine{ + Storage: everestv1alpha1.Storage{ + Size: resource.MustParse("10Gi"), + Class: pointer.String("standard"), + }, + }, + }, + }, + currentSize: resource.MustParse("20Gi"), + storageClassExists: true, + storageClassAllowExpansion: true, + wantSize: resource.MustParse("20Gi"), + expectFailureCond: true, + wantFailureCondReason: everestv1alpha1.ReasonCannotShrinkVolume, + }, + { + name: "expansion disabled", + db: &everestv1alpha1.DatabaseCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-db", + Namespace: "default", + Generation: 1, + }, + Spec: everestv1alpha1.DatabaseClusterSpec{ + Engine: everestv1alpha1.Engine{ + Storage: everestv1alpha1.Storage{ + Size: resource.MustParse("20Gi"), + Class: pointer.String("standard"), + DisableVolumeExpansion: true, + }, + }, + }, + }, + currentSize: resource.MustParse("10Gi"), + storageClassExists: true, + storageClassAllowExpansion: true, + wantSize: resource.MustParse("10Gi"), + expectFailureCond: true, + wantFailureCondReason: everestv1alpha1.ReasonStorageExpansionDisabled, + }, + { + name: "storage class doesn't support expansion", + db: &everestv1alpha1.DatabaseCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-db", + Namespace: "default", + Generation: 1, + }, + Spec: everestv1alpha1.DatabaseClusterSpec{ + Engine: everestv1alpha1.Engine{ + Storage: everestv1alpha1.Storage{ + Size: resource.MustParse("20Gi"), + Class: pointer.String("standard"), + }, + }, + }, + }, + currentSize: resource.MustParse("10Gi"), + storageClassExists: true, + storageClassAllowExpansion: false, + wantSize: resource.MustParse("10Gi"), + expectFailureCond: true, + wantFailureCondReason: everestv1alpha1.ReasonStorageClassDoesNotSupportExpansion, + }, + { + name: "storage class not found", + db: &everestv1alpha1.DatabaseCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-db", + Namespace: "default", + Generation: 1, + }, + Spec: everestv1alpha1.DatabaseClusterSpec{ + Engine: everestv1alpha1.Engine{ + Storage: everestv1alpha1.Storage{ + Size: resource.MustParse("20Gi"), + Class: pointer.String("non-existent"), + }, + }, + }, + }, + currentSize: resource.MustParse("10Gi"), + storageClassExists: false, + expectErr: true, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + // Setup test objects + var actualSize resource.Quantity + setSize := func(size resource.Quantity) { + actualSize = size + } + + // Setup fake client with storage class if needed + builder := fake.NewClientBuilder().WithScheme(scheme.Scheme) + if tt.storageClassExists && tt.db.Spec.Engine.Storage.Class != nil { + sc := &storagev1.StorageClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: *tt.db.Spec.Engine.Storage.Class, + }, + AllowVolumeExpansion: &tt.storageClassAllowExpansion, + } + builder.WithObjects(sc) + } + client := builder.Build() + + // Run the test + err := ConfigureStorage(context.Background(), client, tt.db, tt.currentSize, setSize) + + // Verify results + if tt.expectErr { + require.Error(t, err) + return + } + require.NoError(t, err) + + // Check if the size was set correctly + assert.Equal(t, tt.wantSize, actualSize, "unexpected storage size") + + // Check conditions if expected + if tt.expectFailureCond { + cond := meta.FindStatusCondition(tt.db.Status.Conditions, everestv1alpha1.ConditionTypeCannotResizeVolume) + assert.Equal(t, tt.wantFailureCondReason, cond.Reason) + assert.Equal(t, tt.db.Generation, cond.ObservedGeneration) + assert.NotEmpty(t, cond.LastTransitionTime) + } else { + assert.Empty(t, tt.db.Status.Conditions, "expected no conditions") + } + }) + } +} diff --git a/internal/controller/providers/pxc/applier.go b/internal/controller/providers/pxc/applier.go index 15bf22fa9..0d478c20d 100644 --- a/internal/controller/providers/pxc/applier.go +++ b/internal/controller/providers/pxc/applier.go @@ -99,13 +99,7 @@ func configureStorage( } } - return common.ConfigureStorage(ctx, c, db, common.ConfigureStorageParams{ - DesiredSize: db.Spec.Engine.Storage.Size, - StorageClass: db.Spec.Engine.Storage.Class, - DisableVolumeExpansion: db.Spec.Engine.Storage.DisableVolumeExpansion, - CurrentSize: currentSize, - SetStorageSizeFunc: setStorageSize, - }) + return common.ConfigureStorage(ctx, c, db, currentSize, setStorageSize) } func (p *applier) Engine() error { From 9550d11eb9114b9d43b3495c6ec19c4198e86935 Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Tue, 11 Mar 2025 13:34:33 +0530 Subject: [PATCH 21/23] go mod tidy Signed-off-by: Mayank Shah --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index e32ca20f1..c92a086fd 100644 --- a/go.mod +++ b/go.mod @@ -32,6 +32,7 @@ require ( k8s.io/api v0.32.0 k8s.io/apimachinery v0.32.2 k8s.io/client-go v12.0.0+incompatible + k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 sigs.k8s.io/controller-runtime v0.19.4 ) @@ -167,7 +168,6 @@ require ( k8s.io/component-base v0.31.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect - k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3 // indirect sigs.k8s.io/gateway-api v1.1.0 // indirect sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect From bad2be5de57e976618367566b679d750bc61e182 Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Tue, 11 Mar 2025 14:50:32 +0530 Subject: [PATCH 22/23] formatting Signed-off-by: Mayank Shah --- go.mod | 2 +- internal/controller/common/helper_test.go | 18 ++++++++---------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index c92a086fd..e32ca20f1 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,6 @@ require ( k8s.io/api v0.32.0 k8s.io/apimachinery v0.32.2 k8s.io/client-go v12.0.0+incompatible - k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 sigs.k8s.io/controller-runtime v0.19.4 ) @@ -168,6 +167,7 @@ require ( k8s.io/component-base v0.31.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect + k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3 // indirect sigs.k8s.io/gateway-api v1.1.0 // indirect sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect diff --git a/internal/controller/common/helper_test.go b/internal/controller/common/helper_test.go index 546dbdb22..07dbc2ba3 100644 --- a/internal/controller/common/helper_test.go +++ b/internal/controller/common/helper_test.go @@ -16,9 +16,9 @@ package common import ( - "context" "testing" + "github.com/AlekSi/pointer" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" appsv1 "k8s.io/api/apps/v1" @@ -29,7 +29,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes/scheme" - "k8s.io/utils/pointer" "sigs.k8s.io/controller-runtime/pkg/client/fake" everestv1alpha1 "github.com/percona/everest-operator/api/v1alpha1" @@ -185,7 +184,7 @@ func TestConfigureStorage(t *testing.T) { Engine: everestv1alpha1.Engine{ Storage: everestv1alpha1.Storage{ Size: resource.MustParse("10Gi"), - Class: pointer.String("standard"), + Class: pointer.To("standard"), }, }, }, @@ -207,7 +206,7 @@ func TestConfigureStorage(t *testing.T) { Engine: everestv1alpha1.Engine{ Storage: everestv1alpha1.Storage{ Size: resource.MustParse("20Gi"), - Class: pointer.String("standard"), + Class: pointer.To("standard"), }, }, }, @@ -229,7 +228,7 @@ func TestConfigureStorage(t *testing.T) { Engine: everestv1alpha1.Engine{ Storage: everestv1alpha1.Storage{ Size: resource.MustParse("10Gi"), - Class: pointer.String("standard"), + Class: pointer.To("standard"), }, }, }, @@ -253,7 +252,7 @@ func TestConfigureStorage(t *testing.T) { Engine: everestv1alpha1.Engine{ Storage: everestv1alpha1.Storage{ Size: resource.MustParse("20Gi"), - Class: pointer.String("standard"), + Class: pointer.To("standard"), DisableVolumeExpansion: true, }, }, @@ -278,7 +277,7 @@ func TestConfigureStorage(t *testing.T) { Engine: everestv1alpha1.Engine{ Storage: everestv1alpha1.Storage{ Size: resource.MustParse("20Gi"), - Class: pointer.String("standard"), + Class: pointer.To("standard"), }, }, }, @@ -302,7 +301,7 @@ func TestConfigureStorage(t *testing.T) { Engine: everestv1alpha1.Engine{ Storage: everestv1alpha1.Storage{ Size: resource.MustParse("20Gi"), - Class: pointer.String("non-existent"), + Class: pointer.To("non-existent"), }, }, }, @@ -314,7 +313,6 @@ func TestConfigureStorage(t *testing.T) { } for _, tt := range tests { - tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() @@ -338,7 +336,7 @@ func TestConfigureStorage(t *testing.T) { client := builder.Build() // Run the test - err := ConfigureStorage(context.Background(), client, tt.db, tt.currentSize, setSize) + err := ConfigureStorage(t.Context(), client, tt.db, tt.currentSize, setSize) // Verify results if tt.expectErr { From 45143f5c1b36f00c1e1b11de155225df8f1346ec Mon Sep 17 00:00:00 2001 From: Mayank Shah Date: Wed, 12 Mar 2025 16:54:34 +0530 Subject: [PATCH 23/23] remove DisableVolumeExpansion setting Signed-off-by: Mayank Shah --- api/v1alpha1/databasecluster_types.go | 8 ------ ...verest-operator.clusterserviceversion.yaml | 2 +- .../everest.percona.com_databaseclusters.yaml | 7 ------ .../everest.percona.com_databaseclusters.yaml | 7 ------ deploy/bundle.yaml | 7 ------ internal/controller/common/helper.go | 13 ---------- internal/controller/common/helper_test.go | 25 ------------------- internal/controller/providers/pxc/applier.go | 4 +-- ...pvc-resize.yaml => 62-create-cluster.yaml} | 1 - tests/e2e/core/pxc/63-assert.yaml | 19 +++++++++++--- tests/e2e/core/pxc/64-assert.yaml | 16 +----------- .../core/pxc/64-enable-storage-expansion.yaml | 5 ---- tests/e2e/core/pxc/65-assert.yaml | 10 -------- 13 files changed, 18 insertions(+), 106 deletions(-) rename tests/e2e/core/pxc/{62-create-cluster-no-pvc-resize.yaml => 62-create-cluster.yaml} (93%) delete mode 100644 tests/e2e/core/pxc/64-enable-storage-expansion.yaml delete mode 100644 tests/e2e/core/pxc/65-assert.yaml diff --git a/api/v1alpha1/databasecluster_types.go b/api/v1alpha1/databasecluster_types.go index 6d4143fe2..d8181e33e 100644 --- a/api/v1alpha1/databasecluster_types.go +++ b/api/v1alpha1/databasecluster_types.go @@ -124,11 +124,6 @@ type Storage struct { Size resource.Quantity `json:"size"` // Class is the storage class to use for the persistent volume claim Class *string `json:"class,omitempty"` - // DisableVolumeExpansion controls whether the storage size of an existing cluster can be increased. - // When set to true, the storage size cannot be modified after creation. - // By default, this is false, allowing the storage size to be increased by specifying a new size value. - // Note: Volume expansion requires support from the underlying storage class. - DisableVolumeExpansion bool `json:"disableVolumeExpansion,omitempty"` } // Resources are the resource requirements. @@ -371,9 +366,6 @@ const ( // ReasonStorageClassDoesNotSupportExpansion is a reason for condition ConditionTypeCannotExpandStorage // when the storage class does not support volume expansion. ReasonStorageClassDoesNotSupportExpansion = "StorageClassDoesNotSupportExpansion" - // ReasonStorageExpansionDisabled is a reason for condition ConditionTypeCannotExpandStorage - // when the storage expansion is disabled for the database cluster. - ReasonStorageExpansionDisabled = "StorageExpansionDisabled" // ReasonCannotShrinkVolume is a reason for condition ConditionTypeCannotResizeVolume // when the volume cannot be shrunk. ReasonCannotShrinkVolume = "CannotShrinkVolume" diff --git a/bundle/manifests/everest-operator.clusterserviceversion.yaml b/bundle/manifests/everest-operator.clusterserviceversion.yaml index 30fa5ff3f..d8d8544a2 100644 --- a/bundle/manifests/everest-operator.clusterserviceversion.yaml +++ b/bundle/manifests/everest-operator.clusterserviceversion.yaml @@ -78,7 +78,7 @@ metadata: } ] capabilities: Basic Install - createdAt: "2025-03-10T11:36:03Z" + createdAt: "2025-03-12T11:23:36Z" operators.operatorframework.io/builder: operator-sdk-v1.38.0 operators.operatorframework.io/project_layout: go.kubebuilder.io/v4 name: everest-operator.v0.0.0 diff --git a/bundle/manifests/everest.percona.com_databaseclusters.yaml b/bundle/manifests/everest.percona.com_databaseclusters.yaml index 25bf3374d..606d5f774 100644 --- a/bundle/manifests/everest.percona.com_databaseclusters.yaml +++ b/bundle/manifests/everest.percona.com_databaseclusters.yaml @@ -211,13 +211,6 @@ spec: description: Class is the storage class to use for the persistent volume claim type: string - disableVolumeExpansion: - description: |- - DisableVolumeExpansion controls whether the storage size of an existing cluster can be increased. - When set to true, the storage size cannot be modified after creation. - By default, this is false, allowing the storage size to be increased by specifying a new size value. - Note: Volume expansion requires support from the underlying storage class. - type: boolean size: anyOf: - type: integer diff --git a/config/crd/bases/everest.percona.com_databaseclusters.yaml b/config/crd/bases/everest.percona.com_databaseclusters.yaml index eca5f449d..345d66284 100644 --- a/config/crd/bases/everest.percona.com_databaseclusters.yaml +++ b/config/crd/bases/everest.percona.com_databaseclusters.yaml @@ -211,13 +211,6 @@ spec: description: Class is the storage class to use for the persistent volume claim type: string - disableVolumeExpansion: - description: |- - DisableVolumeExpansion controls whether the storage size of an existing cluster can be increased. - When set to true, the storage size cannot be modified after creation. - By default, this is false, allowing the storage size to be increased by specifying a new size value. - Note: Volume expansion requires support from the underlying storage class. - type: boolean size: anyOf: - type: integer diff --git a/deploy/bundle.yaml b/deploy/bundle.yaml index 4b4a6e686..85a659375 100644 --- a/deploy/bundle.yaml +++ b/deploy/bundle.yaml @@ -557,13 +557,6 @@ spec: class: description: Class is the storage class to use for the persistent volume claim type: string - disableVolumeExpansion: - description: |- - DisableVolumeExpansion controls whether the storage size of an existing cluster can be increased. - When set to true, the storage size cannot be modified after creation. - By default, this is false, allowing the storage size to be increased by specifying a new size value. - Note: Volume expansion requires support from the underlying storage class. - type: boolean size: anyOf: - type: integer diff --git a/internal/controller/common/helper.go b/internal/controller/common/helper.go index 58c010215..b56de4042 100644 --- a/internal/controller/common/helper.go +++ b/internal/controller/common/helper.go @@ -838,7 +838,6 @@ func ConfigureStorage( meta.RemoveStatusCondition(&db.Status.Conditions, everestv1alpha1.ConditionTypeCannotResizeVolume) desiredSize := db.Spec.Engine.Storage.Size - disableVolumeExpansion := db.Spec.Engine.Storage.DisableVolumeExpansion storageClass := db.Spec.Engine.Storage.Class // We cannot shrink the volume size. @@ -862,18 +861,6 @@ func ConfigureStorage( return nil } - if disableVolumeExpansion { - meta.SetStatusCondition(&db.Status.Conditions, metav1.Condition{ - Type: everestv1alpha1.ConditionTypeCannotResizeVolume, - Status: metav1.ConditionTrue, - Reason: everestv1alpha1.ReasonStorageExpansionDisabled, - LastTransitionTime: metav1.Now(), - ObservedGeneration: db.GetGeneration(), - }) - setStorageSizeFunc(currentSize) - return nil - } - allowedByStorageClass, err := storageClassSupportsVolumeExpansion(ctx, c, storageClass) if err != nil { return fmt.Errorf("failed to check if storage class supports volume expansion: %w", err) diff --git a/internal/controller/common/helper_test.go b/internal/controller/common/helper_test.go index 07dbc2ba3..e8cbbf94e 100644 --- a/internal/controller/common/helper_test.go +++ b/internal/controller/common/helper_test.go @@ -240,31 +240,6 @@ func TestConfigureStorage(t *testing.T) { expectFailureCond: true, wantFailureCondReason: everestv1alpha1.ReasonCannotShrinkVolume, }, - { - name: "expansion disabled", - db: &everestv1alpha1.DatabaseCluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-db", - Namespace: "default", - Generation: 1, - }, - Spec: everestv1alpha1.DatabaseClusterSpec{ - Engine: everestv1alpha1.Engine{ - Storage: everestv1alpha1.Storage{ - Size: resource.MustParse("20Gi"), - Class: pointer.To("standard"), - DisableVolumeExpansion: true, - }, - }, - }, - }, - currentSize: resource.MustParse("10Gi"), - storageClassExists: true, - storageClassAllowExpansion: true, - wantSize: resource.MustParse("10Gi"), - expectFailureCond: true, - wantFailureCondReason: everestv1alpha1.ReasonStorageExpansionDisabled, - }, { name: "storage class doesn't support expansion", db: &everestv1alpha1.DatabaseCluster{ diff --git a/internal/controller/providers/pxc/applier.go b/internal/controller/providers/pxc/applier.go index 0d478c20d..6154cf914 100644 --- a/internal/controller/providers/pxc/applier.go +++ b/internal/controller/providers/pxc/applier.go @@ -126,9 +126,7 @@ func (p *applier) Engine() error { } pxc.Spec.PXC.Image = pxcEngineVersion.ImagePath - if !p.DB.Spec.Engine.Storage.DisableVolumeExpansion { - pxc.Spec.VolumeExpansionEnabled = true - } + pxc.Spec.VolumeExpansionEnabled = true if err := configureStorage(p.ctx, p.C, &pxc.Spec, &p.currentPerconaXtraDBClusterSpec, p.DB); err != nil { return err diff --git a/tests/e2e/core/pxc/62-create-cluster-no-pvc-resize.yaml b/tests/e2e/core/pxc/62-create-cluster.yaml similarity index 93% rename from tests/e2e/core/pxc/62-create-cluster-no-pvc-resize.yaml rename to tests/e2e/core/pxc/62-create-cluster.yaml index 66136c7dc..1ba99ab64 100644 --- a/tests/e2e/core/pxc/62-create-cluster-no-pvc-resize.yaml +++ b/tests/e2e/core/pxc/62-create-cluster.yaml @@ -19,7 +19,6 @@ spec: replicas: 1 storage: size: 15G - disableVolumeExpansion: true resources: cpu: 600m memory: 1G diff --git a/tests/e2e/core/pxc/63-assert.yaml b/tests/e2e/core/pxc/63-assert.yaml index 23f1ef191..7685d71cc 100644 --- a/tests/e2e/core/pxc/63-assert.yaml +++ b/tests/e2e/core/pxc/63-assert.yaml @@ -7,7 +7,18 @@ kind: DatabaseCluster metadata: name: test-pxc-cluster status: - conditions: - - reason: StorageExpansionDisabled - status: "True" - type: CannotResizeVolume + status: resizingVolumes +--- +apiVersion: pxc.percona.com/v1 +kind: PerconaXtraDBCluster +metadata: + name: test-pxc-cluster +spec: + enableVolumeExpansion: true + pxc: + size: 1 + volumeSpec: + persistentVolumeClaim: + resources: + requests: + storage: 20G \ No newline at end of file diff --git a/tests/e2e/core/pxc/64-assert.yaml b/tests/e2e/core/pxc/64-assert.yaml index 4bbed75fe..3a7b481da 100644 --- a/tests/e2e/core/pxc/64-assert.yaml +++ b/tests/e2e/core/pxc/64-assert.yaml @@ -7,18 +7,4 @@ kind: DatabaseCluster metadata: name: test-pxc-cluster status: - status: resizingVolumes ---- -apiVersion: pxc.percona.com/v1 -kind: PerconaXtraDBCluster -metadata: - name: test-pxc-cluster -spec: - enableVolumeExpansion: true - pxc: - size: 1 - volumeSpec: - persistentVolumeClaim: - resources: - requests: - storage: 20G + status: ready diff --git a/tests/e2e/core/pxc/64-enable-storage-expansion.yaml b/tests/e2e/core/pxc/64-enable-storage-expansion.yaml deleted file mode 100644 index b39c20ce7..000000000 --- a/tests/e2e/core/pxc/64-enable-storage-expansion.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: kuttl.dev/v1 -kind: TestStep -timeout: 10 -commands: - - script: kubectl patch db test-pxc-cluster -n $NAMESPACE -p '{"spec":{"engine":{"storage":{"disableVolumeExpansion":false}}}}' --type merge diff --git a/tests/e2e/core/pxc/65-assert.yaml b/tests/e2e/core/pxc/65-assert.yaml deleted file mode 100644 index 3a7b481da..000000000 --- a/tests/e2e/core/pxc/65-assert.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: kuttl.dev/v1 -kind: TestAssert -timeout: 600 ---- -apiVersion: everest.percona.com/v1alpha1 -kind: DatabaseCluster -metadata: - name: test-pxc-cluster -status: - status: ready