github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/controllers/apps/clusterdefinition_controller_test.go (about)

     1  /*
     2  Copyright (C) 2022-2023 ApeCloud Co., Ltd
     3  
     4  This file is part of KubeBlocks project
     5  
     6  This program is free software: you can redistribute it and/or modify
     7  it under the terms of the GNU Affero General Public License as published by
     8  the Free Software Foundation, either version 3 of the License, or
     9  (at your option) any later version.
    10  
    11  This program is distributed in the hope that it will be useful
    12  but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  GNU Affero General Public License for more details.
    15  
    16  You should have received a copy of the GNU Affero General Public License
    17  along with this program.  If not, see <http://www.gnu.org/licenses/>.
    18  */
    19  
    20  package apps
    21  
    22  import (
    23  	. "github.com/onsi/ginkgo/v2"
    24  	. "github.com/onsi/gomega"
    25  
    26  	corev1 "k8s.io/api/core/v1"
    27  	"k8s.io/apimachinery/pkg/types"
    28  	"sigs.k8s.io/controller-runtime/pkg/client"
    29  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    30  
    31  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    32  	cfgcore "github.com/1aal/kubeblocks/pkg/configuration/core"
    33  	"github.com/1aal/kubeblocks/pkg/constant"
    34  	intctrlutil "github.com/1aal/kubeblocks/pkg/generics"
    35  	testapps "github.com/1aal/kubeblocks/pkg/testutil/apps"
    36  )
    37  
    38  var _ = Describe("ClusterDefinition Controller", func() {
    39  	const clusterDefName = "test-clusterdef"
    40  	const clusterVersionName = "test-clusterversion"
    41  
    42  	const statefulCompDefName = "replicasets"
    43  
    44  	const configVolumeName = "mysql-config"
    45  
    46  	const cmName = "mysql-tree-node-template-8.0"
    47  
    48  	cleanEnv := func() {
    49  		// must wait till resources deleted and no longer existed before the testcases start,
    50  		// otherwise if later it needs to create some new resource objects with the same name,
    51  		// in race conditions, it will find the existence of old objects, resulting failure to
    52  		// create the new objects.
    53  		By("clean resources")
    54  
    55  		inNS := client.InNamespace(testCtx.DefaultNamespace)
    56  		ml := client.HasLabels{testCtx.TestObjLabelKey}
    57  
    58  		// resources should be released in following order
    59  		// non-namespaced
    60  		testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, intctrlutil.ClusterVersionSignature, true, ml)
    61  		testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, intctrlutil.ClusterDefinitionSignature, true, ml)
    62  		testapps.ClearResources(&testCtx, intctrlutil.ConfigConstraintSignature, ml)
    63  
    64  		// namespaced
    65  		testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, intctrlutil.ConfigMapSignature, true, inNS, ml)
    66  	}
    67  
    68  	BeforeEach(func() {
    69  		cleanEnv()
    70  
    71  	})
    72  
    73  	AfterEach(func() {
    74  		cleanEnv()
    75  	})
    76  
    77  	var (
    78  		clusterDefObj     *appsv1alpha1.ClusterDefinition
    79  		clusterVersionObj *appsv1alpha1.ClusterVersion
    80  	)
    81  
    82  	Context("with no ConfigSpec", func() {
    83  		BeforeEach(func() {
    84  			By("Create a clusterDefinition obj")
    85  			clusterDefObj = testapps.NewClusterDefFactory(clusterDefName).
    86  				AddComponentDef(testapps.StatefulMySQLComponent, statefulCompDefName).
    87  				Create(&testCtx).GetObject()
    88  
    89  			By("Create a clusterVersion obj")
    90  			clusterVersionObj = testapps.NewClusterVersionFactory(clusterVersionName, clusterDefObj.GetName()).
    91  				AddComponentVersion(statefulCompDefName).AddContainerShort("mysql", testapps.ApeCloudMySQLImage).
    92  				Create(&testCtx).GetObject()
    93  		})
    94  
    95  		It("should update status of clusterVersion at the same time when updating clusterDefinition", func() {
    96  			By("Check reconciled finalizer and status of ClusterDefinition")
    97  			var cdGen int64
    98  			Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(clusterDefObj),
    99  				func(g Gomega, cd *appsv1alpha1.ClusterDefinition) {
   100  					g.Expect(cd.Finalizers).NotTo(BeEmpty())
   101  					g.Expect(cd.Status.ObservedGeneration).To(BeEquivalentTo(1))
   102  					cdGen = cd.Status.ObservedGeneration
   103  				})).Should(Succeed())
   104  
   105  			By("Check reconciled finalizer and status of ClusterVersion")
   106  			Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(clusterVersionObj),
   107  				func(g Gomega, cv *appsv1alpha1.ClusterVersion) {
   108  					g.Expect(cv.Finalizers).NotTo(BeEmpty())
   109  					g.Expect(cv.Status.ObservedGeneration).To(BeEquivalentTo(1))
   110  					g.Expect(cv.Status.ClusterDefGeneration).To(Equal(cdGen))
   111  				})).Should(Succeed())
   112  
   113  			By("updating clusterDefinition's spec which then update clusterVersion's status")
   114  			Eventually(testapps.GetAndChangeObj(&testCtx, client.ObjectKeyFromObject(clusterDefObj),
   115  				func(cd *appsv1alpha1.ClusterDefinition) {
   116  					cd.Spec.ConnectionCredential["root"] = "password"
   117  				})).Should(Succeed())
   118  
   119  			By("Check ClusterVersion.Status as updated")
   120  			Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(clusterVersionObj),
   121  				func(g Gomega, cv *appsv1alpha1.ClusterVersion) {
   122  					g.Expect(cv.Status.Phase).To(Equal(appsv1alpha1.AvailablePhase))
   123  					g.Expect(cv.Status.Message).To(Equal(""))
   124  					g.Expect(cv.Status.ClusterDefGeneration > cdGen).To(BeTrue())
   125  				})).Should(Succeed())
   126  
   127  			// TODO: update components to break @validateClusterVersion, and transit ClusterVersion.Status.Phase to UnavailablePhase
   128  		})
   129  	})
   130  
   131  	assureCfgTplConfigMapObj := func() *corev1.ConfigMap {
   132  		By("Create a configmap and config template obj")
   133  		cm := testapps.CreateCustomizedObj(&testCtx, "config/config-template.yaml", &corev1.ConfigMap{},
   134  			testCtx.UseDefaultNamespace())
   135  
   136  		cfgTpl := testapps.CreateCustomizedObj(&testCtx, "config/config-constraint.yaml",
   137  			&appsv1alpha1.ConfigConstraint{})
   138  		Expect(testapps.ChangeObjStatus(&testCtx, cfgTpl, func() {
   139  			cfgTpl.Status.Phase = appsv1alpha1.CCAvailablePhase
   140  		})).Should(Succeed())
   141  		return cm
   142  	}
   143  
   144  	Context("with ConfigSpec", func() {
   145  		BeforeEach(func() {
   146  			By("Create a clusterDefinition obj")
   147  			clusterDefObj = testapps.NewClusterDefFactory(clusterDefName).
   148  				AddComponentDef(testapps.StatefulMySQLComponent, statefulCompDefName).
   149  				AddConfigTemplate(cmName, cmName, cmName, testCtx.DefaultNamespace, configVolumeName).
   150  				Create(&testCtx).GetObject()
   151  
   152  			By("Create a clusterVersion obj")
   153  			clusterVersionObj = testapps.NewClusterVersionFactory(clusterVersionName, clusterDefObj.GetName()).
   154  				AddComponentVersion(statefulCompDefName).AddContainerShort("mysql", testapps.ApeCloudMySQLImage).
   155  				Create(&testCtx).GetObject()
   156  		})
   157  
   158  		It("should stop proceeding the status of clusterDefinition if configmap is invalid or doesn't exist", func() {
   159  			By("check the reconciler won't update Status.ObservedGeneration if configmap doesn't exist.")
   160  			// should use Consistently here, since cd.Status.ObservedGeneration is initialized to be zero,
   161  			// we must watch the value for a while to tell it's not changed by the reconciler.
   162  			Consistently(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(clusterDefObj),
   163  				func(g Gomega, cd *appsv1alpha1.ClusterDefinition) {
   164  					g.Expect(cd.Status.ObservedGeneration).To(BeEquivalentTo(0))
   165  				})).Should(Succeed())
   166  
   167  			assureCfgTplConfigMapObj()
   168  
   169  			By("check the reconciler update Status.ObservedGeneration after configmap is created.")
   170  			Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(clusterDefObj),
   171  				func(g Gomega, cd *appsv1alpha1.ClusterDefinition) {
   172  					g.Expect(cd.Status.ObservedGeneration).To(BeEquivalentTo(1))
   173  
   174  					// check labels and finalizers
   175  					g.Expect(cd.Finalizers).ShouldNot(BeEmpty())
   176  					configCMLabel := cfgcore.GenerateTPLUniqLabelKeyWithConfig(cmName)
   177  					configConstraintLabel := cfgcore.GenerateConstraintsUniqLabelKeyWithConfig(cmName)
   178  					g.Expect(cd.Labels[configCMLabel]).Should(BeEquivalentTo(cmName))
   179  					g.Expect(cd.Labels[configConstraintLabel]).Should(BeEquivalentTo(cmName))
   180  				})).Should(Succeed())
   181  
   182  			By("check the reconciler update configmap.Finalizer after configmap is created.")
   183  			cmKey := types.NamespacedName{
   184  				Namespace: testCtx.DefaultNamespace,
   185  				Name:      cmName,
   186  			}
   187  			Eventually(testapps.CheckObj(&testCtx, cmKey, func(g Gomega, cmObj *corev1.ConfigMap) {
   188  				g.Expect(controllerutil.ContainsFinalizer(cmObj, constant.ConfigurationTemplateFinalizerName)).To(BeTrue())
   189  			})).Should(Succeed())
   190  		})
   191  	})
   192  })