diff --git a/cmd/manager/main.go b/cmd/manager/main.go index ec42b1170d9..66ab14cee1b 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -61,6 +61,8 @@ import ( workloadsv1 "github.com/apecloud/kubeblocks/apis/workloads/v1" workloadsv1alpha1 "github.com/apecloud/kubeblocks/apis/workloads/v1alpha1" appscontrollers "github.com/apecloud/kubeblocks/controllers/apps" + "github.com/apecloud/kubeblocks/controllers/apps/cluster" + "github.com/apecloud/kubeblocks/controllers/apps/component" "github.com/apecloud/kubeblocks/controllers/apps/configuration" experimentalcontrollers "github.com/apecloud/kubeblocks/controllers/experimental" extensionscontrollers "github.com/apecloud/kubeblocks/controllers/extensions" @@ -443,7 +445,7 @@ func main() { os.Exit(1) } - if err = (&appscontrollers.ClusterReconciler{ + if err = (&cluster.ClusterReconciler{ Client: client, Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorderFor("cluster-controller"), @@ -453,7 +455,7 @@ func main() { os.Exit(1) } - if err = (&appscontrollers.ComponentReconciler{ + if err = (&component.ComponentReconciler{ Client: client, Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorderFor("component-controller"), diff --git a/controllers/apps/cluster_controller.go b/controllers/apps/cluster/cluster_controller.go similarity index 99% rename from controllers/apps/cluster_controller.go rename to controllers/apps/cluster/cluster_controller.go index 9c8dff1e7cb..d6edbccac60 100644 --- a/controllers/apps/cluster_controller.go +++ b/controllers/apps/cluster/cluster_controller.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package cluster import ( "context" diff --git a/controllers/apps/cluster_controller_test.go b/controllers/apps/cluster/cluster_controller_test.go similarity index 95% rename from controllers/apps/cluster_controller_test.go rename to controllers/apps/cluster/cluster_controller_test.go index 38f5578816e..0f17c67cfa4 100644 --- a/controllers/apps/cluster_controller_test.go +++ b/controllers/apps/cluster/cluster_controller_test.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package cluster import ( "encoding/json" @@ -422,7 +422,7 @@ var _ = Describe("Cluster Controller", func() { clusterKey = client.ObjectKeyFromObject(clusterObj) By("waiting for the cluster controller to create resources completely") - waitForCreatingResourceCompletely(clusterKey, compName, otherCompName) + waitForCreatingResourceCompletely(clusterKey) By("scale in the target component") Expect(testapps.GetAndChangeObj(&testCtx, clusterKey, func(cluster *appsv1.Cluster) { @@ -512,19 +512,6 @@ var _ = Describe("Cluster Controller", func() { g.Expect(svc.Spec.Selector).Should(HaveKeyWithValue(constant.RoleLabelKey, constant.Follower)) g.Expect(svc.Spec.ExternalTrafficPolicy).Should(BeEquivalentTo(corev1.ServiceExternalTrafficPolicyTypeLocal)) })).Should(Succeed()) - - By("check default component service created") - compSvcKey := types.NamespacedName{ - Namespace: clusterKey.Namespace, - Name: constant.GenerateComponentServiceName(clusterObj.Name, compName, ""), - } - Eventually(testapps.CheckObj(&testCtx, compSvcKey, func(g Gomega, svc *corev1.Service) { - g.Expect(svc.Spec.Selector).Should(HaveKeyWithValue(constant.AppManagedByLabelKey, constant.AppName)) - g.Expect(svc.Spec.Selector).Should(HaveKeyWithValue(constant.AppInstanceLabelKey, clusterObj.Name)) - g.Expect(svc.Spec.Selector).Should(HaveKeyWithValue(constant.KBAppComponentLabelKey, compName)) - g.Expect(svc.Spec.Selector).Should(HaveKey(constant.RoleLabelKey)) - g.Expect(svc.Spec.Selector).Should(HaveKeyWithValue(constant.RoleLabelKey, constant.Leader)) - })).Should(Succeed()) } type expectService struct { @@ -1199,7 +1186,8 @@ var _ = Describe("Cluster Controller", func() { })).Should(Succeed()) }) - Context("cluster component annotations and labels", func() { + // TODO: refactor the case and should not depend on objects created by the component controller + PContext("cluster component annotations and labels", func() { BeforeEach(func() { cleanEnv() createAllDefinitionObjects() @@ -1216,21 +1204,22 @@ var _ = Describe("Cluster Controller", func() { (*metaMap)[key] = value } - checkRelatedObject := func(compName string, checkFunc func(g Gomega, obj client.Object)) { - // check related services of the component - defaultSvcName := constant.GenerateComponentServiceName(clusterObj.Name, compName, "") - Eventually(testapps.CheckObj(&testCtx, client.ObjectKey{Name: defaultSvcName, - Namespace: testCtx.DefaultNamespace}, func(g Gomega, svc *corev1.Service) { - checkFunc(g, svc) - })).Should(Succeed()) - - // check related account secret of the component - rootAccountSecretName := constant.GenerateAccountSecretName(clusterObj.Name, compName, "root") - Eventually(testapps.CheckObj(&testCtx, client.ObjectKey{Name: rootAccountSecretName, - Namespace: testCtx.DefaultNamespace}, func(g Gomega, secret *corev1.Secret) { - checkFunc(g, secret) - })).Should(Succeed()) - } + // TODO: remove it + // checkRelatedObject := func(compName string, checkFunc func(g Gomega, obj client.Object)) { + // // check related services of the component + // defaultSvcName := constant.GenerateComponentServiceName(clusterObj.Name, compName, "") + // Eventually(testapps.CheckObj(&testCtx, client.ObjectKey{Name: defaultSvcName, + // Namespace: testCtx.DefaultNamespace}, func(g Gomega, svc *corev1.Service) { + // checkFunc(g, svc) + // })).Should(Succeed()) + // + // // check related account secret of the component + // rootAccountSecretName := constant.GenerateAccountSecretName(clusterObj.Name, compName, "root") + // Eventually(testapps.CheckObj(&testCtx, client.ObjectKey{Name: rootAccountSecretName, + // Namespace: testCtx.DefaultNamespace}, func(g Gomega, secret *corev1.Secret) { + // checkFunc(g, secret) + // })).Should(Succeed()) + // } testUpdateAnnoAndLabels := func(compName string, changeCluster func(cluster *appsv1.Cluster), @@ -1247,17 +1236,19 @@ var _ = Describe("Cluster Controller", func() { checkWorkloadFunc(g, compObj.Spec.Labels, compObj.Spec.Annotations, false) })).Should(Succeed()) - By("check related objects annotations and labels") - checkRelatedObject(defaultCompName, func(g Gomega, obj client.Object) { - checkRelatedObjFunc(g, obj) - }) - - By("InstanceSet.spec.template.annotations/labels need to be consistent with component") - // The labels and annotations of the Pod will be kept consistent with those of the InstanceSet - Eventually(testapps.CheckObj(&testCtx, client.ObjectKey{Name: workloadName, Namespace: testCtx.DefaultNamespace}, - func(g Gomega, instanceSet *workloadsv1.InstanceSet) { - checkWorkloadFunc(g, instanceSet.Spec.Template.GetLabels(), instanceSet.Spec.Template.GetAnnotations(), true) - })).Should(Succeed()) + // TODO: remove it + // By("check related objects annotations and labels") + // checkRelatedObject(defaultCompName, func(g Gomega, obj client.Object) { + // checkRelatedObjFunc(g, obj) + // }) + + // TODO: remove it + // By("InstanceSet.spec.template.annotations/labels need to be consistent with component") + //// The labels and annotations of the Pod will be kept consistent with those of the InstanceSet + // Eventually(testapps.CheckObj(&testCtx, client.ObjectKey{Name: workloadName, Namespace: testCtx.DefaultNamespace}, + // func(g Gomega, instanceSet *workloadsv1.InstanceSet) { + // checkWorkloadFunc(g, instanceSet.Spec.Template.GetLabels(), instanceSet.Spec.Template.GetAnnotations(), true) + // })).Should(Succeed()) } It("test add/override annotations and labels", func() { @@ -1342,7 +1333,6 @@ var _ = Describe("Cluster Controller", func() { g.Expect(obj.GetLabels()[key2]).Should(Equal(value2)) g.Expect(obj.GetAnnotations()[key2]).Should(Equal(value2)) }) - }) }) }) diff --git a/controllers/apps/cluster_plan_builder.go b/controllers/apps/cluster/cluster_plan_builder.go similarity index 98% rename from controllers/apps/cluster_plan_builder.go rename to controllers/apps/cluster/cluster_plan_builder.go index edc3b3ab218..fcf086161f2 100644 --- a/controllers/apps/cluster_plan_builder.go +++ b/controllers/apps/cluster/cluster_plan_builder.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package cluster import ( "context" @@ -194,11 +194,6 @@ func (c *clusterPlanBuilder) AddTransformer(transformer ...graph.Transformer) gr return c } -func (c *clusterPlanBuilder) AddParallelTransformer(transformer ...graph.Transformer) graph.PlanBuilder { - c.transformers = append(c.transformers, &ParallelTransformers{transformers: transformer}) - return c -} - // Build runs all transformers to generate a plan func (c *clusterPlanBuilder) Build() (graph.Plan, error) { var err error diff --git a/controllers/apps/cluster_plan_builder_test.go b/controllers/apps/cluster/cluster_plan_builder_test.go similarity index 99% rename from controllers/apps/cluster_plan_builder_test.go rename to controllers/apps/cluster/cluster_plan_builder_test.go index 5e214f8215d..43e9bacfb27 100644 --- a/controllers/apps/cluster_plan_builder_test.go +++ b/controllers/apps/cluster/cluster_plan_builder_test.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package cluster import ( . "github.com/onsi/ginkgo/v2" diff --git a/controllers/apps/cluster_status_conditions.go b/controllers/apps/cluster/cluster_status_conditions.go similarity index 99% rename from controllers/apps/cluster_status_conditions.go rename to controllers/apps/cluster/cluster_status_conditions.go index a016853a744..306d2dae8b6 100644 --- a/controllers/apps/cluster_status_conditions.go +++ b/controllers/apps/cluster/cluster_status_conditions.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package cluster import ( "fmt" diff --git a/controllers/apps/cluster/suite_test.go b/controllers/apps/cluster/suite_test.go new file mode 100644 index 00000000000..f7aa2991958 --- /dev/null +++ b/controllers/apps/cluster/suite_test.go @@ -0,0 +1,275 @@ +/* +Copyright (C) 2022-2024 ApeCloud Co., Ltd + +This file is part of KubeBlocks project + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package cluster + +import ( + "context" + "fmt" + "go/build" + "path/filepath" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/go-logr/logr" + snapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1" + "go.uber.org/zap/zapcore" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/record" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/controller-runtime/pkg/metrics/server" + + appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" + appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" + appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + dpv1alpha1 "github.com/apecloud/kubeblocks/apis/dataprotection/v1alpha1" + opsv1alpha1 "github.com/apecloud/kubeblocks/apis/operations/v1alpha1" + workloadsv1 "github.com/apecloud/kubeblocks/apis/workloads/v1" + "github.com/apecloud/kubeblocks/controllers/apps" + "github.com/apecloud/kubeblocks/controllers/apps/configuration" + "github.com/apecloud/kubeblocks/controllers/dataprotection" + "github.com/apecloud/kubeblocks/controllers/k8score" + "github.com/apecloud/kubeblocks/pkg/constant" + "github.com/apecloud/kubeblocks/pkg/controller/model" + intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" + "github.com/apecloud/kubeblocks/pkg/testutil" + viper "github.com/apecloud/kubeblocks/pkg/viperx" +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +const ( + testDataPlaneNodeAffinityKey = "testDataPlaneNodeAffinityKey" + testDataPlaneTolerationKey = "testDataPlaneTolerationKey" +) + +var cfg *rest.Config +var k8sClient client.Client +var testEnv *envtest.Environment +var ctx context.Context +var cancel context.CancelFunc +var testCtx testutil.TestContext +var clusterRecorder record.EventRecorder +var logger logr.Logger + +func init() { + viper.AutomaticEnv() + // viper.Set("ENABLE_DEBUG_LOG", "true") +} + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "Controller Suite") +} + +var _ = BeforeSuite(func() { + if viper.GetBool("ENABLE_DEBUG_LOG") { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true), func(o *zap.Options) { + o.TimeEncoder = zapcore.ISO8601TimeEncoder + })) + } else { + logf.SetLogger(logr.New(logf.NullLogSink{})) + } + + viper.SetDefault(constant.CfgKeyCtrlrReconcileRetryDurationMS, 10) + viper.Set(constant.CfgKeyDataPlaneTolerations, + fmt.Sprintf("[{\"key\":\"%s\", \"operator\": \"Exists\", \"effect\": \"NoSchedule\"}]", testDataPlaneTolerationKey)) + viper.Set(constant.CfgKeyDataPlaneAffinity, + fmt.Sprintf("{\"nodeAffinity\":{\"preferredDuringSchedulingIgnoredDuringExecution\":[{\"preference\":{\"matchExpressions\":[{\"key\":\"%s\",\"operator\":\"In\",\"values\":[\"true\"]}]},\"weight\":100}]}}", testDataPlaneNodeAffinityKey)) + + ctx, cancel = context.WithCancel(context.TODO()) + logger = logf.FromContext(ctx).WithValues() + logger.Info("logger start") + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd", "bases"), + // use dependent external CRDs. + // resolved by ref: https://github.com/operator-framework/operator-sdk/issues/4434#issuecomment-786794418 + filepath.Join(build.Default.GOPATH, "pkg", "mod", "github.com", "kubernetes-csi/external-snapshotter/", + "client/v6@v6.2.0", "config", "crd"), + }, + ErrorIfCRDPathMissing: true, + } + + var err error + // cfg is defined in this file globally. + cfg, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + err = appsv1alpha1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + model.AddScheme(appsv1alpha1.AddToScheme) + + err = opsv1alpha1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + model.AddScheme(opsv1alpha1.AddToScheme) + + err = appsv1beta1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + model.AddScheme(appsv1beta1.AddToScheme) + + err = appsv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + model.AddScheme(appsv1.AddToScheme) + + err = dpv1alpha1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + model.AddScheme(dpv1alpha1.AddToScheme) + + err = snapshotv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + model.AddScheme(snapshotv1.AddToScheme) + + err = workloadsv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + model.AddScheme(workloadsv1.AddToScheme) + + // +kubebuilder:scaffold:rscheme + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + + // run reconcile + k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{ + Scheme: scheme.Scheme, + Metrics: server.Options{BindAddress: "0"}, + Client: client.Options{ + Cache: &client.CacheOptions{ + DisableFor: intctrlutil.GetUncachedObjects(), + }, + }, + }) + Expect(err).ToNot(HaveOccurred()) + + viper.SetDefault("CERT_DIR", "/tmp/k8s-webhook-server/serving-certs") + viper.SetDefault(constant.KBToolsImage, "docker.io/apecloud/kubeblocks-tools:latest") + viper.SetDefault("PROBE_SERVICE_PORT", 3501) + viper.SetDefault("PROBE_SERVICE_LOG_LEVEL", "info") + viper.SetDefault("CM_NAMESPACE", "default") + viper.SetDefault("HOST_PORT_CM_NAME", "kubeblocks-host-ports") + viper.SetDefault(constant.EnableRBACManager, true) + + err = intctrlutil.InitHostPortManager(k8sClient) + Expect(err).ToNot(HaveOccurred()) + + err = (&apps.ClusterDefinitionReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Recorder: k8sManager.GetEventRecorderFor("cluster-definition-controller"), + }).SetupWithManager(k8sManager) + Expect(err).ToNot(HaveOccurred()) + + err = (&apps.ShardingDefinitionReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Recorder: k8sManager.GetEventRecorderFor("sharding-definition-controller"), + }).SetupWithManager(k8sManager) + Expect(err).ToNot(HaveOccurred()) + + err = (&apps.ComponentDefinitionReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Recorder: k8sManager.GetEventRecorderFor("component-definition-controller"), + }).SetupWithManager(k8sManager) + Expect(err).ToNot(HaveOccurred()) + + err = (&apps.ComponentVersionReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Recorder: k8sManager.GetEventRecorderFor("component-version-controller"), + }).SetupWithManager(k8sManager) + Expect(err).ToNot(HaveOccurred()) + + err = (&apps.SidecarDefinitionReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Recorder: k8sManager.GetEventRecorderFor("sidecar-definition-controller"), + }).SetupWithManager(k8sManager) + Expect(err).ToNot(HaveOccurred()) + + clusterRecorder = k8sManager.GetEventRecorderFor("cluster-controller") + err = (&ClusterReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Recorder: clusterRecorder, + }).SetupWithManager(k8sManager) + Expect(err).ToNot(HaveOccurred()) + + err = (&apps.ServiceDescriptorReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Recorder: k8sManager.GetEventRecorderFor("service-descriptor-controller"), + }).SetupWithManager(k8sManager) + Expect(err).ToNot(HaveOccurred()) + + err = (&k8score.EventReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Recorder: k8sManager.GetEventRecorderFor("event-controller"), + }).SetupWithManager(k8sManager, nil) + Expect(err).ToNot(HaveOccurred()) + + err = (&configuration.ConfigConstraintReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Recorder: k8sManager.GetEventRecorderFor("configuration-template-controller"), + }).SetupWithManager(k8sManager) + Expect(err).ToNot(HaveOccurred()) + + err = (&configuration.ConfigurationReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Recorder: k8sManager.GetEventRecorderFor("configuration-controller"), + }).SetupWithManager(k8sManager, nil) + Expect(err).ToNot(HaveOccurred()) + + err = (&dataprotection.BackupPolicyTemplateReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Recorder: k8sManager.GetEventRecorderFor("backup-policy-template-controller"), + }).SetupWithManager(k8sManager) + Expect(err).ToNot(HaveOccurred()) + + testCtx = testutil.NewDefaultTestContext(ctx, k8sClient, testEnv) + + go func() { + defer GinkgoRecover() + err = k8sManager.Start(ctx) + Expect(err).ToNot(HaveOccurred(), "failed to run manager") + }() +}) + +var _ = AfterSuite(func() { + cancel() + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) diff --git a/controllers/apps/cluster/test_utils.go b/controllers/apps/cluster/test_utils.go new file mode 100644 index 00000000000..58f64de91aa --- /dev/null +++ b/controllers/apps/cluster/test_utils.go @@ -0,0 +1,72 @@ +/* +Copyright (C) 2022-2024 ApeCloud Co., Ltd + +This file is part of KubeBlocks project + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package cluster + +import ( + "context" + "fmt" + "reflect" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type mockReader struct { + objs []client.Object +} + +func (r *mockReader) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { + for _, o := range r.objs { + // ignore the GVK check + if client.ObjectKeyFromObject(o) == key { + reflect.ValueOf(obj).Elem().Set(reflect.ValueOf(o).Elem()) + return nil + } + } + return apierrors.NewNotFound(schema.GroupResource{}, key.Name) +} + +func (r *mockReader) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { + items := reflect.ValueOf(list).Elem().FieldByName("Items") + if !items.IsValid() { + return fmt.Errorf("ObjectList has no Items field: %s", list.GetObjectKind().GroupVersionKind().String()) + } + + objs := reflect.MakeSlice(items.Type(), 0, 0) + if len(r.objs) > 0 { + listOpts := &client.ListOptions{} + for _, opt := range opts { + opt.ApplyToList(listOpts) + } + + for i, o := range r.objs { + if reflect.TypeOf(r.objs[i]).Elem().AssignableTo(items.Type().Elem()) { + if listOpts.LabelSelector == nil || listOpts.LabelSelector.Matches(labels.Set(o.GetLabels())) { + objs = reflect.Append(objs, reflect.ValueOf(r.objs[i]).Elem()) + } + } + } + } + items.Set(objs) + + return nil +} diff --git a/controllers/apps/transformer_cluster_backup_policy.go b/controllers/apps/cluster/transformer_cluster_backup_policy.go similarity index 99% rename from controllers/apps/transformer_cluster_backup_policy.go rename to controllers/apps/cluster/transformer_cluster_backup_policy.go index f6867accd71..70d2ca64b69 100644 --- a/controllers/apps/transformer_cluster_backup_policy.go +++ b/controllers/apps/cluster/transformer_cluster_backup_policy.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package cluster import ( "context" @@ -645,7 +645,7 @@ func (r *backupPolicyBuilder) mergeClusterBackup( func (r *backupPolicyBuilder) buildAnnotations() map[string]string { annotations := map[string]string{ - dptypes.DefaultBackupPolicyAnnotationKey: trueVal, + dptypes.DefaultBackupPolicyAnnotationKey: "true", constant.BackupPolicyTemplateAnnotationKey: r.backupPolicyTPL.Name, } if r.backupPolicyTPL.Annotations[dptypes.ReconfigureRefAnnotationKey] != "" { diff --git a/controllers/apps/transformer_cluster_component.go b/controllers/apps/cluster/transformer_cluster_component.go similarity index 99% rename from controllers/apps/transformer_cluster_component.go rename to controllers/apps/cluster/transformer_cluster_component.go index 49db7eedb8e..9912135f803 100644 --- a/controllers/apps/transformer_cluster_component.go +++ b/controllers/apps/cluster/transformer_cluster_component.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package cluster import ( "context" @@ -761,7 +761,7 @@ func (h *clusterShardingHandler) deleteComp(transCtx *clusterTransformContext, if comp.Annotations == nil { comp.Annotations = make(map[string]string) } - comp.Annotations[constant.ComponentScaleInAnnotationKey] = trueVal + comp.Annotations[constant.ComponentScaleInAnnotationKey] = "true" graphCli.Do(dag, compCopy, comp, model.ActionUpdatePtr(), vertex) } } diff --git a/controllers/apps/transformer_cluster_component_status.go b/controllers/apps/cluster/transformer_cluster_component_status.go similarity index 96% rename from controllers/apps/transformer_cluster_component_status.go rename to controllers/apps/cluster/transformer_cluster_component_status.go index 2382ff47d7f..73debda941d 100644 --- a/controllers/apps/transformer_cluster_component_status.go +++ b/controllers/apps/cluster/transformer_cluster_component_status.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package cluster import ( "fmt" @@ -34,6 +34,11 @@ import ( "github.com/apecloud/kubeblocks/pkg/controller/model" ) +const ( + // clusterCompPhaseTransition the event reason indicates that the cluster component transits to a new phase. + clusterCompPhaseTransition = "ClusterComponentPhaseTransition" +) + // clusterComponentStatusTransformer transforms cluster components' status. type clusterComponentStatusTransformer struct{} @@ -171,7 +176,7 @@ func (t *clusterComponentStatusTransformer) buildClusterCompStatus(transCtx *clu if phase != status.Phase { msg := clusterCompNShardingPhaseTransitionMsg("component", compName, status.Phase) if transCtx.GetRecorder() != nil && msg != "" { - transCtx.GetRecorder().Eventf(transCtx.Cluster, corev1.EventTypeNormal, componentPhaseTransition, msg) + transCtx.GetRecorder().Eventf(transCtx.Cluster, corev1.EventTypeNormal, clusterCompPhaseTransition, msg) } transCtx.GetLogger().Info(fmt.Sprintf("cluster component phase transition: %s -> %s (%s)", phase, status.Phase, msg)) } @@ -239,7 +244,7 @@ func (t *clusterComponentStatusTransformer) buildClusterShardingStatus(transCtx if phase != status.Phase { msg := clusterCompNShardingPhaseTransitionMsg("sharding", shardingName, status.Phase) if transCtx.GetRecorder() != nil && msg != "" { - transCtx.GetRecorder().Eventf(transCtx.Cluster, corev1.EventTypeNormal, componentPhaseTransition, msg) + transCtx.GetRecorder().Eventf(transCtx.Cluster, corev1.EventTypeNormal, clusterCompPhaseTransition, msg) } transCtx.GetLogger().Info(fmt.Sprintf("cluster sharding phase transition: %s -> %s (%s)", phase, status.Phase, msg)) } diff --git a/controllers/apps/transformer_cluster_component_status_test.go b/controllers/apps/cluster/transformer_cluster_component_status_test.go similarity index 99% rename from controllers/apps/transformer_cluster_component_status_test.go rename to controllers/apps/cluster/transformer_cluster_component_status_test.go index d4bfe371fd3..0be5168d61a 100644 --- a/controllers/apps/transformer_cluster_component_status_test.go +++ b/controllers/apps/cluster/transformer_cluster_component_status_test.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package cluster import ( . "github.com/onsi/ginkgo/v2" diff --git a/controllers/apps/transformer_cluster_component_test.go b/controllers/apps/cluster/transformer_cluster_component_test.go similarity index 97% rename from controllers/apps/transformer_cluster_component_test.go rename to controllers/apps/cluster/transformer_cluster_component_test.go index f64228540e0..02ed3bffe44 100644 --- a/controllers/apps/transformer_cluster_component_test.go +++ b/controllers/apps/cluster/transformer_cluster_component_test.go @@ -17,12 +17,10 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package cluster import ( - "context" "fmt" - "reflect" "strings" . "github.com/onsi/ginkgo/v2" @@ -30,9 +28,6 @@ import ( "github.com/onsi/gomega/types" corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/utils/pointer" "sigs.k8s.io/controller-runtime/pkg/client" @@ -45,47 +40,6 @@ import ( testapps "github.com/apecloud/kubeblocks/pkg/testutil/apps" ) -type mockReader struct { - objs []client.Object -} - -func (r *mockReader) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { - for _, o := range r.objs { - // ignore the GVK check - if client.ObjectKeyFromObject(o) == key { - reflect.ValueOf(obj).Elem().Set(reflect.ValueOf(o).Elem()) - return nil - } - } - return apierrors.NewNotFound(schema.GroupResource{}, key.Name) -} - -func (r *mockReader) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { - items := reflect.ValueOf(list).Elem().FieldByName("Items") - if !items.IsValid() { - return fmt.Errorf("ObjectList has no Items field: %s", list.GetObjectKind().GroupVersionKind().String()) - } - - objs := reflect.MakeSlice(items.Type(), 0, 0) - if len(r.objs) > 0 { - listOpts := &client.ListOptions{} - for _, opt := range opts { - opt.ApplyToList(listOpts) - } - - for i, o := range r.objs { - if reflect.TypeOf(r.objs[i]).Elem().AssignableTo(items.Type().Elem()) { - if listOpts.LabelSelector == nil || listOpts.LabelSelector.Matches(labels.Set(o.GetLabels())) { - objs = reflect.Append(objs, reflect.ValueOf(r.objs[i]).Elem()) - } - } - } - } - items.Set(objs) - - return nil -} - var _ = Describe("cluster component transformer test", func() { const ( clusterDefName = "test-clusterdef" diff --git a/controllers/apps/transformer_cluster_deletion.go b/controllers/apps/cluster/transformer_cluster_deletion.go similarity index 99% rename from controllers/apps/transformer_cluster_deletion.go rename to controllers/apps/cluster/transformer_cluster_deletion.go index da5c2a90604..b51c3ffd571 100644 --- a/controllers/apps/transformer_cluster_deletion.go +++ b/controllers/apps/cluster/transformer_cluster_deletion.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package cluster import ( "fmt" diff --git a/controllers/apps/transformer_cluster_deletion_test.go b/controllers/apps/cluster/transformer_cluster_deletion_test.go similarity index 99% rename from controllers/apps/transformer_cluster_deletion_test.go rename to controllers/apps/cluster/transformer_cluster_deletion_test.go index 7b90fac7650..cc3fe852d0f 100644 --- a/controllers/apps/transformer_cluster_deletion_test.go +++ b/controllers/apps/cluster/transformer_cluster_deletion_test.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package cluster import ( "slices" diff --git a/controllers/apps/transformer_cluster_init.go b/controllers/apps/cluster/transformer_cluster_init.go similarity index 98% rename from controllers/apps/transformer_cluster_init.go rename to controllers/apps/cluster/transformer_cluster_init.go index 81b487502b7..a71e417394b 100644 --- a/controllers/apps/transformer_cluster_init.go +++ b/controllers/apps/cluster/transformer_cluster_init.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package cluster import ( appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" diff --git a/controllers/apps/transformer_cluster_meta.go b/controllers/apps/cluster/transformer_cluster_meta.go similarity index 99% rename from controllers/apps/transformer_cluster_meta.go rename to controllers/apps/cluster/transformer_cluster_meta.go index 13285913648..b402b304e89 100644 --- a/controllers/apps/transformer_cluster_meta.go +++ b/controllers/apps/cluster/transformer_cluster_meta.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package cluster import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" diff --git a/controllers/apps/transformer_cluster_normalization.go b/controllers/apps/cluster/transformer_cluster_normalization.go similarity index 65% rename from controllers/apps/transformer_cluster_normalization.go rename to controllers/apps/cluster/transformer_cluster_normalization.go index 570e7908d75..e3044fcf02f 100644 --- a/controllers/apps/transformer_cluster_normalization.go +++ b/controllers/apps/cluster/transformer_cluster_normalization.go @@ -17,15 +17,19 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package cluster import ( + "context" "fmt" + "slices" + "strings" "golang.org/x/exp/maps" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/version" "sigs.k8s.io/controller-runtime/pkg/client" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" @@ -475,3 +479,241 @@ func (t *clusterNormalizationTransformer) checkNPatchCRDAPIVersionKey(transCtx * } return graph.ErrPrematureStop // un-supported CRD API version, stop the transformation } + +// referredClusterTopology returns the cluster topology which has name @name. +func referredClusterTopology(clusterDef *appsv1.ClusterDefinition, name string) *appsv1.ClusterTopology { + if clusterDef != nil { + if len(name) == 0 { + return defaultClusterTopology(clusterDef) + } + for i, topology := range clusterDef.Spec.Topologies { + if topology.Name == name { + return &clusterDef.Spec.Topologies[i] + } + } + } + return nil +} + +// defaultClusterTopology returns the default cluster topology in specified cluster definition. +func defaultClusterTopology(clusterDef *appsv1.ClusterDefinition) *appsv1.ClusterTopology { + for i, topology := range clusterDef.Spec.Topologies { + if topology.Default { + return &clusterDef.Spec.Topologies[i] + } + } + return nil +} + +func clusterTopologyCompMatched(comp appsv1.ClusterTopologyComponent, compName string) bool { + if comp.Name == compName { + return true + } + if comp.Template != nil && *comp.Template { + return strings.HasPrefix(compName, comp.Name) + } + return false +} + +// resolveShardingDefinition resolves and returns the specific sharding definition object supported. +func resolveShardingDefinition(ctx context.Context, cli client.Reader, shardingDefName string) (*appsv1.ShardingDefinition, error) { + shardingDefs, err := listShardingDefinitionsWithPattern(ctx, cli, shardingDefName) + if err != nil { + return nil, err + } + if len(shardingDefs) == 0 { + return nil, fmt.Errorf("no sharding definition found for the specified name: %s", shardingDefName) + } + + m := make(map[string]int) + for i, def := range shardingDefs { + m[def.Name] = i + } + // choose the latest one + names := maps.Keys(m) + slices.Sort(names) + latestName := names[len(names)-1] + + return shardingDefs[m[latestName]], nil +} + +// listShardingDefinitionsWithPattern returns all sharding definitions whose names match the given pattern +func listShardingDefinitionsWithPattern(ctx context.Context, cli client.Reader, name string) ([]*appsv1.ShardingDefinition, error) { + shardingDefList := &appsv1.ShardingDefinitionList{} + if err := cli.List(ctx, shardingDefList); err != nil { + return nil, err + } + fullyMatched := make([]*appsv1.ShardingDefinition, 0) + patternMatched := make([]*appsv1.ShardingDefinition, 0) + for i, item := range shardingDefList.Items { + if item.Name == name { + fullyMatched = append(fullyMatched, &shardingDefList.Items[i]) + } + if component.PrefixOrRegexMatched(item.Name, name) { + patternMatched = append(patternMatched, &shardingDefList.Items[i]) + } + } + if len(fullyMatched) > 0 { + return fullyMatched, nil + } + return patternMatched, nil +} + +func validateShardingShards(shardingDef *appsv1.ShardingDefinition, sharding *appsv1.ClusterSharding) error { + var ( + limit = shardingDef.Spec.ShardsLimit + shards = sharding.Shards + ) + if limit == nil || (shards >= limit.MinShards && shards <= limit.MaxShards) { + return nil + } + return shardsOutOfLimitError(sharding.Name, shards, *limit) +} + +func shardsOutOfLimitError(shardingName string, shards int32, limit appsv1.ShardsLimit) error { + return fmt.Errorf("shards %d out-of-limit [%d, %d], sharding: %s", shards, limit.MinShards, limit.MaxShards, shardingName) +} + +// resolveCompDefinitionNServiceVersion resolves and returns the specific component definition object and the service version supported. +func resolveCompDefinitionNServiceVersion(ctx context.Context, cli client.Reader, compDefName, serviceVersion string) (*appsv1.ComponentDefinition, string, error) { + var ( + compDef *appsv1.ComponentDefinition + ) + compDefs, err := listCompDefinitionsWithPattern(ctx, cli, compDefName) + if err != nil { + return compDef, serviceVersion, err + } + + // mapping from to <[]*appsv1.ComponentDefinition> + serviceVersionToCompDefs, err := serviceVersionToCompDefinitions(ctx, cli, compDefs, serviceVersion) + if err != nil { + return compDef, serviceVersion, err + } + + // use specified service version or the latest. + if len(serviceVersion) == 0 { + serviceVersions := maps.Keys(serviceVersionToCompDefs) + if len(serviceVersions) > 0 { + slices.SortFunc(serviceVersions, serviceVersionComparator) + serviceVersion = serviceVersions[len(serviceVersions)-1] + } + } + + // component definitions that support the service version + compatibleCompDefs := serviceVersionToCompDefs[serviceVersion] + if len(compatibleCompDefs) == 0 { + return compDef, serviceVersion, fmt.Errorf(`no matched component definition found with componentDef "%s" and serviceVersion "%s"`, compDefName, serviceVersion) + } + + // choose the latest one + compatibleCompDefNames := maps.Keys(compatibleCompDefs) + slices.Sort(compatibleCompDefNames) + compatibleCompDefName := compatibleCompDefNames[len(compatibleCompDefNames)-1] + + return compatibleCompDefs[compatibleCompDefName], serviceVersion, nil +} + +// listCompDefinitionsWithPattern returns all component definitions whose names match the given pattern +func listCompDefinitionsWithPattern(ctx context.Context, cli client.Reader, name string) ([]*appsv1.ComponentDefinition, error) { + compDefList := &appsv1.ComponentDefinitionList{} + if err := cli.List(ctx, compDefList); err != nil { + return nil, err + } + compDefsFullyMatched := make([]*appsv1.ComponentDefinition, 0) + compDefsPatternMatched := make([]*appsv1.ComponentDefinition, 0) + for i, item := range compDefList.Items { + if item.Name == name { + compDefsFullyMatched = append(compDefsFullyMatched, &compDefList.Items[i]) + } + if component.PrefixOrRegexMatched(item.Name, name) { + compDefsPatternMatched = append(compDefsPatternMatched, &compDefList.Items[i]) + } + } + if len(compDefsFullyMatched) > 0 { + return compDefsFullyMatched, nil + } + return compDefsPatternMatched, nil +} + +func serviceVersionToCompDefinitions(ctx context.Context, cli client.Reader, + compDefs []*appsv1.ComponentDefinition, serviceVersion string) (map[string]map[string]*appsv1.ComponentDefinition, error) { + result := make(map[string]map[string]*appsv1.ComponentDefinition) + + insert := func(version string, compDef *appsv1.ComponentDefinition) { + if _, ok := result[version]; !ok { + result[version] = make(map[string]*appsv1.ComponentDefinition) + } + result[version][compDef.Name] = compDef + } + + checkedInsert := func(version string, compDef *appsv1.ComponentDefinition) error { + match, err := component.CompareServiceVersion(serviceVersion, version) + if err == nil && match { + insert(version, compDef) + } + return err + } + + for _, compDef := range compDefs { + compVersions, err := component.CompatibleCompVersions4Definition(ctx, cli, compDef) + if err != nil { + return nil, err + } + + serviceVersions := sets.New[string]() + // add definition's service version as default, in case there is no component versions provided + if compDef.Spec.ServiceVersion != "" { + serviceVersions.Insert(compDef.Spec.ServiceVersion) + } + for _, compVersion := range compVersions { + serviceVersions = serviceVersions.Union(compatibleServiceVersions4Definition(compDef, compVersion)) + } + + for version := range serviceVersions { + if err = checkedInsert(version, compDef); err != nil { + return nil, err + } + } + } + return result, nil +} + +// compatibleServiceVersions4Definition returns all service versions that are compatible with specified component definition. +func compatibleServiceVersions4Definition(compDef *appsv1.ComponentDefinition, compVersion *appsv1.ComponentVersion) sets.Set[string] { + match := func(pattern string) bool { + return component.PrefixOrRegexMatched(compDef.Name, pattern) + } + releases := make(map[string]bool, 0) + for _, rule := range compVersion.Spec.CompatibilityRules { + if slices.IndexFunc(rule.CompDefs, match) >= 0 { + for _, release := range rule.Releases { + releases[release] = true + } + } + } + serviceVersions := sets.New[string]() + for _, release := range compVersion.Spec.Releases { + if releases[release.Name] { + serviceVersions = serviceVersions.Insert(release.ServiceVersion) + } + } + return serviceVersions +} + +func serviceVersionComparator(a, b string) int { + if len(a) == 0 { + return -1 + } + if len(b) == 0 { + return 1 + } + v, err1 := version.ParseSemantic(a) + if err1 != nil { + panic(fmt.Sprintf("runtime error - invalid service version in comparator: %s", err1.Error())) + } + ret, err2 := v.Compare(b) + if err2 != nil { + panic(fmt.Sprintf("runtime error - invalid service version in comparator: %s", err2.Error())) + } + return ret +} diff --git a/controllers/apps/cluster/transformer_cluster_normalization_test.go b/controllers/apps/cluster/transformer_cluster_normalization_test.go new file mode 100644 index 00000000000..e3a64049447 --- /dev/null +++ b/controllers/apps/cluster/transformer_cluster_normalization_test.go @@ -0,0 +1,495 @@ +/* +Copyright (C) 2022-2024 ApeCloud Co., Ltd + +This file is part of KubeBlocks project + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package cluster + +import ( + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" + "github.com/apecloud/kubeblocks/pkg/controller/component" + "github.com/apecloud/kubeblocks/pkg/generics" + testapps "github.com/apecloud/kubeblocks/pkg/testutil/apps" +) + +var _ = Describe("resolve CompDefinition and ServiceVersion", func() { + var ( + compVersionObj *appsv1.ComponentVersion + compDefNames = []string{ + testapps.CompDefName("v1.0"), + testapps.CompDefName("v1.1"), + testapps.CompDefName("v2.0"), + testapps.CompDefName("v3.0"), + } + ) + + cleanEnv := func() { + // must wait till resources deleted and no longer existed before the testcases start, + // otherwise if later it needs to create some new resource objects with the same name, + // in race conditions, it will find the existence of old objects, resulting failure to + // create the new objects. + By("clean resources") + + // inNS := client.InNamespace(testCtx.DefaultNamespace) + ml := client.HasLabels{testCtx.TestObjLabelKey} + + // non-namespaced + testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.ComponentDefinitionSignature, true, ml) + testapps.ClearResourcesWithRemoveFinalizerOption(&testCtx, generics.ComponentVersionSignature, true, ml) + + // namespaced + } + + BeforeEach(func() { + cleanEnv() + }) + + AfterEach(func() { + cleanEnv() + }) + + createCompDefinitionObjs := func() []*appsv1.ComponentDefinition { + By("create default ComponentDefinition objs") + objs := make([]*appsv1.ComponentDefinition, 0) + for _, name := range compDefNames { + f := testapps.NewComponentDefinitionFactory(name). + SetServiceVersion(testapps.ServiceVersion("v0")) // use v0 as init service version + for _, app := range []string{testapps.AppName, testapps.AppNameSamePrefix} { + // use empty revision as init image tag + f = f.SetRuntime(&corev1.Container{Name: app, Image: testapps.AppImage(app, testapps.ReleaseID(""))}) + } + f.SetLifecycleAction(testapps.DefaultActionName, + &appsv1.Action{Exec: &appsv1.ExecAction{Image: testapps.AppImage(testapps.DefaultActionName, testapps.ReleaseID(""))}}) + objs = append(objs, f.Create(&testCtx).GetObject()) + } + for _, obj := range objs { + Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(obj), + func(g Gomega, compDef *appsv1.ComponentDefinition) { + g.Expect(compDef.Status.ObservedGeneration).Should(Equal(compDef.Generation)) + })).Should(Succeed()) + } + return objs + } + + createCompVersionObj := func() *appsv1.ComponentVersion { + By("create a default ComponentVersion obj with multiple releases") + obj := testapps.NewComponentVersionFactory(testapps.CompVersionName). + SetSpec(appsv1.ComponentVersionSpec{ + CompatibilityRules: []appsv1.ComponentVersionCompatibilityRule{ + { + // use prefix + CompDefs: []string{testapps.CompDefName("v1"), testapps.CompDefName("v2")}, + Releases: []string{testapps.ReleaseID("r0"), testapps.ReleaseID("r1"), testapps.ReleaseID("r2"), testapps.ReleaseID("r3"), testapps.ReleaseID("r4")}, // sv: v1, v2 + }, + { + // use prefix + CompDefs: []string{testapps.CompDefName("v3")}, + Releases: []string{testapps.ReleaseID("r5")}, // sv: v3 + }, + }, + Releases: []appsv1.ComponentVersionRelease{ + { + Name: testapps.ReleaseID("r0"), + Changes: "init release", + ServiceVersion: testapps.ServiceVersion("v1"), + Images: map[string]string{ + testapps.AppName: testapps.AppImage(testapps.AppName, testapps.ReleaseID("r0")), + testapps.AppNameSamePrefix: testapps.AppImage(testapps.AppNameSamePrefix, testapps.ReleaseID("r0")), + testapps.DefaultActionName: testapps.AppImage(testapps.DefaultActionName, testapps.ReleaseID("r0")), + }, + }, + { + Name: testapps.ReleaseID("r1"), + Changes: "update app image", + ServiceVersion: testapps.ServiceVersion("v1"), + Images: map[string]string{ + testapps.AppName: testapps.AppImage(testapps.AppName, testapps.ReleaseID("r1")), + }, + }, + { + Name: testapps.ReleaseID("r2"), + Changes: "publish a new service version", + ServiceVersion: testapps.ServiceVersion("v2"), + Images: map[string]string{ + testapps.AppName: testapps.AppImage(testapps.AppName, testapps.ReleaseID("r2")), + testapps.AppNameSamePrefix: testapps.AppImage(testapps.AppNameSamePrefix, testapps.ReleaseID("r2")), + testapps.DefaultActionName: testapps.AppImage(testapps.DefaultActionName, testapps.ReleaseID("r2")), + }, + }, + { + Name: testapps.ReleaseID("r3"), + Changes: "update app image", + ServiceVersion: testapps.ServiceVersion("v2"), + Images: map[string]string{ + testapps.AppName: testapps.AppImage(testapps.AppName, testapps.ReleaseID("r3")), + }, + }, + { + Name: testapps.ReleaseID("r4"), + Changes: "update all app images for previous service version", + ServiceVersion: testapps.ServiceVersion("v1"), + Images: map[string]string{ + testapps.AppName: testapps.AppImage(testapps.AppName, testapps.ReleaseID("r4")), + testapps.AppNameSamePrefix: testapps.AppImage(testapps.AppNameSamePrefix, testapps.ReleaseID("r4")), + testapps.DefaultActionName: testapps.AppImage(testapps.DefaultActionName, testapps.ReleaseID("r4")), + }, + }, + { + Name: testapps.ReleaseID("r5"), + Changes: "publish a new service version", + ServiceVersion: testapps.ServiceVersion("v3"), + Images: map[string]string{ + testapps.AppName: testapps.AppImage(testapps.AppName, testapps.ReleaseID("r5")), + testapps.AppNameSamePrefix: testapps.AppImage(testapps.AppNameSamePrefix, testapps.ReleaseID("r5")), + testapps.DefaultActionName: testapps.AppImage(testapps.DefaultActionName, testapps.ReleaseID("r5")), + }, + }, + }, + }). + Create(&testCtx). + GetObject() + Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(obj), + func(g Gomega, compVersion *appsv1.ComponentVersion) { + g.Expect(compVersion.Status.ObservedGeneration).Should(Equal(compVersion.Generation)) + })).Should(Succeed()) + + return obj + } + + updateNCheckCompDefinitionImages := func(compDef *appsv1.ComponentDefinition, serviceVersion string, r0, r1 string) { + Expect(compDef.Spec.Runtime.Containers[0].Image).Should(Equal(testapps.AppImage(compDef.Spec.Runtime.Containers[0].Name, testapps.ReleaseID("")))) + Expect(compDef.Spec.Runtime.Containers[1].Image).Should(Equal(testapps.AppImage(compDef.Spec.Runtime.Containers[1].Name, testapps.ReleaseID("")))) + Expect(component.UpdateCompDefinitionImages4ServiceVersion(testCtx.Ctx, testCtx.Cli, compDef, serviceVersion)).Should(Succeed()) + Expect(compDef.Spec.Runtime.Containers).Should(HaveLen(2)) + Expect(compDef.Spec.Runtime.Containers[0].Image).Should(Equal(testapps.AppImage(compDef.Spec.Runtime.Containers[0].Name, testapps.ReleaseID(r0)))) + Expect(compDef.Spec.Runtime.Containers[1].Image).Should(Equal(testapps.AppImage(compDef.Spec.Runtime.Containers[1].Name, testapps.ReleaseID(r1)))) + + Expect(compDef.Spec.LifecycleActions).ShouldNot(BeNil()) + Expect(compDef.Spec.LifecycleActions.PreTerminate).ShouldNot(BeNil()) + Expect(compDef.Spec.LifecycleActions.PreTerminate.Exec).ShouldNot(BeNil()) + Expect(compDef.Spec.LifecycleActions.PreTerminate.Exec.Image).Should(Equal(testapps.AppImage(testapps.DefaultActionName, testapps.ReleaseID(r1)))) + } + + Context("resolve component definition, service version and images", func() { + BeforeEach(func() { + createCompDefinitionObjs() + compVersionObj = createCompVersionObj() + }) + + AfterEach(func() { + cleanEnv() + }) + + It("full match", func() { + By("with definition v1.0 and service version v0") + compDef, resolvedServiceVersion, err := resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefName("v1.0"), testapps.ServiceVersion("v1")) + Expect(err).Should(Succeed()) + Expect(compDef.Name).Should(Equal(testapps.CompDefName("v1.0"))) + Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v1"))) + updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r4", "r4") + + By("with definition v1.1 and service version v0") + compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefName("v1.1"), testapps.ServiceVersion("v1")) + Expect(err).Should(Succeed()) + Expect(compDef.Name).Should(Equal(testapps.CompDefName("v1.1"))) + Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v1"))) + updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r4", "r4") + + By("with definition v2.0 and service version v0") + compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefName("v2.0"), testapps.ServiceVersion("v1")) + Expect(err).Should(Succeed()) + Expect(compDef.Name).Should(Equal(testapps.CompDefName("v2.0"))) + Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v1"))) + updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r4", "r4") + + By("with definition v1.0 and service version v1") + compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefName("v1.0"), testapps.ServiceVersion("v2")) + Expect(err).Should(Succeed()) + Expect(compDef.Name).Should(Equal(testapps.CompDefName("v1.0"))) + Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v2"))) + updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r3", "r2") + + By("with definition v1.1 and service version v1") + compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefName("v1.1"), testapps.ServiceVersion("v2")) + Expect(err).Should(Succeed()) + Expect(compDef.Name).Should(Equal(testapps.CompDefName("v1.1"))) + Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v2"))) + updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r3", "r2") + + By("with definition v2.0 and service version v1") + compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefName("v2.0"), testapps.ServiceVersion("v2")) + Expect(err).Should(Succeed()) + Expect(compDef.Name).Should(Equal(testapps.CompDefName("v2.0"))) + Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v2"))) + updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r3", "r2") + + By("with definition v3.0 and service version v2") + compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefName("v3.0"), testapps.ServiceVersion("v3")) + Expect(err).Should(Succeed()) + Expect(compDef.Name).Should(Equal(testapps.CompDefName("v3.0"))) + Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v3"))) + updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r5", "r5") + }) + + It("w/o service version", func() { + By("with definition v1.0") + compDef, resolvedServiceVersion, err := resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefName("v1.0"), "") + Expect(err).Should(Succeed()) + Expect(compDef.Name).Should(Equal(testapps.CompDefName("v1.0"))) + Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v2"))) + updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r3", "r2") + + By("with definition v1.1") + compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefName("v1.1"), "") + Expect(err).Should(Succeed()) + Expect(compDef.Name).Should(Equal(testapps.CompDefName("v1.1"))) + Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v2"))) + updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r3", "r2") + + By("with definition v2.0") + compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefName("v2.0"), "") + Expect(err).Should(Succeed()) + Expect(compDef.Name).Should(Equal(testapps.CompDefName("v2.0"))) + Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v2"))) + updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r3", "r2") + + By("with definition v3.0") + compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefName("v3.0"), "") + Expect(err).Should(Succeed()) + Expect(compDef.Name).Should(Equal(testapps.CompDefName("v3.0"))) + Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v3"))) + updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r5", "r5") + }) + + It("prefix match definition", func() { + By("with definition prefix and service version v0") + compDef, resolvedServiceVersion, err := resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefinitionName, testapps.ServiceVersion("v1")) + Expect(err).Should(Succeed()) + Expect(compDef.Name).Should(Equal(testapps.CompDefName("v2.0"))) + Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v1"))) + updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r4", "r4") + + By("with definition prefix and service version v1") + compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefinitionName, testapps.ServiceVersion("v2")) + Expect(err).Should(Succeed()) + Expect(compDef.Name).Should(Equal(testapps.CompDefName("v2.0"))) + Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v2"))) + updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r3", "r2") + + By("with definition prefix and service version v2") + compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefinitionName, testapps.ServiceVersion("v3")) + Expect(err).Should(Succeed()) + Expect(compDef.Name).Should(Equal(testapps.CompDefName("v3.0"))) + Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v3"))) + updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r5", "r5") + + By("with definition v1 prefix and service version v0") + compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefName("v1"), testapps.ServiceVersion("v1")) + Expect(err).Should(Succeed()) + Expect(compDef.Name).Should(Equal(testapps.CompDefName("v1.1"))) + Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v1"))) + updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r4", "r4") + + By("with definition v2 prefix and service version v1") + compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefName("v2"), testapps.ServiceVersion("v2")) + Expect(err).Should(Succeed()) + Expect(compDef.Name).Should(Equal(testapps.CompDefName("v2.0"))) + Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v2"))) + updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r3", "r2") + }) + + It("prefix match definition and w/o service version", func() { + By("with definition prefix") + compDef, resolvedServiceVersion, err := resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefinitionName, "") + Expect(err).Should(Succeed()) + Expect(compDef.Name).Should(Equal(testapps.CompDefName("v3.0"))) + Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v3"))) + updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r5", "r5") + + By("with definition v1 prefix") + compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefName("v1"), "") + Expect(err).Should(Succeed()) + Expect(compDef.Name).Should(Equal(testapps.CompDefName("v1.1"))) + Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v2"))) + updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r3", "r2") + + By("with definition v2 prefix") + compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefName("v2"), "") + Expect(err).Should(Succeed()) + Expect(compDef.Name).Should(Equal(testapps.CompDefName("v2.0"))) + Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v2"))) + updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r3", "r2") + }) + + It("regular expression match definition", func() { + By("with definition exact regex and service version 1") + compDef, resolvedServiceVersion, err := resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefNameWithExactRegex("v2.0"), testapps.ServiceVersion("v1")) + Expect(err).Should(Succeed()) + Expect(compDef.Name).Should(Equal(testapps.CompDefName("v2.0"))) + Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v1"))) + updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r4", "r4") + + By("with definition exact regex and service version v2") + compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefNameWithExactRegex("v2.0"), testapps.ServiceVersion("v2")) + Expect(err).Should(Succeed()) + Expect(compDef.Name).Should(Equal(testapps.CompDefName("v2.0"))) + Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v2"))) + updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r3", "r2") + + By("with definition exact regex and service version v3") + compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefNameWithExactRegex("v3.0"), testapps.ServiceVersion("v3")) + Expect(err).Should(Succeed()) + Expect(compDef.Name).Should(Equal(testapps.CompDefName("v3.0"))) + Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v3"))) + updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r5", "r5") + + By("with definition v1 fuzzy regex and service version v0") + compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefNameWithFuzzyRegex("v1"), testapps.ServiceVersion("v1")) + Expect(err).Should(Succeed()) + Expect(compDef.Name).Should(Equal(testapps.CompDefName("v1.1"))) + Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v1"))) + updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r4", "r4") + + By("with definition v2 fuzzy regex and service version v1") + compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefNameWithFuzzyRegex("v2"), testapps.ServiceVersion("v2")) + Expect(err).Should(Succeed()) + Expect(compDef.Name).Should(Equal(testapps.CompDefName("v2.0"))) + Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v2"))) + updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r3", "r2") + }) + + It("regular expression match definition and w/o service version", func() { + By("with definition regex") + compDef, resolvedServiceVersion, err := resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, "^"+testapps.CompDefinitionName, "") + Expect(err).Should(Succeed()) + Expect(compDef.Name).Should(Equal(testapps.CompDefName("v3.0"))) + Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v3"))) + updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r5", "r5") + + By("with definition v1 regex") + compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefNameWithFuzzyRegex("v1"), "") + Expect(err).Should(Succeed()) + Expect(compDef.Name).Should(Equal(testapps.CompDefName("v1.1"))) + Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v2"))) + updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r3", "r2") + + By("with definition v2 regex") + compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefNameWithFuzzyRegex("v2"), "") + Expect(err).Should(Succeed()) + Expect(compDef.Name).Should(Equal(testapps.CompDefName("v2.0"))) + Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v2"))) + updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r3", "r2") + }) + + It("match from definition", func() { + By("with definition v1.0 and service version v0") + compDef, resolvedServiceVersion, err := resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefName("v1.0"), testapps.ServiceVersion("v0")) + Expect(err).Should(Succeed()) + Expect(compDef.Name).Should(Equal(testapps.CompDefName("v1.0"))) + Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v0"))) + updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "", "") // empty revision of image tag + }) + + It("resolve images from definition and version", func() { + By("create new definition v4.0 with service version v4") + compDefObj := testapps.NewComponentDefinitionFactory(testapps.CompDefName("v4.0")). + SetServiceVersion(testapps.ServiceVersion("v4")). + SetRuntime(&corev1.Container{Name: testapps.AppName, Image: testapps.AppImage(testapps.AppName, testapps.ReleaseID(""))}). + SetRuntime(&corev1.Container{Name: testapps.AppNameSamePrefix, Image: testapps.AppImage(testapps.AppNameSamePrefix, testapps.ReleaseID(""))}). + SetLifecycleAction(testapps.DefaultActionName, + &appsv1.Action{Exec: &appsv1.ExecAction{Image: testapps.AppImage(testapps.DefaultActionName, testapps.ReleaseID(""))}}). + Create(&testCtx). + GetObject() + Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(compDefObj), + func(g Gomega, compDef *appsv1.ComponentDefinition) { + g.Expect(compDef.Status.ObservedGeneration).Should(Equal(compDef.Generation)) + })).Should(Succeed()) + + By("new release for the definition") + compVersionKey := client.ObjectKeyFromObject(compVersionObj) + Eventually(testapps.GetAndChangeObj(&testCtx, compVersionKey, func(compVersion *appsv1.ComponentVersion) { + release := appsv1.ComponentVersionRelease{ + Name: testapps.ReleaseID("r6"), + Changes: "publish a new service version", + ServiceVersion: testapps.ServiceVersion("v4"), + Images: map[string]string{ + testapps.AppName: testapps.AppImage(testapps.AppName, testapps.ReleaseID("r6")), + // not provide image for this app + // testapps.AppNameSamePrefix: testapps.AppImage(testapps.AppNameSamePrefix, testapps.ReleaseID("r6")), + }, + } + rule := appsv1.ComponentVersionCompatibilityRule{ + CompDefs: []string{testapps.CompDefName("v4")}, // use prefix + Releases: []string{testapps.ReleaseID("r6")}, + } + compVersion.Spec.CompatibilityRules = append(compVersion.Spec.CompatibilityRules, rule) + compVersion.Spec.Releases = append(compVersion.Spec.Releases, release) + })).Should(Succeed()) + Eventually(testapps.CheckObj(&testCtx, compVersionKey, func(g Gomega, compVersion *appsv1.ComponentVersion) { + g.Expect(compVersion.Status.ObservedGeneration).Should(Equal(compVersion.Generation)) + })).Should(Succeed()) + + By("with definition v4.0 and service version v3") + compDef, resolvedServiceVersion, err := resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefName("v4.0"), testapps.ServiceVersion("v4")) + Expect(err).Should(Succeed()) + Expect(compDef.Name).Should(Equal(testapps.CompDefName("v4.0"))) + Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v4"))) + updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r6", "") // app is r6 and another one is "" + }) + }) + + Context("resolve component definition, service version without serviceVersion in componentDefinition", func() { + BeforeEach(func() { + compDefs := createCompDefinitionObjs() + for _, compDef := range compDefs { + compDefKey := client.ObjectKeyFromObject(compDef) + Eventually(testapps.GetAndChangeObj(&testCtx, compDefKey, func(compDef *appsv1.ComponentDefinition) { + compDef.Spec.ServiceVersion = "" + })).Should(Succeed()) + } + compVersionObj = createCompVersionObj() + }) + + AfterEach(func() { + cleanEnv() + }) + + It("full match", func() { + By("with definition v1.0 and service version v0") + compDef, resolvedServiceVersion, err := resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefName("v1.0"), testapps.ServiceVersion("v1")) + Expect(err).Should(Succeed()) + Expect(compDef.Name).Should(Equal(testapps.CompDefName("v1.0"))) + Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v1"))) + updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r4", "r4") + }) + + It("w/o service version", func() { + By("with definition v1.0") + compDef, resolvedServiceVersion, err := resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefName("v1.0"), "") + Expect(err).Should(Succeed()) + Expect(compDef.Name).Should(Equal(testapps.CompDefName("v1.0"))) + Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v2"))) + updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r3", "r2") + }) + }) +}) diff --git a/controllers/apps/transformer_cluster_ownership.go b/controllers/apps/cluster/transformer_cluster_ownership.go similarity index 99% rename from controllers/apps/transformer_cluster_ownership.go rename to controllers/apps/cluster/transformer_cluster_ownership.go index d4ffd24015a..97f9adeb927 100644 --- a/controllers/apps/transformer_cluster_ownership.go +++ b/controllers/apps/cluster/transformer_cluster_ownership.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package cluster import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" diff --git a/controllers/apps/transformer_cluster_placement.go b/controllers/apps/cluster/transformer_cluster_placement.go similarity index 99% rename from controllers/apps/transformer_cluster_placement.go rename to controllers/apps/cluster/transformer_cluster_placement.go index 4921b46507f..a06b9164078 100644 --- a/controllers/apps/transformer_cluster_placement.go +++ b/controllers/apps/cluster/transformer_cluster_placement.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package cluster import ( "math/rand" diff --git a/controllers/apps/transformer_cluster_restore.go b/controllers/apps/cluster/transformer_cluster_restore.go similarity index 99% rename from controllers/apps/transformer_cluster_restore.go rename to controllers/apps/cluster/transformer_cluster_restore.go index 969dfcec564..c333421f656 100644 --- a/controllers/apps/transformer_cluster_restore.go +++ b/controllers/apps/cluster/transformer_cluster_restore.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package cluster import ( "k8s.io/apimachinery/pkg/util/json" diff --git a/controllers/apps/transformer_cluster_service.go b/controllers/apps/cluster/transformer_cluster_service.go similarity index 99% rename from controllers/apps/transformer_cluster_service.go rename to controllers/apps/cluster/transformer_cluster_service.go index 16944b42000..4253a7ca0c0 100644 --- a/controllers/apps/transformer_cluster_service.go +++ b/controllers/apps/cluster/transformer_cluster_service.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package cluster import ( "fmt" diff --git a/controllers/apps/transformer_cluster_service_test.go b/controllers/apps/cluster/transformer_cluster_service_test.go similarity index 99% rename from controllers/apps/transformer_cluster_service_test.go rename to controllers/apps/cluster/transformer_cluster_service_test.go index 4ccc4f749dc..aa9df856ba2 100644 --- a/controllers/apps/transformer_cluster_service_test.go +++ b/controllers/apps/cluster/transformer_cluster_service_test.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package cluster import ( "slices" diff --git a/controllers/apps/transformer_cluster_sharding_account.go b/controllers/apps/cluster/transformer_cluster_sharding_account.go similarity index 99% rename from controllers/apps/transformer_cluster_sharding_account.go rename to controllers/apps/cluster/transformer_cluster_sharding_account.go index 3718ed19153..6a987deae53 100644 --- a/controllers/apps/transformer_cluster_sharding_account.go +++ b/controllers/apps/cluster/transformer_cluster_sharding_account.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package cluster import ( "fmt" diff --git a/controllers/apps/transformer_cluster_sharding_tls.go b/controllers/apps/cluster/transformer_cluster_sharding_tls.go similarity index 99% rename from controllers/apps/transformer_cluster_sharding_tls.go rename to controllers/apps/cluster/transformer_cluster_sharding_tls.go index 576fa996aa5..96e3af8673e 100644 --- a/controllers/apps/transformer_cluster_sharding_tls.go +++ b/controllers/apps/cluster/transformer_cluster_sharding_tls.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package cluster import ( "fmt" diff --git a/controllers/apps/transformer_cluster_status.go b/controllers/apps/cluster/transformer_cluster_status.go similarity index 99% rename from controllers/apps/transformer_cluster_status.go rename to controllers/apps/cluster/transformer_cluster_status.go index 7646e3d0f4e..8e02789328e 100644 --- a/controllers/apps/transformer_cluster_status.go +++ b/controllers/apps/cluster/transformer_cluster_status.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package cluster import ( "fmt" diff --git a/controllers/apps/transformer_cluster_validation.go b/controllers/apps/cluster/transformer_cluster_validation.go similarity index 99% rename from controllers/apps/transformer_cluster_validation.go rename to controllers/apps/cluster/transformer_cluster_validation.go index d90ea50de80..56450ca3d0e 100644 --- a/controllers/apps/transformer_cluster_validation.go +++ b/controllers/apps/cluster/transformer_cluster_validation.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package cluster import ( "fmt" diff --git a/controllers/apps/transformer_cluster_validation_test.go b/controllers/apps/cluster/transformer_cluster_validation_test.go similarity index 99% rename from controllers/apps/transformer_cluster_validation_test.go rename to controllers/apps/cluster/transformer_cluster_validation_test.go index ea7951c6232..40e78f92d9f 100644 --- a/controllers/apps/transformer_cluster_validation_test.go +++ b/controllers/apps/cluster/transformer_cluster_validation_test.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package cluster import ( . "github.com/onsi/ginkgo/v2" diff --git a/controllers/apps/transform_types.go b/controllers/apps/cluster/types.go similarity index 99% rename from controllers/apps/transform_types.go rename to controllers/apps/cluster/types.go index 5b89554b7a1..1fb23fdf74e 100644 --- a/controllers/apps/transform_types.go +++ b/controllers/apps/cluster/types.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package cluster import ( snapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1" diff --git a/controllers/apps/transform_utils.go b/controllers/apps/cluster/utils.go similarity index 59% rename from controllers/apps/transform_utils.go rename to controllers/apps/cluster/utils.go index 859916a8c25..9491387e2ad 100644 --- a/controllers/apps/transform_utils.go +++ b/controllers/apps/cluster/utils.go @@ -17,10 +17,11 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package cluster import ( "context" + "fmt" "reflect" "time" @@ -34,14 +35,130 @@ import ( dpv1alpha1 "github.com/apecloud/kubeblocks/apis/dataprotection/v1alpha1" workloads "github.com/apecloud/kubeblocks/apis/workloads/v1" "github.com/apecloud/kubeblocks/pkg/constant" + "github.com/apecloud/kubeblocks/pkg/controller/model" + "github.com/apecloud/kubeblocks/pkg/controller/multicluster" intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" dptypes "github.com/apecloud/kubeblocks/pkg/dataprotection/types" ) +// default reconcile requeue after duration +var requeueDuration = time.Millisecond * 1000 + func newRequeueError(after time.Duration, reason string) error { return intctrlutil.NewRequeueError(after, reason) } +func boolValue(b *bool) bool { + if b == nil { + return false + } + return *b +} + +func mergeMap(dst, src map[string]string) { + for key, val := range src { + dst[key] = val + } +} + +func placement(obj client.Object) string { + if obj == nil || obj.GetAnnotations() == nil { + return "" + } + return obj.GetAnnotations()[constant.KBAppMultiClusterPlacementKey] +} + +func intoContext(ctx context.Context, placement string) context.Context { + return multicluster.IntoContext(ctx, placement) +} + +func inUniversalContext4C() *multicluster.ClientOption { + return multicluster.InUniversalContext() +} + +func inDataContext4G() model.GraphOption { + return model.WithClientOption(multicluster.InDataContext()) +} + +func inUniversalContext4G() model.GraphOption { + return model.WithClientOption(multicluster.InUniversalContext()) +} + +func clientOption(v *model.ObjectVertex) *multicluster.ClientOption { + if v.ClientOpt != nil { + opt, ok := v.ClientOpt.(*multicluster.ClientOption) + if ok { + return opt + } + panic(fmt.Sprintf("unknown client option: %T", v.ClientOpt)) + } + return multicluster.InControlContext() +} + +func resolveServiceDefaultFields(oldSpec, newSpec *corev1.ServiceSpec) { + var exist *corev1.ServicePort + for i, port := range newSpec.Ports { + for _, oldPort := range oldSpec.Ports { + // assume that port.Name is user specified, if it is not changed, we need to keep the old NodePort and TargetPort if they are not set + if port.Name != "" && port.Name == oldPort.Name { + exist = &oldPort + break + } + } + if exist == nil { + continue + } + // if the service type is NodePort or LoadBalancer, and the nodeport is not set, we should use the nodeport of the exist service + if shouldAllocateNodePorts(newSpec) && port.NodePort == 0 && exist.NodePort != 0 { + newSpec.Ports[i].NodePort = exist.NodePort + port.NodePort = exist.NodePort + } + if port.TargetPort.IntVal == 0 && port.TargetPort.StrVal == "" { + port.TargetPort = exist.TargetPort + } + if reflect.DeepEqual(port, *exist) { + newSpec.Ports[i].TargetPort = exist.TargetPort + } + } + if len(newSpec.ClusterIP) == 0 { + newSpec.ClusterIP = oldSpec.ClusterIP + } + if len(newSpec.ClusterIPs) == 0 { + newSpec.ClusterIPs = oldSpec.ClusterIPs + } + if len(newSpec.Type) == 0 { + newSpec.Type = oldSpec.Type + } + if len(newSpec.SessionAffinity) == 0 { + newSpec.SessionAffinity = oldSpec.SessionAffinity + } + if len(newSpec.IPFamilies) == 0 || (len(newSpec.IPFamilies) == 1 && *newSpec.IPFamilyPolicy != corev1.IPFamilyPolicySingleStack) { + newSpec.IPFamilies = oldSpec.IPFamilies + } + if newSpec.IPFamilyPolicy == nil { + newSpec.IPFamilyPolicy = oldSpec.IPFamilyPolicy + } + if newSpec.InternalTrafficPolicy == nil { + newSpec.InternalTrafficPolicy = oldSpec.InternalTrafficPolicy + } + if newSpec.ExternalTrafficPolicy == "" && oldSpec.ExternalTrafficPolicy != "" { + newSpec.ExternalTrafficPolicy = oldSpec.ExternalTrafficPolicy + } +} + +func shouldAllocateNodePorts(svc *corev1.ServiceSpec) bool { + if svc.Type == corev1.ServiceTypeNodePort { + return true + } + if svc.Type == corev1.ServiceTypeLoadBalancer { + if svc.AllocateLoadBalancerNodePorts != nil { + return *svc.AllocateLoadBalancerNodePorts + } + return true + } + return false +} + func getGVKName(object client.Object, scheme *runtime.Scheme) (*gvkNObjKey, error) { gvk, err := apiutil.GVKForObject(object, scheme) if err != nil { @@ -147,49 +264,6 @@ func sendWarningEventWithError( recorder.Event(obj, corev1.EventTypeWarning, reason, err.Error()) } -func isVolumeResourceRequirementsEqual(a, b corev1.VolumeResourceRequirements) bool { - return isResourceEqual(a.Requests, b.Requests) && isResourceEqual(a.Limits, b.Limits) -} - -func isResourceRequirementsEqual(a, b corev1.ResourceRequirements) bool { - return isResourceEqual(a.Requests, b.Requests) && isResourceEqual(a.Limits, b.Limits) -} - -func isResourceEqual(a, b corev1.ResourceList) bool { - if len(a) != len(b) { - return false - } - for k, v := range a { - if !v.Equal(b[k]) { - return false - } - } - return true -} - -func isVolumeClaimTemplatesEqual(a, b []appsv1.ClusterComponentVolumeClaimTemplate) bool { - if len(a) != len(b) { - return false - } - - for i := range a { - // first check resource requirements - c := a[i].DeepCopy() - d := b[i].DeepCopy() - if !isVolumeResourceRequirementsEqual(c.Spec.Resources, d.Spec.Resources) { - return false - } - - // then clear resource requirements and check other fields - c.Spec.Resources = corev1.VolumeResourceRequirements{} - d.Spec.Resources = corev1.VolumeResourceRequirements{} - if !reflect.DeepEqual(c, d) { - return false - } - } - return true -} - // isOwnedByComp is used to judge if the obj is owned by Component. func isOwnedByComp(obj client.Object) bool { for _, ref := range obj.GetOwnerReferences() { diff --git a/controllers/apps/transform_utils_test.go b/controllers/apps/cluster/utils_test.go similarity index 67% rename from controllers/apps/transform_utils_test.go rename to controllers/apps/cluster/utils_test.go index 2e5e34e583a..082b1be86cd 100644 --- a/controllers/apps/transform_utils_test.go +++ b/controllers/apps/cluster/utils_test.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package cluster import ( "fmt" @@ -26,8 +26,6 @@ import ( "github.com/stretchr/testify/assert" appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/pointer" "sigs.k8s.io/controller-runtime/pkg/client" @@ -61,39 +59,6 @@ func TestReflect(t *testing.T) { fmt.Println(v) } -func TestIsVolumeClaimTemplatesEqual(t *testing.T) { - buildVCT := func(size string) []kbappsv1.ClusterComponentVolumeClaimTemplate { - return []kbappsv1.ClusterComponentVolumeClaimTemplate{ - { - Name: "data", - Spec: kbappsv1.PersistentVolumeClaimSpec{ - Resources: corev1.VolumeResourceRequirements{ - Requests: corev1.ResourceList{ - corev1.ResourceStorage: resource.MustParse(size), - }, - }, - }, - }, - } - } - - assert.True(t, isVolumeClaimTemplatesEqual(buildVCT("1Gi"), buildVCT("1024Mi"))) -} - -func TestIsResourceRequirementsEqual(t *testing.T) { - buildRR := func(cpu, memory string) corev1.ResourceRequirements { - return corev1.ResourceRequirements{ - Requests: corev1.ResourceList{ - corev1.ResourceCPU: resource.MustParse(cpu), - corev1.ResourceMemory: resource.MustParse(memory), - }, - } - } - a := buildRR("1", "1Gi") - b := buildRR("1000m", "1024Mi") - assert.True(t, isResourceRequirementsEqual(a, b)) -} - func TestIsOwnedByInstanceSet(t *testing.T) { its := &workloads.InstanceSet{} assert.False(t, isOwnedByInstanceSet(its)) diff --git a/controllers/apps/clusterdefinition_controller.go b/controllers/apps/clusterdefinition_controller.go index 22134fc2304..64a444f9058 100644 --- a/controllers/apps/clusterdefinition_controller.go +++ b/controllers/apps/clusterdefinition_controller.go @@ -39,6 +39,10 @@ import ( intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" ) +const ( + clusterDefinitionFinalizerName = "clusterdefinition.kubeblocks.io/finalizer" +) + // +kubebuilder:rbac:groups=apps.kubeblocks.io,resources=clusterdefinitions,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=apps.kubeblocks.io,resources=clusterdefinitions/status,verbs=get;update;patch // +kubebuilder:rbac:groups=apps.kubeblocks.io,resources=clusterdefinitions/finalizers,verbs=update @@ -358,38 +362,3 @@ func (r *ClusterDefinitionReconciler) validateTopologyOrders(topology appsv1.Clu } return nil } - -// defaultClusterTopology returns the default cluster topology in specified cluster definition. -func defaultClusterTopology(clusterDef *appsv1.ClusterDefinition) *appsv1.ClusterTopology { - for i, topology := range clusterDef.Spec.Topologies { - if topology.Default { - return &clusterDef.Spec.Topologies[i] - } - } - return nil -} - -// referredClusterTopology returns the cluster topology which has name @name. -func referredClusterTopology(clusterDef *appsv1.ClusterDefinition, name string) *appsv1.ClusterTopology { - if clusterDef != nil { - if len(name) == 0 { - return defaultClusterTopology(clusterDef) - } - for i, topology := range clusterDef.Spec.Topologies { - if topology.Name == name { - return &clusterDef.Spec.Topologies[i] - } - } - } - return nil -} - -func clusterTopologyCompMatched(comp appsv1.ClusterTopologyComponent, compName string) bool { - if comp.Name == compName { - return true - } - if comp.Template != nil && *comp.Template { - return strings.HasPrefix(compName, comp.Name) - } - return false -} diff --git a/controllers/apps/component_controller.go b/controllers/apps/component/component_controller.go similarity index 99% rename from controllers/apps/component_controller.go rename to controllers/apps/component/component_controller.go index a2bd75a6ae2..c097ea79abf 100644 --- a/controllers/apps/component_controller.go +++ b/controllers/apps/component/component_controller.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package component import ( "context" diff --git a/controllers/apps/component_controller_test.go b/controllers/apps/component/component_controller_test.go similarity index 99% rename from controllers/apps/component_controller_test.go rename to controllers/apps/component/component_controller_test.go index b98dda0faed..8dc36dc1039 100644 --- a/controllers/apps/component_controller_test.go +++ b/controllers/apps/component/component_controller_test.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package component import ( "fmt" diff --git a/controllers/apps/component_plan_builder.go b/controllers/apps/component/component_plan_builder.go similarity index 97% rename from controllers/apps/component_plan_builder.go rename to controllers/apps/component/component_plan_builder.go index 595684ab2af..a366dcfe909 100644 --- a/controllers/apps/component_plan_builder.go +++ b/controllers/apps/component/component_plan_builder.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package component import ( "context" @@ -105,11 +105,6 @@ func (c *componentPlanBuilder) AddTransformer(transformer ...graph.Transformer) return c } -func (c *componentPlanBuilder) AddParallelTransformer(transformer ...graph.Transformer) graph.PlanBuilder { - c.transformers = append(c.transformers, &ParallelTransformers{transformers: transformer}) - return c -} - // Build runs all transformers to generate a plan func (c *componentPlanBuilder) Build() (graph.Plan, error) { dag := graph.NewDAG() diff --git a/controllers/apps/component_plan_builder_test.go b/controllers/apps/component/component_plan_builder_test.go similarity index 99% rename from controllers/apps/component_plan_builder_test.go rename to controllers/apps/component/component_plan_builder_test.go index 90c7ca3b4f8..5b394d035b9 100644 --- a/controllers/apps/component_plan_builder_test.go +++ b/controllers/apps/component/component_plan_builder_test.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package component import ( . "github.com/onsi/ginkgo/v2" diff --git a/controllers/apps/component/suite_test.go b/controllers/apps/component/suite_test.go new file mode 100644 index 00000000000..d6d194373f6 --- /dev/null +++ b/controllers/apps/component/suite_test.go @@ -0,0 +1,283 @@ +/* +Copyright (C) 2022-2024 ApeCloud Co., Ltd + +This file is part of KubeBlocks project + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package component + +import ( + "context" + "fmt" + "go/build" + "path/filepath" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/go-logr/logr" + snapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1" + "go.uber.org/zap/zapcore" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/record" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/controller-runtime/pkg/metrics/server" + + appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" + appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" + appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + dpv1alpha1 "github.com/apecloud/kubeblocks/apis/dataprotection/v1alpha1" + opsv1alpha1 "github.com/apecloud/kubeblocks/apis/operations/v1alpha1" + workloadsv1 "github.com/apecloud/kubeblocks/apis/workloads/v1" + "github.com/apecloud/kubeblocks/controllers/apps" + "github.com/apecloud/kubeblocks/controllers/apps/cluster" + "github.com/apecloud/kubeblocks/controllers/apps/configuration" + "github.com/apecloud/kubeblocks/controllers/dataprotection" + "github.com/apecloud/kubeblocks/controllers/k8score" + "github.com/apecloud/kubeblocks/pkg/constant" + "github.com/apecloud/kubeblocks/pkg/controller/model" + intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" + "github.com/apecloud/kubeblocks/pkg/testutil" + viper "github.com/apecloud/kubeblocks/pkg/viperx" +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +const ( + testDataPlaneNodeAffinityKey = "testDataPlaneNodeAffinityKey" + testDataPlaneTolerationKey = "testDataPlaneTolerationKey" +) + +var cfg *rest.Config +var k8sClient client.Client +var testEnv *envtest.Environment +var ctx context.Context +var cancel context.CancelFunc +var testCtx testutil.TestContext +var clusterRecorder record.EventRecorder +var logger logr.Logger + +func init() { + viper.AutomaticEnv() + // viper.Set("ENABLE_DEBUG_LOG", "true") +} + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "Controller Suite") +} + +var _ = BeforeSuite(func() { + if viper.GetBool("ENABLE_DEBUG_LOG") { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true), func(o *zap.Options) { + o.TimeEncoder = zapcore.ISO8601TimeEncoder + })) + } else { + logf.SetLogger(logr.New(logf.NullLogSink{})) + } + + viper.SetDefault(constant.CfgKeyCtrlrReconcileRetryDurationMS, 10) + viper.Set(constant.CfgKeyDataPlaneTolerations, + fmt.Sprintf("[{\"key\":\"%s\", \"operator\": \"Exists\", \"effect\": \"NoSchedule\"}]", testDataPlaneTolerationKey)) + viper.Set(constant.CfgKeyDataPlaneAffinity, + fmt.Sprintf("{\"nodeAffinity\":{\"preferredDuringSchedulingIgnoredDuringExecution\":[{\"preference\":{\"matchExpressions\":[{\"key\":\"%s\",\"operator\":\"In\",\"values\":[\"true\"]}]},\"weight\":100}]}}", testDataPlaneNodeAffinityKey)) + + ctx, cancel = context.WithCancel(context.TODO()) + logger = logf.FromContext(ctx).WithValues() + logger.Info("logger start") + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd", "bases"), + // use dependent external CRDs. + // resolved by ref: https://github.com/operator-framework/operator-sdk/issues/4434#issuecomment-786794418 + filepath.Join(build.Default.GOPATH, "pkg", "mod", "github.com", "kubernetes-csi/external-snapshotter/", + "client/v6@v6.2.0", "config", "crd"), + }, + ErrorIfCRDPathMissing: true, + } + + var err error + // cfg is defined in this file globally. + cfg, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + err = appsv1alpha1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + model.AddScheme(appsv1alpha1.AddToScheme) + + err = opsv1alpha1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + model.AddScheme(opsv1alpha1.AddToScheme) + + err = appsv1beta1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + model.AddScheme(appsv1beta1.AddToScheme) + + err = appsv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + model.AddScheme(appsv1.AddToScheme) + + err = dpv1alpha1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + model.AddScheme(dpv1alpha1.AddToScheme) + + err = snapshotv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + model.AddScheme(snapshotv1.AddToScheme) + + err = workloadsv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + model.AddScheme(workloadsv1.AddToScheme) + + // +kubebuilder:scaffold:rscheme + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + + // run reconcile + k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{ + Scheme: scheme.Scheme, + Metrics: server.Options{BindAddress: "0"}, + Client: client.Options{ + Cache: &client.CacheOptions{ + DisableFor: intctrlutil.GetUncachedObjects(), + }, + }, + }) + Expect(err).ToNot(HaveOccurred()) + + viper.SetDefault("CERT_DIR", "/tmp/k8s-webhook-server/serving-certs") + viper.SetDefault(constant.KBToolsImage, "docker.io/apecloud/kubeblocks-tools:latest") + viper.SetDefault("PROBE_SERVICE_PORT", 3501) + viper.SetDefault("PROBE_SERVICE_LOG_LEVEL", "info") + viper.SetDefault("CM_NAMESPACE", "default") + viper.SetDefault("HOST_PORT_CM_NAME", "kubeblocks-host-ports") + viper.SetDefault(constant.EnableRBACManager, true) + + err = intctrlutil.InitHostPortManager(k8sClient) + Expect(err).ToNot(HaveOccurred()) + + err = (&apps.ClusterDefinitionReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Recorder: k8sManager.GetEventRecorderFor("cluster-definition-controller"), + }).SetupWithManager(k8sManager) + Expect(err).ToNot(HaveOccurred()) + + err = (&apps.ShardingDefinitionReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Recorder: k8sManager.GetEventRecorderFor("sharding-definition-controller"), + }).SetupWithManager(k8sManager) + Expect(err).ToNot(HaveOccurred()) + + err = (&apps.ComponentDefinitionReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Recorder: k8sManager.GetEventRecorderFor("component-definition-controller"), + }).SetupWithManager(k8sManager) + Expect(err).ToNot(HaveOccurred()) + + err = (&apps.ComponentVersionReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Recorder: k8sManager.GetEventRecorderFor("component-version-controller"), + }).SetupWithManager(k8sManager) + Expect(err).ToNot(HaveOccurred()) + + err = (&apps.SidecarDefinitionReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Recorder: k8sManager.GetEventRecorderFor("sidecar-definition-controller"), + }).SetupWithManager(k8sManager) + Expect(err).ToNot(HaveOccurred()) + + clusterRecorder = k8sManager.GetEventRecorderFor("cluster-controller") + err = (&cluster.ClusterReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Recorder: clusterRecorder, + }).SetupWithManager(k8sManager) + Expect(err).ToNot(HaveOccurred()) + + err = (&ComponentReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Recorder: k8sManager.GetEventRecorderFor("component-controller"), + }).SetupWithManager(k8sManager, nil) + Expect(err).ToNot(HaveOccurred()) + + err = (&apps.ServiceDescriptorReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Recorder: k8sManager.GetEventRecorderFor("service-descriptor-controller"), + }).SetupWithManager(k8sManager) + Expect(err).ToNot(HaveOccurred()) + + err = (&k8score.EventReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Recorder: k8sManager.GetEventRecorderFor("event-controller"), + }).SetupWithManager(k8sManager, nil) + Expect(err).ToNot(HaveOccurred()) + + err = (&configuration.ConfigConstraintReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Recorder: k8sManager.GetEventRecorderFor("configuration-template-controller"), + }).SetupWithManager(k8sManager) + Expect(err).ToNot(HaveOccurred()) + + err = (&configuration.ConfigurationReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Recorder: k8sManager.GetEventRecorderFor("configuration-controller"), + }).SetupWithManager(k8sManager, nil) + Expect(err).ToNot(HaveOccurred()) + + err = (&dataprotection.BackupPolicyTemplateReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Recorder: k8sManager.GetEventRecorderFor("backup-policy-template-controller"), + }).SetupWithManager(k8sManager) + Expect(err).ToNot(HaveOccurred()) + + testCtx = testutil.NewDefaultTestContext(ctx, k8sClient, testEnv) + + go func() { + defer GinkgoRecover() + err = k8sManager.Start(ctx) + Expect(err).ToNot(HaveOccurred(), "failed to run manager") + }() +}) + +var _ = AfterSuite(func() { + cancel() + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) diff --git a/controllers/apps/component/test_utils.go b/controllers/apps/component/test_utils.go new file mode 100644 index 00000000000..a3d4284138a --- /dev/null +++ b/controllers/apps/component/test_utils.go @@ -0,0 +1,72 @@ +/* +Copyright (C) 2022-2024 ApeCloud Co., Ltd + +This file is part of KubeBlocks project + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package component + +import ( + "context" + "fmt" + "reflect" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type mockReader struct { + objs []client.Object +} + +func (r *mockReader) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { + for _, o := range r.objs { + // ignore the GVK check + if client.ObjectKeyFromObject(o) == key { + reflect.ValueOf(obj).Elem().Set(reflect.ValueOf(o).Elem()) + return nil + } + } + return apierrors.NewNotFound(schema.GroupResource{}, key.Name) +} + +func (r *mockReader) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { + items := reflect.ValueOf(list).Elem().FieldByName("Items") + if !items.IsValid() { + return fmt.Errorf("ObjectList has no Items field: %s", list.GetObjectKind().GroupVersionKind().String()) + } + + objs := reflect.MakeSlice(items.Type(), 0, 0) + if len(r.objs) > 0 { + listOpts := &client.ListOptions{} + for _, opt := range opts { + opt.ApplyToList(listOpts) + } + + for i, o := range r.objs { + if reflect.TypeOf(r.objs[i]).Elem().AssignableTo(items.Type().Elem()) { + if listOpts.LabelSelector == nil || listOpts.LabelSelector.Matches(labels.Set(o.GetLabels())) { + objs = reflect.Append(objs, reflect.ValueOf(r.objs[i]).Elem()) + } + } + } + } + items.Set(objs) + + return nil +} diff --git a/controllers/apps/transformer_component_account.go b/controllers/apps/component/transformer_component_account.go similarity index 99% rename from controllers/apps/transformer_component_account.go rename to controllers/apps/component/transformer_component_account.go index 71412527443..909f6526958 100644 --- a/controllers/apps/transformer_component_account.go +++ b/controllers/apps/component/transformer_component_account.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package component import ( "fmt" diff --git a/controllers/apps/transformer_component_account_provision.go b/controllers/apps/component/transformer_component_account_provision.go similarity index 99% rename from controllers/apps/transformer_component_account_provision.go rename to controllers/apps/component/transformer_component_account_provision.go index 1598da82963..35df58f7fef 100644 --- a/controllers/apps/transformer_component_account_provision.go +++ b/controllers/apps/component/transformer_component_account_provision.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package component import ( "slices" diff --git a/controllers/apps/transformer_component_configuration.go b/controllers/apps/component/transformer_component_configuration.go similarity index 99% rename from controllers/apps/transformer_component_configuration.go rename to controllers/apps/component/transformer_component_configuration.go index 26744822678..21f12781bfd 100644 --- a/controllers/apps/transformer_component_configuration.go +++ b/controllers/apps/component/transformer_component_configuration.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package component import ( corev1 "k8s.io/api/core/v1" diff --git a/controllers/apps/transformer_component_deletion.go b/controllers/apps/component/transformer_component_deletion.go similarity index 99% rename from controllers/apps/transformer_component_deletion.go rename to controllers/apps/component/transformer_component_deletion.go index fe7e58beff3..2c35763798b 100644 --- a/controllers/apps/transformer_component_deletion.go +++ b/controllers/apps/component/transformer_component_deletion.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package component import ( "fmt" @@ -72,7 +72,7 @@ func (t *componentDeletionTransformer) Transform(ctx graph.TransformContext, dag ml := constant.GetCompLabels(cluster.Name, compShortName) compScaleIn, ok := comp.Annotations[constant.ComponentScaleInAnnotationKey] - if ok && compScaleIn == trueVal { + if ok && compScaleIn == "true" { return t.handleCompDeleteWhenScaleIn(transCtx, graphCli, dag, comp, ml) } return t.handleCompDeleteWhenClusterDelete(transCtx, graphCli, dag, cluster, comp, ml) diff --git a/controllers/apps/transformer_component_hostnetwork.go b/controllers/apps/component/transformer_component_hostnetwork.go similarity index 99% rename from controllers/apps/transformer_component_hostnetwork.go rename to controllers/apps/component/transformer_component_hostnetwork.go index ec3ab5fb348..c216f986c5a 100644 --- a/controllers/apps/transformer_component_hostnetwork.go +++ b/controllers/apps/component/transformer_component_hostnetwork.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package component import ( corev1 "k8s.io/api/core/v1" diff --git a/controllers/apps/transformer_component_hostnetwork_test.go b/controllers/apps/component/transformer_component_hostnetwork_test.go similarity index 99% rename from controllers/apps/transformer_component_hostnetwork_test.go rename to controllers/apps/component/transformer_component_hostnetwork_test.go index adae0455ec7..45bff28e550 100644 --- a/controllers/apps/transformer_component_hostnetwork_test.go +++ b/controllers/apps/component/transformer_component_hostnetwork_test.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package component import ( . "github.com/onsi/ginkgo/v2" diff --git a/controllers/apps/transformer_component_init.go b/controllers/apps/component/transformer_component_init.go similarity index 98% rename from controllers/apps/transformer_component_init.go rename to controllers/apps/component/transformer_component_init.go index 0564c45b9b1..1a898f1746b 100644 --- a/controllers/apps/transformer_component_init.go +++ b/controllers/apps/component/transformer_component_init.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package component import ( "github.com/apecloud/kubeblocks/pkg/controller/graph" diff --git a/controllers/apps/transformer_component_load_resources.go b/controllers/apps/component/transformer_component_load_resources.go similarity index 80% rename from controllers/apps/transformer_component_load_resources.go rename to controllers/apps/component/transformer_component_load_resources.go index deec5ce05e3..f5204308237 100644 --- a/controllers/apps/transformer_component_load_resources.go +++ b/controllers/apps/component/transformer_component_load_resources.go @@ -17,12 +17,14 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package component import ( + "context" "fmt" "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" "github.com/apecloud/kubeblocks/pkg/controller/component" @@ -82,3 +84,20 @@ func (t *componentLoadResourcesTransformer) transformForNativeComponent(transCtx return nil } + +func getNCheckCompDefinition(ctx context.Context, cli client.Reader, name string) (*appsv1.ComponentDefinition, error) { + compKey := types.NamespacedName{ + Name: name, + } + compDef := &appsv1.ComponentDefinition{} + if err := cli.Get(ctx, compKey, compDef); err != nil { + return nil, err + } + if compDef.Generation != compDef.Status.ObservedGeneration { + return nil, fmt.Errorf("the referenced ComponentDefinition is not up to date: %s", compDef.Name) + } + if compDef.Status.Phase != appsv1.AvailablePhase { + return nil, fmt.Errorf("the referenced ComponentDefinition is unavailable: %s", compDef.Name) + } + return compDef, nil +} diff --git a/controllers/apps/transformer_component_meta.go b/controllers/apps/component/transformer_component_meta.go similarity index 99% rename from controllers/apps/transformer_component_meta.go rename to controllers/apps/component/transformer_component_meta.go index 97d5c37ce49..7762928214f 100644 --- a/controllers/apps/transformer_component_meta.go +++ b/controllers/apps/component/transformer_component_meta.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package component import ( "reflect" diff --git a/controllers/apps/transformer_component_monitor_transformer.go b/controllers/apps/component/transformer_component_monitor_transformer.go similarity index 99% rename from controllers/apps/transformer_component_monitor_transformer.go rename to controllers/apps/component/transformer_component_monitor_transformer.go index f49a7be52ad..127a3d78c92 100644 --- a/controllers/apps/transformer_component_monitor_transformer.go +++ b/controllers/apps/component/transformer_component_monitor_transformer.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package apps +package component import ( "slices" diff --git a/controllers/apps/transformer_component_parameters.go b/controllers/apps/component/transformer_component_parameters.go similarity index 99% rename from controllers/apps/transformer_component_parameters.go rename to controllers/apps/component/transformer_component_parameters.go index b1e2996788e..d4b5f7c8531 100644 --- a/controllers/apps/transformer_component_parameters.go +++ b/controllers/apps/component/transformer_component_parameters.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package component import ( "sigs.k8s.io/controller-runtime/pkg/client" diff --git a/controllers/apps/transformer_component_post_provision.go b/controllers/apps/component/transformer_component_post_provision.go similarity index 99% rename from controllers/apps/transformer_component_post_provision.go rename to controllers/apps/component/transformer_component_post_provision.go index f8ebb050815..06e08b82f7a 100644 --- a/controllers/apps/transformer_component_post_provision.go +++ b/controllers/apps/component/transformer_component_post_provision.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package component import ( "fmt" diff --git a/controllers/apps/transformer_component_pre_terminate.go b/controllers/apps/component/transformer_component_pre_terminate.go similarity index 99% rename from controllers/apps/transformer_component_pre_terminate.go rename to controllers/apps/component/transformer_component_pre_terminate.go index 26f8769eecf..a9de13c6620 100644 --- a/controllers/apps/transformer_component_pre_terminate.go +++ b/controllers/apps/component/transformer_component_pre_terminate.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package component import ( "fmt" diff --git a/controllers/apps/transformer_component_rbac.go b/controllers/apps/component/transformer_component_rbac.go similarity index 99% rename from controllers/apps/transformer_component_rbac.go rename to controllers/apps/component/transformer_component_rbac.go index 46e9e4ed74a..eef1d491498 100644 --- a/controllers/apps/transformer_component_rbac.go +++ b/controllers/apps/component/transformer_component_rbac.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package component import ( "fmt" diff --git a/controllers/apps/transformer_component_rbac_test.go b/controllers/apps/component/transformer_component_rbac_test.go similarity index 99% rename from controllers/apps/transformer_component_rbac_test.go rename to controllers/apps/component/transformer_component_rbac_test.go index 34fbc4afdc4..db8116b1ec8 100644 --- a/controllers/apps/transformer_component_rbac_test.go +++ b/controllers/apps/component/transformer_component_rbac_test.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package component import ( . "github.com/onsi/ginkgo/v2" diff --git a/controllers/apps/transformer_component_restore.go b/controllers/apps/component/transformer_component_restore.go similarity index 99% rename from controllers/apps/transformer_component_restore.go rename to controllers/apps/component/transformer_component_restore.go index 9add3fc1a48..da5ee3f067f 100644 --- a/controllers/apps/transformer_component_restore.go +++ b/controllers/apps/component/transformer_component_restore.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package component import ( "reflect" diff --git a/controllers/apps/transformer_component_service.go b/controllers/apps/component/transformer_component_service.go similarity index 99% rename from controllers/apps/transformer_component_service.go rename to controllers/apps/component/transformer_component_service.go index ba2ae5368b9..685f1952b59 100644 --- a/controllers/apps/transformer_component_service.go +++ b/controllers/apps/component/transformer_component_service.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package component import ( "context" diff --git a/controllers/apps/transformer_component_service_test.go b/controllers/apps/component/transformer_component_service_test.go similarity index 99% rename from controllers/apps/transformer_component_service_test.go rename to controllers/apps/component/transformer_component_service_test.go index 8d53e8c727e..bc9c52acb39 100644 --- a/controllers/apps/transformer_component_service_test.go +++ b/controllers/apps/component/transformer_component_service_test.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package component import ( "fmt" diff --git a/controllers/apps/transformer_component_status.go b/controllers/apps/component/transformer_component_status.go similarity index 99% rename from controllers/apps/transformer_component_status.go rename to controllers/apps/component/transformer_component_status.go index e049518b618..f0c42c17647 100644 --- a/controllers/apps/transformer_component_status.go +++ b/controllers/apps/component/transformer_component_status.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package component import ( "fmt" diff --git a/controllers/apps/transformer_component_tls.go b/controllers/apps/component/transformer_component_tls.go similarity index 99% rename from controllers/apps/transformer_component_tls.go rename to controllers/apps/component/transformer_component_tls.go index 4a2e72f7858..7b855eddaf4 100644 --- a/controllers/apps/transformer_component_tls.go +++ b/controllers/apps/component/transformer_component_tls.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package component import ( "context" diff --git a/controllers/apps/transformer_component_tls_test.go b/controllers/apps/component/transformer_component_tls_test.go similarity index 99% rename from controllers/apps/transformer_component_tls_test.go rename to controllers/apps/component/transformer_component_tls_test.go index bdc75b4b587..71ef04dc832 100644 --- a/controllers/apps/transformer_component_tls_test.go +++ b/controllers/apps/component/transformer_component_tls_test.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package component import ( "context" diff --git a/controllers/apps/transformer_component_utils.go b/controllers/apps/component/transformer_component_utils.go similarity index 99% rename from controllers/apps/transformer_component_utils.go rename to controllers/apps/component/transformer_component_utils.go index 47382baa6d8..80ac1eee756 100644 --- a/controllers/apps/transformer_component_utils.go +++ b/controllers/apps/component/transformer_component_utils.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package component import ( "reflect" diff --git a/controllers/apps/transformer_component_validation.go b/controllers/apps/component/transformer_component_validation.go similarity index 99% rename from controllers/apps/transformer_component_validation.go rename to controllers/apps/component/transformer_component_validation.go index 48e7be416b0..6fe0186788e 100644 --- a/controllers/apps/transformer_component_validation.go +++ b/controllers/apps/component/transformer_component_validation.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package component import ( "fmt" diff --git a/controllers/apps/transformer_component_vars.go b/controllers/apps/component/transformer_component_vars.go similarity index 99% rename from controllers/apps/transformer_component_vars.go rename to controllers/apps/component/transformer_component_vars.go index b732118844e..210a3ad05fd 100644 --- a/controllers/apps/transformer_component_vars.go +++ b/controllers/apps/component/transformer_component_vars.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package component import ( "context" diff --git a/controllers/apps/transformer_component_workload.go b/controllers/apps/component/transformer_component_workload.go similarity index 99% rename from controllers/apps/transformer_component_workload.go rename to controllers/apps/component/transformer_component_workload.go index ce94edfaba4..41989756937 100644 --- a/controllers/apps/transformer_component_workload.go +++ b/controllers/apps/component/transformer_component_workload.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package component import ( "context" diff --git a/controllers/apps/transformer_component_workload_test.go b/controllers/apps/component/transformer_component_workload_test.go similarity index 99% rename from controllers/apps/transformer_component_workload_test.go rename to controllers/apps/component/transformer_component_workload_test.go index 68c44987685..680cc726bc1 100644 --- a/controllers/apps/transformer_component_workload_test.go +++ b/controllers/apps/component/transformer_component_workload_test.go @@ -13,14 +13,15 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package component import ( "context" - "github.com/golang/mock/gomock" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + + "github.com/golang/mock/gomock" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/controllers/apps/component/types.go b/controllers/apps/component/types.go new file mode 100644 index 00000000000..ff6ecf6f3ad --- /dev/null +++ b/controllers/apps/component/types.go @@ -0,0 +1,51 @@ +/* +Copyright (C) 2022-2024 ApeCloud Co., Ltd + +This file is part of KubeBlocks project + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package component + +import ( + snapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1" + batchv1 "k8s.io/api/batch/v1" + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + + appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" + appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" + appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" + dpv1alpha1 "github.com/apecloud/kubeblocks/apis/dataprotection/v1alpha1" + extensionsv1alpha1 "github.com/apecloud/kubeblocks/apis/extensions/v1alpha1" + workloads "github.com/apecloud/kubeblocks/apis/workloads/v1" +) + +var ( + rscheme = runtime.NewScheme() +) + +func init() { + utilruntime.Must(clientgoscheme.AddToScheme(rscheme)) + utilruntime.Must(appsv1alpha1.AddToScheme(rscheme)) + utilruntime.Must(appsv1beta1.AddToScheme(rscheme)) + utilruntime.Must(appsv1.AddToScheme(rscheme)) + utilruntime.Must(dpv1alpha1.AddToScheme(rscheme)) + utilruntime.Must(snapshotv1.AddToScheme(rscheme)) + utilruntime.Must(extensionsv1alpha1.AddToScheme(rscheme)) + utilruntime.Must(batchv1.AddToScheme(rscheme)) + utilruntime.Must(workloads.AddToScheme(rscheme)) +} diff --git a/controllers/apps/utils.go b/controllers/apps/component/utils.go similarity index 57% rename from controllers/apps/utils.go rename to controllers/apps/component/utils.go index 72beb7f3756..7b5ec0746d4 100644 --- a/controllers/apps/utils.go +++ b/controllers/apps/component/utils.go @@ -17,7 +17,7 @@ You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ -package apps +package component import ( "context" @@ -26,27 +26,46 @@ import ( "time" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" + appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" + workloads "github.com/apecloud/kubeblocks/apis/workloads/v1" "github.com/apecloud/kubeblocks/pkg/constant" "github.com/apecloud/kubeblocks/pkg/controller/model" "github.com/apecloud/kubeblocks/pkg/controller/multicluster" + intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" +) + +const ( + ReasonPreCheckSucceed = "PreCheckSucceed" // ReasonPreCheckSucceed preChecks succeeded for provisioning started + ReasonPreCheckFailed = "PreCheckFailed" // ReasonPreCheckFailed preChecks failed for provisioning started ) // default reconcile requeue after duration var requeueDuration = time.Millisecond * 1000 -func boolValue(b *bool) bool { - if b == nil { - return false - } - return *b +func newRequeueError(after time.Duration, reason string) error { + return intctrlutil.NewRequeueError(after, reason) } -func mergeMap(dst, src map[string]string) { - for key, val := range src { - dst[key] = val +// sendWarningEventWithError sends a warning event when occurs error. +func sendWarningEventWithError( + recorder record.EventRecorder, + obj client.Object, + reason string, + err error) { + // ignore requeue error + if err == nil || intctrlutil.IsRequeueError(err) { + return + } + controllerErr := intctrlutil.UnwrapControllerError(err) + if controllerErr != nil { + reason = string(controllerErr.Type) } + recorder.Event(obj, corev1.EventTypeWarning, reason, err.Error()) } func placement(obj client.Object) string { @@ -64,10 +83,6 @@ func inDataContext4C() *multicluster.ClientOption { return multicluster.InDataContext() } -func inUniversalContext4C() *multicluster.ClientOption { - return multicluster.InUniversalContext() -} - func inDataContext4G() model.GraphOption { return model.WithClientOption(multicluster.InDataContext()) } @@ -86,6 +101,7 @@ func clientOption(v *model.ObjectVertex) *multicluster.ClientOption { } return multicluster.InControlContext() } + func resolveServiceDefaultFields(oldSpec, newSpec *corev1.ServiceSpec) { var exist *corev1.ServicePort for i, port := range newSpec.Ports { @@ -149,3 +165,55 @@ func shouldAllocateNodePorts(svc *corev1.ServiceSpec) bool { } return false } + +// isOwnedByInstanceSet is used to judge if the obj is owned by the InstanceSet controller +func isOwnedByInstanceSet(obj client.Object) bool { + for _, ref := range obj.GetOwnerReferences() { + if ref.Kind == workloads.InstanceSetKind && ref.Controller != nil && *ref.Controller { + return true + } + } + return false +} + +func setProvisioningStartedCondition(conditions *[]metav1.Condition, clusterName string, clusterGeneration int64, err error) { + var condition metav1.Condition + if err == nil { + condition = newProvisioningStartedCondition(clusterName, clusterGeneration) + } else { + condition = newFailedProvisioningStartedCondition(err) + } + meta.SetStatusCondition(conditions, condition) +} + +// newProvisioningStartedCondition creates the provisioning started condition in cluster conditions. +func newProvisioningStartedCondition(clusterName string, clusterGeneration int64) metav1.Condition { + return metav1.Condition{ + Type: appsv1.ConditionTypeProvisioningStarted, + ObservedGeneration: clusterGeneration, + Status: metav1.ConditionTrue, + Message: fmt.Sprintf("The operator has started the provisioning of Cluster: %s", clusterName), + Reason: ReasonPreCheckSucceed, + } +} + +func getConditionReasonWithError(defaultReason string, err error) string { + if err == nil { + return defaultReason + } + controllerErr := intctrlutil.UnwrapControllerError(err) + if controllerErr != nil { + defaultReason = string(controllerErr.Type) + } + return defaultReason +} + +// newApplyResourcesCondition creates a condition when applied resources succeed. +func newFailedProvisioningStartedCondition(err error) metav1.Condition { + return metav1.Condition{ + Type: appsv1.ConditionTypeProvisioningStarted, + Status: metav1.ConditionFalse, + Message: err.Error(), + Reason: getConditionReasonWithError(ReasonPreCheckFailed, err), + } +} diff --git a/controllers/apps/component/utils_test.go b/controllers/apps/component/utils_test.go new file mode 100644 index 00000000000..1974121fe7a --- /dev/null +++ b/controllers/apps/component/utils_test.go @@ -0,0 +1,81 @@ +/* +Copyright (C) 2022-2024 ApeCloud Co., Ltd + +This file is part of KubeBlocks project + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ + +package component + +import ( + "fmt" + "reflect" + "testing" + + "github.com/stretchr/testify/assert" + appsv1 "k8s.io/api/apps/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/pointer" + "sigs.k8s.io/controller-runtime/pkg/client" + + kbappsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" + workloads "github.com/apecloud/kubeblocks/apis/workloads/v1" +) + +func TestReflect(t *testing.T) { + var list client.ObjectList + sts := appsv1.StatefulSet{} + sts.SetName("hello") + list = &appsv1.StatefulSetList{Items: []appsv1.StatefulSet{sts}} + v := reflect.ValueOf(list).Elem().FieldByName("Items") + if v.Kind() != reflect.Slice { + t.Error("not slice") + } + c := v.Len() + objects := make([]client.Object, c) + for i := 0; i < c; i++ { + var st = v.Index(i).Addr().Interface() + objects[i] = st.(client.Object) + } + for _, e := range objects { + fmt.Println(e) + } + + var o client.Object = &sts + ptr := reflect.ValueOf(o) + v = ptr.Elem().FieldByName("Spec") + fmt.Println(v) +} + +func TestIsOwnedByInstanceSet(t *testing.T) { + its := &workloads.InstanceSet{} + assert.False(t, isOwnedByInstanceSet(its)) + + its.OwnerReferences = []metav1.OwnerReference{ + { + Kind: workloads.InstanceSetKind, + Controller: pointer.Bool(true), + }, + } + assert.True(t, isOwnedByInstanceSet(its)) + + its.OwnerReferences = []metav1.OwnerReference{ + { + Kind: reflect.TypeOf(kbappsv1.Cluster{}).Name(), + Controller: pointer.Bool(true), + }, + } + assert.False(t, isOwnedByInstanceSet(its)) +} diff --git a/controllers/apps/componentdefinition_controller.go b/controllers/apps/componentdefinition_controller.go index ed7518649f8..8ea751bc945 100644 --- a/controllers/apps/componentdefinition_controller.go +++ b/controllers/apps/componentdefinition_controller.go @@ -31,7 +31,6 @@ import ( "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/rand" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/tools/record" @@ -47,7 +46,8 @@ import ( ) const ( - immutableHashAnnotationKey = "apps.kubeblocks.io/immutable-hash" + componentDefinitionFinalizerName = "componentdefinition.kubeblocks.io/finalizer" + immutableHashAnnotationKey = "apps.kubeblocks.io/immutable-hash" ) // ComponentDefinitionReconciler reconciles a ComponentDefinition object @@ -470,23 +470,6 @@ func (r *ComponentDefinitionReconciler) cmpdHash(cmpd *appsv1.ComponentDefinitio return rand.SafeEncodeString(fmt.Sprintf("%d", hash.Sum32())), nil } -func getNCheckCompDefinition(ctx context.Context, cli client.Reader, name string) (*appsv1.ComponentDefinition, error) { - compKey := types.NamespacedName{ - Name: name, - } - compDef := &appsv1.ComponentDefinition{} - if err := cli.Get(ctx, compKey, compDef); err != nil { - return nil, err - } - if compDef.Generation != compDef.Status.ObservedGeneration { - return nil, fmt.Errorf("the referenced ComponentDefinition is not up to date: %s", compDef.Name) - } - if compDef.Status.Phase != appsv1.AvailablePhase { - return nil, fmt.Errorf("the referenced ComponentDefinition is unavailable: %s", compDef.Name) - } - return compDef, nil -} - // listCompDefinitionsWithPattern returns all component definitions whose names match the given pattern func listCompDefinitionsWithPattern(ctx context.Context, cli client.Reader, name string) ([]*appsv1.ComponentDefinition, error) { compDefList := &appsv1.ComponentDefinitionList{} diff --git a/controllers/apps/componentversion_controller.go b/controllers/apps/componentversion_controller.go index f156930a4cc..a91ea2d23bc 100644 --- a/controllers/apps/componentversion_controller.go +++ b/controllers/apps/componentversion_controller.go @@ -48,7 +48,8 @@ import ( ) const ( - compatibleDefinitionsKey = "componentversion.kubeblocks.io/compatible-definitions" + componentVersionFinalizerName = "componentversion.kubeblocks.io/finalizer" + compatibleDefinitionsKey = "componentversion.kubeblocks.io/compatible-definitions" ) // ComponentVersionReconciler reconciles a ComponentVersion object @@ -393,110 +394,6 @@ func validateCompatibilityRulesCompDef(compVersion *appsv1.ComponentVersion) err return nil } -// resolveCompDefinitionNServiceVersion resolves and returns the specific component definition object and the service version supported. -func resolveCompDefinitionNServiceVersion(ctx context.Context, cli client.Reader, compDefName, serviceVersion string) (*appsv1.ComponentDefinition, string, error) { - var ( - compDef *appsv1.ComponentDefinition - ) - compDefs, err := listCompDefinitionsWithPattern(ctx, cli, compDefName) - if err != nil { - return compDef, serviceVersion, err - } - - // mapping from to <[]*appsv1.ComponentDefinition> - serviceVersionToCompDefs, err := serviceVersionToCompDefinitions(ctx, cli, compDefs, serviceVersion) - if err != nil { - return compDef, serviceVersion, err - } - - // use specified service version or the latest. - if len(serviceVersion) == 0 { - serviceVersions := maps.Keys(serviceVersionToCompDefs) - if len(serviceVersions) > 0 { - slices.SortFunc(serviceVersions, serviceVersionComparator) - serviceVersion = serviceVersions[len(serviceVersions)-1] - } - } - - // component definitions that support the service version - compatibleCompDefs := serviceVersionToCompDefs[serviceVersion] - if len(compatibleCompDefs) == 0 { - return compDef, serviceVersion, fmt.Errorf("no matched component definition found: %s", compDefName) - } - - // choose the latest one - compatibleCompDefNames := maps.Keys(compatibleCompDefs) - slices.Sort(compatibleCompDefNames) - compatibleCompDefName := compatibleCompDefNames[len(compatibleCompDefNames)-1] - - return compatibleCompDefs[compatibleCompDefName], serviceVersion, nil -} - -func serviceVersionToCompDefinitions(ctx context.Context, cli client.Reader, - compDefs []*appsv1.ComponentDefinition, serviceVersion string) (map[string]map[string]*appsv1.ComponentDefinition, error) { - result := make(map[string]map[string]*appsv1.ComponentDefinition) - - insert := func(version string, compDef *appsv1.ComponentDefinition) { - if _, ok := result[version]; !ok { - result[version] = make(map[string]*appsv1.ComponentDefinition) - } - result[version][compDef.Name] = compDef - } - - checkedInsert := func(version string, compDef *appsv1.ComponentDefinition) error { - match, err := component.CompareServiceVersion(serviceVersion, version) - if err == nil && match { - insert(version, compDef) - } - return err - } - - for _, compDef := range compDefs { - compVersions, err := component.CompatibleCompVersions4Definition(ctx, cli, compDef) - if err != nil { - return nil, err - } - - serviceVersions := sets.New[string]() - // add definition's service version as default, in case there is no component versions provided - if compDef.Spec.ServiceVersion != "" { - serviceVersions.Insert(compDef.Spec.ServiceVersion) - } - for _, compVersion := range compVersions { - serviceVersions = serviceVersions.Union(compatibleServiceVersions4Definition(compDef, compVersion)) - } - - for version := range serviceVersions { - if err = checkedInsert(version, compDef); err != nil { - return nil, err - } - } - } - return result, nil -} - -// compatibleServiceVersions4Definition returns all service versions that are compatible with specified component definition. -func compatibleServiceVersions4Definition(compDef *appsv1.ComponentDefinition, compVersion *appsv1.ComponentVersion) sets.Set[string] { - match := func(pattern string) bool { - return component.PrefixOrRegexMatched(compDef.Name, pattern) - } - releases := make(map[string]bool, 0) - for _, rule := range compVersion.Spec.CompatibilityRules { - if slices.IndexFunc(rule.CompDefs, match) >= 0 { - for _, release := range rule.Releases { - releases[release] = true - } - } - } - serviceVersions := sets.New[string]() - for _, release := range compVersion.Spec.Releases { - if releases[release.Name] { - serviceVersions = serviceVersions.Insert(release.ServiceVersion) - } - } - return serviceVersions -} - func serviceVersionComparator(a, b string) int { if len(a) == 0 { return -1 diff --git a/controllers/apps/componentversion_controller_test.go b/controllers/apps/componentversion_controller_test.go index 5290927e554..e27b1b3d633 100644 --- a/controllers/apps/componentversion_controller_test.go +++ b/controllers/apps/componentversion_controller_test.go @@ -31,19 +31,25 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" - "github.com/apecloud/kubeblocks/pkg/controller/component" "github.com/apecloud/kubeblocks/pkg/generics" testapps "github.com/apecloud/kubeblocks/pkg/testutil/apps" ) var _ = Describe("ComponentVersion Controller", func() { var ( - // compDefinitionObjs []*appsv1.ComponentDefinition compVersionObj *appsv1.ComponentVersion - - compDefNames = []string{testapps.CompDefName("v1.0"), testapps.CompDefName("v1.1"), testapps.CompDefName("v2.0"), testapps.CompDefName("v3.0")} + compDefNames = []string{ + testapps.CompDefName("v1.0"), + testapps.CompDefName("v1.1"), + testapps.CompDefName("v2.0"), + testapps.CompDefName("v3.0"), + } // in reverse order - serviceVersions = []string{testapps.ServiceVersion("v3"), testapps.ServiceVersion("v2"), testapps.ServiceVersion("v1")} + serviceVersions = []string{ + testapps.ServiceVersion("v3"), + testapps.ServiceVersion("v2"), + testapps.ServiceVersion("v1"), + } ) cleanEnv := func() { @@ -65,7 +71,6 @@ var _ = Describe("ComponentVersion Controller", func() { BeforeEach(func() { cleanEnv() - }) AfterEach(func() { @@ -180,20 +185,6 @@ var _ = Describe("ComponentVersion Controller", func() { return obj } - updateNCheckCompDefinitionImages := func(compDef *appsv1.ComponentDefinition, serviceVersion string, r0, r1 string) { - Expect(compDef.Spec.Runtime.Containers[0].Image).Should(Equal(testapps.AppImage(compDef.Spec.Runtime.Containers[0].Name, testapps.ReleaseID("")))) - Expect(compDef.Spec.Runtime.Containers[1].Image).Should(Equal(testapps.AppImage(compDef.Spec.Runtime.Containers[1].Name, testapps.ReleaseID("")))) - Expect(component.UpdateCompDefinitionImages4ServiceVersion(testCtx.Ctx, testCtx.Cli, compDef, serviceVersion)).Should(Succeed()) - Expect(compDef.Spec.Runtime.Containers).Should(HaveLen(2)) - Expect(compDef.Spec.Runtime.Containers[0].Image).Should(Equal(testapps.AppImage(compDef.Spec.Runtime.Containers[0].Name, testapps.ReleaseID(r0)))) - Expect(compDef.Spec.Runtime.Containers[1].Image).Should(Equal(testapps.AppImage(compDef.Spec.Runtime.Containers[1].Name, testapps.ReleaseID(r1)))) - - Expect(compDef.Spec.LifecycleActions).ShouldNot(BeNil()) - Expect(compDef.Spec.LifecycleActions.PreTerminate).ShouldNot(BeNil()) - Expect(compDef.Spec.LifecycleActions.PreTerminate.Exec).ShouldNot(BeNil()) - Expect(compDef.Spec.LifecycleActions.PreTerminate.Exec.Image).Should(Equal(testapps.AppImage(testapps.DefaultActionName, testapps.ReleaseID(r1)))) - } - Context("reconcile component version", func() { BeforeEach(func() { createCompDefinitionObjs() @@ -370,307 +361,4 @@ var _ = Describe("ComponentVersion Controller", func() { })).Should(Succeed()) }) }) - - Context("resolve component definition, service version and images", func() { - BeforeEach(func() { - createCompDefinitionObjs() - compVersionObj = createCompVersionObj() - }) - - AfterEach(func() { - cleanEnv() - }) - - It("full match", func() { - By("with definition v1.0 and service version v0") - compDef, resolvedServiceVersion, err := resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefName("v1.0"), testapps.ServiceVersion("v1")) - Expect(err).Should(Succeed()) - Expect(compDef.Name).Should(Equal(testapps.CompDefName("v1.0"))) - Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v1"))) - updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r4", "r4") - - By("with definition v1.1 and service version v0") - compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefName("v1.1"), testapps.ServiceVersion("v1")) - Expect(err).Should(Succeed()) - Expect(compDef.Name).Should(Equal(testapps.CompDefName("v1.1"))) - Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v1"))) - updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r4", "r4") - - By("with definition v2.0 and service version v0") - compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefName("v2.0"), testapps.ServiceVersion("v1")) - Expect(err).Should(Succeed()) - Expect(compDef.Name).Should(Equal(testapps.CompDefName("v2.0"))) - Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v1"))) - updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r4", "r4") - - By("with definition v1.0 and service version v1") - compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefName("v1.0"), testapps.ServiceVersion("v2")) - Expect(err).Should(Succeed()) - Expect(compDef.Name).Should(Equal(testapps.CompDefName("v1.0"))) - Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v2"))) - updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r3", "r2") - - By("with definition v1.1 and service version v1") - compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefName("v1.1"), testapps.ServiceVersion("v2")) - Expect(err).Should(Succeed()) - Expect(compDef.Name).Should(Equal(testapps.CompDefName("v1.1"))) - Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v2"))) - updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r3", "r2") - - By("with definition v2.0 and service version v1") - compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefName("v2.0"), testapps.ServiceVersion("v2")) - Expect(err).Should(Succeed()) - Expect(compDef.Name).Should(Equal(testapps.CompDefName("v2.0"))) - Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v2"))) - updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r3", "r2") - - By("with definition v3.0 and service version v2") - compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefName("v3.0"), testapps.ServiceVersion("v3")) - Expect(err).Should(Succeed()) - Expect(compDef.Name).Should(Equal(testapps.CompDefName("v3.0"))) - Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v3"))) - updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r5", "r5") - }) - - It("w/o service version", func() { - By("with definition v1.0") - compDef, resolvedServiceVersion, err := resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefName("v1.0"), "") - Expect(err).Should(Succeed()) - Expect(compDef.Name).Should(Equal(testapps.CompDefName("v1.0"))) - Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v2"))) - updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r3", "r2") - - By("with definition v1.1") - compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefName("v1.1"), "") - Expect(err).Should(Succeed()) - Expect(compDef.Name).Should(Equal(testapps.CompDefName("v1.1"))) - Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v2"))) - updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r3", "r2") - - By("with definition v2.0") - compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefName("v2.0"), "") - Expect(err).Should(Succeed()) - Expect(compDef.Name).Should(Equal(testapps.CompDefName("v2.0"))) - Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v2"))) - updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r3", "r2") - - By("with definition v3.0") - compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefName("v3.0"), "") - Expect(err).Should(Succeed()) - Expect(compDef.Name).Should(Equal(testapps.CompDefName("v3.0"))) - Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v3"))) - updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r5", "r5") - }) - - It("prefix match definition", func() { - By("with definition prefix and service version v0") - compDef, resolvedServiceVersion, err := resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefinitionName, testapps.ServiceVersion("v1")) - Expect(err).Should(Succeed()) - Expect(compDef.Name).Should(Equal(testapps.CompDefName("v2.0"))) - Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v1"))) - updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r4", "r4") - - By("with definition prefix and service version v1") - compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefinitionName, testapps.ServiceVersion("v2")) - Expect(err).Should(Succeed()) - Expect(compDef.Name).Should(Equal(testapps.CompDefName("v2.0"))) - Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v2"))) - updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r3", "r2") - - By("with definition prefix and service version v2") - compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefinitionName, testapps.ServiceVersion("v3")) - Expect(err).Should(Succeed()) - Expect(compDef.Name).Should(Equal(testapps.CompDefName("v3.0"))) - Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v3"))) - updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r5", "r5") - - By("with definition v1 prefix and service version v0") - compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefName("v1"), testapps.ServiceVersion("v1")) - Expect(err).Should(Succeed()) - Expect(compDef.Name).Should(Equal(testapps.CompDefName("v1.1"))) - Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v1"))) - updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r4", "r4") - - By("with definition v2 prefix and service version v1") - compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefName("v2"), testapps.ServiceVersion("v2")) - Expect(err).Should(Succeed()) - Expect(compDef.Name).Should(Equal(testapps.CompDefName("v2.0"))) - Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v2"))) - updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r3", "r2") - }) - - It("prefix match definition and w/o service version", func() { - By("with definition prefix") - compDef, resolvedServiceVersion, err := resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefinitionName, "") - Expect(err).Should(Succeed()) - Expect(compDef.Name).Should(Equal(testapps.CompDefName("v3.0"))) - Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v3"))) - updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r5", "r5") - - By("with definition v1 prefix") - compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefName("v1"), "") - Expect(err).Should(Succeed()) - Expect(compDef.Name).Should(Equal(testapps.CompDefName("v1.1"))) - Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v2"))) - updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r3", "r2") - - By("with definition v2 prefix") - compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefName("v2"), "") - Expect(err).Should(Succeed()) - Expect(compDef.Name).Should(Equal(testapps.CompDefName("v2.0"))) - Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v2"))) - updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r3", "r2") - }) - - It("regular expression match definition", func() { - By("with definition exact regex and service version 1") - compDef, resolvedServiceVersion, err := resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefNameWithExactRegex("v2.0"), testapps.ServiceVersion("v1")) - Expect(err).Should(Succeed()) - Expect(compDef.Name).Should(Equal(testapps.CompDefName("v2.0"))) - Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v1"))) - updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r4", "r4") - - By("with definition exact regex and service version v2") - compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefNameWithExactRegex("v2.0"), testapps.ServiceVersion("v2")) - Expect(err).Should(Succeed()) - Expect(compDef.Name).Should(Equal(testapps.CompDefName("v2.0"))) - Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v2"))) - updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r3", "r2") - - By("with definition exact regex and service version v3") - compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefNameWithExactRegex("v3.0"), testapps.ServiceVersion("v3")) - Expect(err).Should(Succeed()) - Expect(compDef.Name).Should(Equal(testapps.CompDefName("v3.0"))) - Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v3"))) - updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r5", "r5") - - By("with definition v1 fuzzy regex and service version v0") - compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefNameWithFuzzyRegex("v1"), testapps.ServiceVersion("v1")) - Expect(err).Should(Succeed()) - Expect(compDef.Name).Should(Equal(testapps.CompDefName("v1.1"))) - Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v1"))) - updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r4", "r4") - - By("with definition v2 fuzzy regex and service version v1") - compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefNameWithFuzzyRegex("v2"), testapps.ServiceVersion("v2")) - Expect(err).Should(Succeed()) - Expect(compDef.Name).Should(Equal(testapps.CompDefName("v2.0"))) - Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v2"))) - updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r3", "r2") - }) - - It("regular expression match definition and w/o service version", func() { - By("with definition regex") - compDef, resolvedServiceVersion, err := resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, "^"+testapps.CompDefinitionName, "") - Expect(err).Should(Succeed()) - Expect(compDef.Name).Should(Equal(testapps.CompDefName("v3.0"))) - Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v3"))) - updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r5", "r5") - - By("with definition v1 regex") - compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefNameWithFuzzyRegex("v1"), "") - Expect(err).Should(Succeed()) - Expect(compDef.Name).Should(Equal(testapps.CompDefName("v1.1"))) - Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v2"))) - updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r3", "r2") - - By("with definition v2 regex") - compDef, resolvedServiceVersion, err = resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefNameWithFuzzyRegex("v2"), "") - Expect(err).Should(Succeed()) - Expect(compDef.Name).Should(Equal(testapps.CompDefName("v2.0"))) - Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v2"))) - updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r3", "r2") - }) - - It("match from definition", func() { - By("with definition v1.0 and service version v0") - compDef, resolvedServiceVersion, err := resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefName("v1.0"), testapps.ServiceVersion("v0")) - Expect(err).Should(Succeed()) - Expect(compDef.Name).Should(Equal(testapps.CompDefName("v1.0"))) - Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v0"))) - updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "", "") // empty revision of image tag - }) - - It("resolve images from definition and version", func() { - By("create new definition v4.0 with service version v4") - compDefObj := testapps.NewComponentDefinitionFactory(testapps.CompDefName("v4.0")). - SetServiceVersion(testapps.ServiceVersion("v4")). - SetRuntime(&corev1.Container{Name: testapps.AppName, Image: testapps.AppImage(testapps.AppName, testapps.ReleaseID(""))}). - SetRuntime(&corev1.Container{Name: testapps.AppNameSamePrefix, Image: testapps.AppImage(testapps.AppNameSamePrefix, testapps.ReleaseID(""))}). - SetLifecycleAction(testapps.DefaultActionName, - &appsv1.Action{Exec: &appsv1.ExecAction{Image: testapps.AppImage(testapps.DefaultActionName, testapps.ReleaseID(""))}}). - Create(&testCtx). - GetObject() - Eventually(testapps.CheckObj(&testCtx, client.ObjectKeyFromObject(compDefObj), - func(g Gomega, compDef *appsv1.ComponentDefinition) { - g.Expect(compDef.Status.ObservedGeneration).Should(Equal(compDef.Generation)) - })).Should(Succeed()) - - By("new release for the definition") - compVersionKey := client.ObjectKeyFromObject(compVersionObj) - Eventually(testapps.GetAndChangeObj(&testCtx, compVersionKey, func(compVersion *appsv1.ComponentVersion) { - release := appsv1.ComponentVersionRelease{ - Name: testapps.ReleaseID("r6"), - Changes: "publish a new service version", - ServiceVersion: testapps.ServiceVersion("v4"), - Images: map[string]string{ - testapps.AppName: testapps.AppImage(testapps.AppName, testapps.ReleaseID("r6")), - // not provide image for this app - // testapps.AppNameSamePrefix: testapps.AppImage(testapps.AppNameSamePrefix, testapps.ReleaseID("r6")), - }, - } - rule := appsv1.ComponentVersionCompatibilityRule{ - CompDefs: []string{testapps.CompDefName("v4")}, // use prefix - Releases: []string{testapps.ReleaseID("r6")}, - } - compVersion.Spec.CompatibilityRules = append(compVersion.Spec.CompatibilityRules, rule) - compVersion.Spec.Releases = append(compVersion.Spec.Releases, release) - })).Should(Succeed()) - Eventually(testapps.CheckObj(&testCtx, compVersionKey, func(g Gomega, compVersion *appsv1.ComponentVersion) { - g.Expect(compVersion.Status.ObservedGeneration).Should(Equal(compVersion.Generation)) - })).Should(Succeed()) - - By("with definition v4.0 and service version v3") - compDef, resolvedServiceVersion, err := resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefName("v4.0"), testapps.ServiceVersion("v4")) - Expect(err).Should(Succeed()) - Expect(compDef.Name).Should(Equal(testapps.CompDefName("v4.0"))) - Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v4"))) - updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r6", "") // app is r6 and another one is "" - }) - }) - - Context("resolve component definition, service version without serviceVersion in componentDefinition", func() { - BeforeEach(func() { - compDefs := createCompDefinitionObjs() - for _, compDef := range compDefs { - compDefKey := client.ObjectKeyFromObject(compDef) - Eventually(testapps.GetAndChangeObj(&testCtx, compDefKey, func(compDef *appsv1.ComponentDefinition) { - compDef.Spec.ServiceVersion = "" - })).Should(Succeed()) - } - compVersionObj = createCompVersionObj() - }) - - AfterEach(func() { - cleanEnv() - }) - - It("full match", func() { - By("with definition v1.0 and service version v0") - compDef, resolvedServiceVersion, err := resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefName("v1.0"), testapps.ServiceVersion("v1")) - Expect(err).Should(Succeed()) - Expect(compDef.Name).Should(Equal(testapps.CompDefName("v1.0"))) - Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v1"))) - updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r4", "r4") - }) - - It("w/o service version", func() { - By("with definition v1.0") - compDef, resolvedServiceVersion, err := resolveCompDefinitionNServiceVersion(testCtx.Ctx, testCtx.Cli, testapps.CompDefName("v1.0"), "") - Expect(err).Should(Succeed()) - Expect(compDef.Name).Should(Equal(testapps.CompDefName("v1.0"))) - Expect(resolvedServiceVersion).Should(Equal(testapps.ServiceVersion("v2"))) - updateNCheckCompDefinitionImages(compDef, resolvedServiceVersion, "r3", "r2") - }) - }) }) diff --git a/controllers/apps/shardingdefinition_controller.go b/controllers/apps/shardingdefinition_controller.go index cbf14138dda..f504d04c5fc 100644 --- a/controllers/apps/shardingdefinition_controller.go +++ b/controllers/apps/shardingdefinition_controller.go @@ -24,10 +24,8 @@ import ( "encoding/json" "fmt" "hash/fnv" - "slices" "strings" - "golang.org/x/exp/maps" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/rand" @@ -42,6 +40,10 @@ import ( intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" ) +const ( + shardingDefinitionFinalizerName = "shardingdefinition.kubeblocks.io/finalizer" +) + //+kubebuilder:rbac:groups=apps.kubeblocks.io,resources=shardingdefinitions,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=apps.kubeblocks.io,resources=shardingdefinitions/status,verbs=get;update;patch //+kubebuilder:rbac:groups=apps.kubeblocks.io,resources=shardingdefinitions/finalizers,verbs=update @@ -294,62 +296,3 @@ func (r *ShardingDefinitionReconciler) immutableHash(cli client.Client, rctx int shardingDef.Annotations[immutableHashAnnotationKey], _ = r.specHash(shardingDef) return cli.Patch(rctx.Ctx, shardingDef, patch) } - -// resolveShardingDefinition resolves and returns the specific sharding definition object supported. -func resolveShardingDefinition(ctx context.Context, cli client.Reader, shardingDefName string) (*appsv1.ShardingDefinition, error) { - shardingDefs, err := listShardingDefinitionsWithPattern(ctx, cli, shardingDefName) - if err != nil { - return nil, err - } - if len(shardingDefs) == 0 { - return nil, fmt.Errorf("no sharding definition found for the specified name: %s", shardingDefName) - } - - m := make(map[string]int) - for i, def := range shardingDefs { - m[def.Name] = i - } - // choose the latest one - names := maps.Keys(m) - slices.Sort(names) - latestName := names[len(names)-1] - - return shardingDefs[m[latestName]], nil -} - -// listShardingDefinitionsWithPattern returns all sharding definitions whose names match the given pattern -func listShardingDefinitionsWithPattern(ctx context.Context, cli client.Reader, name string) ([]*appsv1.ShardingDefinition, error) { - shardingDefList := &appsv1.ShardingDefinitionList{} - if err := cli.List(ctx, shardingDefList); err != nil { - return nil, err - } - fullyMatched := make([]*appsv1.ShardingDefinition, 0) - patternMatched := make([]*appsv1.ShardingDefinition, 0) - for i, item := range shardingDefList.Items { - if item.Name == name { - fullyMatched = append(fullyMatched, &shardingDefList.Items[i]) - } - if component.PrefixOrRegexMatched(item.Name, name) { - patternMatched = append(patternMatched, &shardingDefList.Items[i]) - } - } - if len(fullyMatched) > 0 { - return fullyMatched, nil - } - return patternMatched, nil -} - -func validateShardingShards(shardingDef *appsv1.ShardingDefinition, sharding *appsv1.ClusterSharding) error { - var ( - limit = shardingDef.Spec.ShardsLimit - shards = sharding.Shards - ) - if limit == nil || (shards >= limit.MinShards && shards <= limit.MaxShards) { - return nil - } - return shardsOutOfLimitError(sharding.Name, shards, *limit) -} - -func shardsOutOfLimitError(shardingName string, shards int32, limit appsv1.ShardsLimit) error { - return fmt.Errorf("shards %d out-of-limit [%d, %d], sharding: %s", shards, limit.MinShards, limit.MaxShards, shardingName) -} diff --git a/controllers/apps/sidecardefinition_controller.go b/controllers/apps/sidecardefinition_controller.go index 37465557e6b..f8df7d466d6 100644 --- a/controllers/apps/sidecardefinition_controller.go +++ b/controllers/apps/sidecardefinition_controller.go @@ -46,6 +46,10 @@ import ( intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" ) +const ( + sidecarDefinitionFinalizerName = "sidecardefinition.kubeblocks.io/finalizer" +) + // SidecarDefinitionReconciler reconciles a SidecarDefinition object type SidecarDefinitionReconciler struct { client.Client diff --git a/controllers/apps/suite_test.go b/controllers/apps/suite_test.go index 65c60ed3a18..7ed16fe9abf 100644 --- a/controllers/apps/suite_test.go +++ b/controllers/apps/suite_test.go @@ -30,11 +30,9 @@ import ( . "github.com/onsi/gomega" "github.com/go-logr/logr" - snapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1" "go.uber.org/zap/zapcore" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" - "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" @@ -45,12 +43,6 @@ import ( appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" appsv1alpha1 "github.com/apecloud/kubeblocks/apis/apps/v1alpha1" appsv1beta1 "github.com/apecloud/kubeblocks/apis/apps/v1beta1" - dpv1alpha1 "github.com/apecloud/kubeblocks/apis/dataprotection/v1alpha1" - opsv1alpha1 "github.com/apecloud/kubeblocks/apis/operations/v1alpha1" - workloadsv1 "github.com/apecloud/kubeblocks/apis/workloads/v1" - "github.com/apecloud/kubeblocks/controllers/apps/configuration" - "github.com/apecloud/kubeblocks/controllers/dataprotection" - "github.com/apecloud/kubeblocks/controllers/k8score" "github.com/apecloud/kubeblocks/pkg/constant" "github.com/apecloud/kubeblocks/pkg/controller/model" intctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil" @@ -72,7 +64,6 @@ var testEnv *envtest.Environment var ctx context.Context var cancel context.CancelFunc var testCtx testutil.TestContext -var clusterRecorder record.EventRecorder var logger logr.Logger func init() { @@ -126,10 +117,6 @@ var _ = BeforeSuite(func() { Expect(err).NotTo(HaveOccurred()) model.AddScheme(appsv1alpha1.AddToScheme) - err = opsv1alpha1.AddToScheme(scheme.Scheme) - Expect(err).NotTo(HaveOccurred()) - model.AddScheme(opsv1alpha1.AddToScheme) - err = appsv1beta1.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) model.AddScheme(appsv1beta1.AddToScheme) @@ -138,18 +125,6 @@ var _ = BeforeSuite(func() { Expect(err).NotTo(HaveOccurred()) model.AddScheme(appsv1.AddToScheme) - err = dpv1alpha1.AddToScheme(scheme.Scheme) - Expect(err).NotTo(HaveOccurred()) - model.AddScheme(dpv1alpha1.AddToScheme) - - err = snapshotv1.AddToScheme(scheme.Scheme) - Expect(err).NotTo(HaveOccurred()) - model.AddScheme(snapshotv1.AddToScheme) - - err = workloadsv1.AddToScheme(scheme.Scheme) - Expect(err).NotTo(HaveOccurred()) - model.AddScheme(workloadsv1.AddToScheme) - // +kubebuilder:scaffold:rscheme k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) @@ -214,21 +189,6 @@ var _ = BeforeSuite(func() { }).SetupWithManager(k8sManager) Expect(err).ToNot(HaveOccurred()) - clusterRecorder = k8sManager.GetEventRecorderFor("cluster-controller") - err = (&ClusterReconciler{ - Client: k8sManager.GetClient(), - Scheme: k8sManager.GetScheme(), - Recorder: clusterRecorder, - }).SetupWithManager(k8sManager) - Expect(err).ToNot(HaveOccurred()) - - err = (&ComponentReconciler{ - Client: k8sManager.GetClient(), - Scheme: k8sManager.GetScheme(), - Recorder: k8sManager.GetEventRecorderFor("component-controller"), - }).SetupWithManager(k8sManager, nil) - Expect(err).ToNot(HaveOccurred()) - err = (&ServiceDescriptorReconciler{ Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), @@ -236,34 +196,6 @@ var _ = BeforeSuite(func() { }).SetupWithManager(k8sManager) Expect(err).ToNot(HaveOccurred()) - err = (&k8score.EventReconciler{ - Client: k8sManager.GetClient(), - Scheme: k8sManager.GetScheme(), - Recorder: k8sManager.GetEventRecorderFor("event-controller"), - }).SetupWithManager(k8sManager, nil) - Expect(err).ToNot(HaveOccurred()) - - err = (&configuration.ConfigConstraintReconciler{ - Client: k8sManager.GetClient(), - Scheme: k8sManager.GetScheme(), - Recorder: k8sManager.GetEventRecorderFor("configuration-template-controller"), - }).SetupWithManager(k8sManager) - Expect(err).ToNot(HaveOccurred()) - - err = (&configuration.ConfigurationReconciler{ - Client: k8sManager.GetClient(), - Scheme: k8sManager.GetScheme(), - Recorder: k8sManager.GetEventRecorderFor("configuration-controller"), - }).SetupWithManager(k8sManager, nil) - Expect(err).ToNot(HaveOccurred()) - - err = (&dataprotection.BackupPolicyTemplateReconciler{ - Client: k8sManager.GetClient(), - Scheme: k8sManager.GetScheme(), - Recorder: k8sManager.GetEventRecorderFor("backup-policy-template-controller"), - }).SetupWithManager(k8sManager) - Expect(err).ToNot(HaveOccurred()) - testCtx = testutil.NewDefaultTestContext(ctx, k8sClient, testEnv) go func() { diff --git a/controllers/apps/transformers_parallel.go b/controllers/apps/transformers_parallel.go deleted file mode 100644 index 587f17e5b1b..00000000000 --- a/controllers/apps/transformers_parallel.go +++ /dev/null @@ -1,52 +0,0 @@ -/* -Copyright (C) 2022-2024 ApeCloud Co., Ltd - -This file is part of KubeBlocks project - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . -*/ - -package apps - -import ( - "fmt" - "sync" - - "github.com/apecloud/kubeblocks/pkg/controller/graph" -) - -type ParallelTransformers struct { - transformers []graph.Transformer -} - -var _ graph.Transformer = &ParallelTransformers{} - -func (t *ParallelTransformers) Transform(ctx graph.TransformContext, dag *graph.DAG) error { - var group sync.WaitGroup - var errs error - for _, transformer := range t.transformers { - transformer := transformer - group.Add(1) - go func() { - err := transformer.Transform(ctx, dag) - if err != nil { - // TODO: sync.Mutex errs - errs = fmt.Errorf("%v; %v", errs, err) - } - group.Done() - }() - } - group.Wait() - return errs -} diff --git a/controllers/apps/const.go b/controllers/apps/types.go similarity index 61% rename from controllers/apps/const.go rename to controllers/apps/types.go index a1a3bc3a306..9cd5337abff 100644 --- a/controllers/apps/const.go +++ b/controllers/apps/types.go @@ -19,15 +19,21 @@ along with this program. If not, see . package apps -const ( - // name of our custom finalizer - clusterDefinitionFinalizerName = "clusterdefinition.kubeblocks.io/finalizer" - shardingDefinitionFinalizerName = "shardingdefinition.kubeblocks.io/finalizer" - componentDefinitionFinalizerName = "componentdefinition.kubeblocks.io/finalizer" - componentVersionFinalizerName = "componentversion.kubeblocks.io/finalizer" - sidecarDefinitionFinalizerName = "sidecardefinition.kubeblocks.io/finalizer" +import ( + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + + appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" + workloads "github.com/apecloud/kubeblocks/apis/workloads/v1" ) -const ( - trueVal = "true" +var ( + rscheme = runtime.NewScheme() ) + +func init() { + utilruntime.Must(clientgoscheme.AddToScheme(rscheme)) + utilruntime.Must(appsv1.AddToScheme(rscheme)) + utilruntime.Must(workloads.AddToScheme(rscheme)) +} diff --git a/controllers/operations/opsrequest_controller_test.go b/controllers/operations/opsrequest_controller_test.go index ea557de3194..7cad56b3176 100644 --- a/controllers/operations/opsrequest_controller_test.go +++ b/controllers/operations/opsrequest_controller_test.go @@ -40,7 +40,6 @@ import ( appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" opsv1alpha1 "github.com/apecloud/kubeblocks/apis/operations/v1alpha1" workloads "github.com/apecloud/kubeblocks/apis/workloads/v1" - "github.com/apecloud/kubeblocks/controllers/apps" "github.com/apecloud/kubeblocks/pkg/constant" "github.com/apecloud/kubeblocks/pkg/controller/component" intctrlutil "github.com/apecloud/kubeblocks/pkg/generics" @@ -396,7 +395,7 @@ var _ = Describe("OpsRequest Controller", func() { condition := meta.FindStatusCondition(fetched.Status.Conditions, appsv1.ConditionTypeProvisioningStarted) g.Expect(condition).ShouldNot(BeNil()) g.Expect(condition.Status).Should(BeFalse()) - g.Expect(condition.Reason).Should(Equal(apps.ReasonPreCheckFailed)) + g.Expect(condition.Reason).Should(Equal("PreCheckFailed")) g.Expect(condition.Message).Should(Equal("HorizontalScaleFailed: volume snapshot not support")) })) diff --git a/controllers/operations/suite_test.go b/controllers/operations/suite_test.go index ba7ec138078..c3a3ca269c7 100644 --- a/controllers/operations/suite_test.go +++ b/controllers/operations/suite_test.go @@ -49,6 +49,8 @@ import ( opsv1alpha1 "github.com/apecloud/kubeblocks/apis/operations/v1alpha1" workloadsv1 "github.com/apecloud/kubeblocks/apis/workloads/v1" "github.com/apecloud/kubeblocks/controllers/apps" + "github.com/apecloud/kubeblocks/controllers/apps/cluster" + "github.com/apecloud/kubeblocks/controllers/apps/component" "github.com/apecloud/kubeblocks/controllers/apps/configuration" "github.com/apecloud/kubeblocks/controllers/dataprotection" "github.com/apecloud/kubeblocks/controllers/k8score" @@ -182,7 +184,7 @@ var _ = BeforeSuite(func() { Expect(err).ToNot(HaveOccurred()) clusterRecorder = k8sManager.GetEventRecorderFor("cluster-controller") - err = (&apps.ClusterReconciler{ + err = (&cluster.ClusterReconciler{ Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), Recorder: clusterRecorder, @@ -196,7 +198,7 @@ var _ = BeforeSuite(func() { }).SetupWithManager(k8sManager) Expect(err).ToNot(HaveOccurred()) - err = (&apps.ComponentReconciler{ + err = (&component.ComponentReconciler{ Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), Recorder: k8sManager.GetEventRecorderFor("component-controller"), diff --git a/controllers/trace/reconciler_tree.go b/controllers/trace/reconciler_tree.go index a6eaf6e4806..50da88bd816 100644 --- a/controllers/trace/reconciler_tree.go +++ b/controllers/trace/reconciler_tree.go @@ -45,7 +45,8 @@ import ( dpv1alpha1 "github.com/apecloud/kubeblocks/apis/dataprotection/v1alpha1" tracev1 "github.com/apecloud/kubeblocks/apis/trace/v1" workloadsAPI "github.com/apecloud/kubeblocks/apis/workloads/v1" - "github.com/apecloud/kubeblocks/controllers/apps" + "github.com/apecloud/kubeblocks/controllers/apps/cluster" + "github.com/apecloud/kubeblocks/controllers/apps/component" "github.com/apecloud/kubeblocks/controllers/apps/configuration" "github.com/apecloud/kubeblocks/controllers/dataprotection" "github.com/apecloud/kubeblocks/controllers/workloads" @@ -162,7 +163,7 @@ func newReconciler(mClient client.Client, recorder record.EventRecorder, objectT } func newClusterReconciler(cli client.Client, recorder record.EventRecorder) reconcile.Reconciler { - return &apps.ClusterReconciler{ + return &cluster.ClusterReconciler{ Client: cli, Scheme: cli.Scheme(), Recorder: recorder, @@ -170,7 +171,7 @@ func newClusterReconciler(cli client.Client, recorder record.EventRecorder) reco } func newComponentReconciler(cli client.Client, recorder record.EventRecorder) reconcile.Reconciler { - return &apps.ComponentReconciler{ + return &component.ComponentReconciler{ Client: cli, Scheme: cli.Scheme(), Recorder: recorder, diff --git a/pkg/controller/graph/plan_builder.go b/pkg/controller/graph/plan_builder.go index d1e70276080..47db0782b66 100644 --- a/pkg/controller/graph/plan_builder.go +++ b/pkg/controller/graph/plan_builder.go @@ -28,11 +28,7 @@ type PlanBuilder interface { // And the transformers will be executed in the add order. AddTransformer(transformer ...Transformer) PlanBuilder - // AddParallelTransformer adds transformers to the builder. - // And the transformers will be executed in parallel. - AddParallelTransformer(transformer ...Transformer) PlanBuilder - - // Build runs all the transformers added by AddTransformer and/or AddParallelTransformer. + // Build runs all the transformers added by AddTransformer. Build() (Plan, error) } diff --git a/pkg/controller/kubebuilderx/plan_builder.go b/pkg/controller/kubebuilderx/plan_builder.go index 6dea6bb2643..1a324689c1e 100644 --- a/pkg/controller/kubebuilderx/plan_builder.go +++ b/pkg/controller/kubebuilderx/plan_builder.go @@ -92,10 +92,6 @@ func (b *PlanBuilder) AddTransformer(_ ...graph.Transformer) graph.PlanBuilder { return b } -func (b *PlanBuilder) AddParallelTransformer(_ ...graph.Transformer) graph.PlanBuilder { - return b -} - func (b *PlanBuilder) Build() (graph.Plan, error) { vertices := buildOrderedVertices(b.transCtx.GetContext(), b.currentTree, b.desiredTree) plan := &Plan{ diff --git a/pkg/controller/model/parallel_transformer.go b/pkg/controller/model/parallel_transformer.go deleted file mode 100644 index 1735716adaa..00000000000 --- a/pkg/controller/model/parallel_transformer.go +++ /dev/null @@ -1,54 +0,0 @@ -/* -Copyright (C) 2022-2024 ApeCloud Co., Ltd - -This file is part of KubeBlocks project - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . -*/ - -package model - -import ( - "fmt" - "sync" - - "github.com/apecloud/kubeblocks/pkg/controller/graph" -) - -// ParallelTransformer executes a group of transformers in parallel. -// TODO: make DAG thread-safe if ParallelTransformer called. -type ParallelTransformer struct { - Transformers []graph.Transformer -} - -func (t *ParallelTransformer) Transform(ctx graph.TransformContext, dag *graph.DAG) error { - var group sync.WaitGroup - var errs error - for _, transformer := range t.Transformers { - transformer := transformer - group.Add(1) - go func() { - err := transformer.Transform(ctx, dag) - if err != nil { - // TODO: sync.Mutex errs - errs = fmt.Errorf("%v; %v", errs, err) - } - group.Done() - }() - } - group.Wait() - return errs -} - -var _ graph.Transformer = &ParallelTransformer{} diff --git a/pkg/controller/model/parallel_transformer_test.go b/pkg/controller/model/parallel_transformer_test.go deleted file mode 100644 index 5002e0cee6e..00000000000 --- a/pkg/controller/model/parallel_transformer_test.go +++ /dev/null @@ -1,58 +0,0 @@ -/* -Copyright (C) 2022-2024 ApeCloud Co., Ltd - -This file is part of KubeBlocks project - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . -*/ - -package model - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/apecloud/kubeblocks/pkg/controller/graph" -) - -var _ = Describe("parallel transformer test", func() { - Context("Transform function", func() { - It("should work well", func() { - id1 := 1 - transformer := &ParallelTransformer{ - Transformers: []graph.Transformer{ - &testTransformer{id: id1}, - }, - } - dag := graph.NewDAG() - // TODO(free6om): DAG is not thread-safe currently, so parallel transformer has concurrent map writes issue. - // parallel more transformers when DAG is ready. - Expect(transformer.Transform(nil, dag)).Should(Succeed()) - dagExpected := graph.NewDAG() - dagExpected.AddVertex(id1) - Expect(dag.Equals(dagExpected, DefaultLess)).Should(BeTrue()) - }) - }) -}) - -type testTransformer struct { - id int -} - -var _ graph.Transformer = &testTransformer{} - -func (t *testTransformer) Transform(ctx graph.TransformContext, dag *graph.DAG) error { - dag.AddVertex(t.id) - return nil -}