diff --git a/api/v1alpha1/perconaservermysql_types.go b/api/v1alpha1/perconaservermysql_types.go index 874a010e4..061dd3e58 100644 --- a/api/v1alpha1/perconaservermysql_types.go +++ b/api/v1alpha1/perconaservermysql_types.go @@ -39,7 +39,6 @@ import ( "github.com/percona/percona-server-mysql-operator/pkg/naming" "github.com/percona/percona-server-mysql-operator/pkg/platform" - "github.com/percona/percona-server-mysql-operator/pkg/version" ) const ( @@ -581,15 +580,6 @@ func (cr *PerconaServerMySQL) OrchestratorSpec() *OrchestratorSpec { return &cr.Spec.Orchestrator } -// SetVersion sets the CRVersion to the version value if it's not already set. -func (cr *PerconaServerMySQL) SetVersion() { - if len(cr.Spec.CRVersion) > 0 { - return - } - - cr.Spec.CRVersion = version.Version() -} - // CheckNSetDefaults validates and sets default values for the PerconaServerMySQL custom resource. func (cr *PerconaServerMySQL) CheckNSetDefaults(_ context.Context, serverVersion *platform.ServerVersion) error { if len(cr.Spec.MySQL.ClusterType) == 0 { @@ -600,8 +590,6 @@ func (cr *PerconaServerMySQL) CheckNSetDefaults(_ context.Context, serverVersion return errors.Errorf("%s is not a valid clusterType, valid options are %s and %s", cr.Spec.MySQL.ClusterType, ClusterTypeGR, ClusterTypeAsync) } - cr.SetVersion() - if cr.Spec.Backup == nil { cr.Spec.Backup = new(BackupSpec) } diff --git a/pkg/controller/ps/controller.go b/pkg/controller/ps/controller.go index 87c127ea2..d56d24498 100644 --- a/pkg/controller/ps/controller.go +++ b/pkg/controller/ps/controller.go @@ -128,6 +128,10 @@ func (r *PerconaServerMySQLReconciler) Reconcile( return ctrl.Result{}, errors.Wrap(err, "get CR") } + if err := r.setCRVersion(ctx, cr); err != nil { + return ctrl.Result{}, errors.Wrap(err, "set CR version") + } + if cr.ObjectMeta.DeletionTimestamp != nil { log.Info("CR marked for deletion, applying finalizers", "name", cr.Name) if err := r.applyFinalizers(ctx, cr); err != nil { diff --git a/pkg/controller/ps/controller_test.go b/pkg/controller/ps/controller_test.go index dc9549144..d2546450e 100644 --- a/pkg/controller/ps/controller_test.go +++ b/pkg/controller/ps/controller_test.go @@ -40,6 +40,7 @@ import ( psv1alpha1 "github.com/percona/percona-server-mysql-operator/api/v1alpha1" "github.com/percona/percona-server-mysql-operator/pkg/mysql" "github.com/percona/percona-server-mysql-operator/pkg/naming" + "github.com/percona/percona-server-mysql-operator/pkg/version" ) var _ = Describe("Sidecars", Ordered, func() { @@ -1067,3 +1068,77 @@ var _ = Describe("Primary mysql service", Ordered, func() { }) }) }) + +var _ = Describe("CR Version Management", Ordered, func() { + ctx := context.Background() + const crName = "cr-version" + const ns = crName + + namespace := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: crName, + Namespace: ns, + }, + } + + BeforeAll(func() { + By("Creating the Namespace for CR version tests") + err := k8sClient.Create(ctx, namespace) + Expect(err).To(Not(HaveOccurred())) + }) + + AfterAll(func() { + By("Deleting the Namespace after CR version tests") + _ = k8sClient.Delete(ctx, namespace) + }) + + Context("setCRVersion logic", Ordered, func() { + When("the CRVersion is already set", func() { + It("should not change the CRVersion", func() { + cr, err := readDefaultCR("cr-version-1", ns) + Expect(err).NotTo(HaveOccurred()) + + cr.Spec.CRVersion = "0.11.0" + Expect(k8sClient.Create(ctx, cr)).Should(Succeed()) + + reconciler := &PerconaServerMySQLReconciler{Client: k8sClient} + err = reconciler.setCRVersion(ctx, cr) + Expect(err).NotTo(HaveOccurred()) + Expect(cr.Spec.CRVersion).To(Equal("0.11.0")) + }) + }) + + When("the CRVersion is empty", func() { + It("should set CRVersion and patch the resource", func() { + cr, err := readDefaultCR("cr-version-2", ns) + Expect(err).NotTo(HaveOccurred()) + + cr.Spec.CRVersion = "" + Expect(k8sClient.Create(ctx, cr)).Should(Succeed()) + + reconciler := &PerconaServerMySQLReconciler{Client: k8sClient} + err = reconciler.setCRVersion(ctx, cr) + Expect(err).NotTo(HaveOccurred()) + + // Fetch the CR again to verify the patch was applied in the cluster + updated := &psv1alpha1.PerconaServerMySQL{} + Expect(k8sClient.Get(ctx, types.NamespacedName{Name: cr.Name, Namespace: cr.Namespace}, updated)).Should(Succeed()) + Expect(updated.Spec.CRVersion).To(Equal(version.Version())) + }) + }) + + When("the patch operation fails", func() { + It("should return an error", func() { + cr, err := readDefaultCR("cr-version-3", ns) + Expect(err).NotTo(HaveOccurred()) + cr.Spec.CRVersion = "" + + // Do NOT create the CR in k8s, so Patch will fail (object does not exist) + reconciler := &PerconaServerMySQLReconciler{Client: k8sClient} + err = reconciler.setCRVersion(ctx, cr) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("patch CR")) + }) + }) + }) +}) diff --git a/pkg/controller/ps/version.go b/pkg/controller/ps/version.go index ff910ea41..71fd8f866 100644 --- a/pkg/controller/ps/version.go +++ b/pkg/controller/ps/version.go @@ -14,6 +14,7 @@ import ( apiv1alpha1 "github.com/percona/percona-server-mysql-operator/api/v1alpha1" "github.com/percona/percona-server-mysql-operator/pkg/k8s" "github.com/percona/percona-server-mysql-operator/pkg/mysql" + "github.com/percona/percona-server-mysql-operator/pkg/version" vs "github.com/percona/percona-server-mysql-operator/pkg/version/service" ) @@ -194,3 +195,23 @@ func (r *PerconaServerMySQLReconciler) upgradeVersions(ctx context.Context, cr * cr.Status.ToolkitVersion = version.ToolkitVersion return nil } + +// setCRVersion sets operator version of PerconaServerMySQL. +// The new (semver-matching) version is determined by the CR's crVersion field. +// If the crVersion is an empty string, it sets the current operator version. +func (r *PerconaServerMySQLReconciler) setCRVersion(ctx context.Context, cr *apiv1alpha1.PerconaServerMySQL) error { + if len(cr.Spec.CRVersion) > 0 { + return nil + } + + orig := cr.DeepCopy() + cr.Spec.CRVersion = version.Version() + + if err := r.Patch(ctx, cr, client.MergeFrom(orig)); err != nil { + return errors.Wrap(err, "patch CR") + } + + logf.FromContext(ctx).Info("Set CR version", "version", cr.Spec.CRVersion) + + return nil +} diff --git a/pkg/controller/ps/version_test.go b/pkg/controller/ps/version_test.go index 6ad8cc937..40af6c000 100644 --- a/pkg/controller/ps/version_test.go +++ b/pkg/controller/ps/version_test.go @@ -205,6 +205,7 @@ func TestReconcileVersions(t *testing.T) { Backup: &apiv1alpha1.BackupSpec{ Image: "some-image", }, + CRVersion: version.Version(), MySQL: apiv1alpha1.MySQLSpec{ ClusterType: apiv1alpha1.ClusterTypeGR, PodSpec: apiv1alpha1.PodSpec{ @@ -347,6 +348,7 @@ func TestGetVersion(t *testing.T) { Backup: &apiv1alpha1.BackupSpec{ Image: "some-image", }, + CRVersion: version.Version(), MySQL: apiv1alpha1.MySQLSpec{ ClusterType: apiv1alpha1.ClusterTypeGR, PodSpec: apiv1alpha1.PodSpec{ @@ -429,7 +431,6 @@ func TestGetVersion(t *testing.T) { if err != nil { t.Fatal(err, "failed to get cr") } - dv, err := vs.GetVersion(ctx, cr, fmt.Sprintf("http://%s:%d", addr, gwPort), r.ServerVersion) if err != nil { t.Fatal(err, "failed to get version")