// TestHandle_failedTest ensures that failed test deployments have their // replicas set to zero. func TestHandle_failedTest(t *testing.T) { var updatedDeployment *kapi.ReplicationController fake := &ktestclient.Fake{} fake.AddReactor("create", "pods", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) { t.Fatalf("unexpected call to create pod") return true, nil, nil }) fake.AddReactor("update", "replicationcontrollers", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) { rc := action.(ktestclient.UpdateAction).GetObject().(*kapi.ReplicationController) updatedDeployment = rc return true, rc, nil }) // Verify successful cleanup config := deploytest.TestDeploymentConfig(deploytest.OkDeploymentConfig(1)) deployment, _ := deployutil.MakeDeployment(config, codec) deployment.Spec.Replicas = 1 deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(deployapi.DeploymentStatusFailed) controller := okDeploymentController(fake, deployment, nil, true) if err := controller.Handle(deployment); err != nil { t.Fatalf("unexpected error: %v", err) } if updatedDeployment == nil { t.Fatal("deployment not updated") } if e, a := int32(0), updatedDeployment.Spec.Replicas; e != a { t.Fatalf("expected updated deployment replicas to be %d, got %d", e, a) } }
// TestHandle_canceledDeploymentTrigger ensures that a canceled deployment // will trigger a reconcilation of its deploymentconfig (via an annotation // update) so that rolling back can happen on the spot and not rely on the // deploymentconfig cache resync interval. func TestHandle_canceledDeploymentTriggerTest(t *testing.T) { var ( updatedDeployment *kapi.ReplicationController updatedConfig *deployapi.DeploymentConfig ) initial := deploytest.OkDeploymentConfig(1) // Canceled deployment deployment, _ := deployutil.MakeDeployment(deploytest.TestDeploymentConfig(initial), kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion)) deployment.Annotations[deployapi.DeploymentCancelledAnnotation] = deployapi.DeploymentCancelledAnnotationValue kFake := &ktestclient.Fake{} kFake.PrependReactor("get", "replicationcontrollers", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) { return true, deployment, nil }) kFake.PrependReactor("update", "replicationcontrollers", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) { updatedDeployment = deployment return true, deployment, nil }) fake := &testclient.Fake{} fake.PrependReactor("get", "deploymentconfigs", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) { config := initial return true, config, nil }) fake.PrependReactor("update", "deploymentconfigs", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) { updated := action.(ktestclient.UpdateAction).GetObject().(*deployapi.DeploymentConfig) updatedConfig = updated return true, updated, nil }) controller := &DeployerPodController{ decodeConfig: func(deployment *kapi.ReplicationController) (*deployapi.DeploymentConfig, error) { return deployutil.DecodeDeploymentConfig(deployment, kapi.Codecs.UniversalDecoder()) }, store: cache.NewStore(cache.MetaNamespaceKeyFunc), client: fake, kClient: kFake, } err := controller.Handle(terminatedPod(deployment)) if err != nil { t.Fatalf("unexpected error: %v", err) } if updatedDeployment == nil { t.Fatalf("expected deployment update") } if e, a := deployapi.DeploymentStatusFailed, deployutil.DeploymentStatusFor(updatedDeployment); e != a { t.Fatalf("expected updated deployment status %s, got %s", e, a) } if updatedConfig == nil { t.Fatalf("expected config update") } }
// TestHandle_failedTest ensures that failed test deployments have their // replicas set to zero. func TestHandle_failedTest(t *testing.T) { var updatedDeployment *kapi.ReplicationController controller := &DeploymentController{ decodeConfig: func(deployment *kapi.ReplicationController) (*deployapi.DeploymentConfig, error) { return deployutil.DecodeDeploymentConfig(deployment, kapi.Codecs.UniversalDecoder()) }, deploymentClient: &deploymentClientImpl{ updateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { updatedDeployment = deployment return deployment, nil }, }, podClient: &podClientImpl{ createPodFunc: func(namespace string, pod *kapi.Pod) (*kapi.Pod, error) { t.Fatalf("unexpected call to create pod") return nil, nil }, deletePodFunc: func(namespace, name string) error { t.Fatalf("unexpected call to delete pod") return nil }, getDeployerPodsForFunc: func(namespace, name string) ([]kapi.Pod, error) { t.Fatalf("unexpected call to deployer pods") return nil, nil }, }, makeContainer: func(strategy *deployapi.DeploymentStrategy) (*kapi.Container, error) { t.Fatalf("unexpected call to make container") return nil, nil }, recorder: &record.FakeRecorder{}, } // Verify successful cleanup config := deploytest.TestDeploymentConfig(deploytest.OkDeploymentConfig(1)) deployment, _ := deployutil.MakeDeployment(config, kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion)) deployment.Spec.Replicas = 1 deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(deployapi.DeploymentStatusFailed) if err := controller.Handle(deployment); err != nil { t.Fatalf("unexpected error: %v", err) } if updatedDeployment == nil { t.Fatal("deployment not updated") } if e, a := 0, updatedDeployment.Spec.Replicas; e != a { t.Fatalf("expected updated deployment replicas to be %d, got %d", e, a) } }
// TestHandle_cleanupPodOk ensures that deployer pods are cleaned up for // deployments in a completed state on test deployment configs, and // replicas is set back to zero. func TestHandle_cleanupPodOkTest(t *testing.T) { hookPods := []string{"pre", "post"} deletedPodNames := []string{} var updatedDeployment *kapi.ReplicationController fake := &ktestclient.Fake{} fake.AddReactor("delete", "pods", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) { name := action.(ktestclient.DeleteAction).GetName() deletedPodNames = append(deletedPodNames, name) return true, nil, nil }) fake.AddReactor("create", "pods", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) { t.Fatalf("unexpected call to create pod") return true, nil, nil }) fake.AddReactor("update", "replicationcontrollers", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) { rc := action.(ktestclient.UpdateAction).GetObject().(*kapi.ReplicationController) updatedDeployment = rc return true, rc, nil }) // Verify successful cleanup config := deploytest.TestDeploymentConfig(deploytest.OkDeploymentConfig(1)) deployment, _ := deployutil.MakeDeployment(config, codec) deployment.Spec.Replicas = 1 deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(deployapi.DeploymentStatusComplete) controller := okDeploymentController(fake, deployment, hookPods, true) hookPods = append(hookPods, deployment.Name) if err := controller.Handle(deployment); err != nil { t.Fatalf("unexpected error: %v", err) } sort.Strings(hookPods) sort.Strings(deletedPodNames) if !reflect.DeepEqual(deletedPodNames, deletedPodNames) { t.Fatalf("pod deletions - expected: %v, actual: %v", hookPods, deletedPodNames) } if updatedDeployment == nil { t.Fatal("deployment not updated") } if e, a := int32(0), updatedDeployment.Spec.Replicas; e != a { t.Fatalf("expected updated deployment replicas to be %d, got %d", e, a) } }
// TestHandle_podTerminatedFailNoContainerStatus ensures that a failed // deployer pod with no container status results in a transition of the // deployment's status to failed. func TestHandle_podTerminatedFailNoContainerStatusTest(t *testing.T) { var updatedDeployment *kapi.ReplicationController deployment, _ := deployutil.MakeDeployment(deploytest.TestDeploymentConfig(deploytest.OkDeploymentConfig(1)), kapi.Codec) deployment.Spec.Replicas = 1 // since we do not set the desired replicas annotation, // this also tests that the error is just logged and not result in a failure deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(deployapi.DeploymentStatusRunning) controller := &DeployerPodController{ decodeConfig: func(deployment *kapi.ReplicationController) (*deployapi.DeploymentConfig, error) { return deployutil.DecodeDeploymentConfig(deployment, kapi.Codec) }, deploymentClient: &deploymentClientImpl{ getDeploymentFunc: func(namespace, name string) (*kapi.ReplicationController, error) { return deployment, nil }, updateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { updatedDeployment = deployment return deployment, nil }, listDeploymentsForConfigFunc: func(namespace, configName string) (*kapi.ReplicationControllerList, error) { return &kapi.ReplicationControllerList{Items: []kapi.ReplicationController{*deployment}}, nil }, }, } err := controller.Handle(terminatedPod(deployment)) if err != nil { t.Fatalf("unexpected error: %v", err) } if updatedDeployment == nil { t.Fatalf("expected deployment update") } if e, a := deployapi.DeploymentStatusFailed, deployutil.DeploymentStatusFor(updatedDeployment); e != a { t.Fatalf("expected updated deployment status %s, got %s", e, a) } if e, a := 0, updatedDeployment.Spec.Replicas; e != a { t.Fatalf("expected updated deployment replicas to be %d, got %d", e, a) } }
// TestHandle_podTerminatedFailNoContainerStatus ensures that a failed // deployer pod with no container status results in a transition of the // deployment's status to failed. func TestHandle_podTerminatedFailNoContainerStatusTest(t *testing.T) { var updatedDeployment *kapi.ReplicationController deployment, _ := deployutil.MakeDeployment(deploytest.TestDeploymentConfig(deploytest.OkDeploymentConfig(1)), kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion)) deployment.Spec.Replicas = 1 // since we do not set the desired replicas annotation, // this also tests that the error is just logged and not result in a failure deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(deployapi.DeploymentStatusRunning) kFake := &ktestclient.Fake{} kFake.PrependReactor("get", "replicationcontrollers", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) { return true, deployment, nil }) kFake.PrependReactor("update", "replicationcontrollers", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) { updatedDeployment = deployment return true, deployment, nil }) controller := &DeployerPodController{ decodeConfig: func(deployment *kapi.ReplicationController) (*deployapi.DeploymentConfig, error) { return deployutil.DecodeDeploymentConfig(deployment, kapi.Codecs.UniversalDecoder()) }, store: cache.NewStore(cache.MetaNamespaceKeyFunc), kClient: kFake, } err := controller.Handle(terminatedPod(deployment)) if err != nil { t.Fatalf("unexpected error: %v", err) } if updatedDeployment == nil { t.Fatalf("expected deployment update") } if e, a := deployapi.DeploymentStatusFailed, deployutil.DeploymentStatusFor(updatedDeployment); e != a { t.Fatalf("expected updated deployment status %s, got %s", e, a) } if e, a := 0, updatedDeployment.Spec.Replicas; e != a { t.Fatalf("expected updated deployment replicas to be %d, got %d", e, a) } }
// TestHandle_unrelatedPodAlreadyExistsTestScaled ensures that attempts to create a // deployer pod, when a pod with the same name but be scaled to zero results // a transition to failed. func TestHandle_unrelatedPodAlreadyExistsTestScaled(t *testing.T) { var updatedDeployment *kapi.ReplicationController config := deploytest.TestDeploymentConfig(deploytest.OkDeploymentConfig(1)) deployment, _ := deployutil.MakeDeployment(config, codec) deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(deployapi.DeploymentStatusNew) deployment.Spec.Replicas = 1 fake := &ktestclient.Fake{} fake.AddReactor("create", "pods", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) { name := action.(ktestclient.CreateAction).GetObject().(*kapi.Pod).Name return true, nil, kerrors.NewAlreadyExists(kapi.Resource("Pod"), name) }) fake.AddReactor("update", "replicationcontrollers", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) { rc := action.(ktestclient.UpdateAction).GetObject().(*kapi.ReplicationController) updatedDeployment = rc return true, rc, nil }) controller := okDeploymentController(fake, deployment, nil, false, kapi.PodRunning) if err := controller.Handle(deployment); err != nil { t.Fatalf("unexpected error: %v", err) } if _, exists := updatedDeployment.Annotations[deployapi.DeploymentPodAnnotation]; exists { t.Fatalf("deployment updated with pod name annotation") } if e, a := deployapi.DeploymentFailedUnrelatedDeploymentExists, updatedDeployment.Annotations[deployapi.DeploymentStatusReasonAnnotation]; e != a { t.Fatalf("expected reason annotation %s, got %s", e, a) } if e, a := deployapi.DeploymentStatusFailed, deployutil.DeploymentStatusFor(updatedDeployment); e != a { t.Fatalf("expected deployment status %s, got %s", e, a) } if e, a := int32(0), updatedDeployment.Spec.Replicas; e != a { t.Fatalf("expected failed deployment to be scaled to zero: %d", a) } }
// TestHandle_podTerminatedOk ensures that a successfully completed deployer // pod results in a transition of the deployment's status to complete. func TestHandle_podTerminatedOkTest(t *testing.T) { deployment, _ := deployutil.MakeDeployment(deploytest.TestDeploymentConfig(deploytest.OkDeploymentConfig(1)), kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion)) deployment.Spec.Replicas = 1 deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(deployapi.DeploymentStatusRunning) var updatedDeployment *kapi.ReplicationController kFake := &ktestclient.Fake{} kFake.PrependReactor("get", "replicationcontrollers", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) { return true, deployment, nil }) kFake.PrependReactor("update", "replicationcontrollers", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) { updatedDeployment = deployment return true, deployment, nil }) controller := &DeployerPodController{ decodeConfig: func(deployment *kapi.ReplicationController) (*deployapi.DeploymentConfig, error) { return deployutil.DecodeDeploymentConfig(deployment, kapi.Codecs.UniversalDecoder()) }, store: cache.NewStore(cache.MetaNamespaceKeyFunc), kClient: kFake, } err := controller.Handle(succeededPod(deployment)) if err != nil { t.Fatalf("unexpected error: %v", err) } if updatedDeployment == nil { t.Fatalf("expected deployment update") } if e, a := deployapi.DeploymentStatusComplete, deployutil.DeploymentStatusFor(updatedDeployment); e != a { t.Fatalf("expected updated deployment status %s, got %s", e, a) } if e, a := int32(0), updatedDeployment.Spec.Replicas; e != a { t.Fatalf("expected updated deployment replicas to be %d, got %d", e, a) } }
// TestHandle_podTerminatedOk ensures that a successfully completed deployer // pod results in a transition of the deployment's status to complete. func TestHandle_podTerminatedOkTest(t *testing.T) { deployment, _ := deployutil.MakeDeployment(deploytest.TestDeploymentConfig(deploytest.OkDeploymentConfig(1)), kapi.Codec) deployment.Spec.Replicas = 1 deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(deployapi.DeploymentStatusRunning) var updatedDeployment *kapi.ReplicationController controller := &DeployerPodController{ decodeConfig: func(deployment *kapi.ReplicationController) (*deployapi.DeploymentConfig, error) { return deployutil.DecodeDeploymentConfig(deployment, kapi.Codec) }, deploymentClient: &deploymentClientImpl{ getDeploymentFunc: func(namespace, name string) (*kapi.ReplicationController, error) { return deployment, nil }, updateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { updatedDeployment = deployment return deployment, nil }, }, } err := controller.Handle(succeededPod(deployment)) if err != nil { t.Fatalf("unexpected error: %v", err) } if updatedDeployment == nil { t.Fatalf("expected deployment update") } if e, a := deployapi.DeploymentStatusComplete, deployutil.DeploymentStatusFor(updatedDeployment); e != a { t.Fatalf("expected updated deployment status %s, got %s", e, a) } if e, a := 0, updatedDeployment.Spec.Replicas; e != a { t.Fatalf("expected updated deployment replicas to be %d, got %d", e, a) } }
// TestHandle_cleanupPodOk ensures that deployer pods are cleaned up for // deployments in a completed state on test deployment configs, and // replicas is set back to zero. func TestHandle_cleanupPodOkTest(t *testing.T) { deployerPodNames := []string{"pod1", "pod2", "pod3"} deletedPodNames := []string{} var updatedDeployment *kapi.ReplicationController controller := &DeploymentController{ decodeConfig: func(deployment *kapi.ReplicationController) (*deployapi.DeploymentConfig, error) { return deployutil.DecodeDeploymentConfig(deployment, kapi.Codecs.UniversalDecoder()) }, deploymentClient: &deploymentClientImpl{ updateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { updatedDeployment = deployment return deployment, nil }, }, podClient: &podClientImpl{ createPodFunc: func(namespace string, pod *kapi.Pod) (*kapi.Pod, error) { t.Fatalf("unexpected call to create pod") return nil, nil }, deletePodFunc: func(namespace, name string) error { deletedPodNames = append(deletedPodNames, name) return nil }, getDeployerPodsForFunc: func(namespace, name string) ([]kapi.Pod, error) { pods := []kapi.Pod{} for _, podName := range deployerPodNames { pod := *ttlNonZeroPod() pod.Name = podName pods = append(pods, pod) } return pods, nil }, }, makeContainer: func(strategy *deployapi.DeploymentStrategy) (*kapi.Container, error) { t.Fatalf("unexpected call to make container") return nil, nil }, recorder: &record.FakeRecorder{}, } // Verify successful cleanup config := deploytest.TestDeploymentConfig(deploytest.OkDeploymentConfig(1)) deployment, _ := deployutil.MakeDeployment(config, kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion)) deployment.Spec.Replicas = 1 deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(deployapi.DeploymentStatusComplete) err := controller.Handle(deployment) if err != nil { t.Fatalf("unexpected error: %v", err) } sort.Strings(deployerPodNames) sort.Strings(deletedPodNames) if !reflect.DeepEqual(deletedPodNames, deletedPodNames) { t.Fatalf("pod deletions - expected: %v, actual: %v", deployerPodNames, deletedPodNames) } if updatedDeployment == nil { t.Fatal("deployment not updated") } if e, a := 0, updatedDeployment.Spec.Replicas; e != a { t.Fatalf("expected updated deployment replicas to be %d, got %d", e, a) } }
func TestHandleScenarios(t *testing.T) { type deployment struct { // version is the deployment version version int64 // replicas is the spec replicas of the deployment replicas int32 // test is whether this is a test deployment config test bool // replicasA is the annotated replica value for backwards compat checks replicasA *int32 desiredA *int32 status deployapi.DeploymentStatus cancelled bool } mkdeployment := func(d deployment) kapi.ReplicationController { config := deploytest.OkDeploymentConfig(d.version) if d.test { config = deploytest.TestDeploymentConfig(config) } deployment, _ := deployutil.MakeDeployment(config, kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion)) deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(d.status) if d.cancelled { deployment.Annotations[deployapi.DeploymentCancelledAnnotation] = deployapi.DeploymentCancelledAnnotationValue deployment.Annotations[deployapi.DeploymentStatusReasonAnnotation] = deployapi.DeploymentCancelledNewerDeploymentExists } if d.replicasA != nil { deployment.Annotations[deployapi.DeploymentReplicasAnnotation] = strconv.Itoa(int(*d.replicasA)) } else { delete(deployment.Annotations, deployapi.DeploymentReplicasAnnotation) } if d.desiredA != nil { deployment.Annotations[deployapi.DesiredReplicasAnnotation] = strconv.Itoa(int(*d.desiredA)) } else { delete(deployment.Annotations, deployapi.DesiredReplicasAnnotation) } deployment.Spec.Replicas = d.replicas return *deployment } tests := []struct { name string // replicas is the config replicas prior to the update replicas int32 // test is whether this is a test deployment config test bool // newVersion is the version of the config at the time of the update newVersion int64 // expectedReplicas is the expected config replica count after the update expectedReplicas int32 // before is the state of all deployments prior to the update before []deployment // after is the expected state of all deployments after the update after []deployment // errExpected is whether the update should produce an error errExpected bool }{ { name: "version is zero", replicas: 1, newVersion: 0, expectedReplicas: 1, before: []deployment{}, after: []deployment{}, errExpected: false, }, { name: "first deployment", replicas: 1, newVersion: 1, expectedReplicas: 1, before: []deployment{}, after: []deployment{ {version: 1, replicas: 0, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusNew, cancelled: false}, }, errExpected: false, }, { name: "initial deployment already in progress", replicas: 1, newVersion: 1, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 1, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusNew, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 1, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusNew, cancelled: false}, }, errExpected: false, }, { name: "new version", replicas: 1, newVersion: 2, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 1, replicasA: newInt32(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 1, replicasA: newInt32(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusNew, cancelled: false}, }, errExpected: false, }, { name: "already in progress", replicas: 1, newVersion: 2, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 1, replicasA: newInt32(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusNew, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 1, replicasA: newInt32(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusNew, cancelled: false}, }, errExpected: false, }, { name: "already deployed", replicas: 1, newVersion: 1, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 1, replicasA: newInt32(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 1, replicasA: newInt32(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, }, errExpected: false, }, { name: "awaiting cancellation of older deployments", replicas: 1, newVersion: 3, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 1, replicasA: newInt32(1), desiredA: newInt32(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 1, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusRunning, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 1, replicasA: newInt32(1), desiredA: newInt32(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 1, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusRunning, cancelled: true}, }, errExpected: true, }, { name: "awaiting cancellation of older deployments (already cancelled)", replicas: 1, newVersion: 2, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 1, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusRunning, cancelled: true}, }, after: []deployment{ {version: 1, replicas: 1, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusRunning, cancelled: true}, }, errExpected: true, }, { name: "steady state replica corrections (latest == active)", replicas: 1, newVersion: 5, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 0, replicasA: newInt32(0), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 1, replicasA: newInt32(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 3, replicas: 1, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, {version: 4, replicas: 0, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: false}, {version: 5, replicas: 1, replicasA: newInt32(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 0, replicasA: newInt32(0), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, replicasA: newInt32(0), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 3, replicas: 0, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, {version: 4, replicas: 0, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: false}, {version: 5, replicas: 1, replicasA: newInt32(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, }, errExpected: false, }, { name: "steady state replica corrections (latest != active)", replicas: 1, newVersion: 5, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 0, replicasA: newInt32(0), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 1, replicasA: newInt32(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 3, replicas: 1, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, {version: 4, replicas: 1, replicasA: newInt32(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 5, replicas: 1, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 0, replicasA: newInt32(0), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, replicasA: newInt32(0), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 3, replicas: 0, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, {version: 4, replicas: 1, replicasA: newInt32(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 5, replicas: 0, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: false}, }, errExpected: false, }, { name: "already deployed, no active deployment", replicas: 1, newVersion: 2, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 0, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: false}, {version: 2, replicas: 0, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 0, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: false}, {version: 2, replicas: 0, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: false}, }, errExpected: false, }, { name: "scale up latest/active completed deployment", replicas: 5, newVersion: 2, expectedReplicas: 5, before: []deployment{ {version: 1, replicas: 0, replicasA: newInt32(0), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 1, replicasA: newInt32(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 0, replicasA: newInt32(0), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 5, replicasA: newInt32(5), status: deployapi.DeploymentStatusComplete, cancelled: false}, }, errExpected: false, }, { name: "scale up active (not latest) completed deployment", replicas: 5, newVersion: 2, expectedReplicas: 5, before: []deployment{ {version: 1, replicas: 1, replicasA: newInt32(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, }, after: []deployment{ {version: 1, replicas: 5, replicasA: newInt32(5), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, }, errExpected: false, }, { name: "scale down latest/active completed deployment", replicas: 1, newVersion: 2, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 0, replicasA: newInt32(0), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 5, replicasA: newInt32(5), status: deployapi.DeploymentStatusComplete, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 0, replicasA: newInt32(0), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 1, replicasA: newInt32(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, }, errExpected: false, }, { name: "scale down active (not latest) completed deployment", replicas: 1, newVersion: 2, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 5, replicasA: newInt32(5), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, }, after: []deployment{ {version: 1, replicas: 1, replicasA: newInt32(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, }, errExpected: false, }, { name: "fallback to last completed deployment", replicas: 1, newVersion: 2, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 0, replicasA: newInt32(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, }, after: []deployment{ {version: 1, replicas: 1, replicasA: newInt32(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, }, errExpected: false, }, { name: "fallback to last completed deployment (partial rollout)", replicas: 5, newVersion: 2, expectedReplicas: 5, before: []deployment{ {version: 1, replicas: 2, replicasA: newInt32(5), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 2, replicasA: newInt32(0), desiredA: newInt32(5), status: deployapi.DeploymentStatusFailed, cancelled: true}, }, after: []deployment{ {version: 1, replicas: 5, replicasA: newInt32(5), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, replicasA: newInt32(0), desiredA: newInt32(5), status: deployapi.DeploymentStatusFailed, cancelled: true}, }, errExpected: false, }, // The cases below will exercise backwards compatibility for resources // which predate the use of the replica annotation. { name: "(compat) initial deployment already in progress", replicas: 1, newVersion: 1, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 1, desiredA: newInt32(1), status: deployapi.DeploymentStatusNew, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 1, desiredA: newInt32(1), status: deployapi.DeploymentStatusNew, cancelled: false}, }, errExpected: false, }, { name: "(compat) new version", replicas: 1, newVersion: 2, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 1, status: deployapi.DeploymentStatusComplete, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 1, status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusNew, cancelled: false}, }, errExpected: false, }, { name: "(compat) already in progress", replicas: 1, newVersion: 2, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 1, status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, desiredA: newInt32(1), status: deployapi.DeploymentStatusNew, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 1, status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, desiredA: newInt32(1), status: deployapi.DeploymentStatusNew, cancelled: false}, }, errExpected: false, }, { name: "(compat) already deployed", replicas: 1, newVersion: 1, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 1, status: deployapi.DeploymentStatusComplete, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 1, replicasA: newInt32(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, }, errExpected: false, }, { name: "(compat) awaiting cancellation of older deployments", replicas: 1, newVersion: 3, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 1, desiredA: newInt32(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 1, desiredA: newInt32(1), status: deployapi.DeploymentStatusRunning, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 1, desiredA: newInt32(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 1, desiredA: newInt32(1), status: deployapi.DeploymentStatusRunning, cancelled: true}, }, errExpected: true, }, { name: "(compat) awaiting cancellation of older deployments (already cancelled)", replicas: 1, newVersion: 2, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 1, desiredA: newInt32(1), status: deployapi.DeploymentStatusRunning, cancelled: true}, }, after: []deployment{ {version: 1, replicas: 1, desiredA: newInt32(1), status: deployapi.DeploymentStatusRunning, cancelled: true}, }, errExpected: true, }, { name: "(compat) steady state replica corrections (latest == active)", replicas: 5, newVersion: 5, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 0, status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 1, status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 3, replicas: 1, desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, {version: 4, replicas: 0, desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: false}, {version: 5, replicas: 1, status: deployapi.DeploymentStatusComplete, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 0, replicasA: newInt32(0), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, replicasA: newInt32(0), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 3, replicas: 0, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, {version: 4, replicas: 0, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: false}, {version: 5, replicas: 1, replicasA: newInt32(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, }, errExpected: false, }, { name: "(compat) steady state replica corrections of a test config (latest == active)", test: true, replicas: 5, newVersion: 5, expectedReplicas: 5, before: []deployment{ {version: 1, replicas: 0, status: deployapi.DeploymentStatusComplete, cancelled: false, test: true}, {version: 2, replicas: 1, status: deployapi.DeploymentStatusComplete, cancelled: false, test: true}, {version: 3, replicas: 1, desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: true, test: true}, {version: 4, replicas: 0, desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: false, test: true}, {version: 5, replicas: 1, status: deployapi.DeploymentStatusComplete, cancelled: false, test: true}, }, after: []deployment{ {version: 1, replicas: 0, replicasA: newInt32(0), status: deployapi.DeploymentStatusComplete, cancelled: false, test: true}, {version: 2, replicas: 0, replicasA: newInt32(0), status: deployapi.DeploymentStatusComplete, cancelled: false, test: true}, {version: 3, replicas: 0, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: true, test: true}, {version: 4, replicas: 0, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: false, test: true}, {version: 5, replicas: 0, replicasA: newInt32(0), status: deployapi.DeploymentStatusComplete, cancelled: false, test: true}, }, errExpected: false, }, { name: "(compat) steady state replica corrections (latest != active)", replicas: 5, newVersion: 5, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 0, status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 1, status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 3, replicas: 1, desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, {version: 4, replicas: 0, status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 5, replicas: 0, desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 0, replicasA: newInt32(0), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, replicasA: newInt32(0), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 3, replicas: 0, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, {version: 4, replicas: 1, replicasA: newInt32(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 5, replicas: 0, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: false}, }, errExpected: false, }, { name: "(compat) already deployed, no active deployment", replicas: 2, newVersion: 2, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 0, desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: false}, {version: 2, replicas: 0, desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 0, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: false}, {version: 2, replicas: 0, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: false}, }, errExpected: false, }, { name: "(compat) scale up latest/active completed deployment", replicas: 1, newVersion: 2, expectedReplicas: 5, before: []deployment{ {version: 1, replicas: 0, status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 5, status: deployapi.DeploymentStatusComplete, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 0, replicasA: newInt32(0), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 5, replicasA: newInt32(5), status: deployapi.DeploymentStatusComplete, cancelled: false}, }, errExpected: false, }, { name: "(compat) scale up latest/active completed test deployment", test: true, replicas: 1, newVersion: 2, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 0, status: deployapi.DeploymentStatusComplete, cancelled: false, test: true}, {version: 2, replicas: 5, status: deployapi.DeploymentStatusComplete, cancelled: false, test: true}, }, after: []deployment{ {version: 1, replicas: 0, replicasA: newInt32(0), status: deployapi.DeploymentStatusComplete, cancelled: false, test: true}, {version: 2, replicas: 0, replicasA: newInt32(0), status: deployapi.DeploymentStatusComplete, cancelled: false, test: true}, }, errExpected: false, }, { name: "(compat) scale up latest/active running test deployment", test: true, replicas: 1, newVersion: 2, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 0, status: deployapi.DeploymentStatusComplete, cancelled: false, test: true}, {version: 2, replicas: 5, status: deployapi.DeploymentStatusRunning, cancelled: false, test: true}, }, after: []deployment{ {version: 1, replicas: 0, replicasA: nil, status: deployapi.DeploymentStatusComplete, cancelled: false, test: true}, {version: 2, replicas: 5, replicasA: nil, status: deployapi.DeploymentStatusRunning, cancelled: false, test: true}, }, errExpected: false, }, // No longer supported. { name: "(compat) scale up active (not latest) completed deployment (RC targetted directly)", replicas: 2, newVersion: 2, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 5, status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, }, after: []deployment{ {version: 1, replicas: 1, replicasA: newInt32(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, }, errExpected: false, }, { name: "(compat) scale up active (not latest) completed deployment (RC targetted via oc)", replicas: 1, newVersion: 2, expectedReplicas: 2, before: []deployment{ {version: 1, replicas: 2, status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 5, desiredA: newInt32(2), status: deployapi.DeploymentStatusFailed, cancelled: true}, }, after: []deployment{ {version: 1, replicas: 2, replicasA: newInt32(2), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, replicasA: newInt32(0), desiredA: newInt32(2), status: deployapi.DeploymentStatusFailed, cancelled: true}, }, errExpected: false, }, { name: "(compat) fallback to last completed deployment", replicas: 3, newVersion: 2, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 0, status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, }, after: []deployment{ {version: 1, replicas: 1, replicasA: newInt32(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, }, errExpected: false, }, // The cases below exercise old clients performing scaling operations // against new resources and controller behavior. { name: "(compat-2) scale up latest/active completed deployment (via oc)", replicas: 1, newVersion: 2, expectedReplicas: 2, before: []deployment{ {version: 1, replicas: 0, replicasA: newInt32(0), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 2, replicasA: newInt32(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 0, replicasA: newInt32(0), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 2, replicasA: newInt32(2), status: deployapi.DeploymentStatusComplete, cancelled: false}, }, errExpected: false, }, { name: "(compat-2) scale up active (not latest) completed deployment (via oc)", replicas: 1, newVersion: 2, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 1, replicasA: newInt32(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 2, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, }, after: []deployment{ {version: 1, replicas: 1, replicasA: newInt32(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, replicasA: newInt32(0), desiredA: newInt32(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, }, errExpected: false, }, } for _, test := range tests { t.Logf("evaluating test: %s", test.name) deployments := map[string]kapi.ReplicationController{} toStore := []kapi.ReplicationController{} for _, template := range test.before { deployment := mkdeployment(template) deployments[deployment.Name] = deployment toStore = append(toStore, deployment) } oc := &testclient.Fake{} kc := &ktestclient.Fake{} kc.AddReactor("create", "replicationcontrollers", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) { rc := action.(ktestclient.CreateAction).GetObject().(*kapi.ReplicationController) deployments[rc.Name] = *rc return true, rc, nil }) kc.AddReactor("update", "replicationcontrollers", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) { rc := action.(ktestclient.UpdateAction).GetObject().(*kapi.ReplicationController) deployments[rc.Name] = *rc return true, rc, nil }) codec := kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion) dcInformer := framework.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options kapi.ListOptions) (runtime.Object, error) { return oc.DeploymentConfigs(kapi.NamespaceAll).List(options) }, WatchFunc: func(options kapi.ListOptions) (watch.Interface, error) { return oc.DeploymentConfigs(kapi.NamespaceAll).Watch(options) }, }, &deployapi.DeploymentConfig{}, 2*time.Minute, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, ) rcInformer := framework.NewSharedIndexInformer( &cache.ListWatch{ ListFunc: func(options kapi.ListOptions) (runtime.Object, error) { return kc.ReplicationControllers(kapi.NamespaceAll).List(options) }, WatchFunc: func(options kapi.ListOptions) (watch.Interface, error) { return kc.ReplicationControllers(kapi.NamespaceAll).Watch(options) }, }, &kapi.ReplicationController{}, 2*time.Minute, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, ) c := NewDeploymentConfigController(dcInformer, rcInformer, oc, kc, codec) for i := range toStore { c.rcStore.Add(&toStore[i]) } config := deploytest.OkDeploymentConfig(test.newVersion) if test.test { config = deploytest.TestDeploymentConfig(config) } config.Spec.Replicas = test.replicas if err := c.Handle(config); err != nil && !test.errExpected { t.Errorf("unexpected error: %s", err) continue } expectedDeployments := []kapi.ReplicationController{} for _, template := range test.after { expectedDeployments = append(expectedDeployments, mkdeployment(template)) } actualDeployments := []kapi.ReplicationController{} for _, deployment := range deployments { actualDeployments = append(actualDeployments, deployment) } sort.Sort(deployutil.ByLatestVersionDesc(expectedDeployments)) sort.Sort(deployutil.ByLatestVersionDesc(actualDeployments)) if e, a := test.expectedReplicas, config.Spec.Replicas; e != a { t.Errorf("expected config replicas to be %d, got %d", e, a) continue } for i := 0; i < len(expectedDeployments); i++ { expected, actual := expectedDeployments[i], actualDeployments[i] if !kapi.Semantic.DeepEqual(expected, actual) { t.Errorf("actual deployment don't match expected: %v", diff.ObjectDiff(expected, actual)) } } } }
func TestHandleScenarios(t *testing.T) { type deployment struct { // version is the deployment version version int // replicas is the spec replicas of the deployment replicas int // test is whether this is a test deployment config test bool // replicasA is the annotated replica value for backwards compat checks replicasA *int desiredA *int status deployapi.DeploymentStatus cancelled bool } mkdeployment := func(d deployment) kapi.ReplicationController { config := deploytest.OkDeploymentConfig(d.version) if d.test { config = deploytest.TestDeploymentConfig(config) } deployment, _ := deployutil.MakeDeployment(config, kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion)) deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(d.status) if d.cancelled { deployment.Annotations[deployapi.DeploymentCancelledAnnotation] = deployapi.DeploymentCancelledAnnotationValue deployment.Annotations[deployapi.DeploymentStatusReasonAnnotation] = deployapi.DeploymentCancelledNewerDeploymentExists } if d.replicasA != nil { deployment.Annotations[deployapi.DeploymentReplicasAnnotation] = strconv.Itoa(*d.replicasA) } else { delete(deployment.Annotations, deployapi.DeploymentReplicasAnnotation) } if d.desiredA != nil { deployment.Annotations[deployapi.DesiredReplicasAnnotation] = strconv.Itoa(*d.desiredA) } else { delete(deployment.Annotations, deployapi.DesiredReplicasAnnotation) } deployment.Spec.Replicas = d.replicas return *deployment } tests := []struct { name string // replicas is the config replicas prior to the update replicas int // test is whether this is a test deployment config test bool // newVersion is the version of the config at the time of the update newVersion int // expectedReplicas is the expected config replica count after the update expectedReplicas int // before is the state of all deployments prior to the update before []deployment // after is the expected state of all deployments after the update after []deployment // errExpected is whether the update should produce an error errExpected bool }{ { name: "version is zero", replicas: 1, newVersion: 0, expectedReplicas: 1, before: []deployment{}, after: []deployment{}, errExpected: false, }, { name: "first deployment", replicas: 1, newVersion: 1, expectedReplicas: 1, before: []deployment{}, after: []deployment{ {version: 1, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusNew, cancelled: false}, }, errExpected: false, }, { name: "initial deployment already in progress", replicas: 1, newVersion: 1, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 1, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusNew, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 1, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusNew, cancelled: false}, }, errExpected: false, }, { name: "new version", replicas: 1, newVersion: 2, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusNew, cancelled: false}, }, errExpected: false, }, { name: "already in progress", replicas: 1, newVersion: 2, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusNew, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusNew, cancelled: false}, }, errExpected: false, }, { name: "already deployed", replicas: 1, newVersion: 1, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, }, errExpected: false, }, { name: "awaiting cancellation of older deployments", replicas: 1, newVersion: 3, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 1, replicasA: newint(1), desiredA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 1, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusRunning, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 1, replicasA: newint(1), desiredA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 1, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusRunning, cancelled: true}, }, errExpected: true, }, { name: "awaiting cancellation of older deployments (already cancelled)", replicas: 1, newVersion: 2, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 1, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusRunning, cancelled: true}, }, after: []deployment{ {version: 1, replicas: 1, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusRunning, cancelled: true}, }, errExpected: true, }, { name: "steady state replica corrections (latest == active)", replicas: 1, newVersion: 5, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 0, replicasA: newint(0), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 3, replicas: 1, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, {version: 4, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: false}, {version: 5, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 0, replicasA: newint(0), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, replicasA: newint(0), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 3, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, {version: 4, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: false}, {version: 5, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, }, errExpected: false, }, { name: "steady state replica corrections (latest != active)", replicas: 1, newVersion: 5, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 0, replicasA: newint(0), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 3, replicas: 1, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, {version: 4, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 5, replicas: 1, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 0, replicasA: newint(0), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, replicasA: newint(0), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 3, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, {version: 4, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 5, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: false}, }, errExpected: false, }, { name: "already deployed, no active deployment", replicas: 1, newVersion: 2, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: false}, {version: 2, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: false}, {version: 2, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: false}, }, errExpected: false, }, { name: "scale up latest/active completed deployment", replicas: 5, newVersion: 2, expectedReplicas: 5, before: []deployment{ {version: 1, replicas: 0, replicasA: newint(0), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 0, replicasA: newint(0), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 5, replicasA: newint(5), status: deployapi.DeploymentStatusComplete, cancelled: false}, }, errExpected: false, }, { name: "scale up active (not latest) completed deployment", replicas: 5, newVersion: 2, expectedReplicas: 5, before: []deployment{ {version: 1, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, }, after: []deployment{ {version: 1, replicas: 5, replicasA: newint(5), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, }, errExpected: false, }, { name: "scale down latest/active completed deployment", replicas: 1, newVersion: 2, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 0, replicasA: newint(0), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 5, replicasA: newint(5), status: deployapi.DeploymentStatusComplete, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 0, replicasA: newint(0), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, }, errExpected: false, }, { name: "scale down active (not latest) completed deployment", replicas: 1, newVersion: 2, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 5, replicasA: newint(5), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, }, after: []deployment{ {version: 1, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, }, errExpected: false, }, { name: "fallback to last completed deployment", replicas: 1, newVersion: 2, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 0, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, }, after: []deployment{ {version: 1, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, }, errExpected: false, }, { name: "fallback to last completed deployment (partial rollout)", replicas: 5, newVersion: 2, expectedReplicas: 5, before: []deployment{ {version: 1, replicas: 2, replicasA: newint(5), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 2, replicasA: newint(0), desiredA: newint(5), status: deployapi.DeploymentStatusFailed, cancelled: true}, }, after: []deployment{ {version: 1, replicas: 5, replicasA: newint(5), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, replicasA: newint(0), desiredA: newint(5), status: deployapi.DeploymentStatusFailed, cancelled: true}, }, errExpected: false, }, // The cases below will exercise backwards compatibility for resources // which predate the use of the replica annotation. { name: "(compat) initial deployment already in progress", replicas: 1, newVersion: 1, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 1, desiredA: newint(1), status: deployapi.DeploymentStatusNew, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 1, desiredA: newint(1), status: deployapi.DeploymentStatusNew, cancelled: false}, }, errExpected: false, }, { name: "(compat) new version", replicas: 1, newVersion: 2, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 1, status: deployapi.DeploymentStatusComplete, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 1, status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusNew, cancelled: false}, }, errExpected: false, }, { name: "(compat) already in progress", replicas: 1, newVersion: 2, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 1, status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, desiredA: newint(1), status: deployapi.DeploymentStatusNew, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 1, status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, desiredA: newint(1), status: deployapi.DeploymentStatusNew, cancelled: false}, }, errExpected: false, }, { name: "(compat) already deployed", replicas: 1, newVersion: 1, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 1, status: deployapi.DeploymentStatusComplete, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, }, errExpected: false, }, { name: "(compat) awaiting cancellation of older deployments", replicas: 1, newVersion: 3, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 1, desiredA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 1, desiredA: newint(1), status: deployapi.DeploymentStatusRunning, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 1, desiredA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 1, desiredA: newint(1), status: deployapi.DeploymentStatusRunning, cancelled: true}, }, errExpected: true, }, { name: "(compat) awaiting cancellation of older deployments (already cancelled)", replicas: 1, newVersion: 2, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 1, desiredA: newint(1), status: deployapi.DeploymentStatusRunning, cancelled: true}, }, after: []deployment{ {version: 1, replicas: 1, desiredA: newint(1), status: deployapi.DeploymentStatusRunning, cancelled: true}, }, errExpected: true, }, { name: "(compat) steady state replica corrections (latest == active)", replicas: 5, newVersion: 5, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 0, status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 1, status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 3, replicas: 1, desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, {version: 4, replicas: 0, desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: false}, {version: 5, replicas: 1, status: deployapi.DeploymentStatusComplete, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 0, replicasA: newint(0), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, replicasA: newint(0), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 3, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, {version: 4, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: false}, {version: 5, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, }, errExpected: false, }, { name: "(compat) steady state replica corrections of a test config (latest == active)", test: true, replicas: 5, newVersion: 5, expectedReplicas: 5, before: []deployment{ {version: 1, replicas: 0, status: deployapi.DeploymentStatusComplete, cancelled: false, test: true}, {version: 2, replicas: 1, status: deployapi.DeploymentStatusComplete, cancelled: false, test: true}, {version: 3, replicas: 1, desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: true, test: true}, {version: 4, replicas: 0, desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: false, test: true}, {version: 5, replicas: 1, status: deployapi.DeploymentStatusComplete, cancelled: false, test: true}, }, after: []deployment{ {version: 1, replicas: 0, replicasA: newint(0), status: deployapi.DeploymentStatusComplete, cancelled: false, test: true}, {version: 2, replicas: 0, replicasA: newint(0), status: deployapi.DeploymentStatusComplete, cancelled: false, test: true}, {version: 3, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: true, test: true}, {version: 4, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: false, test: true}, {version: 5, replicas: 0, replicasA: newint(0), status: deployapi.DeploymentStatusComplete, cancelled: false, test: true}, }, errExpected: false, }, { name: "(compat) steady state replica corrections (latest != active)", replicas: 5, newVersion: 5, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 0, status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 1, status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 3, replicas: 1, desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, {version: 4, replicas: 0, status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 5, replicas: 0, desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 0, replicasA: newint(0), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, replicasA: newint(0), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 3, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, {version: 4, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 5, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: false}, }, errExpected: false, }, { name: "(compat) already deployed, no active deployment", replicas: 2, newVersion: 2, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 0, desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: false}, {version: 2, replicas: 0, desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: false}, {version: 2, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: false}, }, errExpected: false, }, { name: "(compat) scale up latest/active completed deployment", replicas: 1, newVersion: 2, expectedReplicas: 5, before: []deployment{ {version: 1, replicas: 0, status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 5, status: deployapi.DeploymentStatusComplete, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 0, replicasA: newint(0), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 5, replicasA: newint(5), status: deployapi.DeploymentStatusComplete, cancelled: false}, }, errExpected: false, }, { name: "(compat) scale up latest/active completed test deployment", test: true, replicas: 1, newVersion: 2, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 0, status: deployapi.DeploymentStatusComplete, cancelled: false, test: true}, {version: 2, replicas: 5, status: deployapi.DeploymentStatusComplete, cancelled: false, test: true}, }, after: []deployment{ {version: 1, replicas: 0, replicasA: newint(0), status: deployapi.DeploymentStatusComplete, cancelled: false, test: true}, {version: 2, replicas: 0, replicasA: newint(0), status: deployapi.DeploymentStatusComplete, cancelled: false, test: true}, }, errExpected: false, }, { name: "(compat) scale up latest/active running test deployment", test: true, replicas: 1, newVersion: 2, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 0, status: deployapi.DeploymentStatusComplete, cancelled: false, test: true}, {version: 2, replicas: 5, status: deployapi.DeploymentStatusRunning, cancelled: false, test: true}, }, after: []deployment{ {version: 1, replicas: 0, replicasA: nil, status: deployapi.DeploymentStatusComplete, cancelled: false, test: true}, {version: 2, replicas: 5, replicasA: nil, status: deployapi.DeploymentStatusRunning, cancelled: false, test: true}, }, errExpected: false, }, // No longer supported. { name: "(compat) scale up active (not latest) completed deployment (RC targetted directly)", replicas: 2, newVersion: 2, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 5, status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, }, after: []deployment{ {version: 1, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, }, errExpected: false, }, { name: "(compat) scale up active (not latest) completed deployment (RC targetted via oc)", replicas: 1, newVersion: 2, expectedReplicas: 2, before: []deployment{ {version: 1, replicas: 2, status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 5, desiredA: newint(2), status: deployapi.DeploymentStatusFailed, cancelled: true}, }, after: []deployment{ {version: 1, replicas: 2, replicasA: newint(2), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, replicasA: newint(0), desiredA: newint(2), status: deployapi.DeploymentStatusFailed, cancelled: true}, }, errExpected: false, }, { name: "(compat) fallback to last completed deployment", replicas: 3, newVersion: 2, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 0, status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, }, after: []deployment{ {version: 1, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, }, errExpected: false, }, // The cases below exercise old clients performing scaling operations // against new resources and controller behavior. { name: "(compat-2) scale up latest/active completed deployment (via oc)", replicas: 1, newVersion: 2, expectedReplicas: 2, before: []deployment{ {version: 1, replicas: 0, replicasA: newint(0), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 2, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, }, after: []deployment{ {version: 1, replicas: 0, replicasA: newint(0), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 2, replicasA: newint(2), status: deployapi.DeploymentStatusComplete, cancelled: false}, }, errExpected: false, }, { name: "(compat-2) scale up active (not latest) completed deployment (via oc)", replicas: 1, newVersion: 2, expectedReplicas: 1, before: []deployment{ {version: 1, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 2, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, }, after: []deployment{ {version: 1, replicas: 1, replicasA: newint(1), status: deployapi.DeploymentStatusComplete, cancelled: false}, {version: 2, replicas: 0, replicasA: newint(0), desiredA: newint(1), status: deployapi.DeploymentStatusFailed, cancelled: true}, }, errExpected: false, }, } for _, test := range tests { t.Logf("evaluating test: %s", test.name) deployments := map[string]kapi.ReplicationController{} for _, template := range test.before { deployment := mkdeployment(template) deployments[deployment.Name] = deployment } kc := &ktestclient.Fake{} kc.AddReactor("list", "replicationcontrollers", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) { list := []kapi.ReplicationController{} for _, deployment := range deployments { list = append(list, deployment) } return true, &kapi.ReplicationControllerList{Items: list}, nil }) kc.AddReactor("create", "replicationcontrollers", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) { rc := action.(ktestclient.CreateAction).GetObject().(*kapi.ReplicationController) deployments[rc.Name] = *rc return true, rc, nil }) kc.AddReactor("update", "replicationcontrollers", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) { rc := action.(ktestclient.UpdateAction).GetObject().(*kapi.ReplicationController) deployments[rc.Name] = *rc return true, rc, nil }) oc := &testclient.Fake{} recorder := &record.FakeRecorder{} controller := &DeploymentConfigController{ kubeClient: kc, osClient: oc, codec: kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion), recorder: recorder, } config := deploytest.OkDeploymentConfig(test.newVersion) if test.test { config = deploytest.TestDeploymentConfig(config) } config.Spec.Replicas = test.replicas err := controller.Handle(config) if err != nil && !test.errExpected { t.Fatalf("unexpected error: %s", err) } expectedDeployments := []kapi.ReplicationController{} for _, template := range test.after { expectedDeployments = append(expectedDeployments, mkdeployment(template)) } actualDeployments := []kapi.ReplicationController{} for _, deployment := range deployments { actualDeployments = append(actualDeployments, deployment) } sort.Sort(deployutil.ByLatestVersionDesc(expectedDeployments)) sort.Sort(deployutil.ByLatestVersionDesc(actualDeployments)) if e, a := test.expectedReplicas, config.Spec.Replicas; e != a { t.Errorf("expected config replicas to be %d, got %d", e, a) t.Fatalf("events:\n%s", strings.Join(recorder.Events, "\t\n")) } anyDeploymentMismatches := false for i := 0; i < len(expectedDeployments); i++ { expected, actual := expectedDeployments[i], actualDeployments[i] if !kapi.Semantic.DeepEqual(expected, actual) { anyDeploymentMismatches = true t.Errorf("actual deployment don't match expected: %v", kutil.ObjectDiff(expected, actual)) } } if anyDeploymentMismatches { t.Fatalf("events:\n%s", strings.Join(recorder.Events, "\t\n")) } } }