func (e *HookExecutor) emitEvent(deployment *kapi.ReplicationController, eventType, reason, msg string) { t := unversioned.Time{Time: time.Now()} var ref *kapi.ObjectReference if config, err := deployutil.DecodeDeploymentConfig(deployment, e.decoder); err != nil { glog.Errorf("Unable to decode deployment %s/%s to replication contoller: %v", deployment.Namespace, deployment.Name, err) if ref, err = kapi.GetReference(deployment); err != nil { glog.Errorf("Unable to get reference for %#v: %v", deployment, err) return } } else { if ref, err = kapi.GetReference(config); err != nil { glog.Errorf("Unable to get reference for %#v: %v", config, err) return } } event := &kapi.Event{ ObjectMeta: kapi.ObjectMeta{ Name: fmt.Sprintf("%v.%x", ref.Name, t.UnixNano()), Namespace: ref.Namespace, }, InvolvedObject: *ref, Reason: reason, Message: msg, FirstTimestamp: t, LastTimestamp: t, Count: 1, Type: eventType, } if _, err := e.events.Create(event); err != nil { glog.Errorf("Could not send event '%#v': %v", event, err) } }
// TestHandle_waitForImageController tests an initial deployment with unresolved image. The config // change controller should never increment latestVersion, thus trigger a deployment for this config. func TestHandle_waitForImageController(t *testing.T) { fake := &testclient.Fake{} kFake := &ktestclient.Fake{} fake.PrependReactor("update", "deploymentconfigs/status", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) { t.Fatalf("an update should never run before the template image is resolved") return true, nil, nil }) controller := &DeploymentConfigChangeController{ client: fake, kClient: kFake, decodeConfig: func(deployment *kapi.ReplicationController) (*deployapi.DeploymentConfig, error) { return deployutil.DecodeDeploymentConfig(deployment, kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion)) }, } config := testapi.OkDeploymentConfig(0) config.Namespace = kapi.NamespaceDefault config.Spec.Triggers = []deployapi.DeploymentTriggerPolicy{testapi.OkConfigChangeTrigger(), testapi.OkImageChangeTrigger()} if err := controller.Handle(config); err != nil { t.Fatalf("unexpected error: %v", err) } }
func (c *DeploymentController) emitDeploymentEvent(deployment *kapi.ReplicationController, eventType, title, message string) { if config, _ := deployutil.DecodeDeploymentConfig(deployment, c.codec); config != nil { c.recorder.Eventf(config, eventType, title, fmt.Sprintf("%s: %s", deployment.Name, message)) } else { c.recorder.Eventf(deployment, eventType, title, message) } }
// TestHandle_newConfigNoTriggers ensures that a change to a config with no // triggers doesn't result in a new config version bump. func TestHandle_newConfigNoTriggers(t *testing.T) { 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) { t.Fatalf("unexpected generation of deploymentConfig") return nil, nil }, updateDeploymentConfigFunc: func(namespace string, config *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error) { t.Fatalf("unexpected update of deploymentConfig") return config, nil }, }, } config := deployapitest.OkDeploymentConfig(1) config.Spec.Triggers = []deployapi.DeploymentTriggerPolicy{} err := controller.Handle(config) if err != nil { t.Fatalf("unexpected error: %v", err) } }
// RecordConfigEvent records an event for the deployment config referenced by the // deployment. func RecordConfigEvent(client kclient.EventNamespacer, deployment *kapi.ReplicationController, decoder runtime.Decoder, eventType, reason, msg string) { t := unversioned.Time{Time: time.Now()} var obj runtime.Object = deployment if config, err := deployutil.DecodeDeploymentConfig(deployment, decoder); err == nil { obj = config } else { glog.Errorf("Unable to decode deployment config from %s/%s: %v", deployment.Namespace, deployment.Name, err) } ref, err := kapi.GetReference(obj) if err != nil { glog.Errorf("Unable to get reference for %#v: %v", obj, err) return } event := &kapi.Event{ ObjectMeta: kapi.ObjectMeta{ Name: fmt.Sprintf("%v.%x", ref.Name, t.UnixNano()), Namespace: ref.Namespace, }, InvolvedObject: *ref, Reason: reason, Message: msg, Source: kapi.EventSource{ Component: deployutil.DeployerPodNameFor(deployment), }, FirstTimestamp: t, LastTimestamp: t, Count: 1, Type: eventType, } if _, err := client.Events(ref.Namespace).Create(event); err != nil { glog.Errorf("Could not create event '%#v': %v", event, err) } }
// makeDeployerPod creates a pod which implements deployment behavior. The pod is correlated to // the deployment with an annotation. func (c *DeploymentController) makeDeployerPod(deployment *kapi.ReplicationController) (*kapi.Pod, error) { deploymentConfig, err := deployutil.DecodeDeploymentConfig(deployment, c.codec) if err != nil { return nil, err } container := c.makeDeployerContainer(&deploymentConfig.Spec.Strategy) // Add deployment environment variables to the container. envVars := []kapi.EnvVar{} for _, env := range container.Env { envVars = append(envVars, env) } envVars = append(envVars, kapi.EnvVar{Name: "OPENSHIFT_DEPLOYMENT_NAME", Value: deployment.Name}) envVars = append(envVars, kapi.EnvVar{Name: "OPENSHIFT_DEPLOYMENT_NAMESPACE", Value: deployment.Namespace}) // Assigning to a variable since its address is required maxDeploymentDurationSeconds := deployapi.MaxDeploymentDurationSeconds pod := &kapi.Pod{ ObjectMeta: kapi.ObjectMeta{ Name: deployutil.DeployerPodNameForDeployment(deployment.Name), Annotations: map[string]string{ deployapi.DeploymentAnnotation: deployment.Name, }, Labels: map[string]string{ deployapi.DeployerPodForDeploymentLabel: deployment.Name, }, }, Spec: kapi.PodSpec{ Containers: []kapi.Container{ { Name: "deployment", Command: container.Command, Args: container.Args, Image: container.Image, Env: envVars, Resources: deploymentConfig.Spec.Strategy.Resources, }, }, ActiveDeadlineSeconds: &maxDeploymentDurationSeconds, DNSPolicy: deployment.Spec.Template.Spec.DNSPolicy, ImagePullSecrets: deployment.Spec.Template.Spec.ImagePullSecrets, // Setting the node selector on the deployer pod so that it is created // on the same set of nodes as the pods. NodeSelector: deployment.Spec.Template.Spec.NodeSelector, RestartPolicy: kapi.RestartPolicyNever, ServiceAccountName: c.serviceAccount, }, } // MergeInfo will not overwrite values unless the flag OverwriteExistingDstKey is set. util.MergeInto(pod.Labels, deploymentConfig.Spec.Strategy.Labels, 0) util.MergeInto(pod.Annotations, deploymentConfig.Spec.Strategy.Annotations, 0) pod.Spec.Containers[0].ImagePullPolicy = kapi.PullIfNotPresent return pod, nil }
// TestHandle_cleanupDesiredReplicasAnnotation ensures that the desired replicas annotation // will be cleaned up in a complete deployment and stay around in a failed deployment func TestHandle_cleanupDesiredReplicasAnnotation(t *testing.T) { // shared fixtures shouldn't be used in unit tests shared, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion)) tests := []struct { name string pod *kapi.Pod expected bool }{ { name: "complete deployment - cleaned up annotation", pod: succeededPod(shared), expected: false, }, { name: "failed deployment - annotation stays", pod: terminatedPod(shared), expected: true, }, } for _, test := range tests { deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion)) var updatedDeployment *kapi.ReplicationController deployment.Annotations[deployapi.DesiredReplicasAnnotation] = "1" 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, } if err := controller.Handle(test.pod); err != nil { t.Errorf("%s: unexpected error: %v", test.name, err) continue } if updatedDeployment == nil { t.Errorf("%s: expected deployment update", test.name) continue } if _, got := updatedDeployment.Annotations[deployapi.DesiredReplicasAnnotation]; got != test.expected { t.Errorf("%s: expected annotation: %t, got %t", test.name, test.expected, got) } } }
// TestHandle_cleanupPodOk ensures that deployer pods are cleaned up for // deployments in a completed state. func TestHandle_cleanupPodOk(t *testing.T) { deployerPodNames := []string{"pod1", "pod2", "pod3"} deletedPodNames := []string{} controller := &DeploymentController{ decodeConfig: func(deployment *kapi.ReplicationController) (*deployapi.DeploymentConfig, error) { return deployutil.DecodeDeploymentConfig(deployment, api.Codec) }, deploymentClient: &deploymentClientImpl{ updateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { t.Fatalf("unexpected deployment update") return nil, 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.OkDeploymentConfig(1) deployment, _ := deployutil.MakeDeployment(config, kapi.Codec) 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) } }
// TestHandle_cleanupDesiredReplicasAnnotation ensures that the desired replicas annotation // will be cleaned up in a complete deployment and stay around in a failed deployment func TestHandle_cleanupDesiredReplicasAnnotation(t *testing.T) { deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codec) tests := []struct { name string pod *kapi.Pod expected bool }{ { name: "complete deployment - cleaned up annotation", pod: succeededPod(deployment), expected: false, }, { name: "failed deployment - annotation stays", pod: terminatedPod(deployment), expected: true, }, } for _, test := range tests { var updatedDeployment *kapi.ReplicationController deployment.Annotations[deployapi.DesiredReplicasAnnotation] = "1" 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 }, }, } if err := controller.Handle(test.pod); err != nil { t.Errorf("%s: unexpected error: %v", test.name, err) continue } if updatedDeployment == nil { t.Errorf("%s: expected deployment update", test.name) continue } if _, got := updatedDeployment.Annotations[deployapi.DesiredReplicasAnnotation]; got != test.expected { t.Errorf("%s: expected annotation: %t, got %t", test.name, test.expected, got) } } }
// TestHandle_cleanupPendingRunning ensures that deployer pods are deleted // for deployments in post-New phases. func TestHandle_cleanupPendingRunning(t *testing.T) { deployerPodCount := 3 deletedPods := 0 controller := &DeploymentController{ decodeConfig: func(deployment *kapi.ReplicationController) (*deployapi.DeploymentConfig, error) { return deployutil.DecodeDeploymentConfig(deployment, kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion)) }, deploymentClient: &deploymentClientImpl{ updateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { // None of these tests should transition the phase. t.Errorf("unexpected call to updateDeployment") return nil, nil }, }, podClient: &podClientImpl{ getPodFunc: func(namespace, name string) (*kapi.Pod, error) { return ttlNonZeroPod(), nil }, deletePodFunc: func(namespace, name string) error { deletedPods++ return nil }, getDeployerPodsForFunc: func(namespace, name string) ([]kapi.Pod, error) { pods := []kapi.Pod{} for i := 0; i < deployerPodCount; i++ { pods = append(pods, *ttlNonZeroPod()) } return pods, nil }, }, makeContainer: func(strategy *deployapi.DeploymentStrategy) *kapi.Container { return okContainer() }, recorder: &record.FakeRecorder{}, } cases := []deployapi.DeploymentStatus{ deployapi.DeploymentStatusPending, deployapi.DeploymentStatusRunning, } for _, status := range cases { deletedPods = 0 deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion)) deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(status) deployment.Annotations[deployapi.DeploymentCancelledAnnotation] = deployapi.DeploymentCancelledAnnotationValue err := controller.Handle(deployment) if err != nil { t.Fatalf("unexpected error: %v", err) } if e, a := deletedPods, deployerPodCount; e != a { t.Fatalf("expected %d deleted pods, got %d", e, a) } } }
// Create creates a DeploymentConfigChangeController. func (factory *DeploymentConfigChangeControllerFactory) 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("")) changeController := &DeploymentConfigChangeController{ changeStrategy: &changeStrategyImpl{ getDeploymentFunc: func(namespace, name string) (*kapi.ReplicationController, error) { return factory.KubeClient.ReplicationControllers(namespace).Get(name) }, generateDeploymentConfigFunc: func(namespace, name string) (*deployapi.DeploymentConfig, error) { return factory.Client.DeploymentConfigs(namespace).Generate(name) }, updateDeploymentConfigFunc: func(namespace string, config *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error) { return factory.Client.DeploymentConfigs(namespace).Update(config) }, }, decodeConfig: func(deployment *kapi.ReplicationController) (*deployapi.DeploymentConfig, error) { return deployutil.DecodeDeploymentConfig(deployment, 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) if _, isFatal := err.(fatalError); isFatal { return false } if retries.Count > 0 { return false } return true }, kutil.NewTokenBucketRateLimiter(1, 10), ), Handle: func(obj interface{}) error { config := obj.(*deployapi.DeploymentConfig) return changeController.Handle(config) }, } }
// 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") } }
func TestDeployerCustomLabelsAndAnnotations(t *testing.T) { controller := &DeploymentController{ decodeConfig: func(deployment *kapi.ReplicationController) (*deployapi.DeploymentConfig, error) { return deployutil.DecodeDeploymentConfig(deployment, api.Codec) }, podClient: &podClientImpl{ createPodFunc: func(namespace string, pod *kapi.Pod) (*kapi.Pod, error) { return pod, nil }, }, makeContainer: func(strategy *deployapi.DeploymentStrategy) (*kapi.Container, error) { return okContainer(), nil }, } testCases := []struct { name string strategy deployapi.DeploymentStrategy labels map[string]string annotations map[string]string verifyLabels bool }{ {name: "labels and annotations", strategy: deploytest.OkStrategy(), labels: map[string]string{"label1": "value1"}, annotations: map[string]string{"annotation1": "value1"}, verifyLabels: true}, {name: "custom strategy, no annotations", strategy: deploytest.OkCustomStrategy(), labels: map[string]string{"label2": "value2", "label3": "value3"}, verifyLabels: true}, {name: "custom strategy, no labels", strategy: deploytest.OkCustomStrategy(), annotations: map[string]string{"annotation3": "value3"}, verifyLabels: true}, {name: "no overrride", strategy: deploytest.OkStrategy(), labels: map[string]string{deployapi.DeployerPodForDeploymentLabel: "ignored"}, verifyLabels: false}, } for _, test := range testCases { t.Logf("evaluating test case %s", test.name) config := deploytest.OkDeploymentConfig(1) config.Spec.Strategy = test.strategy config.Spec.Strategy.Labels = test.labels config.Spec.Strategy.Annotations = test.annotations deployment, _ := deployutil.MakeDeployment(config, kapi.Codec) podTemplate, err := controller.makeDeployerPod(deployment) if err != nil { t.Fatal(err) } nameLabel, ok := podTemplate.Labels[deployapi.DeployerPodForDeploymentLabel] if ok && nameLabel != deployment.Name { t.Errorf("label %s expected %s, got %s", deployapi.DeployerPodForDeploymentLabel, deployment.Name, nameLabel) } else if !ok { t.Errorf("label %s not present", deployapi.DeployerPodForDeploymentLabel) } if test.verifyLabels { expectMapContains(t, podTemplate.Labels, test.labels, "labels") } expectMapContains(t, podTemplate.Annotations, test.annotations, "annotations") } }
// Create generates a new DeploymentConfig representing a rollback. func (r *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, error) { namespace, ok := kapi.NamespaceFrom(ctx) if !ok { return nil, kerrors.NewBadRequest("namespace parameter required.") } rollback, ok := obj.(*deployapi.DeploymentConfigRollback) if !ok { return nil, kerrors.NewBadRequest(fmt.Sprintf("not a rollback spec: %#v", obj)) } if errs := validation.ValidateDeploymentConfigRollback(rollback); len(errs) > 0 { return nil, kerrors.NewInvalid(deployapi.Kind("DeploymentConfigRollback"), rollback.Name, errs) } from, err := r.dn.DeploymentConfigs(namespace).Get(rollback.Name) if err != nil { return nil, newInvalidError(rollback, fmt.Sprintf("cannot get deployment config %q: %v", rollback.Name, err)) } switch from.Status.LatestVersion { case 0: return nil, newInvalidError(rollback, "cannot rollback an undeployed config") case 1: return nil, newInvalidError(rollback, fmt.Sprintf("no previous deployment exists for %q", deployutil.LabelForDeploymentConfig(from))) } revision := from.Status.LatestVersion - 1 if rollback.Spec.Revision > 0 { revision = rollback.Spec.Revision } // Find the target deployment and decode its config. name := deployutil.DeploymentNameForConfigVersion(from.Name, revision) targetDeployment, err := r.rn.ReplicationControllers(namespace).Get(name) if err != nil { return nil, newInvalidError(rollback, err.Error()) } to, err := deployutil.DecodeDeploymentConfig(targetDeployment, r.codec) if err != nil { return nil, newInvalidError(rollback, fmt.Sprintf("couldn't decode deployment config from deployment: %v", err)) } if from.Annotations == nil && len(rollback.UpdatedAnnotations) > 0 { from.Annotations = make(map[string]string) } for key, value := range rollback.UpdatedAnnotations { from.Annotations[key] = value } return r.generator.GenerateRollback(from, to, &rollback.Spec) }
// executeExecNewPod executes a ExecNewPod hook by creating a new pod based on // the hook parameters and deployment. The pod is then synchronously watched // until the pod completes, and if the pod failed, an error is returned. // // The hook pod inherits the following from the container the hook refers to: // // * Environment (hook keys take precedence) // * Working directory // * Resources func (e *HookExecutor) executeExecNewPod(hook *deployapi.LifecycleHook, deployment *kapi.ReplicationController, label string) error { config, err := deployutil.DecodeDeploymentConfig(deployment, e.codec) if err != nil { return err } // Build a pod spec from the hook config and deployment podSpec, err := makeHookPod(hook, deployment, &config.Template.Strategy, label) if err != nil { return err } // Try to create the pod. pod, err := e.podClient.CreatePod(deployment.Namespace, podSpec) if err != nil { if !kerrors.IsAlreadyExists(err) { return fmt.Errorf("couldn't create lifecycle pod for %s: %v", deployutil.LabelForDeployment(deployment), err) } } else { glog.V(0).Infof("Created lifecycle pod %s/%s for deployment %s", pod.Namespace, pod.Name, deployutil.LabelForDeployment(deployment)) } stopChannel := make(chan struct{}) defer close(stopChannel) nextPod := e.podClient.PodWatch(pod.Namespace, pod.Name, pod.ResourceVersion, stopChannel) // Wait for the hook pod to reach a terminal phase. Start reading logs as // soon as the pod enters a usable phase. var updatedPod *kapi.Pod var once sync.Once wg := &sync.WaitGroup{} wg.Add(1) glog.V(0).Infof("Watching logs for hook pod %s/%s while awaiting completion", pod.Namespace, pod.Name) waitLoop: for { updatedPod = nextPod() switch updatedPod.Status.Phase { case kapi.PodRunning: go once.Do(func() { e.readPodLogs(pod, wg) }) case kapi.PodSucceeded, kapi.PodFailed: go once.Do(func() { e.readPodLogs(pod, wg) }) break waitLoop } } // The pod is finished, wait for all logs to be consumed before returning. wg.Wait() if updatedPod.Status.Phase == kapi.PodFailed { return fmt.Errorf(updatedPod.Status.Message) } return nil }
// 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_unrelatedPodAlreadyExists ensures that attempts to create a // deployer pod, when a pod with the same name but missing annotations results // a transition to failed. func TestHandle_unrelatedPodAlreadyExists(t *testing.T) { var updatedDeployment *kapi.ReplicationController config := deploytest.OkDeploymentConfig(1) deployment, _ := deployutil.MakeDeployment(config, kapi.Codec) deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(deployapi.DeploymentStatusNew) otherPod := unrelatedPod(deployment) controller := &DeploymentController{ decodeConfig: func(deployment *kapi.ReplicationController) (*deployapi.DeploymentConfig, error) { return deployutil.DecodeDeploymentConfig(deployment, api.Codec) }, deploymentClient: &deploymentClientImpl{ updateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { updatedDeployment = deployment return updatedDeployment, nil }, }, podClient: &podClientImpl{ getPodFunc: func(namespace, name string) (*kapi.Pod, error) { return otherPod, nil }, createPodFunc: func(namespace string, pod *kapi.Pod) (*kapi.Pod, error) { return nil, kerrors.NewAlreadyExists("Pod", pod.Name) }, }, makeContainer: func(strategy *deployapi.DeploymentStrategy) (*kapi.Container, error) { return okContainer(), nil }, recorder: &record.FakeRecorder{}, } err := controller.Handle(deployment) if 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, deployment.Annotations[deployapi.DeploymentStatusReasonAnnotation]; e != a { t.Errorf("expected reason annotation %s, got %s", e, a) } if e, a := deployapi.DeploymentStatusFailed, deployutil.DeploymentStatusFor(updatedDeployment); e != a { t.Errorf("expected deployment status %s, got %s", e, a) } }
func TestHandle_cleanupNewWithDeployers(t *testing.T) { var updatedDeployment *kapi.ReplicationController deletedDeployer := false deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion)) deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(deployapi.DeploymentStatusNew) deployment.Annotations[deployapi.DeploymentCancelledAnnotation] = deployapi.DeploymentCancelledAnnotationValue controller := &DeploymentController{ decodeConfig: func(deployment *kapi.ReplicationController) (*deployapi.DeploymentConfig, error) { return deployutil.DecodeDeploymentConfig(deployment, kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion)) }, deploymentClient: &deploymentClientImpl{ updateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { updatedDeployment = deployment return updatedDeployment, nil }, }, podClient: &podClientImpl{ createPodFunc: func(namespace string, pod *kapi.Pod) (*kapi.Pod, error) { t.Fatalf("unexpected call to make container") return nil, nil }, getDeployerPodsForFunc: func(namespace, name string) ([]kapi.Pod, error) { return []kapi.Pod{*relatedPod(deployment)}, nil }, deletePodFunc: func(namespace, name string) error { deletedDeployer = true return nil }, }, makeContainer: func(strategy *deployapi.DeploymentStrategy) *kapi.Container { return okContainer() }, recorder: &record.FakeRecorder{}, } err := controller.Handle(deployment) if err != nil { t.Fatalf("unexpected error: %v", err) } if e, a := deployapi.DeploymentStatusPending, deployutil.DeploymentStatusFor(updatedDeployment); e != a { t.Fatalf("expected deployment status %s, got %s", e, a) } if !deletedDeployer { t.Fatalf("expected deployer delete") } }
// 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 (c *DeploymentTriggerController) updateDeploymentConfig(old, cur interface{}) { newDc := cur.(*deployapi.DeploymentConfig) oldDc := old.(*deployapi.DeploymentConfig) // A periodic relist will send update events for all known deployment configs. if newDc.ResourceVersion == oldDc.ResourceVersion { return } // No need to enqueue deployment configs that have no triggers or are paused. if len(newDc.Spec.Triggers) == 0 || newDc.Spec.Paused { return } // We don't want to compete with the main deployment config controller. Let's process this // config once it's synced. Note that this does not eliminate conflicts between the two // controllers because the main controller is constantly updating deployment configs as // owning replication controllers and pods are updated. if !deployutil.HasSynced(newDc, newDc.Generation) { return } // Enqueue the deployment config if it hasn't been deployed yet. if newDc.Status.LatestVersion == 0 { c.enqueueDeploymentConfig(newDc) return } // Compare deployment config templates before enqueueing. This reduces the amount of times // we will try to instantiate a deployment config at the expense of duplicating some of the // work that the instantiate endpoint is already doing but I think this is fine. shouldInstantiate := true latestRc, err := c.rcLister.ReplicationControllers(newDc.Namespace).Get(deployutil.LatestDeploymentNameForConfig(newDc)) if err != nil { // If we get an error here it may be due to the rc cache lagging behind. In such a case // just defer to the api server (instantiate REST) where we will retry this. glog.V(2).Infof("Cannot get latest rc for dc %s:%d (%v) - will defer to instantiate", deployutil.LabelForDeploymentConfig(newDc), newDc.Status.LatestVersion, err) } else { initial, err := deployutil.DecodeDeploymentConfig(latestRc, c.codec) if err != nil { glog.V(2).Infof("Cannot decode dc from replication controller %s: %v", deployutil.LabelForDeployment(latestRc), err) return } shouldInstantiate = !reflect.DeepEqual(newDc.Spec.Template, initial.Spec.Template) } if !shouldInstantiate { return } c.enqueueDeploymentConfig(newDc) }
// Create creates a DeploymentConfigChangeController. func (factory *DeploymentConfigChangeControllerFactory) Create() controller.RunnableController { deploymentConfigLW := &cache.ListWatch{ ListFunc: func(options kapi.ListOptions) (runtime.Object, error) { return factory.Client.DeploymentConfigs(kapi.NamespaceAll).List(options) }, WatchFunc: func(options kapi.ListOptions) (watch.Interface, error) { return factory.Client.DeploymentConfigs(kapi.NamespaceAll).Watch(options) }, } queue := cache.NewFIFO(cache.MetaNamespaceKeyFunc) cache.NewReflector(deploymentConfigLW, &deployapi.DeploymentConfig{}, queue, 2*time.Minute).Run() eventBroadcaster := record.NewBroadcaster() eventBroadcaster.StartRecordingToSink(factory.KubeClient.Events("")) changeController := &DeploymentConfigChangeController{ client: factory.Client, kClient: factory.KubeClient, decodeConfig: func(deployment *kapi.ReplicationController) (*deployapi.DeploymentConfig, error) { return deployutil.DecodeDeploymentConfig(deployment, factory.Codec) }, } return &controller.RetryController{ Queue: queue, RetryManager: controller.NewQueueRetryManager( queue, cache.MetaNamespaceKeyFunc, func(obj interface{}, err error, retries controller.Retry) bool { utilruntime.HandleError(err) if _, isFatal := err.(fatalError); isFatal { return false } if retries.Count > 0 { return false } return true }, flowcontrol.NewTokenBucketRateLimiter(1, 10), ), Handle: func(obj interface{}) error { config := obj.(*deployapi.DeploymentConfig) return changeController.Handle(config) }, } }
// TestHandle_deployerPodAlreadyExists ensures that attempts to create a // deployer pod which was already created don't result in an error // (effectively skipping the handling as redundant). func TestHandle_deployerPodAlreadyExists(t *testing.T) { var updatedDeployment *kapi.ReplicationController config := deploytest.OkDeploymentConfig(1) deployment, _ := deployutil.MakeDeployment(config, kapi.Codec) deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(deployapi.DeploymentStatusNew) deployerPod := relatedPod(deployment) controller := &DeploymentController{ decodeConfig: func(deployment *kapi.ReplicationController) (*deployapi.DeploymentConfig, error) { return deployutil.DecodeDeploymentConfig(deployment, api.Codec) }, deploymentClient: &deploymentClientImpl{ updateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { updatedDeployment = deployment return updatedDeployment, nil }, }, podClient: &podClientImpl{ getPodFunc: func(namespace, name string) (*kapi.Pod, error) { return deployerPod, nil }, createPodFunc: func(namespace string, pod *kapi.Pod) (*kapi.Pod, error) { return nil, kerrors.NewAlreadyExists("Pod", pod.Name) }, }, makeContainer: func(strategy *deployapi.DeploymentStrategy) (*kapi.Container, error) { return okContainer(), nil }, recorder: &record.FakeRecorder{}, } err := controller.Handle(deployment) if err != nil { t.Fatalf("unexpected error: %v", err) } if updatedDeployment.Annotations[deployapi.DeploymentPodAnnotation] != deployerPod.Name { t.Fatalf("deployment not updated with pod name annotation") } if updatedDeployment.Annotations[deployapi.DeploymentStatusAnnotation] != string(deployapi.DeploymentStatusPending) { t.Fatalf("deployment status not updated to pending") } }
// TestHandle_noop ensures that pending, running, and failed states result in // no action by the controller (as these represent in-progress or terminal // states). func TestHandle_noop(t *testing.T) { controller := &DeploymentController{ decodeConfig: func(deployment *kapi.ReplicationController) (*deployapi.DeploymentConfig, error) { return deployutil.DecodeDeploymentConfig(deployment, api.Codec) }, deploymentClient: &deploymentClientImpl{ updateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { t.Fatalf("unexpected deployment update") return nil, nil }, }, podClient: &podClientImpl{ createPodFunc: func(namespace string, pod *kapi.Pod) (*kapi.Pod, error) { t.Fatalf("unexpected call to create pod") return nil, nil }, getPodFunc: func(namespace, name string) (*kapi.Pod, error) { return &kapi.Pod{}, nil }, }, makeContainer: func(strategy *deployapi.DeploymentStrategy) (*kapi.Container, error) { t.Fatalf("unexpected call to make container") return nil, nil }, recorder: &record.FakeRecorder{}, } // Verify no-op config := deploytest.OkDeploymentConfig(1) deployment, _ := deployutil.MakeDeployment(config, kapi.Codec) noopStatus := []deployapi.DeploymentStatus{ deployapi.DeploymentStatusPending, deployapi.DeploymentStatusRunning, deployapi.DeploymentStatusFailed, } for _, status := range noopStatus { deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(status) err := controller.Handle(deployment) if err != nil { t.Fatalf("unexpected error: %v", err) } } }
func TestHandle_nonAutomaticImageUpdates(t *testing.T) { var updated *deployapi.DeploymentConfig controller := &DeploymentConfigChangeController{ decodeConfig: func(deployment *kapi.ReplicationController) (*deployapi.DeploymentConfig, error) { return deployutil.DecodeDeploymentConfig(deployment, kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion)) }, changeStrategy: &changeStrategyImpl{ generateDeploymentConfigFunc: func(namespace, name string) (*deployapi.DeploymentConfig, error) { generated := deployapitest.OkDeploymentConfig(1) // The generator doesn't change automatic so it's ok to fake it here. generated.Spec.Triggers[0].ImageChangeParams.Automatic = false generated.Status.Details = deployapitest.OkImageChangeDetails() updated = generated return generated, nil }, updateDeploymentConfigFunc: func(namespace string, config *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error) { updated.Status.Details = deployapitest.OkConfigChangeDetails() return updated, nil }, }, } config := deployapitest.OkDeploymentConfig(0) ict := deployapitest.OkImageChangeTrigger() ict.ImageChangeParams.Automatic = false config.Spec.Triggers = []deployapi.DeploymentTriggerPolicy{deployapitest.OkConfigChangeTrigger(), ict} if err := controller.Handle(config); err != nil { t.Fatalf("unexpected error: %v", err) } if e, a := 1, updated.Status.LatestVersion; e != a { t.Fatalf("expected update to latestversion=%d, got %d", e, a) } if updated.Status.Details == nil { t.Fatalf("expected config change details to be set") } else if updated.Status.Details.Causes == nil { t.Fatalf("expected config change causes to be set") } else if updated.Status.Details.Causes[0].Type != deployapi.DeploymentTriggerOnConfigChange { t.Fatalf("expected config change cause to be set to config change trigger, got %s", updated.Status.Details.Causes[0].Type) } }
// decodeFromLatestDeployment will try to return the decoded version of the current deploymentconfig // found in the annotations of its latest deployment. If there is no previous deploymentconfig (ie. // latestVersion == 0), the returned deploymentconfig will be the same. func decodeFromLatestDeployment(config *deployapi.DeploymentConfig, rn kclient.ReplicationControllersNamespacer, decoder runtime.Decoder) (*deployapi.DeploymentConfig, error) { if config.Status.LatestVersion == 0 { return config, nil } latestDeploymentName := deployutil.LatestDeploymentNameForConfig(config) deployment, err := rn.ReplicationControllers(config.Namespace).Get(latestDeploymentName) if err != nil { // If there's no deployment for the latest config, we have no basis of // comparison. It's the responsibility of the deployment config controller // to make the deployment for the config, so return early. return nil, err } decoded, err := deployutil.DecodeDeploymentConfig(deployment, decoder) if err != nil { return nil, errors.NewInternalError(err) } return decoded, nil }
// 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_newConfigTriggers ensures that the creation of a new config // (with version 0) with a config change trigger results in a version bump and // cause update for initial deployment. func TestHandle_newConfigTriggers(t *testing.T) { var updated *deployapi.DeploymentConfig controller := &DeploymentConfigChangeController{ decodeConfig: func(deployment *kapi.ReplicationController) (*deployapi.DeploymentConfig, error) { return deployutil.DecodeDeploymentConfig(deployment, kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion)) }, changeStrategy: &changeStrategyImpl{ generateDeploymentConfigFunc: func(namespace, name string) (*deployapi.DeploymentConfig, error) { return deployapitest.OkDeploymentConfig(1), nil }, updateDeploymentConfigFunc: func(namespace string, config *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error) { updated = config return config, nil }, }, } config := deployapitest.OkDeploymentConfig(0) config.Spec.Triggers = []deployapi.DeploymentTriggerPolicy{deployapitest.OkConfigChangeTrigger()} 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 := 1, updated.Status.LatestVersion; e != a { t.Fatalf("expected update to latestversion=%d, got %d", e, a) } if updated.Status.Details == nil { t.Fatalf("expected config change details to be set") } else if updated.Status.Details.Causes == nil { t.Fatalf("expected config change causes to be set") } else if updated.Status.Details.Causes[0].Type != deployapi.DeploymentTriggerOnConfigChange { t.Fatalf("expected config change cause to be set to config change trigger, got %s", updated.Status.Details.Causes[0].Type) } }
// TestHandle_deployerPodDisappeared ensures that a pending/running deployment // is failed when its deployer pod vanishes. func TestHandle_deployerPodDisappeared(t *testing.T) { var updatedDeployment *kapi.ReplicationController updateCalled := false controller := &DeploymentController{ decodeConfig: func(deployment *kapi.ReplicationController) (*deployapi.DeploymentConfig, error) { return deployutil.DecodeDeploymentConfig(deployment, api.Codec) }, deploymentClient: &deploymentClientImpl{ updateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { updatedDeployment = deployment updateCalled = true return updatedDeployment, nil }, }, podClient: &podClientImpl{ getPodFunc: func(namespace, name string) (*kapi.Pod, error) { return nil, kerrors.NewNotFound("Pod", name) }, }, makeContainer: func(strategy *deployapi.DeploymentStrategy) (*kapi.Container, error) { return okContainer(), nil }, recorder: &record.FakeRecorder{}, } deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codec) deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(deployapi.DeploymentStatusRunning) err := controller.Handle(deployment) if err != nil { t.Fatalf("unexpected error: %v", err) } if !updateCalled { t.Fatalf("expected update") } if e, a := deployapi.DeploymentStatusFailed, deployutil.DeploymentStatusFor(updatedDeployment); e != a { t.Fatalf("expected deployment status %s, got %s", e, a) } }
// decodeFromLatest will try to return the decoded version of the current deploymentconfig found // in the annotations of its latest deployment. If there is no previous deploymentconfig (ie. // latestVersion == 0), the returned deploymentconfig will be the same. func (c *DeploymentTriggerController) decodeFromLatest(config *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error) { if config.Status.LatestVersion == 0 { return config, nil } latestDeploymentName := deployutil.LatestDeploymentNameForConfig(config) deployment, err := c.rn.ReplicationControllers(config.Namespace).Get(latestDeploymentName) if err != nil { // If there's no deployment for the latest config, we have no basis of // comparison. It's the responsibility of the deployment config controller // to make the deployment for the config, so return early. return nil, fmt.Errorf("couldn't retrieve deployment for deployment config %q: %v", deployutil.LabelForDeploymentConfig(config), err) } latest, err := deployutil.DecodeDeploymentConfig(deployment, c.codec) if err != nil { return nil, fatalError(err.Error()) } return latest, nil }
// TestHandle_cleanupPodNoop ensures that an attempt to delete pods are not made // if the deployer pods are not listed based on a label query func TestHandle_cleanupPodNoop(t *testing.T) { controller := &DeploymentController{ decodeConfig: func(deployment *kapi.ReplicationController) (*deployapi.DeploymentConfig, error) { return deployutil.DecodeDeploymentConfig(deployment, api.Codec) }, deploymentClient: &deploymentClientImpl{ updateDeploymentFunc: func(namespace string, deployment *kapi.ReplicationController) (*kapi.ReplicationController, error) { t.Fatalf("unexpected deployment update") return nil, 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) { return []kapi.Pod{}, nil }, }, makeContainer: func(strategy *deployapi.DeploymentStrategy) (*kapi.Container, error) { t.Fatalf("unexpected call to make container") return nil, nil }, recorder: &record.FakeRecorder{}, } // Verify no-op config := deploytest.OkDeploymentConfig(1) deployment, _ := deployutil.MakeDeployment(config, kapi.Codec) deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(deployapi.DeploymentStatusComplete) err := controller.Handle(deployment) if err != nil { t.Fatalf("unexpected error: %v", err) } }