diff --git a/api/v1alpha1/batchrelease_plan_types.go b/api/v1alpha1/batchrelease_plan_types.go index fd33c903..0cf37e8c 100644 --- a/api/v1alpha1/batchrelease_plan_types.go +++ b/api/v1alpha1/batchrelease_plan_types.go @@ -50,6 +50,10 @@ type ReleasePlan struct { // FinalizingPolicy define the behavior of controller when phase enter Finalizing // Defaults to "Immediate" FinalizingPolicy FinalizingPolicyType `json:"finalizingPolicy,omitempty"` + // PatchPodTemplateMetadata indicates patch configuration(e.g. labels, annotations) to the canary deployment podTemplateSpec.metadata + // only support for canary deployment + // +optional + PatchPodTemplateMetadata *PatchPodTemplateMetadata `json:"patchPodTemplateMetadata,omitempty"` } type FinalizingPolicyType string diff --git a/api/v1alpha1/rollout_types.go b/api/v1alpha1/rollout_types.go index 3c97f6b3..8ea30fed 100644 --- a/api/v1alpha1/rollout_types.go +++ b/api/v1alpha1/rollout_types.go @@ -113,6 +113,17 @@ type CanaryStrategy struct { // FailureThreshold. // Defaults to nil. FailureThreshold *intstr.IntOrString `json:"failureThreshold,omitempty"` + // PatchPodTemplateMetadata indicates patch configuration(e.g. labels, annotations) to the canary deployment podTemplateSpec.metadata + // only support for canary deployment + // +optional + PatchPodTemplateMetadata *PatchPodTemplateMetadata `json:"patchPodTemplateMetadata,omitempty"` +} + +type PatchPodTemplateMetadata struct { + // annotations + Annotations map[string]string `json:"annotations,omitempty"` + // labels + Labels map[string]string `json:"labels,omitempty"` } // CanaryStep defines a step of a canary workload. diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index a5e87273..26dd669d 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -239,6 +239,11 @@ func (in *CanaryStrategy) DeepCopyInto(out *CanaryStrategy) { *out = new(intstr.IntOrString) **out = **in } + if in.PatchPodTemplateMetadata != nil { + in, out := &in.PatchPodTemplateMetadata, &out.PatchPodTemplateMetadata + *out = new(PatchPodTemplateMetadata) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CanaryStrategy. @@ -412,6 +417,35 @@ func (in *ObjectRef) DeepCopy() *ObjectRef { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PatchPodTemplateMetadata) DeepCopyInto(out *PatchPodTemplateMetadata) { + *out = *in + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PatchPodTemplateMetadata. +func (in *PatchPodTemplateMetadata) DeepCopy() *PatchPodTemplateMetadata { + if in == nil { + return nil + } + out := new(PatchPodTemplateMetadata) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Pod) DeepCopyInto(out *Pod) { *out = *in @@ -461,6 +495,11 @@ func (in *ReleasePlan) DeepCopyInto(out *ReleasePlan) { *out = new(intstr.IntOrString) **out = **in } + if in.PatchPodTemplateMetadata != nil { + in, out := &in.PatchPodTemplateMetadata, &out.PatchPodTemplateMetadata + *out = new(PatchPodTemplateMetadata) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReleasePlan. diff --git a/config/crd/bases/rollouts.kruise.io_batchreleases.yaml b/config/crd/bases/rollouts.kruise.io_batchreleases.yaml index 61752ad3..421b07f4 100644 --- a/config/crd/bases/rollouts.kruise.io_batchreleases.yaml +++ b/config/crd/bases/rollouts.kruise.io_batchreleases.yaml @@ -102,6 +102,22 @@ spec: description: FinalizingPolicy define the behavior of controller when phase enter Finalizing Defaults to "Immediate" type: string + patchPodTemplateMetadata: + description: PatchPodTemplateMetadata indicates patch configuration(e.g. + labels, annotations) to the canary deployment podTemplateSpec.metadata + only support for canary deployment + properties: + annotations: + additionalProperties: + type: string + description: annotations + type: object + labels: + additionalProperties: + type: string + description: labels + type: object + type: object rolloutID: description: RolloutID indicates an id for each rollout progress type: string diff --git a/config/crd/bases/rollouts.kruise.io_rollouts.yaml b/config/crd/bases/rollouts.kruise.io_rollouts.yaml index 4d375139..57a98ff6 100644 --- a/config/crd/bases/rollouts.kruise.io_rollouts.yaml +++ b/config/crd/bases/rollouts.kruise.io_rollouts.yaml @@ -104,6 +104,22 @@ spec: is nil, Rollout will use the MaxUnavailable of workload as its FailureThreshold. Defaults to nil. x-kubernetes-int-or-string: true + patchPodTemplateMetadata: + description: PatchPodTemplateMetadata indicates patch configuration(e.g. + labels, annotations) to the canary deployment podTemplateSpec.metadata + only support for canary deployment + properties: + annotations: + additionalProperties: + type: string + description: annotations + type: object + labels: + additionalProperties: + type: string + description: labels + type: object + type: object steps: description: Steps define the order of phases to execute release in batches(20%, 40%, 60%, 80%, 100%) diff --git a/pkg/controller/batchrelease/control/canarystyle/deployment/canary.go b/pkg/controller/batchrelease/control/canarystyle/deployment/canary.go index 9e3f7884..adeead0a 100644 --- a/pkg/controller/batchrelease/control/canarystyle/deployment/canary.go +++ b/pkg/controller/batchrelease/control/canarystyle/deployment/canary.go @@ -132,7 +132,19 @@ func (r *realCanaryController) create(release *v1alpha1.BatchRelease, template * // spec canary.Spec = *template.Spec.DeepCopy() - // todo, patch canary pod metadata + // patch canary pod metadata + if release.Spec.ReleasePlan.PatchPodTemplateMetadata != nil { + patch := release.Spec.ReleasePlan.PatchPodTemplateMetadata + for k, v := range patch.Labels { + canary.Spec.Template.Labels[k] = v + } + if canary.Spec.Template.Annotations == nil { + canary.Spec.Template.Annotations = map[string]string{} + } + for k, v := range patch.Annotations { + canary.Spec.Template.Annotations[k] = v + } + } canary.Spec.Replicas = pointer.Int32Ptr(0) canary.Spec.Paused = false @@ -169,7 +181,7 @@ func (r *realCanaryController) listDeployment(release *v1alpha1.BatchRelease, op } // return the latest deployment with the newer creation time -func filterCanaryDeployment(ds []*apps.Deployment, template *corev1.PodTemplateSpec) *apps.Deployment { +func filterCanaryDeployment(release *v1alpha1.BatchRelease, ds []*apps.Deployment, template *corev1.PodTemplateSpec) *apps.Deployment { if len(ds) == 0 { return nil } @@ -179,9 +191,22 @@ func filterCanaryDeployment(ds []*apps.Deployment, template *corev1.PodTemplateS if template == nil { return ds[0] } + dTemplate := template.DeepCopy() for _, d := range ds { - // todo, remove the canary pod metadata - if util.EqualIgnoreHash(template, &d.Spec.Template) { + // remove the canary pod metadata + dClone := d.DeepCopy() + if release.Spec.ReleasePlan.PatchPodTemplateMetadata != nil { + patch := release.Spec.ReleasePlan.PatchPodTemplateMetadata + for k := range patch.Labels { + delete(dClone.Spec.Template.Labels, k) + delete(dTemplate.Labels, k) + } + for k := range patch.Annotations { + delete(dClone.Spec.Template.Annotations, k) + delete(dTemplate.Annotations, k) + } + } + if util.EqualIgnoreHash(dTemplate, &dClone.Spec.Template) { return d } } diff --git a/pkg/controller/batchrelease/control/canarystyle/deployment/control.go b/pkg/controller/batchrelease/control/canarystyle/deployment/control.go index 04938d0e..15f46021 100644 --- a/pkg/controller/batchrelease/control/canarystyle/deployment/control.go +++ b/pkg/controller/batchrelease/control/canarystyle/deployment/control.go @@ -73,7 +73,7 @@ func (rc *realController) BuildCanaryController(release *v1alpha1.BatchRelease) if client.IgnoreNotFound(err) != nil { return rc, err } - rc.canaryObject = filterCanaryDeployment(util.FilterActiveDeployment(ds), template) + rc.canaryObject = filterCanaryDeployment(release, util.FilterActiveDeployment(ds), template) if rc.canaryObject == nil { return rc, control.GenerateNotFoundError(fmt.Sprintf("%v-canary", rc.stableKey), "Deployment") } diff --git a/pkg/controller/batchrelease/control/canarystyle/deployment/control_test.go b/pkg/controller/batchrelease/control/canarystyle/deployment/control_test.go index 6846f829..2eaf31a0 100644 --- a/pkg/controller/batchrelease/control/canarystyle/deployment/control_test.go +++ b/pkg/controller/batchrelease/control/canarystyle/deployment/control_test.go @@ -316,7 +316,7 @@ func getCanaryDeployment(release *v1alpha1.BatchRelease, stable *apps.Deployment if len(ds) == 0 { return nil } - return filterCanaryDeployment(ds, &stable.Spec.Template) + return filterCanaryDeployment(release, ds, &stable.Spec.Template) } func checkWorkloadInfo(stableInfo *util.WorkloadInfo, deployment *apps.Deployment) { diff --git a/pkg/controller/rollout/rollout_canary.go b/pkg/controller/rollout/rollout_canary.go index 4ae89517..9cd18e13 100644 --- a/pkg/controller/rollout/rollout_canary.go +++ b/pkg/controller/rollout/rollout_canary.go @@ -361,11 +361,11 @@ func createBatchRelease(rollout *v1alpha1.Rollout, rolloutID string, batch int32 }, }, ReleasePlan: v1alpha1.ReleasePlan{ - Batches: batches, - RolloutID: rolloutID, - BatchPartition: utilpointer.Int32Ptr(batch), - FailureThreshold: rollout.Spec.Strategy.Canary.FailureThreshold, - // PatchPodTemplateMetadata: rollout.Spec.Strategy.Canary.PatchPodTemplateMetadata, + Batches: batches, + RolloutID: rolloutID, + BatchPartition: utilpointer.Int32Ptr(batch), + FailureThreshold: rollout.Spec.Strategy.Canary.FailureThreshold, + PatchPodTemplateMetadata: rollout.Spec.Strategy.Canary.PatchPodTemplateMetadata, }, }, } diff --git a/test/e2e/rollout_test.go b/test/e2e/rollout_test.go index fdb06439..bd1d1e5f 100644 --- a/test/e2e/rollout_test.go +++ b/test/e2e/rollout_test.go @@ -442,6 +442,10 @@ var _ = SIGDescribe("Rollout", func() { }, }, } + rollout.Spec.Strategy.Canary.PatchPodTemplateMetadata = &v1alpha1.PatchPodTemplateMetadata{ + Labels: map[string]string{"pod": "canary"}, + Annotations: map[string]string{"pod": "canary"}, + } CreateObject(rollout) By("Creating workload and waiting for all pods ready...") @@ -457,6 +461,10 @@ var _ = SIGDescribe("Rollout", func() { workload := &apps.Deployment{} Expect(ReadYamlToObject("./test_data/rollout/deployment.yaml", workload)).ToNot(HaveOccurred()) workload.Spec.Replicas = utilpointer.Int32(3) + workload.Spec.Template.Labels["pod"] = "stable" + workload.Spec.Template.Annotations = map[string]string{ + "pod": "stable", + } CreateObject(workload) WaitDeploymentAllPodsReady(workload) @@ -484,6 +492,10 @@ var _ = SIGDescribe("Rollout", func() { cIngress := &netv1.Ingress{} Expect(GetObject(service.Name+"-canary", cIngress)).NotTo(HaveOccurred()) Expect(cIngress.Annotations[fmt.Sprintf("%s/canary-weight", nginxIngressAnnotationDefaultPrefix)]).Should(Equal("20")) + canaryWorkload, err := GetCanaryDeployment(workload) + Expect(err).NotTo(HaveOccurred()) + Expect(canaryWorkload.Spec.Template.Annotations["pod"]).Should(Equal("canary")) + Expect(canaryWorkload.Spec.Template.Labels["pod"]).Should(Equal("canary")) // resume rollout canary ResumeRolloutCanary(rollout.Name)