Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EVEREST-1874 | support volume expansion for PXC #688

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions api/v1alpha1/databasecluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ""

Expand Down Expand Up @@ -122,6 +124,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.
Expand Down Expand Up @@ -354,6 +361,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.
Expand All @@ -380,6 +401,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
Expand Down
8 changes: 8 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

64 changes: 64 additions & 0 deletions config/crd/bases/everest.percona.com_databaseclusters.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
34 changes: 34 additions & 0 deletions internal/controller/common/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -793,3 +793,37 @@
}
return ud, nil
}

const (
storageClassDefaultAnnotation = "storageclass.kubernetes.io/is-default-class"
)

// StorageClassSupportsVolumeExpansion returns true if the storage class supports volume expansion.

Check failure on line 801 in internal/controller/common/helper.go

View workflow job for this annotation

GitHub Actions / Checks (1.24.x, false)

Comment should end in a period (godot)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [golangci-lint] reported by reviewdog 🐶
Comment should end in a period (godot)

// If className is unspecified, uses the default storage class
func StorageClassSupportsVolumeExpansion(c client.Client, ctx context.Context, className *string) (bool, error) {

Check failure on line 803 in internal/controller/common/helper.go

View workflow job for this annotation

GitHub Actions / Checks (1.24.x, false)

context-as-argument: context.Context should be the first parameter of a function (revive)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [golangci-lint] reported by reviewdog 🐶
context-as-argument: context.Context should be the first parameter of a function (revive)

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) {

Check failure on line 811 in internal/controller/common/helper.go

View workflow job for this annotation

GitHub Actions / Checks (1.24.x, false)

context-as-argument: context.Context should be the first parameter of a function (revive)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [golangci-lint] reported by reviewdog 🐶
context-as-argument: context.Context should be the first parameter of a function (revive)

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")

Check failure on line 823 in internal/controller/common/helper.go

View workflow job for this annotation

GitHub Actions / Checks (1.24.x, false)

fmt.Errorf can be replaced with errors.New (perfsprint)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [golangci-lint] reported by reviewdog 🐶
fmt.Errorf can be replaced with errors.New (perfsprint)

}
if err := c.Get(ctx, types.NamespacedName{Name: *scName}, storageClass); err != nil {
return nil, err
}
return storageClass, nil
}
8 changes: 4 additions & 4 deletions internal/controller/databasecluster_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
74 changes: 65 additions & 9 deletions internal/controller/providers/pxc/applier.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@
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"
Expand Down Expand Up @@ -73,6 +75,63 @@
}
}

func configureStorage(
c client.Client,
ctx context.Context,

Check failure on line 80 in internal/controller/providers/pxc/applier.go

View workflow job for this annotation

GitHub Actions / Checks (1.24.x, false)

context-as-argument: context.Context should be the first parameter of a function (revive)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚫 [golangci-lint] reported by reviewdog 🐶
context-as-argument: context.Context should be the first parameter of a function (revive)

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.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.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 == "" {
Expand All @@ -97,15 +156,12 @@
}
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() {
Expand Down
6 changes: 6 additions & 0 deletions internal/controller/providers/pxc/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
Loading