Skip to content

Commit

Permalink
feat: support ComponentDefinition custom annotations and Component in…
Browse files Browse the repository at this point in the history
…herit Cluster annotations (apecloud#6724)
  • Loading branch information
Y-Rookie authored Mar 5, 2024
1 parent 2e52a56 commit 6d44f2d
Show file tree
Hide file tree
Showing 23 changed files with 327 additions and 140 deletions.
9 changes: 8 additions & 1 deletion apis/apps/v1alpha1/componentdefinition_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,13 @@ type ComponentDefinitionSpec struct {
// +optional
Labels map[string]string `json:"labels,omitempty"`

// Defines static annotations that will be patched to all k8s resources created for the component.
// If an annotation key conflicts with any other system annotations or user-specified annotations, it will be silently ignored.
// This field is immutable.
//
// +optional
Annotations map[string]string `json:"annotations,omitempty"`

// Defines the limit of valid replicas supported.
// This field is immutable.
//
Expand Down Expand Up @@ -629,7 +636,7 @@ type ComponentLifecycleActions struct {

// RoleProbe defines the mechanism to probe the role of replicas periodically. The specified action will be
// executed by Lorry at the configured interval. If the execution is successful, the output will be used as
// the replica's assigned role, and the role must be one of the names defined in the componentdefinition roles.
// the replica's assigned role, and the role must be one of the names defined in the ComponentDefinition roles.
// The output will be compared with the last successful result. If there is a change, a role change event will
// be created to notify the controller and trigger updating the replica's role.
// Defining a RoleProbe is required if roles are configured for the component. Otherwise, the replicas' pods will
Expand Down
7 changes: 7 additions & 0 deletions apis/apps/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 9 additions & 1 deletion config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ spec:
with attributes that strongly work with stateful workloads and day-2
operation behaviors.
properties:
annotations:
additionalProperties:
type: string
description: Defines static annotations that will be patched to all
k8s resources created for the component. If an annotation key conflicts
with any other system annotations or user-specified annotations,
it will be silently ignored. This field is immutable.
type: object
configs:
description: "The configs field is provided by the provider, and finally,
these configTemplateRefs will be rendered into the user's own configuration
Expand Down Expand Up @@ -3188,7 +3196,7 @@ spec:
of replicas periodically. The specified action will be executed
by Lorry at the configured interval. If the execution is successful,
the output will be used as the replica's assigned role, and
the role must be one of the names defined in the componentdefinition
the role must be one of the names defined in the ComponentDefinition
roles. The output will be compared with the last successful
result. If there is a change, a role change event will be created
to notify the controller and trigger updating the replica's
Expand Down
2 changes: 2 additions & 0 deletions controllers/apps/cluster_plan_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ type clusterTransformContext struct {
ShardingComponentSpecs map[string][]*appsv1alpha1.ClusterComponentSpec
// Labels to be added to components, mapping with ComponentSpecs.
Labels map[string]map[string]string
// Annotations to be added to components, mapping with ComponentSpecs.
Annotations map[string]map[string]string
}

// clusterPlanBuilder a graph.PlanBuilder implementation for Cluster reconciliation
Expand Down
41 changes: 41 additions & 0 deletions controllers/apps/component_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2012,6 +2012,43 @@ var _ = Describe("Component Controller", func() {
checkWorkloadGenerationAndToolsImage(Eventually, initWorkloadGeneration+1, 0, 1)
}

testCompInheritLabelsAndAnnotations := func(compName, compDefName string) {
By("Mock a cluster obj with custom labels and annotations.")
customLabelKey := "custom-inherit-label-key"
customLabelValue := "custom-inherit-label-value"
customLabelKeyBeFiltered := constant.RoleLabelKey
customLabelValueBeFiltered := "cluster-role-should-be-filtered"
customLabels := map[string]string{
customLabelKey: customLabelValue,
customLabelKeyBeFiltered: customLabelValueBeFiltered,
}

customAnnotationKey := "custom-inherit-annotation-key"
customAnnotationValue := "custom-inherit-annotation-value"
customAnnotationKeyBeFiltered := constant.KubeBlocksGenerationKey
customAnnotationValueBeFiltered := "cluster-annotation-should-be-filtered"
customAnnotations := map[string]string{
customAnnotationKey: customAnnotationValue,
customAnnotationKeyBeFiltered: customAnnotationValueBeFiltered,
constant.IgnoreResourceConstraint: "true",
constant.FeatureReconciliationInCompactModeAnnotationKey: "true",
}
createClusterObjV2(compName, compDefObj.Name, func(f *testapps.MockClusterFactory) {
f.AddLabelsInMap(customLabels)
f.AddAnnotationsInMap(customAnnotations)
})

By("check component inherit clusters labels and annotations")
Eventually(testapps.CheckObj(&testCtx, compKey, func(g Gomega, comp *appsv1alpha1.Component) {
g.Expect(comp.Labels).Should(HaveKeyWithValue(customLabelKey, customLabelValue))
g.Expect(comp.Labels).ShouldNot(HaveKeyWithValue(customLabelKeyBeFiltered, customLabelValueBeFiltered))
g.Expect(comp.Annotations).Should(HaveKeyWithValue(customAnnotationKey, customAnnotationValue))
g.Expect(comp.Annotations).ShouldNot(HaveKeyWithValue(customAnnotationKeyBeFiltered, customAnnotationValueBeFiltered))
g.Expect(comp.Annotations).Should(HaveKeyWithValue(constant.IgnoreResourceConstraint, "true"))
g.Expect(comp.Annotations).Should(HaveKeyWithValue(constant.FeatureReconciliationInCompactModeAnnotationKey, "true"))
})).Should(Succeed())
}

Context("component resources provisioning", func() {
BeforeEach(func() {
createAllWorkloadTypesClusterDef()
Expand All @@ -2026,6 +2063,10 @@ var _ = Describe("Component Controller", func() {
testCompFinalizerNLabel(defaultCompName, compDefName)
})

It("with inherit cluster labels and annotations", func() {
testCompInheritLabelsAndAnnotations(defaultCompName, compDefName)
})

It("with component services", func() {
testCompService(defaultCompName, compDefName)
})
Expand Down
70 changes: 42 additions & 28 deletions controllers/apps/component_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,48 +220,62 @@ func UpdateComponentInfoToPods(
return nil
}

// UpdateCustomLabelToPods updates custom label to pods
func UpdateCustomLabelToPods(ctx context.Context,
// UpdateCustomLabelsAndAnnotationsToPods updates custom labels and annotations to pods.
func UpdateCustomLabelsAndAnnotationsToPods(ctx context.Context,
cli client.Client,
cluster *appsv1alpha1.Cluster,
component *intctrlcomp.SynthesizedComponent,
synthesizedComp *intctrlcomp.SynthesizedComponent,
dag *graph.DAG) error {
if cluster == nil || component == nil {
if cluster == nil || synthesizedComp == nil {
return nil
}
// list all pods in dag
graphCli := model.NewGraphClient(cli)
pods := graphCli.FindAll(dag, &corev1.Pod{})

for labelKey, labelValue := range component.Labels {
podList := &corev1.PodList{}
matchLabels := constant.GetComponentWellKnownLabels(cluster.Name, component.Name)
if err := getObjectListByCustomLabels(ctx, cli, *cluster, podList, client.MatchingLabels(matchLabels)); err != nil {
return err
}
// list all pods in cache
podList := &corev1.PodList{}
matchLabels := constant.GetComponentWellKnownLabels(cluster.Name, synthesizedComp.Name)
if err := getObjectListByCustomLabels(ctx, cli, *cluster, podList, client.MatchingLabels(matchLabels)); err != nil {
return err
}
for i := range podList.Items {
idx := slices.IndexFunc(pods, func(obj client.Object) bool {
return obj.GetName() == podList.Items[i].Name
})

for i := range podList.Items {
idx := slices.IndexFunc(pods, func(obj client.Object) bool {
return obj.GetName() == podList.Items[i].Name
})
// pod already in dag, merge labels
if idx >= 0 {
updateObjLabel(labelKey, labelValue, pods[idx])
continue
}
pod := &podList.Items[i]
updateObjLabel(labelKey, labelValue, pod)
graphCli.Do(dag, nil, pod, model.ActionUpdatePtr(), nil)
// pod already in dag, update labels and annotations
if idx >= 0 {
updateObjLabelsAndAnnotations(pods[idx], synthesizedComp.Labels, synthesizedComp.Annotations)
continue
}

pod := &podList.Items[i]
updateObjLabelsAndAnnotations(pod, synthesizedComp.Labels, synthesizedComp.Annotations)
graphCli.Do(dag, nil, pod, model.ActionUpdatePtr(), nil)
}
return nil
}

func updateObjLabel(labelKey, labelValue string, obj client.Object) {
labels := obj.GetLabels()
if labels == nil {
labels = make(map[string]string, 0)
func updateObjLabelsAndAnnotations(obj client.Object, customLabels, customAnnotations map[string]string) {
if customLabels != nil {
labels := obj.GetLabels()
if labels == nil {
labels = make(map[string]string, 0)
}
for k, v := range customLabels {
labels[k] = v
}
obj.SetLabels(labels)
}
if customAnnotations != nil {
annotations := obj.GetAnnotations()
if annotations == nil {
annotations = make(map[string]string, 0)
}
for k, v := range customAnnotations {
annotations[k] = v
}
obj.SetAnnotations(annotations)
}
labels[labelKey] = labelValue
obj.SetLabels(labels)
}
17 changes: 12 additions & 5 deletions controllers/apps/component_utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,32 +190,39 @@ var _ = Describe("Component Utils", func() {
})
})

// TODO(xingran: add test case for updateCustomLabelToObjs func
Context("updateCustomLabelToPods func", func() {
Context("UpdateCustomLabelsAndAnnotationsToPods func", func() {
It("should work well", func() {
_, _, cluster := testapps.InitClusterWithHybridComps(&testCtx, clusterDefName,
clusterVersionName, clusterName, statelessCompName, "stateful", consensusCompName)
sts := testapps.MockConsensusComponentStatefulSet(&testCtx, clusterName, consensusCompName)
pods := testapps.MockConsensusComponentPods(&testCtx, sts, clusterName, consensusCompName)
mockLabelKey := "mock-label-key"
mockLabelValue := "mock-label-value"
mockAnnotationKey := "mock-anno-key"
mockAnnotationKeyValue := "mock-anno-value"
customLabels := map[string]string{
mockLabelKey: mockLabelValue,
}
customAnnotations := map[string]string{
mockAnnotationKey: mockAnnotationKeyValue,
}
comp := &component.SynthesizedComponent{
Name: consensusCompName,
Labels: customLabels,
Name: consensusCompName,
Labels: customLabels,
Annotations: customAnnotations,
}

dag := graph.NewDAG()
dag.AddVertex(&model.ObjectVertex{Obj: pods[0], Action: model.ActionUpdatePtr()})
Expect(UpdateCustomLabelToPods(testCtx.Ctx, k8sClient, cluster, comp, dag)).Should(Succeed())
Expect(UpdateCustomLabelsAndAnnotationsToPods(testCtx.Ctx, k8sClient, cluster, comp, dag)).Should(Succeed())
graphCli := model.NewGraphClient(k8sClient)
podList := graphCli.FindAll(dag, &corev1.Pod{})
Expect(podList).Should(HaveLen(3))
for _, pod := range podList {
Expect(pod.GetLabels()).ShouldNot(BeNil())
Expect(pod.GetLabels()[mockLabelKey]).Should(Equal(mockLabelValue))
Expect(pod.GetAnnotations()).ShouldNot(BeNil())
Expect(pod.GetAnnotations()[mockAnnotationKey]).Should(Equal(mockAnnotationKeyValue))
}
})
})
Expand Down
43 changes: 31 additions & 12 deletions controllers/apps/transformer_cluster_api_normalization.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,17 @@ func (t *ClusterAPINormalizationTransformer) Transform(ctx graph.TransformContex
transCtx.ComponentSpecs = make([]*appsv1alpha1.ClusterComponentSpec, 0)
transCtx.ShardingComponentSpecs = make(map[string][]*appsv1alpha1.ClusterComponentSpec, 0)
transCtx.Labels = make(map[string]map[string]string, 0)
transCtx.Annotations = make(map[string]map[string]string, 0)
cluster := transCtx.Cluster
filteredClusterLabels := filterReservedLabels(cluster.Labels)
filteredClusterAnnotations := filterReservedAnnotations(cluster.Annotations)

for i := range cluster.Spec.ComponentSpecs {
clusterComSpec := cluster.Spec.ComponentSpecs[i]
transCtx.ComponentSpecs = append(transCtx.ComponentSpecs, &clusterComSpec)
// inherit cluster labels
// inherit cluster labels and annotations
transCtx.Labels[clusterComSpec.Name] = filteredClusterLabels
transCtx.Annotations[clusterComSpec.Name] = filteredClusterAnnotations
}
for i := range cluster.Spec.ShardingSpecs {
shardingSpec := cluster.Spec.ShardingSpecs[i]
Expand All @@ -66,12 +69,14 @@ func (t *ClusterAPINormalizationTransformer) Transform(ctx graph.TransformContex
genShardCompSpec := genShardingCompSpecList[j]
transCtx.ComponentSpecs = append(transCtx.ComponentSpecs, genShardCompSpec)
transCtx.Labels[genShardCompSpec.Name] = controllerutil.MergeMetadataMaps(filteredClusterLabels, constant.GetShardingNameLabel(shardingSpec.Name))
transCtx.Annotations[genShardCompSpec.Name] = filteredClusterAnnotations
}
}

if compSpec := apiconversion.HandleSimplifiedClusterAPI(transCtx.ClusterDef, cluster); compSpec != nil {
transCtx.ComponentSpecs = append(transCtx.ComponentSpecs, compSpec)
transCtx.Labels[compSpec.Name] = filteredClusterLabels
transCtx.Annotations[compSpec.Name] = filteredClusterAnnotations
}

// validate componentDef and componentDefRef
Expand Down Expand Up @@ -121,18 +126,32 @@ func validateComponentDefNComponentDefRef(transCtx *clusterTransformContext) err
return nil
}

func filterReservedLabels(labels map[string]string) map[string]string {
reservedLabelKeys := constant.GetKBReservedLabelKeys()
reservedLabelSet := make(map[string]struct{}, len(reservedLabelKeys))
for _, k := range reservedLabelKeys {
reservedLabelSet[k] = struct{}{}
// filterReservedEntries filters out reserved keys from a map based on a provided set of reserved keys
func filterReservedEntries(entries map[string]string, reservedKeys []string) map[string]string {
reservedSet := make(map[string]struct{}, len(reservedKeys))
for _, key := range reservedKeys {
reservedSet[key] = struct{}{}
}
filteredLabels := make(map[string]string)
for k, v := range labels {
if _, exists := reservedLabelSet[k]; exists {
continue

filteredEntries := make(map[string]string)
for key, value := range entries {
if _, exists := reservedSet[key]; !exists {
filteredEntries[key] = value
}
filteredLabels[k] = v
}
return filteredLabels
return filteredEntries
}

func filterReservedLabels(labels map[string]string) map[string]string {
if labels == nil {
return nil
}
return filterReservedEntries(labels, constant.GetKBReservedLabelKeys())
}

func filterReservedAnnotations(annotations map[string]string) map[string]string {
if annotations == nil {
return nil
}
return filterReservedEntries(annotations, constant.GetKBReservedAnnotationKeys())
}
14 changes: 8 additions & 6 deletions controllers/apps/transformer_cluster_component.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,24 +87,25 @@ func (t *clusterComponentTransformer) reconcileComponents(transCtx *clusterTrans
}

// component objects to be created
if err := t.handleCompsCreate(transCtx, dag, protoCompSpecMap, transCtx.Labels, createCompSet); err != nil {
if err := t.handleCompsCreate(transCtx, dag, protoCompSpecMap, createCompSet, transCtx.Labels, transCtx.Annotations); err != nil {
return err
}

// component objects to be updated
if err := t.handleCompsUpdate(transCtx, dag, protoCompSpecMap, transCtx.Labels, updateCompSet); err != nil {
if err := t.handleCompsUpdate(transCtx, dag, protoCompSpecMap, updateCompSet, transCtx.Labels, transCtx.Annotations); err != nil {
return err
}

return nil
}

func (t *clusterComponentTransformer) handleCompsCreate(transCtx *clusterTransformContext, dag *graph.DAG,
protoCompSpecMap map[string]*appsv1alpha1.ClusterComponentSpec, protoCompLabelsMap map[string]map[string]string, createCompSet sets.Set[string]) error {
protoCompSpecMap map[string]*appsv1alpha1.ClusterComponentSpec, createCompSet sets.Set[string],
protoCompLabelsMap, protoCompAnnotationsMap map[string]map[string]string) error {
cluster := transCtx.Cluster
graphCli, _ := transCtx.Client.(model.GraphClient)
for compName := range createCompSet {
comp, err := component.BuildComponent(cluster, protoCompSpecMap[compName], protoCompLabelsMap[compName])
comp, err := component.BuildComponent(cluster, protoCompSpecMap[compName], protoCompLabelsMap[compName], protoCompAnnotationsMap[compName])
if err != nil {
return err
}
Expand All @@ -122,15 +123,16 @@ func (t *clusterComponentTransformer) initClusterCompStatus(cluster *appsv1alpha
}

func (t *clusterComponentTransformer) handleCompsUpdate(transCtx *clusterTransformContext, dag *graph.DAG,
protoCompSpecMap map[string]*appsv1alpha1.ClusterComponentSpec, protoCompLabelsMap map[string]map[string]string, updateCompSet sets.Set[string]) error {
protoCompSpecMap map[string]*appsv1alpha1.ClusterComponentSpec, updateCompSet sets.Set[string],
protoCompLabelsMap, protoCompAnnotationsMap map[string]map[string]string) error {
cluster := transCtx.Cluster
graphCli, _ := transCtx.Client.(model.GraphClient)
for compName := range updateCompSet {
runningComp, getErr := getRunningCompObject(transCtx, cluster, compName)
if getErr != nil {
return getErr
}
comp, buildErr := component.BuildComponent(cluster, protoCompSpecMap[compName], protoCompLabelsMap[compName])
comp, buildErr := component.BuildComponent(cluster, protoCompSpecMap[compName], protoCompLabelsMap[compName], protoCompAnnotationsMap[compName])
if buildErr != nil {
return buildErr
}
Expand Down
Loading

0 comments on commit 6d44f2d

Please sign in to comment.