// TestHandle_configAlreadyDeployed ensures that an attempt to create a // deployment for an updated config for which the deployment was already // created results in a no-op. func TestHandle_configAlreadyDeployed(t *testing.T) { deploymentConfig := deploytest.OkDeploymentConfig(0) controller := &DeploymentConfigController{ makeDeployment: func(config *deployapi.DeploymentConfig) (*kapi.ReplicationController, error) { return deployutil.MakeDeployment(config, api.Codec) }, deploymentClient: &deploymentClientImpl{ createDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { t.Fatalf("unexpected call to to create deployment: %v", deployment) return nil, nil }, listDeploymentsForConfigFunc: func(namespace, configName string) (*kapi.ReplicationControllerList, error) { existingDeployments := []kapi.ReplicationController{} deployment, _ := deployutil.MakeDeployment(deploymentConfig, kapi.Codec) existingDeployments = append(existingDeployments, *deployment) return &kapi.ReplicationControllerList{Items: existingDeployments}, nil }, updateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { t.Fatalf("unexpected update call with deployment %v", deployment) return nil, nil }, }, } err := controller.Handle(deploymentConfig) if err != nil { t.Fatalf("unexpected error: %v", err) } }
func TestRolling_deployInitial(t *testing.T) { initialStrategyInvoked := false strategy := &RollingDeploymentStrategy{ codec: api.Codec, client: &rollingUpdaterClient{ GetReplicationControllerFn: func(namespace, name string) (*kapi.ReplicationController, error) { t.Fatalf("unexpected call to GetReplicationController") return nil, nil }, }, initialStrategy: &testStrategy{ deployFn: func(from *kapi.ReplicationController, to *kapi.ReplicationController, desiredReplicas int, updateAcceptor kubectl.UpdateAcceptor) error { initialStrategyInvoked = true return nil }, }, rollingUpdate: func(config *kubectl.RollingUpdaterConfig) error { t.Fatalf("unexpected call to rollingUpdate") return nil }, getUpdateAcceptor: getUpdateAcceptor, } config := deploytest.OkDeploymentConfig(1) config.Template.Strategy = deploytest.OkRollingStrategy() deployment, _ := deployutil.MakeDeployment(config, kapi.Codec) err := strategy.Deploy(nil, deployment, 2) if err != nil { t.Fatalf("unexpected error: %v", err) } if !initialStrategyInvoked { t.Fatalf("expected initial strategy to be invoked") } }
func TestHookExecutor_executeExecNewPodFailed(t *testing.T) { hook := &deployapi.LifecycleHook{ FailurePolicy: deployapi.LifecycleHookFailurePolicyAbort, ExecNewPod: &deployapi.ExecNewPodHook{ ContainerName: "container1", }, } deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codec) var createdPod *kapi.Pod executor := &HookExecutor{ PodClient: &HookExecutorPodClientImpl{ CreatePodFunc: func(namespace string, pod *kapi.Pod) (*kapi.Pod, error) { createdPod = pod return createdPod, nil }, PodWatchFunc: func(namespace, name, resourceVersion string, stopChannel chan struct{}) func() *kapi.Pod { createdPod.Status.Phase = kapi.PodFailed return func() *kapi.Pod { return createdPod } }, }, } err := executor.executeExecNewPod(hook, deployment, "hook") if err == nil { t.Fatalf("expected an error", err) } t.Logf("got expected error: %s", err) }
// TestHandle_initialOk ensures that an initial config (version 0) doesn't result // in a new deployment. func TestHandle_initialOk(t *testing.T) { controller := &DeploymentConfigController{ makeDeployment: func(config *deployapi.DeploymentConfig) (*kapi.ReplicationController, error) { return deployutil.MakeDeployment(config, api.Codec) }, deploymentClient: &deploymentClientImpl{ createDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { t.Fatalf("unexpected call with deployment %v", deployment) return nil, nil }, listDeploymentsForConfigFunc: func(namespace, configName string) (*kapi.ReplicationControllerList, error) { t.Fatalf("unexpected call to list deployments") return nil, nil }, updateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { t.Fatalf("unexpected update call with deployment %v", deployment) return nil, nil }, }, recorder: &record.FakeRecorder{}, } err := controller.Handle(deploytest.OkDeploymentConfig(0)) if err != nil { t.Fatalf("unexpected error: %v", err) } }
// TestHandle_nonfatalCreateError ensures that a failed API attempt to create // a new deployment for an updated config results in a nonfatal error. func TestHandle_nonfatalCreateError(t *testing.T) { configController := &DeploymentConfigController{ makeDeployment: func(config *deployapi.DeploymentConfig) (*kapi.ReplicationController, error) { return deployutil.MakeDeployment(config, api.Codec) }, deploymentClient: &deploymentClientImpl{ createDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { return nil, kerrors.NewInternalError(fmt.Errorf("test error")) }, listDeploymentsForConfigFunc: func(namespace, configName string) (*kapi.ReplicationControllerList, error) { return &kapi.ReplicationControllerList{}, nil }, updateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { t.Fatalf("unexpected update call with deployment %v", deployment) return nil, nil }, }, recorder: &record.FakeRecorder{}, } err := configController.Handle(deploytest.OkDeploymentConfig(1)) if err == nil { t.Fatalf("expected error") } if _, isFatal := err.(fatalError); isFatal { t.Fatalf("expected a nonfatal error, got a fatal error: %v", err) } }
func TestCreateGeneratorError(t *testing.T) { rest := REST{ generator: Client{ GRFn: func(from, to *deployapi.DeploymentConfig, spec *deployapi.DeploymentConfigRollbackSpec) (*deployapi.DeploymentConfig, error) { return nil, kerrors.NewInternalError(fmt.Errorf("something terrible happened")) }, RCFn: func(ctx kapi.Context, name string) (*kapi.ReplicationController, error) { deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codec) return deployment, nil }, DCFn: func(ctx kapi.Context, name string) (*deployapi.DeploymentConfig, error) { return deploytest.OkDeploymentConfig(1), nil }, }, codec: api.Codec, } _, err := rest.Create(kapi.NewDefaultContext(), &deployapi.DeploymentConfigRollback{ Spec: deployapi.DeploymentConfigRollbackSpec{ From: kapi.ObjectReference{ Name: "deployment", Namespace: kapi.NamespaceDefault, }, }, }) if err == nil || !strings.Contains(err.Error(), "something terrible happened") { t.Errorf("Unexpected error: %v", err) } }
func TestCreateMissingDeploymentConfig(t *testing.T) { rest := REST{ generator: Client{ GRFn: func(from, to *deployapi.DeploymentConfig, spec *deployapi.DeploymentConfigRollbackSpec) (*deployapi.DeploymentConfig, error) { t.Fatal("unexpected call to generator") return nil, errors.New("something terrible happened") }, RCFn: func(ctx kapi.Context, name string) (*kapi.ReplicationController, error) { deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codec) return deployment, nil }, DCFn: func(ctx kapi.Context, name string) (*deployapi.DeploymentConfig, error) { return nil, kerrors.NewNotFound("deploymentConfig", name) }, }, codec: api.Codec, } obj, err := rest.Create(kapi.NewDefaultContext(), &deployapi.DeploymentConfigRollback{ Spec: deployapi.DeploymentConfigRollbackSpec{ From: kapi.ObjectReference{ Name: "deployment", Namespace: kapi.NamespaceDefault, }, }, }) if err == nil { t.Errorf("Expected an error") } if obj != nil { t.Error("Unexpected result obj") } }
// TestHandle_runningPod ensures that a running deployer pod results in a // transition of the deployment's status to running. func TestHandle_runningPod(t *testing.T) { deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codec) deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(deployapi.DeploymentStatusPending) var updatedDeployment *kapi.ReplicationController controller := &DeployerPodController{ 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(runningPod(deployment)) if err != nil { t.Fatalf("unexpected error: %v", err) } if updatedDeployment == nil { t.Fatalf("expected deployment update") } if e, a := deployapi.DeploymentStatusRunning, deployutil.DeploymentStatusFor(updatedDeployment); e != a { t.Fatalf("expected updated deployment status %s, got %s", e, a) } }
// TestHandle_orphanedPod ensures that deployer pods associated with a non- // existent deployment results in all deployer pods being deleted. func TestHandle_orphanedPod(t *testing.T) { deleted := kutil.NewStringSet() controller := &DeployerPodController{ deploymentClient: &deploymentClientImpl{ updateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { t.Fatalf("Unexpected deployment update") return nil, nil }, getDeploymentFunc: func(namespace, name string) (*kapi.ReplicationController, error) { return nil, kerrors.NewNotFound("ReplicationController", name) }, }, deployerPodsFor: func(namespace, name string) (*kapi.PodList, error) { mkpod := func(suffix string) kapi.Pod { deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codec) p := okPod(deployment) p.Name = p.Name + suffix return *p } return &kapi.PodList{ Items: []kapi.Pod{ mkpod(""), mkpod("-prehook"), mkpod("-posthook"), }, }, nil }, deletePod: func(namespace, name string) error { deleted.Insert(name) return nil }, } deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codec) err := controller.Handle(runningPod(deployment)) if err != nil { t.Fatalf("unexpected error: %v", err) } deployerName := deployutil.DeployerPodNameForDeployment(deployment.Name) if !deleted.HasAll(deployerName, deployerName+"-prehook", deployerName+"-posthook") { t.Fatalf("unexpected deleted names: %v", deleted.List()) } }
// TestHandle_deploymentCleanupTransientError ensures that a failure // to clean up a failed deployment results in a transient error // and the deployment status is not set to Failed. func TestHandle_deploymentCleanupTransientError(t *testing.T) { completedDeployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codec) completedDeployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(deployapi.DeploymentStatusComplete) currentDeployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(2), kapi.Codec) currentDeployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(deployapi.DeploymentStatusRunning) currentDeployment.Annotations[deployapi.DesiredReplicasAnnotation] = "2" controller := &DeployerPodController{ deploymentClient: &deploymentClientImpl{ getDeploymentFunc: func(namespace, name string) (*kapi.ReplicationController, error) { return currentDeployment, nil }, updateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { // simulate failure ONLY for the completed deployment if deployutil.DeploymentStatusFor(deployment) == deployapi.DeploymentStatusComplete { return nil, fmt.Errorf("test failure in updating completed deployment") } return deployment, nil }, listDeploymentsForConfigFunc: func(namespace, configName string) (*kapi.ReplicationControllerList, error) { return &kapi.ReplicationControllerList{Items: []kapi.ReplicationController{*currentDeployment, *completedDeployment}}, nil }, }, } err := controller.Handle(terminatedPod(currentDeployment)) if err == nil { t.Fatalf("unexpected error: %v", err) } if _, isTransient := err.(transientError); !isTransient { t.Fatalf("expected transientError on failure to update deployment") } if e, a := deployapi.DeploymentStatusRunning, deployutil.DeploymentStatusFor(currentDeployment); e != a { t.Fatalf("expected updated deployment status to remain %s, got %s", e, a) } }
// TestHandle_changeWithTemplateDiff ensures that a pod template change to a // config with a config change trigger results in a version bump and cause // update. func TestHandle_changeWithTemplateDiff(t *testing.T) { var updated *deployapi.DeploymentConfig controller := &DeploymentConfigChangeController{ decodeConfig: func(deployment *kapi.ReplicationController) (*deployapi.DeploymentConfig, error) { return deployutil.DecodeDeploymentConfig(deployment, api.Codec) }, changeStrategy: &changeStrategyImpl{ generateDeploymentConfigFunc: func(namespace, name string) (*deployapi.DeploymentConfig, error) { return deployapitest.OkDeploymentConfig(2), nil }, updateDeploymentConfigFunc: func(namespace string, config *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error) { updated = config return config, nil }, getDeploymentFunc: func(namespace, name string) (*kapi.ReplicationController, error) { deployment, _ := deployutil.MakeDeployment(deployapitest.OkDeploymentConfig(1), kapi.Codec) return deployment, nil }, }, } config := deployapitest.OkDeploymentConfig(1) config.Triggers = []deployapi.DeploymentTriggerPolicy{deployapitest.OkConfigChangeTrigger()} config.Template.ControllerTemplate.Template.Spec.Containers[1].Name = "modified" err := controller.Handle(config) if err != nil { t.Fatalf("unexpected error: %v", err) } if updated == nil { t.Fatalf("expected config to be updated") } if e, a := 2, updated.LatestVersion; e != a { t.Fatalf("expected update to latestversion=%d, got %d", e, a) } if updated.Details == nil { t.Fatalf("expected config change details to be set") } else if updated.Details.Causes == nil { t.Fatalf("expected config change causes to be set") } else if updated.Details.Causes[0].Type != deployapi.DeploymentTriggerOnConfigChange { t.Fatalf("expected config change cause to be set to config change trigger, got %s", updated.Details.Causes[0].Type) } }
func TestHookExecutor_makeHookPodInvalidContainerRef(t *testing.T) { hook := &deployapi.LifecycleHook{ FailurePolicy: deployapi.LifecycleHookFailurePolicyAbort, ExecNewPod: &deployapi.ExecNewPodHook{ ContainerName: "undefined", }, } deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codec) _, err := makeHookPod(hook, deployment, "hook") if err == nil { t.Fatalf("expected an error") } t.Logf("got expected error: %s", err) }
func TestHookExecutor_executeExecNewPodSucceeded(t *testing.T) { hook := &deployapi.LifecycleHook{ FailurePolicy: deployapi.LifecycleHookFailurePolicyAbort, ExecNewPod: &deployapi.ExecNewPodHook{ ContainerName: "container1", }, } config := deploytest.OkDeploymentConfig(1) deployment, _ := deployutil.MakeDeployment(config, kapi.Codec) deployment.Spec.Template.Spec.NodeSelector = map[string]string{"labelKey1": "labelValue1", "labelKey2": "labelValue2"} var createdPod *kapi.Pod executor := &HookExecutor{ PodClient: &HookExecutorPodClientImpl{ CreatePodFunc: func(namespace string, pod *kapi.Pod) (*kapi.Pod, error) { createdPod = pod return createdPod, nil }, PodWatchFunc: func(namespace, name, resourceVersion string, stopChannel chan struct{}) func() *kapi.Pod { createdPod.Status.Phase = kapi.PodSucceeded return func() *kapi.Pod { return createdPod } }, }, } err := executor.executeExecNewPod(hook, deployment, "hook") if err != nil { t.Fatalf("unexpected error: %s", err) } if e, a := deployment.Spec.Template.Spec.NodeSelector, createdPod.Spec.NodeSelector; !reflect.DeepEqual(e, a) { t.Fatalf("expected pod NodeSelector %v, got %v", e, a) } if createdPod.Spec.ActiveDeadlineSeconds == nil { t.Fatalf("expected ActiveDeadlineSeconds to be set on the deployment hook executor pod") } if *createdPod.Spec.ActiveDeadlineSeconds != deployapi.MaxDeploymentDurationSeconds { t.Fatalf("expected ActiveDeadlineSeconds to be set to %d; found: %d", deployapi.MaxDeploymentDurationSeconds, *createdPod.Spec.ActiveDeadlineSeconds) } }
func TestHookExecutor_makeHookPodRestart(t *testing.T) { hook := &deployapi.LifecycleHook{ FailurePolicy: deployapi.LifecycleHookFailurePolicyRetry, ExecNewPod: &deployapi.ExecNewPodHook{ ContainerName: "container1", }, } deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codec) pod, err := makeHookPod(hook, deployment, "hook") if err != nil { t.Fatalf("unexpected error: %s", err) } if e, a := kapi.RestartPolicyOnFailure, pod.Spec.RestartPolicy; e != a { t.Errorf("expected pod restart policy %s, got %s", e, a) } }
// TestHandle_uncorrelatedPod ensures that pods uncorrelated with a deployment // are ignored. func TestHandle_uncorrelatedPod(t *testing.T) { controller := &DeployerPodController{ deploymentClient: &deploymentClientImpl{ updateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { t.Fatalf("unexpected deployment update") return nil, nil }, }, } // Verify no-op deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codec) pod := runningPod(deployment) pod.Annotations = make(map[string]string) err := controller.Handle(pod) if err != nil { t.Fatalf("unexpected err: %v", err) } }
// TestHandle_changeWithoutTemplateDiff ensures that an updated config with no // pod template diff results in the config version remaining the same. func TestHandle_changeWithoutTemplateDiff(t *testing.T) { config := deployapitest.OkDeploymentConfig(1) config.Triggers = []deployapi.DeploymentTriggerPolicy{deployapitest.OkConfigChangeTrigger()} generated := false updated := false controller := &DeploymentConfigChangeController{ decodeConfig: func(deployment *kapi.ReplicationController) (*deployapi.DeploymentConfig, error) { return deployutil.DecodeDeploymentConfig(deployment, api.Codec) }, changeStrategy: &changeStrategyImpl{ generateDeploymentConfigFunc: func(namespace, name string) (*deployapi.DeploymentConfig, error) { generated = true return config, nil }, updateDeploymentConfigFunc: func(namespace string, config *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error) { updated = true return config, nil }, getDeploymentFunc: func(namespace, name string) (*kapi.ReplicationController, error) { deployment, _ := deployutil.MakeDeployment(deployapitest.OkDeploymentConfig(1), kapi.Codec) return deployment, nil }, }, } err := controller.Handle(config) if err != nil { t.Fatalf("unexpected error: %v", err) } if generated { t.Error("Unexpected generation of deploymentConfig") } if updated { t.Error("Unexpected update of deploymentConfig") } }
func TestCreateOk(t *testing.T) { rest := REST{ generator: Client{ GRFn: func(from, to *deployapi.DeploymentConfig, spec *deployapi.DeploymentConfigRollbackSpec) (*deployapi.DeploymentConfig, error) { return &deployapi.DeploymentConfig{}, nil }, RCFn: func(ctx kapi.Context, name string) (*kapi.ReplicationController, error) { deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codec) return deployment, nil }, DCFn: func(ctx kapi.Context, name string) (*deployapi.DeploymentConfig, error) { return deploytest.OkDeploymentConfig(1), nil }, }, codec: api.Codec, } obj, err := rest.Create(kapi.NewDefaultContext(), &deployapi.DeploymentConfigRollback{ Spec: deployapi.DeploymentConfigRollbackSpec{ From: kapi.ObjectReference{ Name: "deployment", Namespace: kapi.NamespaceDefault, }, }, }) if err != nil { t.Errorf("Unexpected error: %v", err) } if obj == nil { t.Errorf("Expected a result obj") } if _, ok := obj.(*deployapi.DeploymentConfig); !ok { t.Errorf("expected a DeploymentConfig, got a %#v", obj) } }
// 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_podTerminatedFailNoContainerStatus(t *testing.T) { var updatedDeployment *kapi.ReplicationController deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codec) // 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{ 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) } }
// TestCmdDeploy_cancelOk ensures that attempts to cancel deployments // for a config result in cancelling all in-progress deployments // and none of the completed/faild ones. func TestCmdDeploy_cancelOk(t *testing.T) { var ( config *deployapi.DeploymentConfig existingDeployments *kapi.ReplicationControllerList updatedDeployments []kapi.ReplicationController ) commandClient := &deployCommandClientImpl{ GetDeploymentFn: func(namespace, name string) (*kapi.ReplicationController, error) { t.Fatalf("unexpected call to GetDeployment: %s", name) return nil, nil }, ListDeploymentsForConfigFn: func(namespace, configName string) (*kapi.ReplicationControllerList, error) { return existingDeployments, nil }, UpdateDeploymentConfigFn: func(config *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error) { t.Fatalf("unexpected call to UpdateDeploymentConfig") return nil, nil }, UpdateDeploymentFn: func(deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { updatedDeployments = append(updatedDeployments, *deployment) return deployment, nil }, } type existing struct { version int status deployapi.DeploymentStatus shouldCancel bool } type scenario struct { version int existing []existing } scenarios := []scenario{ // No existing deployments {1, []existing{{1, deployapi.DeploymentStatusComplete, false}}}, // A single existing failed deployment {1, []existing{{1, deployapi.DeploymentStatusFailed, false}}}, // Multiple existing completed/failed deployments {2, []existing{{2, deployapi.DeploymentStatusFailed, false}, {1, deployapi.DeploymentStatusComplete, false}}}, // A single existing new deployment {1, []existing{{1, deployapi.DeploymentStatusNew, true}}}, // A single existing pending deployment {1, []existing{{1, deployapi.DeploymentStatusPending, true}}}, // A single existing running deployment {1, []existing{{1, deployapi.DeploymentStatusRunning, true}}}, // Multiple existing deployments with one in new/pending/running {3, []existing{{3, deployapi.DeploymentStatusRunning, true}, {2, deployapi.DeploymentStatusComplete, false}, {1, deployapi.DeploymentStatusFailed, false}}}, // Multiple existing deployments with more than one in new/pending/running {3, []existing{{3, deployapi.DeploymentStatusNew, true}, {2, deployapi.DeploymentStatusRunning, true}, {1, deployapi.DeploymentStatusFailed, false}}}, } c := &cancelDeploymentCommand{client: commandClient} for _, scenario := range scenarios { updatedDeployments = []kapi.ReplicationController{} config = deploytest.OkDeploymentConfig(scenario.version) existingDeployments = &kapi.ReplicationControllerList{} for _, e := range scenario.existing { d, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(e.version), api.Codec) d.Annotations[deployapi.DeploymentStatusAnnotation] = string(e.status) existingDeployments.Items = append(existingDeployments.Items, *d) } err := c.cancel(config, ioutil.Discard) if err != nil { t.Fatalf("unexpected error: %v", err) } expectedCancellations := []int{} actualCancellations := []int{} for _, e := range scenario.existing { if e.shouldCancel { expectedCancellations = append(expectedCancellations, e.version) } } for _, d := range updatedDeployments { actualCancellations = append(actualCancellations, deployutil.DeploymentVersionFor(&d)) } sort.Ints(actualCancellations) sort.Ints(expectedCancellations) if !reflect.DeepEqual(actualCancellations, expectedCancellations) { t.Fatalf("expected cancellations: %v, actual: %v", expectedCancellations, actualCancellations) } } }
// TestHandle_updateOk ensures that an updated config (version >0) results in // a new deployment with the appropriate replica count based on a variety of // existing prior deployments. func TestHandle_updateOk(t *testing.T) { var ( config *deployapi.DeploymentConfig deployed *kapi.ReplicationController existingDeployments *kapi.ReplicationControllerList ) controller := &DeploymentConfigController{ makeDeployment: func(config *deployapi.DeploymentConfig) (*kapi.ReplicationController, error) { return deployutil.MakeDeployment(config, api.Codec) }, deploymentClient: &deploymentClientImpl{ createDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { deployed = deployment return deployment, nil }, listDeploymentsForConfigFunc: func(namespace, configName string) (*kapi.ReplicationControllerList, error) { return existingDeployments, nil }, updateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { t.Fatalf("unexpected update call with deployment %v", deployment) return nil, nil }, }, recorder: &record.FakeRecorder{}, } type existing struct { version int replicas int status deployapi.DeploymentStatus } type scenario struct { version int expectedReplicas int existing []existing } scenarios := []scenario{ {1, 1, []existing{}}, {2, 1, []existing{ {1, 1, deployapi.DeploymentStatusComplete}, }}, {3, 4, []existing{ {1, 0, deployapi.DeploymentStatusComplete}, {2, 4, deployapi.DeploymentStatusComplete}, }}, {3, 4, []existing{ {1, 4, deployapi.DeploymentStatusComplete}, {2, 1, deployapi.DeploymentStatusFailed}, }}, {4, 2, []existing{ {1, 0, deployapi.DeploymentStatusComplete}, {2, 0, deployapi.DeploymentStatusFailed}, {3, 2, deployapi.DeploymentStatusComplete}, }}, // Scramble the order of the previous to ensure we still get it right. {4, 2, []existing{ {2, 0, deployapi.DeploymentStatusFailed}, {3, 2, deployapi.DeploymentStatusComplete}, {1, 0, deployapi.DeploymentStatusComplete}, }}, } for _, scenario := range scenarios { deployed = nil config = deploytest.OkDeploymentConfig(scenario.version) existingDeployments = &kapi.ReplicationControllerList{} for _, e := range scenario.existing { d, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(e.version), api.Codec) d.Spec.Replicas = e.replicas d.Annotations[deployapi.DeploymentStatusAnnotation] = string(e.status) existingDeployments.Items = append(existingDeployments.Items, *d) } err := controller.Handle(config) if deployed == nil { t.Fatalf("expected a deployment") } if err != nil { t.Fatalf("unexpected error: %v", err) } desired, hasDesired := deployutil.DeploymentDesiredReplicas(deployed) if !hasDesired { t.Fatalf("expected desired replicas") } if e, a := scenario.expectedReplicas, desired; e != a { t.Errorf("expected desired replicas %d, got %d", e, a) } } }
// TestHandle_existingDeployments ensures that an attempt to create a // new deployment for a config that has existing deployments succeeds of fails // depending upon the state of the existing deployments func TestHandle_existingDeployments(t *testing.T) { var updatedDeployments []kapi.ReplicationController var ( config *deployapi.DeploymentConfig deployed *kapi.ReplicationController existingDeployments *kapi.ReplicationControllerList ) controller := &DeploymentConfigController{ makeDeployment: func(config *deployapi.DeploymentConfig) (*kapi.ReplicationController, error) { return deployutil.MakeDeployment(config, api.Codec) }, deploymentClient: &deploymentClientImpl{ createDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { deployed = deployment return deployment, nil }, listDeploymentsForConfigFunc: func(namespace, configName string) (*kapi.ReplicationControllerList, error) { return existingDeployments, nil }, updateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { updatedDeployments = append(updatedDeployments, *deployment) //t.Fatalf("unexpected update call with deployment %v", deployment) return deployment, nil }, }, recorder: &record.FakeRecorder{}, } type existing struct { version int status deployapi.DeploymentStatus shouldCancel bool } type scenario struct { version int existing []existing errorType reflect.Type expectDeployment bool } transientErrorType := reflect.TypeOf(transientError("")) scenarios := []scenario{ // No existing deployments {1, []existing{}, nil, true}, // A single existing completed deployment {2, []existing{{1, deployapi.DeploymentStatusComplete, false}}, nil, true}, // A single existing failed deployment {2, []existing{{1, deployapi.DeploymentStatusFailed, false}}, nil, true}, // Multiple existing completed/failed deployments {3, []existing{{2, deployapi.DeploymentStatusFailed, false}, {1, deployapi.DeploymentStatusComplete, false}}, nil, true}, // A single existing deployment in the default state {2, []existing{{1, "", false}}, transientErrorType, false}, // A single existing new deployment {2, []existing{{1, deployapi.DeploymentStatusNew, false}}, transientErrorType, false}, // A single existing pending deployment {2, []existing{{1, deployapi.DeploymentStatusPending, false}}, transientErrorType, false}, // A single existing running deployment {2, []existing{{1, deployapi.DeploymentStatusRunning, false}}, transientErrorType, false}, // Multiple existing deployments with one in new/pending/running {4, []existing{{3, deployapi.DeploymentStatusRunning, false}, {2, deployapi.DeploymentStatusComplete, false}, {1, deployapi.DeploymentStatusFailed, false}}, transientErrorType, false}, // Latest deployment exists and has already failed/completed {2, []existing{{2, deployapi.DeploymentStatusFailed, false}, {1, deployapi.DeploymentStatusComplete, false}}, nil, false}, // Latest deployment exists and is in new/pending/running state {2, []existing{{2, deployapi.DeploymentStatusRunning, false}, {1, deployapi.DeploymentStatusComplete, false}}, nil, false}, // Multiple existing deployments with more than one in new/pending/running {4, []existing{{3, deployapi.DeploymentStatusNew, false}, {2, deployapi.DeploymentStatusRunning, true}, {1, deployapi.DeploymentStatusFailed, false}}, transientErrorType, false}, // Multiple existing deployments with more than one in new/pending/running // Latest deployment has already failed {6, []existing{{5, deployapi.DeploymentStatusFailed, false}, {4, deployapi.DeploymentStatusRunning, false}, {3, deployapi.DeploymentStatusNew, true}, {2, deployapi.DeploymentStatusComplete, false}, {1, deployapi.DeploymentStatusNew, true}}, transientErrorType, false}, } for _, scenario := range scenarios { updatedDeployments = []kapi.ReplicationController{} deployed = nil config = deploytest.OkDeploymentConfig(scenario.version) existingDeployments = &kapi.ReplicationControllerList{} for _, e := range scenario.existing { d, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(e.version), api.Codec) if e.status != "" { d.Annotations[deployapi.DeploymentStatusAnnotation] = string(e.status) } existingDeployments.Items = append(existingDeployments.Items, *d) } err := controller.Handle(config) if scenario.expectDeployment && deployed == nil { t.Fatalf("expected a deployment") } if scenario.errorType == nil { if err != nil { t.Fatalf("unexpected error: %v", err) } } else { if err == nil { t.Fatalf("expected error") } if reflect.TypeOf(err) != scenario.errorType { t.Fatalf("error expected: %s, got: %s", scenario.errorType, reflect.TypeOf(err)) } } expectedCancellations := []int{} actualCancellations := []int{} for _, e := range scenario.existing { if e.shouldCancel { expectedCancellations = append(expectedCancellations, e.version) } } for _, d := range updatedDeployments { actualCancellations = append(actualCancellations, deployutil.DeploymentVersionFor(&d)) } sort.Ints(actualCancellations) sort.Ints(expectedCancellations) if !reflect.DeepEqual(actualCancellations, expectedCancellations) { t.Fatalf("expected cancellations: %v, actual: %v", expectedCancellations, actualCancellations) } } }
func mkdeployment(version int, status deployapi.DeploymentStatus) *kapi.ReplicationController { deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(version), kapi.Codec) deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(status) return deployment }
func mkdeployment(version int) kapi.ReplicationController { deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(version), kapi.Codec) return *deployment }
func deploymentFor(config *deployapi.DeploymentConfig, status deployapi.DeploymentStatus) *kapi.ReplicationController { d, _ := deployutil.MakeDeployment(config, kapi.Codec) d.Annotations[deployapi.DeploymentStatusAnnotation] = string(status) return d }
// TestRolling_deployInitialHooks can go away once the rolling strategy // supports initial deployments. func TestRolling_deployInitialHooks(t *testing.T) { var hookError error strategy := &RollingDeploymentStrategy{ codec: api.Codec, client: &rollingUpdaterClient{ GetReplicationControllerFn: func(namespace, name string) (*kapi.ReplicationController, error) { t.Fatalf("unexpected call to GetReplicationController") return nil, nil }, UpdateReplicationControllerFn: func(namespace string, rc *kapi.ReplicationController) (*kapi.ReplicationController, error) { return rc, nil }, }, initialStrategy: &testStrategy{ deployFn: func(from *kapi.ReplicationController, to *kapi.ReplicationController, desiredReplicas int, updateAcceptor kubectl.UpdateAcceptor) error { return nil }, }, rollingUpdate: func(config *kubectl.RollingUpdaterConfig) error { return nil }, hookExecutor: &hookExecutorImpl{ executeFunc: func(hook *deployapi.LifecycleHook, deployment *kapi.ReplicationController, label string) error { return hookError }, }, getUpdateAcceptor: getUpdateAcceptor, } cases := []struct { params *deployapi.RollingDeploymentStrategyParams hookShouldFail bool deploymentShouldFail bool }{ {rollingParams(deployapi.LifecycleHookFailurePolicyAbort, ""), true, true}, {rollingParams(deployapi.LifecycleHookFailurePolicyAbort, ""), false, false}, {rollingParams("", deployapi.LifecycleHookFailurePolicyAbort), true, false}, {rollingParams("", deployapi.LifecycleHookFailurePolicyAbort), false, false}, } for _, tc := range cases { config := deploytest.OkDeploymentConfig(2) config.Template.Strategy.RollingParams = tc.params deployment, _ := deployutil.MakeDeployment(config, kapi.Codec) hookError = nil if tc.hookShouldFail { hookError = fmt.Errorf("hook failure") } err := strategy.Deploy(nil, deployment, 2) if err != nil && tc.deploymentShouldFail { t.Logf("got expected error: %v", err) } if err == nil && tc.deploymentShouldFail { t.Errorf("expected an error for case: %v", tc) } if err != nil && !tc.deploymentShouldFail { t.Errorf("unexpected error for case: %v: %v", tc, err) } } }
func TestFirstContainerReady_scenarios(t *testing.T) { type containerReady struct { name string ready bool } scenarios := []struct { name string specContainers []string initialReadiness []containerReady updatedReadiness []containerReady accept bool }{ { "all ready", []string{"1", "2"}, []containerReady{{"1", false}, {"2", false}}, []containerReady{{"1", true}, {"2", true}}, true, }, { "none ready", []string{"1", "2"}, []containerReady{{"1", false}, {"2", false}}, []containerReady{{"1", false}, {"2", false}}, false, }, { "some ready", []string{"1", "2"}, []containerReady{{"1", false}, {"2", false}}, []containerReady{{"1", true}, {"2", false}}, false, }, } for _, s := range scenarios { t.Logf("running scenario: %s", s.name) mkpod := func(name string, readiness []containerReady) kapi.Pod { containers := []kapi.Container{} for _, c := range s.specContainers { containers = append(containers, kapi.Container{Name: c}) } containerStatuses := []kapi.ContainerStatus{} for _, r := range readiness { containerStatuses = append(containerStatuses, kapi.ContainerStatus{Name: r.name, Ready: r.ready}) } return kapi.Pod{ ObjectMeta: kapi.ObjectMeta{ Name: name, }, Spec: kapi.PodSpec{ Containers: containers, }, Status: kapi.PodStatus{ ContainerStatuses: containerStatuses, }, } } store := cache.NewStore(cache.MetaNamespaceKeyFunc) ready := &FirstContainerReady{ podsForDeployment: func(deployment *kapi.ReplicationController) (*kapi.PodList, error) { return &kapi.PodList{ Items: []kapi.Pod{ mkpod(deployment.Name+"-pod", s.initialReadiness), }, }, nil }, getPodStore: func(namespace, name string) (cache.Store, chan struct{}) { return store, make(chan struct{}) }, timeout: 10 * time.Millisecond, interval: 1 * time.Millisecond, } deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codec) deployment.Spec.Replicas = 1 pod := mkpod(deployment.Name+"-pod", s.updatedReadiness) store.Add(&pod) err := ready.Accept(deployment) if s.accept && err != nil { t.Fatalf("unexpected error: %v", err) } if !s.accept && err == nil { t.Fatalf("expected an error") } else { t.Logf("got expected error: %s", err) } } }
// Create creates a DeploymentConfigController. func (factory *DeploymentConfigControllerFactory) Create() controller.RunnableController { deploymentConfigLW := &deployutil.ListWatcherImpl{ ListFunc: func() (runtime.Object, error) { return factory.Client.DeploymentConfigs(kapi.NamespaceAll).List(labels.Everything(), fields.Everything()) }, WatchFunc: func(resourceVersion string) (watch.Interface, error) { return factory.Client.DeploymentConfigs(kapi.NamespaceAll).Watch(labels.Everything(), fields.Everything(), resourceVersion) }, } queue := cache.NewFIFO(cache.MetaNamespaceKeyFunc) cache.NewReflector(deploymentConfigLW, &deployapi.DeploymentConfig{}, queue, 2*time.Minute).Run() eventBroadcaster := record.NewBroadcaster() eventBroadcaster.StartRecordingToSink(factory.KubeClient.Events("")) configController := &DeploymentConfigController{ deploymentClient: &deploymentClientImpl{ createDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { return factory.KubeClient.ReplicationControllers(namespace).Create(deployment) }, listDeploymentsForConfigFunc: func(namespace, configName string) (*kapi.ReplicationControllerList, error) { sel := deployutil.ConfigSelector(configName) list, err := factory.KubeClient.ReplicationControllers(namespace).List(sel) if err != nil { return nil, err } return list, nil }, updateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { return factory.KubeClient.ReplicationControllers(namespace).Update(deployment) }, }, makeDeployment: func(config *deployapi.DeploymentConfig) (*kapi.ReplicationController, error) { return deployutil.MakeDeployment(config, factory.Codec) }, recorder: eventBroadcaster.NewRecorder(kapi.EventSource{Component: "deployer"}), } return &controller.RetryController{ Queue: queue, RetryManager: controller.NewQueueRetryManager( queue, cache.MetaNamespaceKeyFunc, func(obj interface{}, err error, retries controller.Retry) bool { kutil.HandleError(err) // no retries for a fatal error if _, isFatal := err.(fatalError); isFatal { return false } // infinite retries for a transient error if _, isTransient := err.(transientError); isTransient { return true } // no retries for anything else if retries.Count > 0 { return false } return true }, kutil.NewTokenBucketRateLimiter(1, 10), ), Handle: func(obj interface{}) error { config := obj.(*deployapi.DeploymentConfig) return configController.Handle(config) }, } }
func TestHookExecutor_makeHookPodOk(t *testing.T) { hook := &deployapi.LifecycleHook{ FailurePolicy: deployapi.LifecycleHookFailurePolicyAbort, ExecNewPod: &deployapi.ExecNewPodHook{ ContainerName: "container1", Command: []string{"overridden"}, Env: []kapi.EnvVar{ { Name: "name", Value: "value", }, { Name: "ENV1", Value: "overridden", }, }, }, } config := deploytest.OkDeploymentConfig(1) cpuLimit := resource.MustParse("10") memoryLimit := resource.MustParse("10M") config.Template.ControllerTemplate.Template.Spec.Containers[0].Resources = kapi.ResourceRequirements{ Limits: kapi.ResourceList{ kapi.ResourceCPU: cpuLimit, kapi.ResourceMemory: memoryLimit, }, } deployment, _ := deployutil.MakeDeployment(config, kapi.Codec) pod, err := makeHookPod(hook, deployment, "hook") if err != nil { t.Fatalf("unexpected error: %s", err) } if e, a := namer.GetPodName(deployment.Name, "hook"), pod.Name; e != a { t.Errorf("expected pod name %s, got %s", e, a) } if e, a := kapi.RestartPolicyNever, pod.Spec.RestartPolicy; e != a { t.Errorf("expected pod restart policy %s, got %s", e, a) } gotContainer := pod.Spec.Containers[0] // Verify the correct image was selected if e, a := deployment.Spec.Template.Spec.Containers[0].Image, gotContainer.Image; e != a { t.Fatalf("expected container image %s, got %s", e, a) } // Verify command overriding if e, a := "overridden", gotContainer.Command[0]; e != a { t.Fatalf("expected container command %s, got %s", e, a) } // Verify environment merging expectedEnv := map[string]string{ "name": "value", "ENV1": "overridden", } for k, v := range expectedEnv { found := false for _, env := range gotContainer.Env { if env.Name == k && env.Value == v { found = true break } } if !found { t.Errorf("expected to find %s=%s in pod environment", k, v) } } for _, env := range gotContainer.Env { val, found := expectedEnv[env.Name] if !found || val != env.Value { t.Errorf("container has unexpected environment entry %s=%s", env.Name, env.Value) } } // Verify resource limit inheritance if cpu := gotContainer.Resources.Limits.Cpu(); cpu.Value() != cpuLimit.Value() { t.Errorf("expected cpu %v, got: %v", cpuLimit, cpu) } if memory := gotContainer.Resources.Limits.Memory(); memory.Value() != memoryLimit.Value() { t.Errorf("expected memory %v, got: %v", memoryLimit, memory) } // Verify restart policy if e, a := kapi.RestartPolicyNever, pod.Spec.RestartPolicy; e != a { t.Fatalf("expected restart policy %s, got %s", e, a) } // Verify correlation stuff if l, e, a := deployapi.DeployerPodForDeploymentLabel, deployment.Name, pod.Labels[deployapi.DeployerPodForDeploymentLabel]; e != a { t.Errorf("expected label %s=%s, got %s", l, e, a) } if l, e, a := deployapi.DeploymentAnnotation, deployment.Name, pod.Annotations[deployapi.DeploymentAnnotation]; e != a { t.Errorf("expected annotation %s=%s, got %s", l, e, a) } }
func TestRolling_deployRolling(t *testing.T) { latestConfig := deploytest.OkDeploymentConfig(1) latestConfig.Template.Strategy = deploytest.OkRollingStrategy() latest, _ := deployutil.MakeDeployment(latestConfig, kapi.Codec) config := deploytest.OkDeploymentConfig(2) config.Template.Strategy = deploytest.OkRollingStrategy() deployment, _ := deployutil.MakeDeployment(config, kapi.Codec) deployments := map[string]*kapi.ReplicationController{ latest.Name: latest, deployment.Name: deployment, } var rollingConfig *kubectl.RollingUpdaterConfig deploymentUpdated := false strategy := &RollingDeploymentStrategy{ codec: api.Codec, client: &rollingUpdaterClient{ GetReplicationControllerFn: func(namespace, name string) (*kapi.ReplicationController, error) { return deployments[name], nil }, UpdateReplicationControllerFn: func(namespace string, rc *kapi.ReplicationController) (*kapi.ReplicationController, error) { if rc.Name != deployment.Name { t.Fatalf("unexpected call to UpdateReplicationController for %s", rc.Name) } deploymentUpdated = true return rc, nil }, }, initialStrategy: &testStrategy{ deployFn: func(from *kapi.ReplicationController, to *kapi.ReplicationController, desiredReplicas int, updateAcceptor kubectl.UpdateAcceptor) error { t.Fatalf("unexpected call to initial strategy") return nil }, }, rollingUpdate: func(config *kubectl.RollingUpdaterConfig) error { rollingConfig = config return nil }, getUpdateAcceptor: getUpdateAcceptor, } err := strategy.Deploy(latest, deployment, 2) if err != nil { t.Fatalf("unexpected error: %v", err) } if rollingConfig == nil { t.Fatalf("expected rolling update to be invoked") } if e, a := latest, rollingConfig.OldRc; e != a { t.Errorf("expected rollingConfig.OldRc %v, got %v", e, a) } if e, a := deployment, rollingConfig.NewRc; e != a { t.Errorf("expected rollingConfig.NewRc %v, got %v", e, a) } if e, a := 1*time.Second, rollingConfig.Interval; e != a { t.Errorf("expected Interval %d, got %d", e, a) } if e, a := 1*time.Second, rollingConfig.UpdatePeriod; e != a { t.Errorf("expected UpdatePeriod %d, got %d", e, a) } if e, a := 20*time.Second, rollingConfig.Timeout; e != a { t.Errorf("expected Timeout %d, got %d", e, a) } // verify hack if e, a := 1, rollingConfig.NewRc.Spec.Replicas; e != a { t.Errorf("expected rollingConfig.NewRc.Spec.Replicas %d, got %d", e, a) } // verify hack if !deploymentUpdated { t.Errorf("expected deployment to be updated for source annotation") } sid := fmt.Sprintf("%s:%s", latest.Name, latest.ObjectMeta.UID) if e, a := sid, rollingConfig.NewRc.Annotations[sourceIdAnnotation]; e != a { t.Errorf("expected sourceIdAnnotation %s, got %s", e, a) } }
func TestDeploymentConfigDescriber(t *testing.T) { config := deployapitest.OkDeploymentConfig(1) deployment, _ := deployutil.MakeDeployment(config, kapi.Codec) podList := &kapi.PodList{} eventList := &kapi.EventList{} deploymentList := &kapi.ReplicationControllerList{} d := &DeploymentConfigDescriber{ client: &genericDeploymentDescriberClient{ getDeploymentConfigFunc: func(namespace, name string) (*deployapi.DeploymentConfig, error) { return config, nil }, getDeploymentFunc: func(namespace, name string) (*kapi.ReplicationController, error) { return deployment, nil }, listDeploymentsFunc: func(namespace string, selector labels.Selector) (*kapi.ReplicationControllerList, error) { return deploymentList, nil }, listPodsFunc: func(namespace string, selector labels.Selector) (*kapi.PodList, error) { return podList, nil }, listEventsFunc: func(deploymentConfig *deployapi.DeploymentConfig) (*kapi.EventList, error) { return eventList, nil }, }, } describe := func() { if output, err := d.Describe("test", "deployment"); err != nil { t.Fatalf("unexpected error: %v", err) } else { t.Logf("describer output:\n%s\n", output) } } podList.Items = []kapi.Pod{*mkPod(kapi.PodRunning, 0)} describe() config.Triggers = append(config.Triggers, deployapitest.OkConfigChangeTrigger()) describe() config.Template.Strategy = deployapitest.OkCustomStrategy() describe() config.Triggers[0].ImageChangeParams.RepositoryName = "" config.Triggers[0].ImageChangeParams.From = kapi.ObjectReference{Name: "imageRepo"} describe() config.Template.Strategy = deployapitest.OkStrategy() config.Template.Strategy.RecreateParams = &deployapi.RecreateDeploymentStrategyParams{ Pre: &deployapi.LifecycleHook{ FailurePolicy: deployapi.LifecycleHookFailurePolicyAbort, ExecNewPod: &deployapi.ExecNewPodHook{ ContainerName: "container", Command: []string{"/command1", "args"}, Env: []kapi.EnvVar{ { Name: "KEY1", Value: "value1", }, }, }, }, Post: &deployapi.LifecycleHook{ FailurePolicy: deployapi.LifecycleHookFailurePolicyIgnore, ExecNewPod: &deployapi.ExecNewPodHook{ ContainerName: "container", Command: []string{"/command2", "args"}, Env: []kapi.EnvVar{ { Name: "KEY2", Value: "value2", }, }, }, }, } describe() }