diff --git a/apis/apps/v1/componentdefinition_types.go b/apis/apps/v1/componentdefinition_types.go index d602b67f9c0..da182180b6f 100644 --- a/apis/apps/v1/componentdefinition_types.go +++ b/apis/apps/v1/componentdefinition_types.go @@ -877,6 +877,12 @@ type ClusterObjectReference struct { // MultipleClusterObjectOption defines the options for handling multiple cluster objects matched. type MultipleClusterObjectOption struct { + // RequireAllComponentObjects controls whether all component objects must exist before resolving. + // If set to true, resolving will only proceed if all component objects are present. + // + // +optional + RequireAllComponentObjects *bool `json:"requireAllComponentObjects,omitempty"` + // Define the strategy for handling multiple cluster objects. // // +kubebuilder:validation:Required diff --git a/apis/apps/v1/zz_generated.deepcopy.go b/apis/apps/v1/zz_generated.deepcopy.go index 300a52d5052..8c210044fc0 100644 --- a/apis/apps/v1/zz_generated.deepcopy.go +++ b/apis/apps/v1/zz_generated.deepcopy.go @@ -2246,6 +2246,11 @@ func (in *MultipleClusterObjectCombinedOption) DeepCopy() *MultipleClusterObject // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MultipleClusterObjectOption) DeepCopyInto(out *MultipleClusterObjectOption) { *out = *in + if in.RequireAllComponentObjects != nil { + in, out := &in.RequireAllComponentObjects, &out.RequireAllComponentObjects + *out = new(bool) + **out = **in + } if in.CombinedOption != nil { in, out := &in.CombinedOption, &out.CombinedOption *out = new(MultipleClusterObjectCombinedOption) diff --git a/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml b/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml index fe6bec4e932..ed1e024888c 100644 --- a/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml +++ b/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml @@ -16986,6 +16986,11 @@ spec: components. type: string type: object + requireAllComponentObjects: + description: |- + RequireAllComponentObjects controls whether all component objects must exist before resolving. + If set to true, resolving will only proceed if all component objects are present. + type: boolean strategy: description: Define the strategy for handling multiple cluster objects. @@ -17133,6 +17138,11 @@ spec: components. type: string type: object + requireAllComponentObjects: + description: |- + RequireAllComponentObjects controls whether all component objects must exist before resolving. + If set to true, resolving will only proceed if all component objects are present. + type: boolean strategy: description: Define the strategy for handling multiple cluster objects. @@ -17238,6 +17248,11 @@ spec: components. type: string type: object + requireAllComponentObjects: + description: |- + RequireAllComponentObjects controls whether all component objects must exist before resolving. + If set to true, resolving will only proceed if all component objects are present. + type: boolean strategy: description: Define the strategy for handling multiple cluster objects. @@ -17341,6 +17356,11 @@ spec: components. type: string type: object + requireAllComponentObjects: + description: |- + RequireAllComponentObjects controls whether all component objects must exist before resolving. + If set to true, resolving will only proceed if all component objects are present. + type: boolean strategy: description: Define the strategy for handling multiple cluster objects. @@ -17454,6 +17474,11 @@ spec: components. type: string type: object + requireAllComponentObjects: + description: |- + RequireAllComponentObjects controls whether all component objects must exist before resolving. + If set to true, resolving will only proceed if all component objects are present. + type: boolean strategy: description: Define the strategy for handling multiple cluster objects. @@ -17554,6 +17579,11 @@ spec: components. type: string type: object + requireAllComponentObjects: + description: |- + RequireAllComponentObjects controls whether all component objects must exist before resolving. + If set to true, resolving will only proceed if all component objects are present. + type: boolean strategy: description: Define the strategy for handling multiple cluster objects. diff --git a/config/crd/bases/apps.kubeblocks.io_sidecardefinitions.yaml b/config/crd/bases/apps.kubeblocks.io_sidecardefinitions.yaml index 6e7abd5e058..0fd9c030ca1 100644 --- a/config/crd/bases/apps.kubeblocks.io_sidecardefinitions.yaml +++ b/config/crd/bases/apps.kubeblocks.io_sidecardefinitions.yaml @@ -1683,6 +1683,11 @@ spec: components. type: string type: object + requireAllComponentObjects: + description: |- + RequireAllComponentObjects controls whether all component objects must exist before resolving. + If set to true, resolving will only proceed if all component objects are present. + type: boolean strategy: description: Define the strategy for handling multiple cluster objects. @@ -1830,6 +1835,11 @@ spec: components. type: string type: object + requireAllComponentObjects: + description: |- + RequireAllComponentObjects controls whether all component objects must exist before resolving. + If set to true, resolving will only proceed if all component objects are present. + type: boolean strategy: description: Define the strategy for handling multiple cluster objects. @@ -1935,6 +1945,11 @@ spec: components. type: string type: object + requireAllComponentObjects: + description: |- + RequireAllComponentObjects controls whether all component objects must exist before resolving. + If set to true, resolving will only proceed if all component objects are present. + type: boolean strategy: description: Define the strategy for handling multiple cluster objects. @@ -2038,6 +2053,11 @@ spec: components. type: string type: object + requireAllComponentObjects: + description: |- + RequireAllComponentObjects controls whether all component objects must exist before resolving. + If set to true, resolving will only proceed if all component objects are present. + type: boolean strategy: description: Define the strategy for handling multiple cluster objects. @@ -2151,6 +2171,11 @@ spec: components. type: string type: object + requireAllComponentObjects: + description: |- + RequireAllComponentObjects controls whether all component objects must exist before resolving. + If set to true, resolving will only proceed if all component objects are present. + type: boolean strategy: description: Define the strategy for handling multiple cluster objects. @@ -2251,6 +2276,11 @@ spec: components. type: string type: object + requireAllComponentObjects: + description: |- + RequireAllComponentObjects controls whether all component objects must exist before resolving. + If set to true, resolving will only proceed if all component objects are present. + type: boolean strategy: description: Define the strategy for handling multiple cluster objects. diff --git a/controllers/apps/shardingdefinition_controller.go b/controllers/apps/shardingdefinition_controller.go index f504d04c5fc..8d757bd29c1 100644 --- a/controllers/apps/shardingdefinition_controller.go +++ b/controllers/apps/shardingdefinition_controller.go @@ -190,21 +190,67 @@ func (r *ShardingDefinitionReconciler) validateShardsLimit(ctx context.Context, func (r *ShardingDefinitionReconciler) validateProvisionNUpdateStrategy(ctx context.Context, cli client.Client, shardingDef *appsv1.ShardingDefinition) error { + var ( + provision = shardingDef.Spec.ProvisionStrategy + update = shardingDef.Spec.UpdateStrategy + ) + supported := func(strategy *appsv1.UpdateStrategy) bool { if strategy == nil { return true } return *strategy == appsv1.SerialStrategy || *strategy == appsv1.ParallelStrategy } - if !supported(shardingDef.Spec.ProvisionStrategy) { - return fmt.Errorf("unsupported provision strategy: %s", *shardingDef.Spec.ProvisionStrategy) + if !supported(provision) { + return fmt.Errorf("unsupported provision strategy: %s", *provision) + } + if !supported(update) { + return fmt.Errorf("unsupported update strategy: %s", *update) } - if !supported(shardingDef.Spec.UpdateStrategy) { - return fmt.Errorf("unsupported update strategy: %s", *shardingDef.Spec.UpdateStrategy) + + if provision != nil && *provision == appsv1.SerialStrategy && r.requireParallelProvision() { + return fmt.Errorf("serial provision strategy is conflicted with vars that requires parallel provision when mutiple objects matched") } return nil } +func (r *ShardingDefinitionReconciler) requireParallelProvision() bool { + requireAll := func(opt *appsv1.MultipleClusterObjectOption) bool { + return opt != nil && opt.RequireAllComponentObjects != nil && *opt.RequireAllComponentObjects + } + require := func(v appsv1.EnvVar) bool { + if v.ValueFrom != nil { + if v.ValueFrom.HostNetworkVarRef != nil { + return requireAll(v.ValueFrom.HostNetworkVarRef.MultipleClusterObjectOption) + } + if v.ValueFrom.ServiceVarRef != nil { + return requireAll(v.ValueFrom.ServiceVarRef.MultipleClusterObjectOption) + } + if v.ValueFrom.CredentialVarRef != nil { + return requireAll(v.ValueFrom.CredentialVarRef.MultipleClusterObjectOption) + } + if v.ValueFrom.TLSVarRef != nil { + return requireAll(v.ValueFrom.TLSVarRef.MultipleClusterObjectOption) + } + if v.ValueFrom.ServiceRefVarRef != nil { + return requireAll(v.ValueFrom.ServiceRefVarRef.MultipleClusterObjectOption) + } + if v.ValueFrom.ComponentVarRef != nil { + return requireAll(v.ValueFrom.ComponentVarRef.MultipleClusterObjectOption) + } + } + return false + } + for _, compDef := range r.compDefs { + for _, v := range compDef.Spec.Vars { + if require(v) { + return true + } + } + } + return false +} + func (r *ShardingDefinitionReconciler) validateLifecycleActions(ctx context.Context, cli client.Client, shardingDef *appsv1.ShardingDefinition) error { return nil diff --git a/controllers/apps/shardingdefinition_controller_test.go b/controllers/apps/shardingdefinition_controller_test.go index ee8aff37eba..fb454d8ef69 100644 --- a/controllers/apps/shardingdefinition_controller_test.go +++ b/controllers/apps/shardingdefinition_controller_test.go @@ -26,7 +26,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "k8s.io/utils/pointer" + "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" @@ -131,13 +131,87 @@ var _ = Describe("ShardingDefinition Controller", func() { checkObjectStatus(shardingDefObj, appsv1.UnavailablePhase) }) + + It("provision strategy & vars - serial", func() { + By("create a ShardingDefinition obj") + shardingDefObj := testapps.NewShardingDefinitionFactory(shardingDefName, compDefObj.GetName()). + SetProvisionStrategy(appsv1.SerialStrategy). + SetUpdateStrategy(appsv1.SerialStrategy). + Create(&testCtx).GetObject() + + checkObjectStatus(shardingDefObj, appsv1.AvailablePhase) + }) + + It("provision strategy & vars - parallel & all", func() { + By("add a var in cmpd") + Expect(testapps.GetAndChangeObj(&testCtx, client.ObjectKeyFromObject(compDefObj), func(compDef *appsv1.ComponentDefinition) { + compDef.Spec.Vars = []appsv1.EnvVar{ + { + Name: "test", + ValueFrom: &appsv1.VarSource{ + ComponentVarRef: &appsv1.ComponentVarSelector{ + ClusterObjectReference: appsv1.ClusterObjectReference{ + MultipleClusterObjectOption: &appsv1.MultipleClusterObjectOption{ + RequireAllComponentObjects: ptr.To(true), + Strategy: appsv1.MultipleClusterObjectStrategyCombined, + }, + }, + ComponentVars: appsv1.ComponentVars{ + PodFQDNs: &appsv1.VarRequired, + }, + }, + }, + }, + } + })()).Should(Succeed()) + + By("create a ShardingDefinition obj") + shardingDefObj := testapps.NewShardingDefinitionFactory(shardingDefName, compDefObj.GetName()). + SetProvisionStrategy(appsv1.ParallelStrategy). + SetUpdateStrategy(appsv1.ParallelStrategy). + Create(&testCtx).GetObject() + + checkObjectStatus(shardingDefObj, appsv1.AvailablePhase) + }) + + It("provision strategy & vars - serial & all", func() { + By("add a var in cmpd") + Expect(testapps.GetAndChangeObj(&testCtx, client.ObjectKeyFromObject(compDefObj), func(compDef *appsv1.ComponentDefinition) { + compDef.Spec.Vars = []appsv1.EnvVar{ + { + Name: "test", + ValueFrom: &appsv1.VarSource{ + ComponentVarRef: &appsv1.ComponentVarSelector{ + ClusterObjectReference: appsv1.ClusterObjectReference{ + MultipleClusterObjectOption: &appsv1.MultipleClusterObjectOption{ + RequireAllComponentObjects: ptr.To(true), + Strategy: appsv1.MultipleClusterObjectStrategyCombined, + }, + }, + ComponentVars: appsv1.ComponentVars{ + PodFQDNs: &appsv1.VarRequired, + }, + }, + }, + }, + } + })()).Should(Succeed()) + + By("create a ShardingDefinition obj") + shardingDefObj := testapps.NewShardingDefinitionFactory(shardingDefName, compDefObj.GetName()). + SetProvisionStrategy(appsv1.SerialStrategy). + SetUpdateStrategy(appsv1.SerialStrategy). + Create(&testCtx).GetObject() + + checkObjectStatus(shardingDefObj, appsv1.UnavailablePhase) + }) }) Context("system accounts", func() { It("ok", func() { By("create a ShardingDefinition obj") shardingDefObj := testapps.NewShardingDefinitionFactory(shardingDefName, compDefObj.GetName()). - AddSystemAccount(appsv1.ShardingSystemAccount{Name: adminAccount, Shared: pointer.Bool(true)}). + AddSystemAccount(appsv1.ShardingSystemAccount{Name: adminAccount, Shared: ptr.To(true)}). Create(&testCtx).GetObject() checkObjectStatus(shardingDefObj, appsv1.AvailablePhase) @@ -147,7 +221,7 @@ var _ = Describe("ShardingDefinition Controller", func() { By("create a ShardingDefinition obj") shardingDefObj := testapps.NewShardingDefinitionFactory(shardingDefName, compDefObj.GetName()). AddSystemAccount(appsv1.ShardingSystemAccount{Name: adminAccount}). - AddSystemAccount(appsv1.ShardingSystemAccount{Name: adminAccount, Shared: pointer.Bool(true)}). + AddSystemAccount(appsv1.ShardingSystemAccount{Name: adminAccount, Shared: ptr.To(true)}). Create(&testCtx).GetObject() checkObjectStatus(shardingDefObj, appsv1.UnavailablePhase) diff --git a/deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml b/deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml index fe6bec4e932..ed1e024888c 100644 --- a/deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml +++ b/deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml @@ -16986,6 +16986,11 @@ spec: components. type: string type: object + requireAllComponentObjects: + description: |- + RequireAllComponentObjects controls whether all component objects must exist before resolving. + If set to true, resolving will only proceed if all component objects are present. + type: boolean strategy: description: Define the strategy for handling multiple cluster objects. @@ -17133,6 +17138,11 @@ spec: components. type: string type: object + requireAllComponentObjects: + description: |- + RequireAllComponentObjects controls whether all component objects must exist before resolving. + If set to true, resolving will only proceed if all component objects are present. + type: boolean strategy: description: Define the strategy for handling multiple cluster objects. @@ -17238,6 +17248,11 @@ spec: components. type: string type: object + requireAllComponentObjects: + description: |- + RequireAllComponentObjects controls whether all component objects must exist before resolving. + If set to true, resolving will only proceed if all component objects are present. + type: boolean strategy: description: Define the strategy for handling multiple cluster objects. @@ -17341,6 +17356,11 @@ spec: components. type: string type: object + requireAllComponentObjects: + description: |- + RequireAllComponentObjects controls whether all component objects must exist before resolving. + If set to true, resolving will only proceed if all component objects are present. + type: boolean strategy: description: Define the strategy for handling multiple cluster objects. @@ -17454,6 +17474,11 @@ spec: components. type: string type: object + requireAllComponentObjects: + description: |- + RequireAllComponentObjects controls whether all component objects must exist before resolving. + If set to true, resolving will only proceed if all component objects are present. + type: boolean strategy: description: Define the strategy for handling multiple cluster objects. @@ -17554,6 +17579,11 @@ spec: components. type: string type: object + requireAllComponentObjects: + description: |- + RequireAllComponentObjects controls whether all component objects must exist before resolving. + If set to true, resolving will only proceed if all component objects are present. + type: boolean strategy: description: Define the strategy for handling multiple cluster objects. diff --git a/deploy/helm/crds/apps.kubeblocks.io_sidecardefinitions.yaml b/deploy/helm/crds/apps.kubeblocks.io_sidecardefinitions.yaml index 6e7abd5e058..0fd9c030ca1 100644 --- a/deploy/helm/crds/apps.kubeblocks.io_sidecardefinitions.yaml +++ b/deploy/helm/crds/apps.kubeblocks.io_sidecardefinitions.yaml @@ -1683,6 +1683,11 @@ spec: components. type: string type: object + requireAllComponentObjects: + description: |- + RequireAllComponentObjects controls whether all component objects must exist before resolving. + If set to true, resolving will only proceed if all component objects are present. + type: boolean strategy: description: Define the strategy for handling multiple cluster objects. @@ -1830,6 +1835,11 @@ spec: components. type: string type: object + requireAllComponentObjects: + description: |- + RequireAllComponentObjects controls whether all component objects must exist before resolving. + If set to true, resolving will only proceed if all component objects are present. + type: boolean strategy: description: Define the strategy for handling multiple cluster objects. @@ -1935,6 +1945,11 @@ spec: components. type: string type: object + requireAllComponentObjects: + description: |- + RequireAllComponentObjects controls whether all component objects must exist before resolving. + If set to true, resolving will only proceed if all component objects are present. + type: boolean strategy: description: Define the strategy for handling multiple cluster objects. @@ -2038,6 +2053,11 @@ spec: components. type: string type: object + requireAllComponentObjects: + description: |- + RequireAllComponentObjects controls whether all component objects must exist before resolving. + If set to true, resolving will only proceed if all component objects are present. + type: boolean strategy: description: Define the strategy for handling multiple cluster objects. @@ -2151,6 +2171,11 @@ spec: components. type: string type: object + requireAllComponentObjects: + description: |- + RequireAllComponentObjects controls whether all component objects must exist before resolving. + If set to true, resolving will only proceed if all component objects are present. + type: boolean strategy: description: Define the strategy for handling multiple cluster objects. @@ -2251,6 +2276,11 @@ spec: components. type: string type: object + requireAllComponentObjects: + description: |- + RequireAllComponentObjects controls whether all component objects must exist before resolving. + If set to true, resolving will only proceed if all component objects are present. + type: boolean strategy: description: Define the strategy for handling multiple cluster objects. diff --git a/docs/developer_docs/api-reference/cluster.md b/docs/developer_docs/api-reference/cluster.md index 1fb5b6f1892..2edf4fa0db1 100644 --- a/docs/developer_docs/api-reference/cluster.md +++ b/docs/developer_docs/api-reference/cluster.md @@ -8225,6 +8225,19 @@ MultipleClusterObjectValueFormatFlatten +requireAllComponentObjects
+ +bool + + + +(Optional) +

RequireAllComponentObjects controls whether all component objects must exist before resolving. +If set to true, resolving will only proceed if all component objects are present.

+ + + + strategy
diff --git a/pkg/controller/component/synthesize_component.go b/pkg/controller/component/synthesize_component.go index 9e81dc006a5..743d8c56ede 100644 --- a/pkg/controller/component/synthesize_component.go +++ b/pkg/controller/component/synthesize_component.go @@ -70,6 +70,7 @@ func BuildSynthesizedComponent(ctx context.Context, cli client.Reader, ClusterName: clusterName, ClusterUID: clusterUID, Comp2CompDefs: comp2CompDef, + CompDef2CompCnt: buildCompDef2CompCount(cluster), Name: compName, FullCompName: comp.Name, Generation: strconv.FormatInt(comp.Generation, 10), @@ -157,6 +158,7 @@ func buildComp2CompDefs(ctx context.Context, cli client.Reader, cluster *appsv1. if cluster == nil { return nil, nil } + mapping := make(map[string]string) // build from componentSpecs @@ -182,10 +184,35 @@ func buildComp2CompDefs(ctx context.Context, cli client.Reader, cluster *appsv1. } } } - return mapping, nil } +func buildCompDef2CompCount(cluster *appsv1.Cluster) map[string]int32 { + if cluster == nil { + return nil + } + + result := make(map[string]int32) + + add := func(name string, cnt int32) { + if len(name) > 0 { + if val, ok := result[name]; !ok { + result[name] = cnt + } else { + result[name] = val + cnt + } + } + } + + for _, comp := range cluster.Spec.ComponentSpecs { + add(comp.ComponentDef, 1) + } + for _, spec := range cluster.Spec.Shardings { + add(spec.Template.ComponentDef, spec.Shards) + } + return result +} + func mergeUserDefinedEnv(synthesizedComp *SynthesizedComponent, comp *appsv1.Component) error { if comp == nil || len(comp.Spec.Env) == 0 { return nil diff --git a/pkg/controller/component/synthesize_component_test.go b/pkg/controller/component/synthesize_component_test.go index edde838f7f5..5cd56686253 100644 --- a/pkg/controller/component/synthesize_component_test.go +++ b/pkg/controller/component/synthesize_component_test.go @@ -328,4 +328,130 @@ var _ = Describe("synthesized component", func() { Expect(synthesizedComp.PodSpec.Volumes[3].Name).Should(Equal("not-defined")) }) }) + + Context("components and definitions", func() { + var ( + reader *mockReader + cluster *appsv1.Cluster + shardComp *appsv1.Component + ) + + BeforeEach(func() { + compDef = &appsv1.ComponentDefinition{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-compdef-a", + }, + Spec: appsv1.ComponentDefinitionSpec{ + ServiceVersion: "8.0.30", + Runtime: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "app", + }, + }, + }, + }, + } + cluster = &appsv1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster", + }, + Spec: appsv1.ClusterSpec{ + ComponentSpecs: []appsv1.ClusterComponentSpec{ + { + Name: "comp1", + ComponentDef: "test-compdef-a", + }, + { + Name: "comp2", + ComponentDef: "test-compdef-b", + }, + }, + Shardings: []appsv1.ClusterSharding{ + { + Name: "sharding1", + Shards: 3, + Template: appsv1.ClusterComponentSpec{ + ComponentDef: "test-compdef-a", + }, + }, + { + Name: "sharding2", + Shards: 5, + Template: appsv1.ClusterComponentSpec{ + ComponentDef: "test-compdef-c", + }, + }, + }, + }, + } + comp = &appsv1.Component{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster-comp1", + Labels: map[string]string{ + constant.AppInstanceLabelKey: "test-cluster", + }, + Annotations: map[string]string{ + constant.KBAppClusterUIDKey: "uuid", + constant.KubeBlocksGenerationKey: "1", + }, + }, + Spec: appsv1.ComponentSpec{ + CompDef: "test-compdef-a", + ServiceVersion: "8.0.30", + }, + } + shardComp = &appsv1.Component{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster-sharding1-a", + Labels: map[string]string{ + constant.AppInstanceLabelKey: "test-cluster", + constant.KBAppShardingNameLabelKey: "sharding1", + }, + Annotations: map[string]string{ + constant.KBAppClusterUIDKey: "uuid", + constant.KubeBlocksGenerationKey: "1", + }, + }, + Spec: appsv1.ComponentSpec{ + CompDef: "test-compdef-a", + ServiceVersion: "8.0.30", + }, + } + reader = &mockReader{ + cli: k8sClient, + } + }) + + It("buildComp2CompDefs", func() { + synthesizedComp, err := BuildSynthesizedComponent(ctx, reader, compDef, comp, cluster) + Expect(err).Should(BeNil()) + Expect(synthesizedComp).ShouldNot(BeNil()) + Expect(synthesizedComp.Comp2CompDefs).Should(HaveLen(2)) + Expect(synthesizedComp.Comp2CompDefs).Should(HaveKeyWithValue("comp1", "test-compdef-a")) + Expect(synthesizedComp.Comp2CompDefs).Should(HaveKeyWithValue("comp2", "test-compdef-b")) + }) + + It("buildComp2CompDefs - with sharding comp", func() { + reader.objs = []client.Object{shardComp} + + synthesizedComp, err := BuildSynthesizedComponent(ctx, reader, compDef, comp, cluster) + Expect(err).Should(BeNil()) + Expect(synthesizedComp).ShouldNot(BeNil()) + Expect(synthesizedComp.Comp2CompDefs).Should(HaveLen(3)) + Expect(synthesizedComp.Comp2CompDefs).Should(HaveKeyWithValue("comp1", "test-compdef-a")) + Expect(synthesizedComp.Comp2CompDefs).Should(HaveKeyWithValue("comp2", "test-compdef-b")) + Expect(synthesizedComp.Comp2CompDefs).Should(HaveKeyWithValue("sharding1-a", "test-compdef-a")) + }) + + It("buildCompDef2CompCount", func() { + synthesizedComp, err := BuildSynthesizedComponent(ctx, reader, compDef, comp, cluster) + Expect(err).Should(BeNil()) + Expect(synthesizedComp).ShouldNot(BeNil()) + Expect(synthesizedComp.CompDef2CompCnt).Should(HaveLen(3)) + Expect(synthesizedComp.CompDef2CompCnt).Should(HaveKeyWithValue("test-compdef-a", int32(4))) + Expect(synthesizedComp.CompDef2CompCnt).Should(HaveKeyWithValue("test-compdef-b", int32(1))) + Expect(synthesizedComp.CompDef2CompCnt).Should(HaveKeyWithValue("test-compdef-c", int32(5))) + }) + }) }) diff --git a/pkg/controller/component/type.go b/pkg/controller/component/type.go index 7b6d06d9f20..b0225b599e5 100644 --- a/pkg/controller/component/type.go +++ b/pkg/controller/component/type.go @@ -32,9 +32,10 @@ type SynthesizedComponent struct { Namespace string `json:"namespace,omitempty"` ClusterName string `json:"clusterName,omitempty"` ClusterUID string `json:"clusterUID,omitempty"` - Comp2CompDefs map[string]string `json:"comp2CompDefs,omitempty"` // {compName: compDefName} - Name string `json:"name,omitempty"` // the name of the component w/o clusterName prefix - FullCompName string `json:"fullCompName,omitempty"` // the full name of the component w/ clusterName prefix + Comp2CompDefs map[string]string `json:"comp2CompDefs,omitempty"` // {compName: compDefName} + CompDef2CompCnt map[string]int32 `json:"compDef2CompCnt,omitempty"` // {compDefName: expected comp cnt} + Name string `json:"name,omitempty"` // the name of the component w/o clusterName prefix + FullCompName string `json:"fullCompName,omitempty"` // the full name of the component w/ clusterName prefix Generation string CompDefName string `json:"compDefName,omitempty"` // the name of the componentDefinition ServiceKind string diff --git a/pkg/controller/component/vars.go b/pkg/controller/component/vars.go index 5d8504b4b3a..2b7f42d6efb 100644 --- a/pkg/controller/component/vars.go +++ b/pkg/controller/component/vars.go @@ -1252,8 +1252,12 @@ func resolveReferentObjects(synthesizedComp *SynthesizedComponent, } func resolveReferentComponents(synthesizedComp *SynthesizedComponent, objRef appsv1.ClusterObjectReference) ([]string, error) { + var ( + mopt = objRef.MultipleClusterObjectOption + ) + // match the current component when the multiple cluster object option not set - if len(objRef.CompDef) == 0 || (PrefixOrRegexMatched(synthesizedComp.CompDefName, objRef.CompDef) && objRef.MultipleClusterObjectOption == nil) { + if len(objRef.CompDef) == 0 || (PrefixOrRegexMatched(synthesizedComp.CompDefName, objRef.CompDef) && mopt == nil) { return []string{synthesizedComp.Name}, nil } @@ -1263,18 +1267,36 @@ func resolveReferentComponents(synthesizedComp *SynthesizedComponent, objRef app compNames = append(compNames, k) } } - switch len(compNames) { - case 1: - return compNames, nil - case 0: - return nil, apierrors.NewNotFound(schema.GroupResource{}, "") // the error msg is trivial - default: - if objRef.MultipleClusterObjectOption == nil { - return nil, fmt.Errorf("more than one referent component found: %s", strings.Join(compNames, ",")) - } else { + + if mopt == nil || mopt.RequireAllComponentObjects == nil || !*mopt.RequireAllComponentObjects { + switch len(compNames) { + case 1: return compNames, nil + case 0: + return nil, apierrors.NewNotFound(schema.GroupResource{}, "") // the error msg is trivial + default: + if mopt == nil { + return nil, fmt.Errorf("more than one referent component found: %s", strings.Join(compNames, ",")) + } else { + return compNames, nil + } + } + } + + // objRef.MultipleClusterObjectOption.RequireAllComponentObjects == true + total := int32(0) + for compDef, cnt := range synthesizedComp.CompDef2CompCnt { + if PrefixOrRegexMatched(compDef, objRef.CompDef) { + total += cnt } } + if len(compNames) != int(total) { + return nil, fmt.Errorf("insufficient component objects to resolve vars, expected: %d, actual: %d", total, len(compNames)) + } + if len(compNames) == 0 { + return nil, apierrors.NewNotFound(schema.GroupResource{}, "") // the error msg is trivial + } + return compNames, nil } func resolveClusterObjectVars(kind string, objRef appsv1.ClusterObjectReference, option *appsv1.VarOption, diff --git a/pkg/controller/component/vars_test.go b/pkg/controller/component/vars_test.go index fe7cd91d495..e13f021aa89 100644 --- a/pkg/controller/component/vars_test.go +++ b/pkg/controller/component/vars_test.go @@ -30,6 +30,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/uuid" + "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1" @@ -2517,6 +2518,117 @@ var _ = Describe("vars", func() { checkEnvVarNotExist(envVars, svcVarName2) checkEnvVarNotExist(envVars, svcVarName3) }) + + It("require all component objects - false", func() { + synthesizedComp.Comp2CompDefs = map[string]string{ + compName1: synthesizedComp.CompDefName, + } + synthesizedComp.CompDef2CompCnt = map[string]int32{ + synthesizedComp.CompDefName: int32(2), + } + vars := []appsv1.EnvVar{ + { + Name: "service-host", + ValueFrom: &appsv1.VarSource{ + ServiceVarRef: &appsv1.ServiceVarSelector{ + ClusterObjectReference: appsv1.ClusterObjectReference{ + CompDef: synthesizedComp.CompDefName, + Name: "service", + Optional: required(), + MultipleClusterObjectOption: &appsv1.MultipleClusterObjectOption{ + RequireAllComponentObjects: ptr.To(false), // false + Strategy: appsv1.MultipleClusterObjectStrategyIndividual, + }, + }, + ServiceVars: appsv1.ServiceVars{ + Host: &appsv1.VarRequired, + }, + }, + }, + }, + } + templateVars, envVars, err := ResolveTemplateNEnvVars(testCtx.Ctx, reader, synthesizedComp, vars) + Expect(err).Should(BeNil()) + // Expect(templateVars).Should(HaveKeyWithValue("service-host", "")) + // Expect(templateVars).Should(HaveKeyWithValue(svcVarName1, svcName1)) + // checkEnvVarWithValue(envVars, "service-host", "") + // checkEnvVarWithValue(envVars, svcVarName1, svcName1) + Expect(templateVars).Should(HaveKeyWithValue("service-host", svcName1)) + checkEnvVarWithValue(envVars, "service-host", svcName1) + }) + + It("require all component objects - true", func() { + synthesizedComp.Comp2CompDefs = map[string]string{ + compName1: synthesizedComp.CompDefName, + } + synthesizedComp.CompDef2CompCnt = map[string]int32{ + synthesizedComp.CompDefName: int32(2), + } + vars := []appsv1.EnvVar{ + { + Name: "service-host", + ValueFrom: &appsv1.VarSource{ + ServiceVarRef: &appsv1.ServiceVarSelector{ + ClusterObjectReference: appsv1.ClusterObjectReference{ + CompDef: synthesizedComp.CompDefName, + Name: "service", + Optional: required(), + MultipleClusterObjectOption: &appsv1.MultipleClusterObjectOption{ + RequireAllComponentObjects: ptr.To(true), // true + Strategy: appsv1.MultipleClusterObjectStrategyIndividual, + }, + }, + ServiceVars: appsv1.ServiceVars{ + Host: &appsv1.VarRequired, + }, + }, + }, + }, + } + _, _, err := ResolveTemplateNEnvVars(testCtx.Ctx, reader, synthesizedComp, vars) + Expect(err).ShouldNot(BeNil()) + Expect(err.Error()).Should(And(ContainSubstring("insufficient component objects to resolve vars"), + ContainSubstring(fmt.Sprintf("expected: %d, actual: %d", 2, 1)))) + }) + + It("require all component objects - true and ok", func() { + synthesizedComp.Comp2CompDefs = map[string]string{ + compName1: synthesizedComp.CompDefName, + compName2: synthesizedComp.CompDefName, + } + synthesizedComp.CompDef2CompCnt = map[string]int32{ + synthesizedComp.CompDefName: int32(2), + } + vars := []appsv1.EnvVar{ + { + Name: "service-host", + ValueFrom: &appsv1.VarSource{ + ServiceVarRef: &appsv1.ServiceVarSelector{ + ClusterObjectReference: appsv1.ClusterObjectReference{ + CompDef: synthesizedComp.CompDefName, + Name: "service", + Optional: required(), + MultipleClusterObjectOption: &appsv1.MultipleClusterObjectOption{ + RequireAllComponentObjects: ptr.To(true), // true + Strategy: appsv1.MultipleClusterObjectStrategyIndividual, + }, + }, + ServiceVars: appsv1.ServiceVars{ + Host: &appsv1.VarRequired, + }, + }, + }, + }, + } + templateVars, envVars, err := ResolveTemplateNEnvVars(testCtx.Ctx, reader, synthesizedComp, vars) + Expect(err).Should(BeNil()) + Expect(templateVars).Should(HaveKeyWithValue("service-host", "")) + Expect(templateVars).Should(HaveKeyWithValue(svcVarName1, svcName1)) + Expect(templateVars).Should(HaveKeyWithValue(svcVarName2, svcName2)) + checkEnvVarWithValue(envVars, "service-host", "") + checkEnvVarWithValue(envVars, svcVarName1, svcName1) + checkEnvVarWithValue(envVars, svcVarName2, svcName2) + }) }) Context("vars reference and escaping", func() {