// Returns a replica set that matches the intent of the given deployment. Returns nil if the new replica set doesn't exist yet. // 1. Get existing new RS (the RS that the given deployment targets, whose pod template is the same as deployment's). // 2. If there's existing new RS, update its revision number if it's smaller than (maxOldRevision + 1), where maxOldRevision is the max revision number among all old RSes. // 3. If there's no existing new RS and createIfNotExisted is true, create one with appropriate revision number (maxOldRevision + 1) and replicas. // Note that the pod-template-hash will be added to adopted RSes and pods. func (dc *DeploymentController) getNewReplicaSet(deployment *extensions.Deployment, rsList []extensions.ReplicaSet, maxOldRevision int64, oldRSs []*extensions.ReplicaSet, createIfNotExisted bool) (*extensions.ReplicaSet, error) { // Calculate revision number for this new replica set newRevision := strconv.FormatInt(maxOldRevision+1, 10) existingNewRS, err := deploymentutil.FindNewReplicaSet(deployment, rsList) if err != nil { return nil, err } else if existingNewRS != nil { // Set existing new replica set's annotation if deploymentutil.SetNewReplicaSetAnnotations(deployment, existingNewRS, newRevision, true) { return dc.client.Extensions().ReplicaSets(deployment.ObjectMeta.Namespace).Update(existingNewRS) } return existingNewRS, nil } if !createIfNotExisted { return nil, nil } // new ReplicaSet does not exist, create one. namespace := deployment.ObjectMeta.Namespace podTemplateSpecHash := podutil.GetPodTemplateSpecHash(deployment.Spec.Template) newRSTemplate := deploymentutil.GetNewReplicaSetTemplate(deployment) // Add podTemplateHash label to selector. newRSSelector := labelsutil.CloneSelectorAndAddLabel(deployment.Spec.Selector, extensions.DefaultDeploymentUniqueLabelKey, podTemplateSpecHash) // Create new ReplicaSet newRS := extensions.ReplicaSet{ ObjectMeta: api.ObjectMeta{ // Make the name deterministic, to ensure idempotence Name: deployment.Name + "-" + fmt.Sprintf("%d", podTemplateSpecHash), Namespace: namespace, }, Spec: extensions.ReplicaSetSpec{ Replicas: 0, Selector: newRSSelector, Template: newRSTemplate, }, } allRSs := append(oldRSs, &newRS) newReplicasCount, err := deploymentutil.NewRSNewReplicas(deployment, allRSs, &newRS) if err != nil { return nil, err } newRS.Spec.Replicas = newReplicasCount // Set new replica set's annotation deploymentutil.SetNewReplicaSetAnnotations(deployment, &newRS, newRevision, false) createdRS, err := dc.client.Extensions().ReplicaSets(namespace).Create(&newRS) if err != nil { return nil, fmt.Errorf("error creating replica set %v: %v", deployment.Name, err) } if newReplicasCount > 0 { dc.eventRecorder.Eventf(deployment, api.EventTypeNormal, "ScalingReplicaSet", "Scaled %s replica set %s to %d", "up", createdRS.Name, newReplicasCount) } return createdRS, dc.updateDeploymentRevision(deployment, newRevision) }
func TestDeploymentNotFoundError(t *testing.T) { name := "foo" ns := "default" deployment := &extensions.Deployment{ ObjectMeta: api.ObjectMeta{ Name: name, Namespace: ns, }, Spec: extensions.DeploymentSpec{ Replicas: 0, Selector: &unversioned.LabelSelector{MatchLabels: map[string]string{"k1": "v1"}}, }, Status: extensions.DeploymentStatus{ Replicas: 0, }, } template := deploymentutil.GetNewReplicaSetTemplate(deployment) fake := &testclient.Fake{} fake.AddReactor("get", "deployments", func(action testclient.Action) (handled bool, ret runtime.Object, err error) { return true, deployment, nil }) fake.AddReactor("list", "replicasets", func(action testclient.Action) (handled bool, ret runtime.Object, err error) { list := &extensions.ReplicaSetList{Items: []extensions.ReplicaSet{ { ObjectMeta: api.ObjectMeta{ Name: name, Namespace: ns, }, Spec: extensions.ReplicaSetSpec{ Template: template, }, }, }} return true, list, nil }) fake.AddReactor("get", "replicasets", func(action testclient.Action) (handled bool, ret runtime.Object, err error) { return true, nil, ScaleError{ActualError: errors.NewNotFound(api.Resource("replicaset"), "doesn't-matter")} }) reaper := DeploymentReaper{fake, time.Millisecond, time.Millisecond} if err := reaper.Stop(ns, name, 0, nil); err != nil { t.Fatalf("unexpected error: %#v", err) } }
func (dc *DeploymentController) rollbackToTemplate(deployment *extensions.Deployment, rs *extensions.ReplicaSet) (d *extensions.Deployment, performedRollback bool, err error) { if !reflect.DeepEqual(deploymentutil.GetNewReplicaSetTemplate(deployment), rs.Spec.Template) { glog.Infof("Rolling back deployment %s to template spec %+v", deployment.Name, rs.Spec.Template.Spec) deploymentutil.SetFromReplicaSetTemplate(deployment, rs.Spec.Template) // set RS (the old RS we'll rolling back to) annotations back to the deployment; // otherwise, the deployment's current annotations (should be the same as current new RS) will be copied to the RS after the rollback. // // For example, // A Deployment has old RS1 with annotation {change-cause:create}, and new RS2 {change-cause:edit}. // Note that both annotations are copied from Deployment, and the Deployment should be annotated {change-cause:edit} as well. // Now, rollback Deployment to RS1, we should update Deployment's pod-template and also copy annotation from RS1. // Deployment is now annotated {change-cause:create}, and we have new RS1 {change-cause:create}, old RS2 {change-cause:edit}. // // If we don't copy the annotations back from RS to deployment on rollback, the Deployment will stay as {change-cause:edit}, // and new RS1 becomes {change-cause:edit} (copied from deployment after rollback), old RS2 {change-cause:edit}, which is not correct. deploymentutil.SetDeploymentAnnotationsTo(deployment, rs) performedRollback = true } else { glog.V(4).Infof("Rolling back to a revision that contains the same template as current deployment %s, skipping rollback...", deployment.Name) dc.emitRollbackWarningEvent(deployment, deploymentutil.RollbackTemplateUnchanged, fmt.Sprintf("The rollback revision contains the same template as current deployment %q", deployment.Name)) } d, err = dc.updateDeploymentAndClearRollbackTo(deployment) return }
func TestDeploymentStop(t *testing.T) { name := "foo" ns := "default" deployment := extensions.Deployment{ ObjectMeta: api.ObjectMeta{ Name: name, Namespace: ns, }, Spec: extensions.DeploymentSpec{ Replicas: 0, Selector: &unversioned.LabelSelector{MatchLabels: map[string]string{"k1": "v1"}}, }, Status: extensions.DeploymentStatus{ Replicas: 0, }, } template := deploymentutil.GetNewReplicaSetTemplate(&deployment) tests := []struct { Name string Objs []runtime.Object StopError error ExpectedActions []string }{ { Name: "SimpleDeployment", Objs: []runtime.Object{ &extensions.Deployment{ // GET ObjectMeta: api.ObjectMeta{ Name: name, Namespace: ns, }, Spec: extensions.DeploymentSpec{ Replicas: 0, Selector: &unversioned.LabelSelector{MatchLabels: map[string]string{"k1": "v1"}}, }, Status: extensions.DeploymentStatus{ Replicas: 0, }, }, }, StopError: nil, ExpectedActions: []string{"get:deployments", "update:deployments", "get:deployments", "list:replicasets", "delete:deployments"}, }, { Name: "Deployment with single replicaset", Objs: []runtime.Object{ &deployment, // GET &extensions.ReplicaSetList{ // LIST Items: []extensions.ReplicaSet{ { ObjectMeta: api.ObjectMeta{ Name: name, Namespace: ns, }, Spec: extensions.ReplicaSetSpec{ Template: template, }, }, }, }, }, StopError: nil, ExpectedActions: []string{"get:deployments", "update:deployments", "get:deployments", "list:replicasets", "get:replicasets", "get:replicasets", "update:replicasets", "get:replicasets", "get:replicasets", "delete:replicasets", "delete:deployments"}, }, } for _, test := range tests { fake := testclient.NewSimpleFake(test.Objs...) reaper := DeploymentReaper{fake, time.Millisecond, time.Millisecond} err := reaper.Stop(ns, name, 0, nil) if !reflect.DeepEqual(err, test.StopError) { t.Errorf("%s unexpected error: %v", test.Name, err) continue } actions := fake.Actions() if len(actions) != len(test.ExpectedActions) { t.Errorf("%s unexpected actions: %v, expected %d actions got %d", test.Name, actions, len(test.ExpectedActions), len(actions)) continue } for i, expAction := range test.ExpectedActions { action := strings.Split(expAction, ":") if actions[i].GetVerb() != action[0] { t.Errorf("%s unexpected verb: %+v, expected %s", test.Name, actions[i], expAction) } if actions[i].GetResource() != action[1] { t.Errorf("%s unexpected resource: %+v, expected %s", test.Name, actions[i], expAction) } if len(action) == 3 && actions[i].GetSubresource() != action[2] { t.Errorf("%s unexpected subresource: %+v, expected %s", test.Name, actions[i], expAction) } } } }
// Returns a replica set that matches the intent of the given deployment. Returns nil if the new replica set doesn't exist yet. // 1. Get existing new RS (the RS that the given deployment targets, whose pod template is the same as deployment's). // 2. If there's existing new RS, update its revision number if it's smaller than (maxOldRevision + 1), where maxOldRevision is the max revision number among all old RSes. // 3. If there's no existing new RS and createIfNotExisted is true, create one with appropriate revision number (maxOldRevision + 1) and replicas. // Note that the pod-template-hash will be added to adopted RSes and pods. func (dc *DeploymentController) getNewReplicaSet(deployment *extensions.Deployment, rsList, oldRSs []*extensions.ReplicaSet, createIfNotExisted bool) (*extensions.ReplicaSet, error) { existingNewRS, err := deploymentutil.FindNewReplicaSet(deployment, rsList) if err != nil { return nil, err } // Calculate the max revision number among all old RSes maxOldRevision := deploymentutil.MaxRevision(oldRSs) // Calculate revision number for this new replica set newRevision := strconv.FormatInt(maxOldRevision+1, 10) // Latest replica set exists. We need to sync its annotations (includes copying all but // annotationsToSkip from the parent deployment, and update revision, desiredReplicas, // and maxReplicas) and also update the revision annotation in the deployment with the // latest revision. if existingNewRS != nil { objCopy, err := api.Scheme.Copy(existingNewRS) if err != nil { return nil, err } rsCopy := objCopy.(*extensions.ReplicaSet) // Set existing new replica set's annotation annotationsUpdated := deploymentutil.SetNewReplicaSetAnnotations(deployment, rsCopy, newRevision, true) minReadySecondsNeedsUpdate := rsCopy.Spec.MinReadySeconds != deployment.Spec.MinReadySeconds if annotationsUpdated || minReadySecondsNeedsUpdate { rsCopy.Spec.MinReadySeconds = deployment.Spec.MinReadySeconds return dc.client.Extensions().ReplicaSets(rsCopy.ObjectMeta.Namespace).Update(rsCopy) } updateConditions := deploymentutil.SetDeploymentRevision(deployment, newRevision) // If no other Progressing condition has been recorded and we need to estimate the progress // of this deployment then it is likely that old users started caring about progress. In that // case we need to take into account the first time we noticed their new replica set. cond := deploymentutil.GetDeploymentCondition(deployment.Status, extensions.DeploymentProgressing) if deployment.Spec.ProgressDeadlineSeconds != nil && cond == nil { msg := fmt.Sprintf("Found new replica set %q", rsCopy.Name) condition := deploymentutil.NewDeploymentCondition(extensions.DeploymentProgressing, v1.ConditionTrue, deploymentutil.FoundNewRSReason, msg) deploymentutil.SetDeploymentCondition(&deployment.Status, *condition) updateConditions = true } if updateConditions { if deployment, err = dc.client.Extensions().Deployments(deployment.Namespace).UpdateStatus(deployment); err != nil { return nil, err } } return rsCopy, nil } if !createIfNotExisted { return nil, nil } // new ReplicaSet does not exist, create one. namespace := deployment.Namespace podTemplateSpecHash := podutil.GetPodTemplateSpecHash(deployment.Spec.Template) newRSTemplate := deploymentutil.GetNewReplicaSetTemplate(deployment) // Add podTemplateHash label to selector. newRSSelector := labelsutil.CloneSelectorAndAddLabel(deployment.Spec.Selector, extensions.DefaultDeploymentUniqueLabelKey, podTemplateSpecHash) // Create new ReplicaSet newRS := extensions.ReplicaSet{ ObjectMeta: v1.ObjectMeta{ // Make the name deterministic, to ensure idempotence Name: deployment.Name + "-" + fmt.Sprintf("%d", podTemplateSpecHash), Namespace: namespace, }, Spec: extensions.ReplicaSetSpec{ Replicas: func(i int32) *int32 { return &i }(0), MinReadySeconds: deployment.Spec.MinReadySeconds, Selector: newRSSelector, Template: newRSTemplate, }, } allRSs := append(oldRSs, &newRS) newReplicasCount, err := deploymentutil.NewRSNewReplicas(deployment, allRSs, &newRS) if err != nil { return nil, err } *(newRS.Spec.Replicas) = newReplicasCount // Set new replica set's annotation deploymentutil.SetNewReplicaSetAnnotations(deployment, &newRS, newRevision, false) createdRS, err := dc.client.Extensions().ReplicaSets(namespace).Create(&newRS) switch { // We may end up hitting this due to a slow cache or a fast resync of the deployment. // TODO: Restore once https://github.com/kubernetes/kubernetes/issues/29735 is fixed // ie. we start using a new hashing algorithm. case errors.IsAlreadyExists(err): return nil, err // return dc.rsLister.ReplicaSets(namespace).Get(newRS.Name) case err != nil: msg := fmt.Sprintf("Failed to create new replica set %q: %v", newRS.Name, err) if deployment.Spec.ProgressDeadlineSeconds != nil { cond := deploymentutil.NewDeploymentCondition(extensions.DeploymentProgressing, v1.ConditionFalse, deploymentutil.FailedRSCreateReason, msg) deploymentutil.SetDeploymentCondition(&deployment.Status, *cond) // We don't really care about this error at this point, since we have a bigger issue to report. // TODO: Update the rest of the Deployment status, too. We may need to do this every time we // error out in all other places in the controller so that we let users know that their deployments // have been noticed by the controller, albeit with errors. // TODO: Identify which errors are permanent and switch DeploymentIsFailed to take into account // these reasons as well. Related issue: https://github.com/kubernetes/kubernetes/issues/18568 _, _ = dc.client.Extensions().Deployments(deployment.ObjectMeta.Namespace).UpdateStatus(deployment) } dc.eventRecorder.Eventf(deployment, v1.EventTypeWarning, deploymentutil.FailedRSCreateReason, msg) return nil, err } if newReplicasCount > 0 { dc.eventRecorder.Eventf(deployment, v1.EventTypeNormal, "ScalingReplicaSet", "Scaled up replica set %s to %d", createdRS.Name, newReplicasCount) } deploymentutil.SetDeploymentRevision(deployment, newRevision) if deployment.Spec.ProgressDeadlineSeconds != nil { msg := fmt.Sprintf("Created new replica set %q", createdRS.Name) condition := deploymentutil.NewDeploymentCondition(extensions.DeploymentProgressing, v1.ConditionTrue, deploymentutil.NewReplicaSetReason, msg) deploymentutil.SetDeploymentCondition(&deployment.Status, *condition) } _, err = dc.client.Extensions().Deployments(deployment.Namespace).UpdateStatus(deployment) return createdRS, err }
func TestGetDeploymentDetail(t *testing.T) { podList := &api.PodList{} eventList := &api.EventList{} deployment := createDeployment("dp-1", "ns-1", "pod-1", map[string]string{"track": "beta"}, map[string]string{"foo": "bar"}) podTemplateSpec := util.GetNewReplicaSetTemplate(deployment) newReplicaSet := createReplicaSet("rs-1", "ns-1", map[string]string{"foo": "bar"}, podTemplateSpec) replicaSetList := &extensions.ReplicaSetList{ Items: []extensions.ReplicaSet{ newReplicaSet, createReplicaSet("rs-2", "ns-1", map[string]string{"foo": "bar"}, podTemplateSpec), }, } cases := []struct { namespace, name string expectedActions []string deployment *extensions.Deployment expected *DeploymentDetail }{ { "ns-1", "dp-1", []string{"get", "list", "list", "get", "list", "get", "list", "list", "get", "list", "list", "list"}, deployment, &DeploymentDetail{ ObjectMeta: common.ObjectMeta{ Name: "dp-1", Namespace: "ns-1", Labels: map[string]string{"foo": "bar"}, }, TypeMeta: common.TypeMeta{Kind: common.ResourceKindDeployment}, PodList: pod.PodList{ Pods: []pod.Pod{}, CumulativeMetrics: make([]metric.Metric, 0), }, Selector: map[string]string{"foo": "bar"}, StatusInfo: StatusInfo{ Replicas: 4, Updated: 2, Available: 3, Unavailable: 1, }, Strategy: "RollingUpdate", MinReadySeconds: 5, RollingUpdateStrategy: &RollingUpdateStrategy{ MaxSurge: 1, MaxUnavailable: 1, }, OldReplicaSetList: replicasetlist.ReplicaSetList{ ReplicaSets: []replicaset.ReplicaSet{}, CumulativeMetrics: make([]metric.Metric, 0), }, NewReplicaSet: replicaset.ReplicaSet{ ObjectMeta: common.NewObjectMeta(newReplicaSet.ObjectMeta), TypeMeta: common.NewTypeMeta(common.ResourceKindReplicaSet), Pods: common.PodInfo{Warnings: []common.Event{}}, }, EventList: common.EventList{ Events: []common.Event{}, }, HorizontalPodAutoscalerList: horizontalpodautoscalerlist.HorizontalPodAutoscalerList{HorizontalPodAutoscalers: []horizontalpodautoscalerlist.HorizontalPodAutoscaler{}}, }, }, } for _, c := range cases { fakeClient := fake.NewSimpleClientset(c.deployment, replicaSetList, podList, eventList) dataselect.DefaultDataSelectWithMetrics.MetricQuery = dataselect.NoMetrics actual, _ := GetDeploymentDetail(fakeClient, nil, c.namespace, c.name) actions := fakeClient.Actions() if len(actions) != len(c.expectedActions) { t.Errorf("Unexpected actions: %v, expected %d actions got %d", actions, len(c.expectedActions), len(actions)) continue } for i, verb := range c.expectedActions { if actions[i].GetVerb() != verb { t.Errorf("Unexpected action: %+v, expected %s", actions[i], verb) } } if !reflect.DeepEqual(actual, c.expected) { t.Errorf("GetDeploymentDetail(client, namespace, name) == \ngot: %#v, \nexpected %#v", actual, c.expected) } } }
// Returns a replica set that matches the intent of the given deployment. Returns nil if the new replica set doesn't exist yet. // 1. Get existing new RS (the RS that the given deployment targets, whose pod template is the same as deployment's). // 2. If there's existing new RS, update its revision number if it's smaller than (maxOldRevision + 1), where maxOldRevision is the max revision number among all old RSes. // 3. If there's no existing new RS and createIfNotExisted is true, create one with appropriate revision number (maxOldRevision + 1) and replicas. // Note that the pod-template-hash will be added to adopted RSes and pods. func (dc *DeploymentController) getNewReplicaSet(deployment *extensions.Deployment, rsList, oldRSs []*extensions.ReplicaSet, createIfNotExisted bool) (*extensions.ReplicaSet, error) { existingNewRS, err := deploymentutil.FindNewReplicaSet(deployment, rsList) if err != nil { return nil, err } // Calculate the max revision number among all old RSes maxOldRevision := deploymentutil.MaxRevision(oldRSs) // Calculate revision number for this new replica set newRevision := strconv.FormatInt(maxOldRevision+1, 10) // Latest replica set exists. We need to sync its annotations (includes copying all but // annotationsToSkip from the parent deployment, and update revision, desiredReplicas, // and maxReplicas) and also update the revision annotation in the deployment with the // latest revision. if existingNewRS != nil { objCopy, err := api.Scheme.Copy(existingNewRS) if err != nil { return nil, err } rsCopy := objCopy.(*extensions.ReplicaSet) // Set existing new replica set's annotation if deploymentutil.SetNewReplicaSetAnnotations(deployment, rsCopy, newRevision, true) { if rsCopy, err = dc.client.Extensions().ReplicaSets(rsCopy.Namespace).Update(rsCopy); err != nil { return nil, err } } updateConditions := deploymentutil.SetDeploymentRevision(deployment, newRevision) if updateConditions { if deployment, err = dc.client.Extensions().Deployments(deployment.Namespace).UpdateStatus(deployment); err != nil { return nil, err } } return rsCopy, nil } if !createIfNotExisted { return nil, nil } // new ReplicaSet does not exist, create one. namespace := deployment.Namespace podTemplateSpecHash := podutil.GetPodTemplateSpecHash(deployment.Spec.Template) newRSTemplate := deploymentutil.GetNewReplicaSetTemplate(deployment) // Add podTemplateHash label to selector. newRSSelector := labelsutil.CloneSelectorAndAddLabel(deployment.Spec.Selector, extensions.DefaultDeploymentUniqueLabelKey, podTemplateSpecHash) // Create new ReplicaSet newRS := extensions.ReplicaSet{ ObjectMeta: api.ObjectMeta{ // Make the name deterministic, to ensure idempotence Name: deployment.Name + "-" + fmt.Sprintf("%d", podTemplateSpecHash), Namespace: namespace, }, Spec: extensions.ReplicaSetSpec{ Replicas: 0, Selector: newRSSelector, Template: newRSTemplate, }, } allRSs := append(oldRSs, &newRS) newReplicasCount, err := deploymentutil.NewRSNewReplicas(deployment, allRSs, &newRS) if err != nil { return nil, err } newRS.Spec.Replicas = newReplicasCount // Set new replica set's annotation deploymentutil.SetNewReplicaSetAnnotations(deployment, &newRS, newRevision, false) createdRS, err := dc.client.Extensions().ReplicaSets(namespace).Create(&newRS) if err != nil { return nil, fmt.Errorf("error creating replica set %v: %v", deployment.Name, err) } if newReplicasCount > 0 { dc.eventRecorder.Eventf(deployment, api.EventTypeNormal, "ScalingReplicaSet", "Scaled %s replica set %s to %d", "up", createdRS.Name, newReplicasCount) } deploymentutil.SetDeploymentRevision(deployment, newRevision) _, err = dc.client.Extensions().Deployments(deployment.Namespace).UpdateStatus(deployment) return createdRS, err }
func TestGetDeploymentDetail(t *testing.T) { podList := &api.PodList{} eventList := &api.EventList{} deployment := &extensions.Deployment{ ObjectMeta: api.ObjectMeta{ Name: "test-name", Labels: map[string]string{"track": "beta"}, }, Spec: extensions.DeploymentSpec{ Selector: &unversioned.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}, Replicas: 4, MinReadySeconds: 5, Strategy: extensions.DeploymentStrategy{ Type: extensions.RollingUpdateDeploymentStrategyType, RollingUpdate: &extensions.RollingUpdateDeployment{ MaxSurge: intstr.FromInt(1), MaxUnavailable: intstr.FromString("1"), }, }, Template: api.PodTemplateSpec{ ObjectMeta: api.ObjectMeta{ Name: "test-pod-name", Labels: map[string]string{"track": "beta"}, }, }, }, Status: extensions.DeploymentStatus{ Replicas: 4, UpdatedReplicas: 2, AvailableReplicas: 3, UnavailableReplicas: 1, }, } podTemplateSpec := deploymentutil.GetNewReplicaSetTemplate(deployment) newReplicaSet := extensions.ReplicaSet{ ObjectMeta: api.ObjectMeta{ Name: "replica-set-1", Namespace: "test-namespace", }, Spec: extensions.ReplicaSetSpec{ Template: podTemplateSpec, }, } replicaSetList := &extensions.ReplicaSetList{ Items: []extensions.ReplicaSet{ newReplicaSet, { ObjectMeta: api.ObjectMeta{ Name: "replica-set-2", Namespace: "test-namespace", }, }, }, } cases := []struct { namespace, name string expectedActions []string deployment *extensions.Deployment expected *DeploymentDetail }{ { "test-namespace", "test-name", []string{"get", "list", "list", "get", "list", "get", "list", "get", "list", "list", "list"}, deployment, &DeploymentDetail{ ObjectMeta: common.ObjectMeta{ Name: "test-name", Labels: map[string]string{"track": "beta"}, }, TypeMeta: common.TypeMeta{Kind: common.ResourceKindDeployment}, PodList: pod.PodList{ Pods: []pod.Pod{}, CumulativeMetrics: make([]metric.Metric, 0), }, Selector: map[string]string{"foo": "bar"}, StatusInfo: StatusInfo{ Replicas: 4, Updated: 2, Available: 3, Unavailable: 1, }, Strategy: "RollingUpdate", MinReadySeconds: 5, RollingUpdateStrategy: &RollingUpdateStrategy{ MaxSurge: 1, MaxUnavailable: 1, }, OldReplicaSetList: replicasetlist.ReplicaSetList{ ReplicaSets: []replicaset.ReplicaSet{}, CumulativeMetrics: make([]metric.Metric, 0), }, NewReplicaSet: replicaset.ReplicaSet{ ObjectMeta: common.NewObjectMeta(newReplicaSet.ObjectMeta), TypeMeta: common.NewTypeMeta(common.ResourceKindReplicaSet), Pods: common.PodInfo{Warnings: []common.Event{}}, }, EventList: common.EventList{ Events: []common.Event{}, }, }, }, } for _, c := range cases { fakeClient := testclient.NewSimpleFake(c.deployment, replicaSetList, podList, eventList) dataselect.DefaultDataSelectWithMetrics.MetricQuery = dataselect.NoMetrics actual, _ := GetDeploymentDetail(fakeClient, nil, c.namespace, c.name) actions := fakeClient.Actions() if len(actions) != len(c.expectedActions) { t.Errorf("Unexpected actions: %v, expected %d actions got %d", actions, len(c.expectedActions), len(actions)) continue } for i, verb := range c.expectedActions { if actions[i].GetVerb() != verb { t.Errorf("Unexpected action: %+v, expected %s", actions[i], verb) } } if !reflect.DeepEqual(actual, c.expected) { t.Errorf("GetDeploymentDetail(client, namespace, name) == \ngot: %#v, \nexpected %#v", actual, c.expected) } } }