diff --git a/apis/apps/v1alpha1/uniteddeployment_types.go b/apis/apps/v1alpha1/uniteddeployment_types.go index 332ae13f83..c8a5671137 100644 --- a/apis/apps/v1alpha1/uniteddeployment_types.go +++ b/apis/apps/v1alpha1/uniteddeployment_types.go @@ -21,6 +21,7 @@ import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" ) @@ -187,6 +188,13 @@ type Subset struct { // Controller will try to keep all the subsets with nil replicas have average pods. // +optional Replicas *intstr.IntOrString `json:"replicas,omitempty"` + // Patch indicates patching to the templateSpec. + // Patch takes precedence over other fields + // If the Patch also modifies the Replicas, NodeSelectorTerm or Tolerations, use value in the Patch + // +optional + // +kubebuilder:pruning:PreserveUnknownFields + // +kubebuilder:validation:Schemaless + Patch runtime.RawExtension `json:"patch,omitempty"` } // UnitedDeploymentStatus defines the observed state of UnitedDeployment. diff --git a/apis/apps/v1alpha1/well_known_labels.go b/apis/apps/v1alpha1/well_known_labels.go index 786cd94ef4..85e37780ef 100644 --- a/apis/apps/v1alpha1/well_known_labels.go +++ b/apis/apps/v1alpha1/well_known_labels.go @@ -15,6 +15,8 @@ const ( // ImagePreDownloadIgnoredKey indicates the images of this revision have been ignored to pre-download ImagePreDownloadIgnoredKey = "apps.kruise.io/image-predownload-ignored" + // AnnotationSubsetPatchKey indicates the patch for every subset + AnnotationSubsetPatchKey = "apps.kruise.io/subset-patch" ) // Sidecar container environment variable definitions which are used to enable SidecarTerminator to take effect on the sidecar container. diff --git a/apis/apps/v1alpha1/zz_generated.deepcopy.go b/apis/apps/v1alpha1/zz_generated.deepcopy.go index e68143c143..c685ae7c5d 100644 --- a/apis/apps/v1alpha1/zz_generated.deepcopy.go +++ b/apis/apps/v1alpha1/zz_generated.deepcopy.go @@ -3165,6 +3165,7 @@ func (in *Subset) DeepCopyInto(out *Subset) { *out = new(intstr.IntOrString) **out = **in } + in.Patch.DeepCopyInto(&out.Patch) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Subset. diff --git a/config/crd/bases/apps.kruise.io_uniteddeployments.yaml b/config/crd/bases/apps.kruise.io_uniteddeployments.yaml index 9a41b0bc97..e411d1a012 100644 --- a/config/crd/bases/apps.kruise.io_uniteddeployments.yaml +++ b/config/crd/bases/apps.kruise.io_uniteddeployments.yaml @@ -1056,6 +1056,12 @@ spec: type: object type: array type: object + patch: + description: Patch indicates patching to the templateSpec. + Patch takes precedence over other fields If the Patch + also modifies the Replicas, NodeSelectorTerm or Tolerations, + use value in the Patch + x-kubernetes-preserve-unknown-fields: true replicas: anyOf: - type: integer diff --git a/pkg/controller/uniteddeployment/adapter/advanced_statefulset_adapter.go b/pkg/controller/uniteddeployment/adapter/advanced_statefulset_adapter.go index 8efeb4c54d..c999a2bd9e 100644 --- a/pkg/controller/uniteddeployment/adapter/advanced_statefulset_adapter.go +++ b/pkg/controller/uniteddeployment/adapter/advanced_statefulset_adapter.go @@ -18,8 +18,12 @@ package adapter import ( "context" + "encoding/json" "fmt" + "k8s.io/apimachinery/pkg/util/strategicpatch" + "k8s.io/klog/v2" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -173,6 +177,26 @@ func (a *AdvancedStatefulSetAdapter) ApplySubsetTemplate(ud *alpha1.UnitedDeploy attachNodeAffinity(&set.Spec.Template.Spec, subSetConfig) attachTolerations(&set.Spec.Template.Spec, subSetConfig) + if subSetConfig.Patch.Raw != nil { + TemplateSpecBytes, _ := json.Marshal(set.Spec.Template) + modified, err := strategicpatch.StrategicMergePatch(TemplateSpecBytes, subSetConfig.Patch.Raw, &corev1.PodTemplateSpec{}) + if err != nil { + klog.Errorf("failed to merge patch raw %s", subSetConfig.Patch.Raw) + return err + } + patchedTemplateSpec := corev1.PodTemplateSpec{} + if err = json.Unmarshal(modified, &patchedTemplateSpec); err != nil { + klog.Errorf("failed to unmarshal %s to podTemplateSpec", modified) + return err + } + + set.Spec.Template = patchedTemplateSpec + klog.V(2).Infof("AdvancedStatefulSet [%s/%s] was patched successfully: %s", set.Namespace, set.GenerateName, subSetConfig.Patch.Raw) + } + if set.Annotations == nil { + set.Annotations = make(map[string]string) + } + set.Annotations[alpha1.AnnotationSubsetPatchKey] = string(subSetConfig.Patch.Raw) return nil } diff --git a/pkg/controller/uniteddeployment/adapter/cloneset_adapter.go b/pkg/controller/uniteddeployment/adapter/cloneset_adapter.go index 5009c563f7..344dafad2b 100644 --- a/pkg/controller/uniteddeployment/adapter/cloneset_adapter.go +++ b/pkg/controller/uniteddeployment/adapter/cloneset_adapter.go @@ -2,8 +2,12 @@ package adapter import ( "context" + "encoding/json" "fmt" + "k8s.io/apimachinery/pkg/util/strategicpatch" + "k8s.io/klog/v2" + alpha1 "github.com/openkruise/kruise/apis/apps/v1alpha1" "github.com/openkruise/kruise/pkg/util" "github.com/openkruise/kruise/pkg/util/refmanager" @@ -134,7 +138,26 @@ func (a *CloneSetAdapter) ApplySubsetTemplate(ud *alpha1.UnitedDeployment, subse attachNodeAffinity(&set.Spec.Template.Spec, subSetConfig) attachTolerations(&set.Spec.Template.Spec, subSetConfig) + if subSetConfig.Patch.Raw != nil { + TemplateSpecBytes, _ := json.Marshal(set.Spec.Template) + modified, err := strategicpatch.StrategicMergePatch(TemplateSpecBytes, subSetConfig.Patch.Raw, &corev1.PodTemplateSpec{}) + if err != nil { + klog.Errorf("failed to merge patch raw %s", subSetConfig.Patch.Raw) + return err + } + patchedTemplateSpec := corev1.PodTemplateSpec{} + if err = json.Unmarshal(modified, &patchedTemplateSpec); err != nil { + klog.Errorf("failed to unmarshal %s to podTemplateSpec", modified) + return err + } + set.Spec.Template = patchedTemplateSpec + klog.V(2).Infof("CloneSet [%s/%s] was patched successfully: %s", set.Namespace, set.GenerateName, subSetConfig.Patch.Raw) + } + if set.Annotations == nil { + set.Annotations = make(map[string]string) + } + set.Annotations[alpha1.AnnotationSubsetPatchKey] = string(subSetConfig.Patch.Raw) return nil } diff --git a/pkg/controller/uniteddeployment/adapter/deployment_adapter.go b/pkg/controller/uniteddeployment/adapter/deployment_adapter.go index 7cec307644..b1a49a2bab 100644 --- a/pkg/controller/uniteddeployment/adapter/deployment_adapter.go +++ b/pkg/controller/uniteddeployment/adapter/deployment_adapter.go @@ -18,8 +18,12 @@ package adapter import ( "context" + "encoding/json" "fmt" + "k8s.io/apimachinery/pkg/util/strategicpatch" + "k8s.io/klog/v2" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -146,6 +150,27 @@ func (a *DeploymentAdapter) ApplySubsetTemplate(ud *alpha1.UnitedDeployment, sub attachNodeAffinity(&set.Spec.Template.Spec, subSetConfig) attachTolerations(&set.Spec.Template.Spec, subSetConfig) + if subSetConfig.Patch.Raw != nil { + TemplateSpecBytes, _ := json.Marshal(set.Spec.Template) + modified, err := strategicpatch.StrategicMergePatch(TemplateSpecBytes, subSetConfig.Patch.Raw, &corev1.PodTemplateSpec{}) + if err != nil { + klog.Errorf("failed to merge patch raw %s", subSetConfig.Patch.Raw) + return err + } + patchedTemplateSpec := corev1.PodTemplateSpec{} + if err = json.Unmarshal(modified, &patchedTemplateSpec); err != nil { + klog.Errorf("failed to unmarshal %s to podTemplateSpec", modified) + return err + } + + set.Spec.Template = patchedTemplateSpec + klog.V(2).Infof("Deployment [%s/%s] was patched successfully: %s", set.Namespace, set.GenerateName, subSetConfig.Patch.Raw) + } + if set.Annotations == nil { + set.Annotations = make(map[string]string) + } + set.Annotations[alpha1.AnnotationSubsetPatchKey] = string(subSetConfig.Patch.Raw) + return nil } diff --git a/pkg/controller/uniteddeployment/adapter/statefulset_adapter.go b/pkg/controller/uniteddeployment/adapter/statefulset_adapter.go index c7850b3380..471d297519 100644 --- a/pkg/controller/uniteddeployment/adapter/statefulset_adapter.go +++ b/pkg/controller/uniteddeployment/adapter/statefulset_adapter.go @@ -18,8 +18,11 @@ package adapter import ( "context" + "encoding/json" "fmt" + "k8s.io/apimachinery/pkg/util/strategicpatch" + appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -157,6 +160,26 @@ func (a *StatefulSetAdapter) ApplySubsetTemplate(ud *alpha1.UnitedDeployment, su attachNodeAffinity(&set.Spec.Template.Spec, subSetConfig) attachTolerations(&set.Spec.Template.Spec, subSetConfig) + if subSetConfig.Patch.Raw != nil { + TemplateSpecBytes, _ := json.Marshal(set.Spec.Template) + modified, err := strategicpatch.StrategicMergePatch(TemplateSpecBytes, subSetConfig.Patch.Raw, &corev1.PodTemplateSpec{}) + if err != nil { + klog.Errorf("failed to merge patch raw %s", subSetConfig.Patch.Raw) + return err + } + patchedTemplateSpec := corev1.PodTemplateSpec{} + if err = json.Unmarshal(modified, &patchedTemplateSpec); err != nil { + klog.Errorf("failed to unmarshal %s to podTemplateSpec", modified) + return err + } + + set.Spec.Template = patchedTemplateSpec + klog.V(2).Infof("StatefulSet [%s/%s] was patched successfully: %s", set.Namespace, set.GenerateName, subSetConfig.Patch.Raw) + } + if set.Annotations == nil { + set.Annotations = make(map[string]string) + } + set.Annotations[alpha1.AnnotationSubsetPatchKey] = string(subSetConfig.Patch.Raw) return nil } diff --git a/pkg/controller/uniteddeployment/subset.go b/pkg/controller/uniteddeployment/subset.go index b985d93cc4..79b8f006c6 100644 --- a/pkg/controller/uniteddeployment/subset.go +++ b/pkg/controller/uniteddeployment/subset.go @@ -52,6 +52,13 @@ type SubsetUpdateStrategy struct { Partition int32 } +// SubsetUpdate stores the subset field that may need to be updated +type SubsetUpdate struct { + Replicas int32 + Partition int32 + Patch string +} + // ResourceRef stores the Subset resource it represents. type ResourceRef struct { Resources []metav1.Object diff --git a/pkg/controller/uniteddeployment/uniteddeployment_controller.go b/pkg/controller/uniteddeployment/uniteddeployment_controller.go index 7743dc2391..d16ff496cb 100644 --- a/pkg/controller/uniteddeployment/uniteddeployment_controller.go +++ b/pkg/controller/uniteddeployment/uniteddeployment_controller.go @@ -222,9 +222,10 @@ func (r *ReconcileUnitedDeployment) Reconcile(_ context.Context, request reconci } nextPartitions := calcNextPartitions(instance, nextReplicas) - klog.V(4).Infof("Get UnitedDeployment %s/%s next partition %v", instance.Namespace, instance.Name, nextPartitions) + nextUpdate := getNextUpdate(instance, nextReplicas, nextPartitions) + klog.V(4).Infof("Get UnitedDeployment %s/%s next update %v", instance.Namespace, instance.Name, nextUpdate) - newStatus, err := r.manageSubsets(instance, nameToSubset, nextReplicas, nextPartitions, currentRevision, updatedRevision, subsetType) + newStatus, err := r.manageSubsets(instance, nameToSubset, nextUpdate, currentRevision, updatedRevision, subsetType) if err != nil { klog.Errorf("Fail to update UnitedDeployment %s/%s: %s", instance.Namespace, instance.Name, err) r.recorder.Event(instance.DeepCopy(), corev1.EventTypeWarning, fmt.Sprintf("Failed%s", eventTypeSubsetsUpdate), err.Error()) @@ -273,6 +274,19 @@ func calcNextPartitions(ud *appsv1alpha1.UnitedDeployment, nextReplicas *map[str return &partitions } +func getNextUpdate(ud *appsv1alpha1.UnitedDeployment, nextReplicas *map[string]int32, nextPartitions *map[string]int32) map[string]SubsetUpdate { + next := make(map[string]SubsetUpdate) + for _, subset := range ud.Spec.Topology.Subsets { + t := SubsetUpdate{} + t.Replicas = (*nextReplicas)[subset.Name] + t.Partition = (*nextPartitions)[subset.Name] + t.Patch = string(subset.Patch.Raw) + + next[subset.Name] = t + } + return next +} + func (r *ReconcileUnitedDeployment) deleteDupSubset(ud *appsv1alpha1.UnitedDeployment, nameToSubsets map[string][]*Subset, control ControlInterface) (*map[string]*Subset, error) { nameToSubset := map[string]*Subset{} for name, subsets := range nameToSubsets { diff --git a/pkg/controller/uniteddeployment/uniteddeployment_controller_advancedstatefulset_test.go b/pkg/controller/uniteddeployment/uniteddeployment_controller_advancedstatefulset_test.go index bda4a6671f..d31308ef7a 100644 --- a/pkg/controller/uniteddeployment/uniteddeployment_controller_advancedstatefulset_test.go +++ b/pkg/controller/uniteddeployment/uniteddeployment_controller_advancedstatefulset_test.go @@ -17,6 +17,7 @@ limitations under the License. package uniteddeployment import ( + "encoding/json" "fmt" "reflect" "testing" @@ -27,6 +28,7 @@ import ( corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" "sigs.k8s.io/controller-runtime/pkg/client" @@ -453,6 +455,215 @@ func TestAstsSubsetProvision(t *testing.T) { g.Expect(sts.Spec.Template.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[1].MatchExpressions[1].Values[0]).Should(gomega.BeEquivalentTo("node-b")) } +func TestAstsSubsetPatch(t *testing.T) { + g, requests, cancel, mgrStopped := setUp(t) + defer func() { + clean(g, c) + cancel() + mgrStopped.Wait() + }() + + caseName := "test-asts-subset-patch" + + imagePatch := map[string]interface{}{ + "spec": map[string]interface{}{ + "containers": []map[string]interface{}{ + { + "name": "container-a", + "image": "nginx:2.0", + }, + }, + }, + } + labelPatch := map[string]interface{}{ + "metadata": map[string]interface{}{ + "labels": map[string]string{ + "zone": "a", + }, + }, + } + resourcePatch := map[string]interface{}{ + "spec": map[string]interface{}{ + "containers": []map[string]interface{}{ + { + "name": "container-a", + "resources": map[string]interface{}{ + "limits": map[string]interface{}{ + "cpu": "2", + "memory": "800Mi", + }, + }, + }, + }, + }, + } + envPatch := map[string]interface{}{ + "spec": map[string]interface{}{ + "containers": []map[string]interface{}{ + { + "name": "container-a", + "env": []map[string]string{ + { + "name": "K8S_CONTAINER_NAME", + "value": "main", + }, + }, + }, + }, + }, + } + labelPatchBytes, _ := json.Marshal(labelPatch) + imagePatchBytes, _ := json.Marshal(imagePatch) + resourcePatchBytes, _ := json.Marshal(resourcePatch) + envPatchBytes, _ := json.Marshal(envPatch) + instance := &appsv1alpha1.UnitedDeployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: caseName, + Namespace: "default", + }, + Spec: appsv1alpha1.UnitedDeploymentSpec{ + Replicas: &one, + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "name": caseName, + }, + }, + Template: appsv1alpha1.SubsetTemplate{ + AdvancedStatefulSetTemplate: &appsv1alpha1.AdvancedStatefulSetTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "name": caseName, + }, + }, + Spec: appsv1beta1.StatefulSetSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "name": caseName, + }, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "name": caseName, + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "container-a", + Image: "nginx:1.0", + }, + }, + }, + }, + }, + }, + }, + Topology: appsv1alpha1.Topology{ + Subsets: []appsv1alpha1.Subset{ + { + Name: "subset-a", + Patch: runtime.RawExtension{ + Raw: imagePatchBytes, + }, + NodeSelectorTerm: corev1.NodeSelectorTerm{ + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "node-name", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"node-a"}, + }, + }, + }, + }, + { + Name: "subset-b", + Patch: runtime.RawExtension{ + Raw: labelPatchBytes, + }, + NodeSelectorTerm: corev1.NodeSelectorTerm{ + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "node-name", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"node-b"}, + }, + }, + }, + }, + { + Name: "subset-c", + Patch: runtime.RawExtension{ + Raw: resourcePatchBytes, + }, + NodeSelectorTerm: corev1.NodeSelectorTerm{ + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "node-name", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"node-c"}, + }, + }, + }, + }, + { + Name: "subset-d", + Patch: runtime.RawExtension{ + Raw: envPatchBytes, + }, + NodeSelectorTerm: corev1.NodeSelectorTerm{ + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "node-name", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"node-d"}, + }, + }, + }, + }, + }, + }, + RevisionHistoryLimit: &ten, + }, + } + + // Create the UnitedDeployment object and expect the Reconcile and Deployment to be created + err := c.Create(context.TODO(), instance) + // The instance object may not be a valid object because it might be missing some required fields. + // Please modify the instance object by adding required fields and then remove the following if statement. + if apierrors.IsInvalid(err) { + t.Logf("failed to create object, got an invalid object error: %v", err) + return + } + g.Expect(err).NotTo(gomega.HaveOccurred()) + defer c.Delete(context.TODO(), instance) + waitReconcilerProcessFinished(g, requests, 3) + + astsList := expectedAstsCount(g, instance, 4) + asts := getSubsetAstsByName(astsList, "subset-a") + g.Expect(asts.Spec).ShouldNot(gomega.BeNil()) + g.Expect(asts.Spec.Template.Spec.Containers[0].Image).Should(gomega.BeEquivalentTo("nginx:2.0")) + + asts = getSubsetAstsByName(astsList, "subset-b") + g.Expect(asts.Spec).ShouldNot(gomega.BeNil()) + g.Expect(asts.Spec.Template.Labels).Should(gomega.HaveKeyWithValue("zone", "a")) + + asts = getSubsetAstsByName(astsList, "subset-c") + g.Expect(asts.Spec).ShouldNot(gomega.BeNil()) + g.Expect(asts.Spec.Template.Spec.Containers[0].Resources).ShouldNot(gomega.BeNil()) + g.Expect(asts.Spec.Template.Spec.Containers[0].Resources.Limits).ShouldNot(gomega.BeNil()) + g.Expect(asts.Spec.Template.Spec.Containers[0].Resources.Limits.Cpu()).ShouldNot(gomega.BeNil()) + g.Expect(asts.Spec.Template.Spec.Containers[0].Resources.Limits.Cpu().Value()).Should(gomega.BeEquivalentTo(2)) + g.Expect(asts.Spec.Template.Spec.Containers[0].Resources.Limits.Memory()).ShouldNot(gomega.BeNil()) + g.Expect(asts.Spec.Template.Spec.Containers[0].Resources.Limits.Memory().String()).Should(gomega.BeEquivalentTo("800Mi")) + + asts = getSubsetAstsByName(astsList, "subset-d") + g.Expect(asts.Spec).ShouldNot(gomega.BeNil()) + g.Expect(asts.Spec.Template.Spec.Containers[0].Env).ShouldNot(gomega.BeNil()) + g.Expect(asts.Spec.Template.Spec.Containers[0].Env[0].Name).Should(gomega.BeEquivalentTo("K8S_CONTAINER_NAME")) + g.Expect(asts.Spec.Template.Spec.Containers[0].Env[0].Value).Should(gomega.BeEquivalentTo("main")) +} + func TestAstsSubsetProvisionWithToleration(t *testing.T) { g, requests, cancel, mgrStopped := setUp(t) defer func() { diff --git a/pkg/controller/uniteddeployment/uniteddeployment_controller_cloneset_test.go b/pkg/controller/uniteddeployment/uniteddeployment_controller_cloneset_test.go index 90b1b72d84..3ea8f468a8 100644 --- a/pkg/controller/uniteddeployment/uniteddeployment_controller_cloneset_test.go +++ b/pkg/controller/uniteddeployment/uniteddeployment_controller_cloneset_test.go @@ -1,6 +1,7 @@ package uniteddeployment import ( + "encoding/json" "fmt" "reflect" "testing" @@ -13,6 +14,7 @@ import ( corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/rand" "sigs.k8s.io/controller-runtime/pkg/client" @@ -459,6 +461,216 @@ func TestCsSubsetProvision(t *testing.T) { g.Expect(cs.Spec.Template.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[1].MatchExpressions[1].Values[0]).Should(gomega.BeEquivalentTo("node-b")) } +func TestCsSubsetPatch(t *testing.T) { + g, requests, cancel, mgrStopped := setUp(t) + defer func() { + clean(g, c) + cancel() + mgrStopped.Wait() + }() + + caseName := "test-cs-subset-patch" + + imagePatch := map[string]interface{}{ + "spec": map[string]interface{}{ + "containers": []map[string]interface{}{ + { + "name": "container-a", + "image": "nginx:2.0", + }, + }, + }, + } + labelPatch := map[string]interface{}{ + "metadata": map[string]interface{}{ + "labels": map[string]string{ + "zone": "a", + }, + }, + } + resourcePatch := map[string]interface{}{ + "spec": map[string]interface{}{ + "containers": []map[string]interface{}{ + { + "name": "container-a", + "resources": map[string]interface{}{ + "limits": map[string]interface{}{ + "cpu": "2", + "memory": "800Mi", + }, + }, + }, + }, + }, + } + envPatch := map[string]interface{}{ + "spec": map[string]interface{}{ + "containers": []map[string]interface{}{ + { + "name": "container-a", + "env": []map[string]string{ + { + "name": "K8S_CONTAINER_NAME", + "value": "main", + }, + }, + }, + }, + }, + } + labelPatchBytes, _ := json.Marshal(labelPatch) + imagePatchBytes, _ := json.Marshal(imagePatch) + resourcePatchBytes, _ := json.Marshal(resourcePatch) + envPatchBytes, _ := json.Marshal(envPatch) + instance := &appsv1alpha1.UnitedDeployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: caseName, + Namespace: "default", + }, + Spec: appsv1alpha1.UnitedDeploymentSpec{ + Replicas: &one, + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "name": caseName, + }, + }, + Template: appsv1alpha1.SubsetTemplate{ + CloneSetTemplate: &appsv1alpha1.CloneSetTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "name": caseName, + }, + }, + Spec: appsv1alpha1.CloneSetSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "name": caseName, + }, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "name": caseName, + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "container-a", + Image: "nginx:1.0", + }, + }, + }, + }, + }, + }, + }, + Topology: appsv1alpha1.Topology{ + Subsets: []appsv1alpha1.Subset{ + { + Name: "subset-a", + Patch: runtime.RawExtension{ + Raw: imagePatchBytes, + }, + NodeSelectorTerm: corev1.NodeSelectorTerm{ + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "node-name", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"node-a"}, + }, + }, + }, + }, + { + Name: "subset-b", + Patch: runtime.RawExtension{ + Raw: labelPatchBytes, + }, + NodeSelectorTerm: corev1.NodeSelectorTerm{ + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "node-name", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"node-b"}, + }, + }, + }, + }, + { + Name: "subset-c", + Patch: runtime.RawExtension{ + Raw: resourcePatchBytes, + }, + NodeSelectorTerm: corev1.NodeSelectorTerm{ + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "node-name", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"node-c"}, + }, + }, + }, + }, + { + Name: "subset-d", + Patch: runtime.RawExtension{ + Raw: envPatchBytes, + }, + NodeSelectorTerm: corev1.NodeSelectorTerm{ + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "node-name", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"node-d"}, + }, + }, + }, + }, + }, + }, + RevisionHistoryLimit: &ten, + }, + } + + // Create the UnitedDeployment object and expect the Reconcile and Deployment to be created + err := c.Create(context.TODO(), instance) + // The instance object may not be a valid object because it might be missing some required fields. + // Please modify the instance object by adding required fields and then remove the following if statement. + if apierrors.IsInvalid(err) { + t.Logf("failed to create object, got an invalid object error: %v", err) + return + } + g.Expect(err).NotTo(gomega.HaveOccurred()) + defer c.Delete(context.TODO(), instance) + waitReconcilerProcessFinished(g, requests, 3) + + csList := expectedCsCount(g, instance, 4) + cs := getSubsetCsByName(csList, "subset-a") + g.Expect(cs.Spec).ShouldNot(gomega.BeNil()) + g.Expect(cs.Spec.Template.Spec.Containers[0].Image).Should(gomega.BeEquivalentTo("nginx:2.0")) + + cs = getSubsetCsByName(csList, "subset-b") + g.Expect(cs.Spec).ShouldNot(gomega.BeNil()) + g.Expect(cs.Spec.Template.Labels).Should(gomega.HaveKeyWithValue("zone", "a")) + + cs = getSubsetCsByName(csList, "subset-c") + g.Expect(cs.Spec).ShouldNot(gomega.BeNil()) + g.Expect(cs.Spec.Template.Spec.Containers[0].Resources).ShouldNot(gomega.BeNil()) + g.Expect(cs.Spec.Template.Spec.Containers[0].Resources.Limits).ShouldNot(gomega.BeNil()) + g.Expect(cs.Spec.Template.Spec.Containers[0].Resources.Limits.Cpu()).ShouldNot(gomega.BeNil()) + g.Expect(cs.Spec.Template.Spec.Containers[0].Resources.Limits.Cpu().Value()).Should(gomega.BeEquivalentTo(2)) + g.Expect(cs.Spec.Template.Spec.Containers[0].Resources.Limits.Memory()).ShouldNot(gomega.BeNil()) + g.Expect(cs.Spec.Template.Spec.Containers[0].Resources.Limits.Memory().String()).Should(gomega.BeEquivalentTo("800Mi")) + + cs = getSubsetCsByName(csList, "subset-d") + g.Expect(cs.Spec).ShouldNot(gomega.BeNil()) + g.Expect(cs.Spec.Template.Spec.Containers[0].Env).ShouldNot(gomega.BeNil()) + g.Expect(cs.Spec.Template.Spec.Containers[0].Env[0].Name).Should(gomega.BeEquivalentTo("K8S_CONTAINER_NAME")) + g.Expect(cs.Spec.Template.Spec.Containers[0].Env[0].Value).Should(gomega.BeEquivalentTo("main")) + +} + func TestCsSubsetProvisionWithToleration(t *testing.T) { g, requests, cancel, mgrStopped := setUp(t) defer func() { diff --git a/pkg/controller/uniteddeployment/uniteddeployment_controller_deployment_test.go b/pkg/controller/uniteddeployment/uniteddeployment_controller_deployment_test.go index 799fe2fb93..c672829155 100644 --- a/pkg/controller/uniteddeployment/uniteddeployment_controller_deployment_test.go +++ b/pkg/controller/uniteddeployment/uniteddeployment_controller_deployment_test.go @@ -17,6 +17,7 @@ limitations under the License. package uniteddeployment import ( + "encoding/json" "fmt" "reflect" "testing" @@ -27,6 +28,7 @@ import ( corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" "sigs.k8s.io/controller-runtime/pkg/client" @@ -312,6 +314,211 @@ func TestDeploymentSubsetProvision(t *testing.T) { g.Expect(deployment.Spec.Template.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[1].MatchExpressions[1].Values[0]).Should(gomega.BeEquivalentTo("node-b")) } +func TestDeploymentSubsetPatch(t *testing.T) { + g, requests, cancel, mgrStopped := setUp(t) + defer func() { + clean(g, c) + cancel() + mgrStopped.Wait() + }() + + caseName := "test-deployment-subset-patch" + + imagePatch := map[string]interface{}{ + "spec": map[string]interface{}{ + "containers": []map[string]interface{}{ + { + "name": "container-a", + "image": "nginx:2.0", + }, + }, + }, + } + labelPatch := map[string]interface{}{ + "metadata": map[string]interface{}{ + "labels": map[string]string{ + "zone": "a", + }, + }, + } + resourcePatch := map[string]interface{}{ + "spec": map[string]interface{}{ + "containers": []map[string]interface{}{ + { + "name": "container-a", + "resources": map[string]interface{}{ + "limits": map[string]interface{}{ + "cpu": "2", + "memory": "800Mi", + }, + }, + }, + }, + }, + } + envPatch := map[string]interface{}{ + "spec": map[string]interface{}{ + "containers": []map[string]interface{}{ + { + "name": "container-a", + "env": []map[string]string{ + { + "name": "K8S_CONTAINER_NAME", + "value": "main", + }, + }, + }, + }, + }, + } + labelPatchBytes, _ := json.Marshal(labelPatch) + imagePatchBytes, _ := json.Marshal(imagePatch) + resourcePatchBytes, _ := json.Marshal(resourcePatch) + envPatchBytes, _ := json.Marshal(envPatch) + instance := &appsv1alpha1.UnitedDeployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: caseName, + Namespace: "default", + }, + Spec: appsv1alpha1.UnitedDeploymentSpec{ + Replicas: &one, + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "name": caseName, + }, + }, + Template: appsv1alpha1.SubsetTemplate{ + DeploymentTemplate: &appsv1alpha1.DeploymentTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "name": caseName, + }, + }, + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "name": caseName, + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "container-a", + Image: "nginx:1.0", + }, + }, + }, + }, + }, + }, + }, + Topology: appsv1alpha1.Topology{ + Subsets: []appsv1alpha1.Subset{ + { + Name: "subset-a", + Patch: runtime.RawExtension{ + Raw: imagePatchBytes, + }, + NodeSelectorTerm: corev1.NodeSelectorTerm{ + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "node-name", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"node-a"}, + }, + }, + }, + }, + { + Name: "subset-b", + Patch: runtime.RawExtension{ + Raw: labelPatchBytes, + }, + NodeSelectorTerm: corev1.NodeSelectorTerm{ + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "node-name", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"node-b"}, + }, + }, + }, + }, + { + Name: "subset-c", + Patch: runtime.RawExtension{ + Raw: resourcePatchBytes, + }, + NodeSelectorTerm: corev1.NodeSelectorTerm{ + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "node-name", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"node-c"}, + }, + }, + }, + }, + { + Name: "subset-d", + Patch: runtime.RawExtension{ + Raw: envPatchBytes, + }, + NodeSelectorTerm: corev1.NodeSelectorTerm{ + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "node-name", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"node-d"}, + }, + }, + }, + }, + }, + }, + RevisionHistoryLimit: &ten, + }, + } + + // Create the UnitedDeployment object and expect the Reconcile and Deployment to be created + err := c.Create(context.TODO(), instance) + // The instance object may not be a valid object because it might be missing some required fields. + // Please modify the instance object by adding required fields and then remove the following if statement. + if apierrors.IsInvalid(err) { + t.Logf("failed to create object, got an invalid object error: %v", err) + return + } + g.Expect(err).NotTo(gomega.HaveOccurred()) + defer c.Delete(context.TODO(), instance) + waitReconcilerProcessFinished(g, requests, 3) + + deploymentList := expectedDeploymentCount(g, instance, 4) + deployment := getDeploymentSubsetByName(deploymentList, "subset-a") + g.Expect(deployment.Spec).ShouldNot(gomega.BeNil()) + g.Expect(deployment.Spec.Template.Spec.Containers[0].Image).Should(gomega.BeEquivalentTo("nginx:2.0")) + + deployment = getDeploymentSubsetByName(deploymentList, "subset-b") + g.Expect(deployment.Spec).ShouldNot(gomega.BeNil()) + g.Expect(deployment.Spec.Template.Labels).Should(gomega.HaveKeyWithValue("zone", "a")) + + deployment = getDeploymentSubsetByName(deploymentList, "subset-c") + g.Expect(deployment.Spec).ShouldNot(gomega.BeNil()) + g.Expect(deployment.Spec.Template.Spec.Containers[0].Resources).ShouldNot(gomega.BeNil()) + g.Expect(deployment.Spec.Template.Spec.Containers[0].Resources.Limits).ShouldNot(gomega.BeNil()) + g.Expect(deployment.Spec.Template.Spec.Containers[0].Resources.Limits.Cpu()).ShouldNot(gomega.BeNil()) + g.Expect(deployment.Spec.Template.Spec.Containers[0].Resources.Limits.Cpu().Value()).Should(gomega.BeEquivalentTo(2)) + g.Expect(deployment.Spec.Template.Spec.Containers[0].Resources.Limits.Memory()).ShouldNot(gomega.BeNil()) + g.Expect(deployment.Spec.Template.Spec.Containers[0].Resources.Limits.Memory().String()).Should(gomega.BeEquivalentTo("800Mi")) + + deployment = getDeploymentSubsetByName(deploymentList, "subset-d") + g.Expect(deployment.Spec).ShouldNot(gomega.BeNil()) + g.Expect(deployment.Spec.Template.Spec.Containers[0].Env).ShouldNot(gomega.BeNil()) + g.Expect(deployment.Spec.Template.Spec.Containers[0].Env[0].Name).Should(gomega.BeEquivalentTo("K8S_CONTAINER_NAME")) + g.Expect(deployment.Spec.Template.Spec.Containers[0].Env[0].Value).Should(gomega.BeEquivalentTo("main")) + +} + func TestDeploymentSubsetProvisionWithToleration(t *testing.T) { g, requests, cancel, mgrStopped := setUp(t) defer func() { diff --git a/pkg/controller/uniteddeployment/uniteddeployment_controller_statefulset_test.go b/pkg/controller/uniteddeployment/uniteddeployment_controller_statefulset_test.go index 6307872897..c6a9bb04a8 100644 --- a/pkg/controller/uniteddeployment/uniteddeployment_controller_statefulset_test.go +++ b/pkg/controller/uniteddeployment/uniteddeployment_controller_statefulset_test.go @@ -17,6 +17,7 @@ limitations under the License. package uniteddeployment import ( + "encoding/json" "fmt" "reflect" "sync" @@ -29,6 +30,7 @@ import ( corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/manager" @@ -329,6 +331,210 @@ func TestStsSubsetProvision(t *testing.T) { g.Expect(sts.Spec.Template.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms[1].MatchExpressions[1].Values[0]).Should(gomega.BeEquivalentTo("node-b")) } +func TestStsSubsetPatch(t *testing.T) { + g, requests, cancel, mgrStopped := setUp(t) + defer func() { + clean(g, c) + cancel() + mgrStopped.Wait() + }() + + caseName := "test-sts-subset-patch" + + imagePatch := map[string]interface{}{ + "spec": map[string]interface{}{ + "containers": []map[string]interface{}{ + { + "name": "container-a", + "image": "nginx:2.0", + }, + }, + }, + } + labelPatch := map[string]interface{}{ + "metadata": map[string]interface{}{ + "labels": map[string]string{ + "zone": "a", + }, + }, + } + resourcePatch := map[string]interface{}{ + "spec": map[string]interface{}{ + "containers": []map[string]interface{}{ + { + "name": "container-a", + "resources": map[string]interface{}{ + "limits": map[string]interface{}{ + "cpu": "2", + "memory": "800Mi", + }, + }, + }, + }, + }, + } + envPatch := map[string]interface{}{ + "spec": map[string]interface{}{ + "containers": []map[string]interface{}{ + { + "name": "container-a", + "env": []map[string]string{ + { + "name": "K8S_CONTAINER_NAME", + "value": "main", + }, + }, + }, + }, + }, + } + labelPatchBytes, _ := json.Marshal(labelPatch) + imagePatchBytes, _ := json.Marshal(imagePatch) + resourcePatchBytes, _ := json.Marshal(resourcePatch) + envPatchBytes, _ := json.Marshal(envPatch) + instance := &appsv1alpha1.UnitedDeployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: caseName, + Namespace: "default", + }, + Spec: appsv1alpha1.UnitedDeploymentSpec{ + Replicas: &one, + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "name": caseName, + }, + }, + Template: appsv1alpha1.SubsetTemplate{ + StatefulSetTemplate: &appsv1alpha1.StatefulSetTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "name": caseName, + }, + }, + Spec: appsv1.StatefulSetSpec{ + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "name": caseName, + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "container-a", + Image: "nginx:1.0", + }, + }, + }, + }, + }, + }, + }, + Topology: appsv1alpha1.Topology{ + Subsets: []appsv1alpha1.Subset{ + { + Name: "subset-a", + Patch: runtime.RawExtension{ + Raw: imagePatchBytes, + }, + NodeSelectorTerm: corev1.NodeSelectorTerm{ + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "node-name", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"node-a"}, + }, + }, + }, + }, + { + Name: "subset-b", + Patch: runtime.RawExtension{ + Raw: labelPatchBytes, + }, + NodeSelectorTerm: corev1.NodeSelectorTerm{ + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "node-name", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"node-b"}, + }, + }, + }, + }, + { + Name: "subset-c", + Patch: runtime.RawExtension{ + Raw: resourcePatchBytes, + }, + NodeSelectorTerm: corev1.NodeSelectorTerm{ + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "node-name", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"node-c"}, + }, + }, + }, + }, + { + Name: "subset-d", + Patch: runtime.RawExtension{ + Raw: envPatchBytes, + }, + NodeSelectorTerm: corev1.NodeSelectorTerm{ + MatchExpressions: []corev1.NodeSelectorRequirement{ + { + Key: "node-name", + Operator: corev1.NodeSelectorOpIn, + Values: []string{"node-d"}, + }, + }, + }, + }, + }, + }, + RevisionHistoryLimit: &ten, + }, + } + + // Create the UnitedDeployment object and expect the Reconcile and Deployment to be created + err := c.Create(context.TODO(), instance) + // The instance object may not be a valid object because it might be missing some required fields. + // Please modify the instance object by adding required fields and then remove the following if statement. + if apierrors.IsInvalid(err) { + t.Logf("failed to create object, got an invalid object error: %v", err) + return + } + g.Expect(err).NotTo(gomega.HaveOccurred()) + defer c.Delete(context.TODO(), instance) + waitReconcilerProcessFinished(g, requests, 3) + + stsList := expectedStsCount(g, instance, 4) + sts := getSubsetByName(stsList, "subset-a") + g.Expect(sts.Spec).ShouldNot(gomega.BeNil()) + g.Expect(sts.Spec.Template.Spec.Containers[0].Image).Should(gomega.BeEquivalentTo("nginx:2.0")) + + sts = getSubsetByName(stsList, "subset-b") + g.Expect(sts.Spec).ShouldNot(gomega.BeNil()) + g.Expect(sts.Spec.Template.Labels).Should(gomega.HaveKeyWithValue("zone", "a")) + + sts = getSubsetByName(stsList, "subset-c") + g.Expect(sts.Spec).ShouldNot(gomega.BeNil()) + g.Expect(sts.Spec.Template.Spec.Containers[0].Resources).ShouldNot(gomega.BeNil()) + g.Expect(sts.Spec.Template.Spec.Containers[0].Resources.Limits).ShouldNot(gomega.BeNil()) + g.Expect(sts.Spec.Template.Spec.Containers[0].Resources.Limits.Cpu()).ShouldNot(gomega.BeNil()) + g.Expect(sts.Spec.Template.Spec.Containers[0].Resources.Limits.Cpu().Value()).Should(gomega.BeEquivalentTo(2)) + g.Expect(sts.Spec.Template.Spec.Containers[0].Resources.Limits.Memory()).ShouldNot(gomega.BeNil()) + g.Expect(sts.Spec.Template.Spec.Containers[0].Resources.Limits.Memory().String()).Should(gomega.BeEquivalentTo("800Mi")) + + sts = getSubsetByName(stsList, "subset-d") + g.Expect(sts.Spec).ShouldNot(gomega.BeNil()) + g.Expect(sts.Spec.Template.Spec.Containers[0].Env).ShouldNot(gomega.BeNil()) + g.Expect(sts.Spec.Template.Spec.Containers[0].Env[0].Name).Should(gomega.BeEquivalentTo("K8S_CONTAINER_NAME")) + g.Expect(sts.Spec.Template.Spec.Containers[0].Env[0].Value).Should(gomega.BeEquivalentTo("main")) +} + func TestStsSubsetProvisionWithToleration(t *testing.T) { g, requests, cancel, mgrStopped := setUp(t) defer func() { diff --git a/pkg/controller/uniteddeployment/uniteddeployment_update.go b/pkg/controller/uniteddeployment/uniteddeployment_update.go index c43a20e475..1b724e406f 100644 --- a/pkg/controller/uniteddeployment/uniteddeployment_update.go +++ b/pkg/controller/uniteddeployment/uniteddeployment_update.go @@ -30,9 +30,9 @@ import ( "github.com/openkruise/kruise/pkg/util" ) -func (r *ReconcileUnitedDeployment) manageSubsets(ud *appsv1alpha1.UnitedDeployment, nameToSubset *map[string]*Subset, nextReplicas, nextPartitions *map[string]int32, currentRevision, updatedRevision *appsv1.ControllerRevision, subsetType subSetType) (newStatus *appsv1alpha1.UnitedDeploymentStatus, updateErr error) { +func (r *ReconcileUnitedDeployment) manageSubsets(ud *appsv1alpha1.UnitedDeployment, nameToSubset *map[string]*Subset, nextUpdate map[string]SubsetUpdate, currentRevision, updatedRevision *appsv1.ControllerRevision, subsetType subSetType) (newStatus *appsv1alpha1.UnitedDeploymentStatus, updateErr error) { newStatus = ud.Status.DeepCopy() - exists, provisioned, err := r.manageSubsetProvision(ud, nameToSubset, nextReplicas, nextPartitions, currentRevision, updatedRevision, subsetType) + exists, provisioned, err := r.manageSubsetProvision(ud, nameToSubset, nextUpdate, currentRevision, updatedRevision, subsetType) if err != nil { SetUnitedDeploymentCondition(newStatus, NewUnitedDeploymentCondition(appsv1alpha1.SubsetProvisioned, corev1.ConditionFalse, "Error", err.Error())) return newStatus, fmt.Errorf("fail to manage Subset provision: %s", err) @@ -51,8 +51,9 @@ func (r *ReconcileUnitedDeployment) manageSubsets(ud *appsv1alpha1.UnitedDeploym for _, name := range exists.List() { subset := (*nameToSubset)[name] if r.subSetControls[subsetType].IsExpected(subset, expectedRevision.Name) || - subset.Spec.Replicas != (*nextReplicas)[name] || - subset.Spec.UpdateStrategy.Partition != (*nextPartitions)[name] { + subset.Spec.Replicas != nextUpdate[name].Replicas || + subset.Spec.UpdateStrategy.Partition != nextUpdate[name].Partition || + subset.GetAnnotations()[appsv1alpha1.AnnotationSubsetPatchKey] != nextUpdate[name].Patch { needUpdate = append(needUpdate, name) } } @@ -61,8 +62,8 @@ func (r *ReconcileUnitedDeployment) manageSubsets(ud *appsv1alpha1.UnitedDeploym _, updateErr = util.SlowStartBatch(len(needUpdate), slowStartInitialBatchSize, func(index int) error { cell := needUpdate[index] subset := (*nameToSubset)[cell] - replicas := (*nextReplicas)[cell] - partition := (*nextPartitions)[cell] + replicas := nextUpdate[cell].Replicas + partition := nextUpdate[cell].Partition klog.V(0).Infof("UnitedDeployment %s/%s needs to update Subset (%s) %s/%s with revision %s, replicas %d, partition %d", ud.Namespace, ud.Name, subsetType, subset.Namespace, subset.Name, expectedRevision.Name, replicas, partition) updateSubsetErr := r.subSetControls[subsetType].UpdateSubset(subset, ud, expectedRevision.Name, replicas, partition) @@ -81,7 +82,7 @@ func (r *ReconcileUnitedDeployment) manageSubsets(ud *appsv1alpha1.UnitedDeploym return } -func (r *ReconcileUnitedDeployment) manageSubsetProvision(ud *appsv1alpha1.UnitedDeployment, nameToSubset *map[string]*Subset, nextReplicas, nextPartitions *map[string]int32, currentRevision, updatedRevision *appsv1.ControllerRevision, subsetType subSetType) (sets.String, bool, error) { +func (r *ReconcileUnitedDeployment) manageSubsetProvision(ud *appsv1alpha1.UnitedDeployment, nameToSubset *map[string]*Subset, nextUpdate map[string]SubsetUpdate, currentRevision, updatedRevision *appsv1.ControllerRevision, subsetType subSetType) (sets.String, bool, error) { expectedSubsets := sets.String{} gotSubsets := sets.String{} @@ -117,8 +118,8 @@ func (r *ReconcileUnitedDeployment) manageSubsetProvision(ud *appsv1alpha1.Unite createdNum, createdErr = util.SlowStartBatch(len(creates), slowStartInitialBatchSize, func(idx int) error { subsetName := createdSubsets[idx] - replicas := (*nextReplicas)[subsetName] - partition := (*nextPartitions)[subsetName] + replicas := nextUpdate[subsetName].Replicas + partition := nextUpdate[subsetName].Partition err := r.subSetControls[subsetType].CreateSubset(ud, subsetName, revision, replicas, partition) if err != nil { if !errors.IsTimeout(err) {