From d8f685013f51565197d30f399bbadadc0041724a Mon Sep 17 00:00:00 2001 From: Gabriel Saratura Date: Thu, 9 Jan 2025 15:53:43 +0100 Subject: [PATCH] Add major upgrade support for postgres --- apis/helm/release/v1alpha1/types.go | 2 +- apis/vshn/v1/dbaas_vshn_postgresql.go | 4 + crds/vshn.appcat.vshn.io_vshnpostgresqls.yaml | 3 + .../vshn.appcat.vshn.io_xvshnpostgresqls.yaml | 3 + .../{loadBalancer.go => load_balancer.go} | 0 ...Balancer_test.go => load_balancer_test.go} | 0 .../vshnpostgres/major_version_upgrade.go | 122 ++++++++++++++++++ .../functions/vshnpostgres/register.go | 4 + pkg/controller/webhooks/postgresql.go | 2 +- pkg/controller/webhooks/postgresql_test.go | 24 ++++ 10 files changed, 162 insertions(+), 2 deletions(-) rename pkg/comp-functions/functions/vshnpostgres/{loadBalancer.go => load_balancer.go} (100%) rename pkg/comp-functions/functions/vshnpostgres/{loadBalancer_test.go => load_balancer_test.go} (100%) create mode 100644 pkg/comp-functions/functions/vshnpostgres/major_version_upgrade.go diff --git a/apis/helm/release/v1alpha1/types.go b/apis/helm/release/v1alpha1/types.go index 3b438d064c..bf684c9987 100644 --- a/apis/helm/release/v1alpha1/types.go +++ b/apis/helm/release/v1alpha1/types.go @@ -40,7 +40,7 @@ type NamespacedName struct { // DataKeySelector defines required spec to access a key of a configmap or secret type DataKeySelector struct { - NamespacedName `json:",inline,omitempty"` + NamespacedName `json:",inline"` Key string `json:"key,omitempty"` Optional bool `json:"optional,omitempty"` } diff --git a/apis/vshn/v1/dbaas_vshn_postgresql.go b/apis/vshn/v1/dbaas_vshn_postgresql.go index e573b87058..60b533bf8f 100644 --- a/apis/vshn/v1/dbaas_vshn_postgresql.go +++ b/apis/vshn/v1/dbaas_vshn_postgresql.go @@ -242,6 +242,10 @@ type VSHNPostgreSQLTLS struct { type VSHNPostgreSQLStatus struct { // InstanceNamespace contains the name of the namespace where the instance resides InstanceNamespace string `json:"instanceNamespace,omitempty"` + + // MajorVersion contains the current version of PostgreSQL. + MajorVersion string `json:"majorVersion,omitempty"` + // PostgreSQLConditions contains the status conditions of the backing object. PostgreSQLConditions []Condition `json:"postgresqlConditions,omitempty"` NamespaceConditions []Condition `json:"namespaceConditions,omitempty"` diff --git a/crds/vshn.appcat.vshn.io_vshnpostgresqls.yaml b/crds/vshn.appcat.vshn.io_vshnpostgresqls.yaml index 78b0ff2a4e..470c609e21 100644 --- a/crds/vshn.appcat.vshn.io_vshnpostgresqls.yaml +++ b/crds/vshn.appcat.vshn.io_vshnpostgresqls.yaml @@ -5359,6 +5359,9 @@ spec: type: string type: object type: array + majorVersion: + description: MajorVersion contains the current version of PostgreSQL. + type: string namespaceConditions: items: properties: diff --git a/crds/vshn.appcat.vshn.io_xvshnpostgresqls.yaml b/crds/vshn.appcat.vshn.io_xvshnpostgresqls.yaml index 8fb991e3ea..6f6cfb713b 100644 --- a/crds/vshn.appcat.vshn.io_xvshnpostgresqls.yaml +++ b/crds/vshn.appcat.vshn.io_xvshnpostgresqls.yaml @@ -6101,6 +6101,9 @@ spec: type: string type: object type: array + majorVersion: + description: MajorVersion contains the current version of PostgreSQL. + type: string namespaceConditions: items: properties: diff --git a/pkg/comp-functions/functions/vshnpostgres/loadBalancer.go b/pkg/comp-functions/functions/vshnpostgres/load_balancer.go similarity index 100% rename from pkg/comp-functions/functions/vshnpostgres/loadBalancer.go rename to pkg/comp-functions/functions/vshnpostgres/load_balancer.go diff --git a/pkg/comp-functions/functions/vshnpostgres/loadBalancer_test.go b/pkg/comp-functions/functions/vshnpostgres/load_balancer_test.go similarity index 100% rename from pkg/comp-functions/functions/vshnpostgres/loadBalancer_test.go rename to pkg/comp-functions/functions/vshnpostgres/load_balancer_test.go diff --git a/pkg/comp-functions/functions/vshnpostgres/major_version_upgrade.go b/pkg/comp-functions/functions/vshnpostgres/major_version_upgrade.go new file mode 100644 index 0000000000..4279c8898d --- /dev/null +++ b/pkg/comp-functions/functions/vshnpostgres/major_version_upgrade.go @@ -0,0 +1,122 @@ +package vshnpostgres + +import ( + "context" + "errors" + "fmt" + xfnproto "github.com/crossplane/function-sdk-go/proto/v1beta1" + stackgresv1 "github.com/vshn/appcat/v4/apis/stackgres/v1" + vshnv1 "github.com/vshn/appcat/v4/apis/vshn/v1" + "github.com/vshn/appcat/v4/pkg/comp-functions/runtime" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + pointer "k8s.io/utils/ptr" +) + +const ( + majorUpgradeSuffix = "-major-upgrade-dbops" +) + +func MajorVersionUpgrade(ctx context.Context, comp *vshnv1.VSHNPostgreSQL, svc *runtime.ServiceRuntime) *xfnproto.Result { + comp, err := getVSHNPostgreSQL(ctx, svc) + + if err != nil { + return runtime.NewFatalResult(fmt.Errorf("cannot get composite from function io: %w", err)) + } + + expectedV := comp.Spec.Parameters.Service.MajorVersion + currentV := comp.Status.MajorVersion + + majorUpgradeDbOps := &stackgresv1.SGDbOps{} + err = svc.GetObservedKubeObject(majorUpgradeDbOps, comp.GetName()+majorUpgradeSuffix) + if err != nil && !errors.Is(err, runtime.ErrNotFound) { + return runtime.NewFatalResult(fmt.Errorf("cannot get observed kube object major upgrade sgdbops: %w", err)) + } + + // If current and expected versions do not match then issue a major version upgrade via SGDBOps resource + if currentV != expectedV { + // If SGDBOps resource does not exist create it otherwise cleanup if successful or keep the resource on fail + if errors.Is(err, runtime.ErrNotFound) { + return createMajorUpgradeSgDbOps(svc, comp, expectedV) + } else if isSuccessful(majorUpgradeDbOps.Status.Conditions) { + return cleanUp(svc, comp, expectedV) + } else { + return keepSgDbOpsResource(svc, comp, majorUpgradeDbOps) + } + } + + return runtime.NewNormalResult("No major upgrade issued") +} + +func keepSgDbOpsResource(svc *runtime.ServiceRuntime, comp *vshnv1.VSHNPostgreSQL, majorUpgradeDbOps *stackgresv1.SGDbOps) *xfnproto.Result { + err := svc.SetDesiredKubeObject(majorUpgradeDbOps, comp.GetName()+majorUpgradeSuffix) + if err != nil { + return runtime.NewWarningResult(fmt.Sprintf("cannot keep major upgrade kube object %s", comp.GetName())) + } + return runtime.NewWarningResult("Major upgrade is not completed or it failed") +} + +func cleanUp(svc *runtime.ServiceRuntime, comp *vshnv1.VSHNPostgreSQL, expectedV string) *xfnproto.Result { + comp.Status.MajorVersion = expectedV + err := svc.SetDesiredCompositeStatus(comp) + if err != nil { + return runtime.NewFatalResult(fmt.Errorf("cannot update status field with the newest major postgres version: %w", err)) + } + return runtime.NewNormalResult("Major upgrade successfully finished, SGDBOps cleaned up") +} + +func isSuccessful(conditions *[]stackgresv1.SGDbOpsStatusConditionsItem) bool { + var successful, completed bool + if conditions != nil { + for _, c := range *conditions { + if !(*c.Reason == "OperationFailed" && *c.Status == "True") { + successful = true + } + if *c.Reason == "OperationCompleted" && *c.Status == "True" { + completed = true + } + } + } + return successful && completed +} + +func createMajorUpgradeSgDbOps(svc *runtime.ServiceRuntime, comp *vshnv1.VSHNPostgreSQL, expectedV string) *xfnproto.Result { + cluster := &stackgresv1.SGCluster{} + err := svc.GetObservedKubeObject(cluster, "cluster") + if err != nil { + return runtime.NewFatalResult(fmt.Errorf("cannot get observed kube object cluster: %w", err)) + } + + conf := &stackgresv1.SGPostgresConfig{} + err = svc.GetObservedKubeObject(conf, comp.GetName()+"-"+configResourceName) + if err != nil { + return runtime.NewFatalResult(fmt.Errorf("cannot get observed kube object postgres config: %w", err)) + } + + sgdbops := &stackgresv1.SGDbOps{ + ObjectMeta: v1.ObjectMeta{ + Name: comp.GetName() + majorUpgradeSuffix, + Namespace: comp.GetInstanceNamespace(), + }, + Spec: stackgresv1.SGDbOpsSpec{ + MajorVersionUpgrade: &stackgresv1.SGDbOpsSpecMajorVersionUpgrade{ + //TODO do we need to configure the backup path? + BackupPath: nil, + + Check: pointer.To(true), + Clone: nil, + Link: pointer.To(true), + PostgresVersion: &expectedV, + + //TODO do we reuse the old version config? + SgPostgresConfig: pointer.To(conf.GetName()), + }, + Op: "majorVersionUpgrade", + SgCluster: cluster.GetName(), + }, + } + err = svc.SetDesiredKubeObject(sgdbops, comp.GetName()+majorUpgradeSuffix) + if err != nil { + return runtime.NewWarningResult(fmt.Sprintf("cannot create major upgrade kube object %s", comp.GetName())) + } + return runtime.NewNormalResult("SGDBOps for major upgrade created") +} diff --git a/pkg/comp-functions/functions/vshnpostgres/register.go b/pkg/comp-functions/functions/vshnpostgres/register.go index 46343c0471..0f986cd5c4 100644 --- a/pkg/comp-functions/functions/vshnpostgres/register.go +++ b/pkg/comp-functions/functions/vshnpostgres/register.go @@ -84,6 +84,10 @@ func init() { Name: "custom-exporter-configs", Execute: PgExporterConfig, }, + { + Name: "major-version-upgrade", + Execute: MajorVersionUpgrade, + }, }, }) } diff --git a/pkg/controller/webhooks/postgresql.go b/pkg/controller/webhooks/postgresql.go index f00a9ec289..4f12cd6899 100644 --- a/pkg/controller/webhooks/postgresql.go +++ b/pkg/controller/webhooks/postgresql.go @@ -282,7 +282,7 @@ func validateMajorVersionUpgrade(newPg *vshnv1.VSHNPostgreSQL, oldPg *vshnv1.VSH fmt.Sprintf("invalid major version: %s", err.Error()), ) } - oldVersion, err := strconv.Atoi(oldPg.Spec.Parameters.Service.MajorVersion) + oldVersion, err := strconv.Atoi(oldPg.Status.MajorVersion) if err != nil { return field.Invalid( field.NewPath("spec.parameters.service.majorVersion"), diff --git a/pkg/controller/webhooks/postgresql_test.go b/pkg/controller/webhooks/postgresql_test.go index 999ffbeb27..debc3cf662 100644 --- a/pkg/controller/webhooks/postgresql_test.go +++ b/pkg/controller/webhooks/postgresql_test.go @@ -511,6 +511,9 @@ func TestPostgreSQLWebhookHandler_ValidateMajorVersionUpgrade(t *testing.T) { }, }, }, + Status: vshnv1.VSHNPostgreSQLStatus{ + MajorVersion: "15", + }, }, old: &vshnv1.VSHNPostgreSQL{ Spec: vshnv1.VSHNPostgreSQLSpec{ @@ -520,6 +523,9 @@ func TestPostgreSQLWebhookHandler_ValidateMajorVersionUpgrade(t *testing.T) { }, }, }, + Status: vshnv1.VSHNPostgreSQLStatus{ + MajorVersion: "15", + }, }, expectErr: nil, }, @@ -533,6 +539,9 @@ func TestPostgreSQLWebhookHandler_ValidateMajorVersionUpgrade(t *testing.T) { }, }, }, + Status: vshnv1.VSHNPostgreSQLStatus{ + MajorVersion: "15", + }, }, old: &vshnv1.VSHNPostgreSQL{ Spec: vshnv1.VSHNPostgreSQLSpec{ @@ -542,6 +551,9 @@ func TestPostgreSQLWebhookHandler_ValidateMajorVersionUpgrade(t *testing.T) { }, }, }, + Status: vshnv1.VSHNPostgreSQLStatus{ + MajorVersion: "15", + }, }, expectErr: nil, }, @@ -555,6 +567,9 @@ func TestPostgreSQLWebhookHandler_ValidateMajorVersionUpgrade(t *testing.T) { }, }, }, + Status: vshnv1.VSHNPostgreSQLStatus{ + MajorVersion: "15", + }, }, old: &vshnv1.VSHNPostgreSQL{ Spec: vshnv1.VSHNPostgreSQLSpec{ @@ -564,6 +579,9 @@ func TestPostgreSQLWebhookHandler_ValidateMajorVersionUpgrade(t *testing.T) { }, }, }, + Status: vshnv1.VSHNPostgreSQLStatus{ + MajorVersion: "15", + }, }, expectErr: field.Forbidden( field.NewPath("spec.parameters.service.majorVersion"), @@ -580,6 +598,9 @@ func TestPostgreSQLWebhookHandler_ValidateMajorVersionUpgrade(t *testing.T) { }, }, }, + Status: vshnv1.VSHNPostgreSQLStatus{ + MajorVersion: "15", + }, }, old: &vshnv1.VSHNPostgreSQL{ Spec: vshnv1.VSHNPostgreSQLSpec{ @@ -589,6 +610,9 @@ func TestPostgreSQLWebhookHandler_ValidateMajorVersionUpgrade(t *testing.T) { }, }, }, + Status: vshnv1.VSHNPostgreSQLStatus{ + MajorVersion: "15", + }, }, expectErr: field.Forbidden( field.NewPath("spec.parameters.service.majorVersion"),