// Handle processes deployment and either creates a deployer pod or responds // to a terminal deployment status. func (c *DeploymentController) Handle(deployment *kapi.ReplicationController) error { currentStatus := deployutil.DeploymentStatusFor(deployment) nextStatus := currentStatus deploymentScaled := false switch currentStatus { case deployapi.DeploymentStatusNew: // If the deployment has been cancelled, don't create a deployer pod, and // transition to failed immediately. if deployutil.IsDeploymentCancelled(deployment) { nextStatus = deployapi.DeploymentStatusFailed break } // Generate a deployer pod spec. podTemplate, err := c.makeDeployerPod(deployment) if err != nil { return fatalError(fmt.Sprintf("couldn't make deployer pod for %s: %v", deployutil.LabelForDeployment(deployment), err)) } // Create the deployer pod. deploymentPod, err := c.podClient.createPod(deployment.Namespace, podTemplate) if err == nil { deployment.Annotations[deployapi.DeploymentPodAnnotation] = deploymentPod.Name nextStatus = deployapi.DeploymentStatusPending glog.V(4).Infof("Created pod %s for deployment %s", deploymentPod.Name, deployutil.LabelForDeployment(deployment)) break } // Retry on error. if !kerrors.IsAlreadyExists(err) { c.recorder.Eventf(deployment, kapi.EventTypeWarning, "FailedCreate", "Error creating deployer pod for %s: %v", deployutil.LabelForDeployment(deployment), err) return fmt.Errorf("couldn't create deployer pod for %s: %v", deployutil.LabelForDeployment(deployment), err) } // If the pod already exists, it's possible that a previous CreatePod // succeeded but the deployment state update failed and now we're re- // entering. Ensure that the pod is the one we created by verifying the // annotation on it, and throw a retryable error. existingPod, err := c.podClient.getPod(deployment.Namespace, deployutil.DeployerPodNameForDeployment(deployment.Name)) if err != nil { c.recorder.Eventf(deployment, kapi.EventTypeWarning, "FailedCreate", "Error getting existing deployer pod for %s: %v", deployutil.LabelForDeployment(deployment), err) return fmt.Errorf("couldn't fetch existing deployer pod for %s: %v", deployutil.LabelForDeployment(deployment), err) } // Do a stronger check to validate that the existing deployer pod is // actually for this deployment, and if not, fail this deployment. // // TODO: Investigate checking the container image of the running pod and // comparing with the intended deployer pod image. If we do so, we'll need // to ensure that changes to 'unrelated' pods don't result in updates to // the deployment. So, the image check will have to be done in other areas // of the code as well. if deployutil.DeploymentNameFor(existingPod) != deployment.Name { nextStatus = deployapi.DeploymentStatusFailed deployment.Annotations[deployapi.DeploymentStatusReasonAnnotation] = deployapi.DeploymentFailedUnrelatedDeploymentExists c.recorder.Eventf(deployment, kapi.EventTypeWarning, "FailedCreate", "Error creating deployer pod for %s since another pod with the same name (%q) exists", deployutil.LabelForDeployment(deployment), existingPod.Name) glog.V(2).Infof("Couldn't create deployer pod for %s since an unrelated pod with the same name (%q) exists", deployutil.LabelForDeployment(deployment), existingPod.Name) break } // Update to pending relative to the existing validated deployer pod. deployment.Annotations[deployapi.DeploymentPodAnnotation] = existingPod.Name nextStatus = deployapi.DeploymentStatusPending glog.V(4).Infof("Detected existing deployer pod %s for deployment %s", existingPod.Name, deployutil.LabelForDeployment(deployment)) case deployapi.DeploymentStatusPending, deployapi.DeploymentStatusRunning: // If the deployer pod has vanished, consider the deployment a failure. deployerPodName := deployutil.DeployerPodNameForDeployment(deployment.Name) if _, err := c.podClient.getPod(deployment.Namespace, deployerPodName); err != nil { if kerrors.IsNotFound(err) { nextStatus = deployapi.DeploymentStatusFailed deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(nextStatus) deployment.Annotations[deployapi.DeploymentStatusReasonAnnotation] = deployapi.DeploymentFailedDeployerPodNoLongerExists c.recorder.Eventf(deployment, kapi.EventTypeWarning, "failed", "Deployer pod %q has gone missing", deployerPodName) glog.V(4).Infof("Failing deployment %q because its deployer pod %q disappeared", deployutil.LabelForDeployment(deployment), deployerPodName) break } else { // We'll try again later on resync. Continue to process cancellations. glog.V(2).Infof("Error getting deployer pod %s for deployment %s: %#v", deployerPodName, deployutil.LabelForDeployment(deployment), err) } } // If the deployment is cancelled, terminate any deployer/hook pods. // NOTE: Do not mark the deployment as Failed just yet. // The deployment will be marked as Failed by the deployer pod controller // when the deployer pod failure state is picked up // Also, it will scale down the failed deployment and scale back up // the last successful completed deployment if deployutil.IsDeploymentCancelled(deployment) { deployerPods, err := c.podClient.getDeployerPodsFor(deployment.Namespace, deployment.Name) if err != nil { return fmt.Errorf("couldn't fetch deployer pods for %s while trying to cancel deployment: %v", deployutil.LabelForDeployment(deployment), err) } glog.V(4).Infof("Cancelling %d deployer pods for deployment %s", len(deployerPods), deployutil.LabelForDeployment(deployment)) zeroDelay := int64(1) for _, deployerPod := range deployerPods { // Set the ActiveDeadlineSeconds on the pod so it's terminated very soon. if deployerPod.Spec.ActiveDeadlineSeconds == nil || *deployerPod.Spec.ActiveDeadlineSeconds != zeroDelay { deployerPod.Spec.ActiveDeadlineSeconds = &zeroDelay if _, err := c.podClient.updatePod(deployerPod.Namespace, &deployerPod); err != nil { c.recorder.Eventf(deployment, kapi.EventTypeWarning, "failedCancellation", "Error cancelling deployer pod %s for deployment %s: %v", deployerPod.Name, deployutil.LabelForDeployment(deployment), err) return fmt.Errorf("couldn't cancel deployer pod %s for deployment %s: %v", deployerPod.Name, deployutil.LabelForDeployment(deployment), err) } glog.V(4).Infof("Cancelled deployer pod %s for deployment %s", deployerPod.Name, deployutil.LabelForDeployment(deployment)) } } c.recorder.Eventf(deployment, kapi.EventTypeNormal, "Cancelled", "Cancelled deployment") } case deployapi.DeploymentStatusFailed: // Check for test deployment and ensure the deployment scale matches if config, err := c.decodeConfig(deployment); err == nil && config.Spec.Test { deploymentScaled = deployment.Spec.Replicas != 0 deployment.Spec.Replicas = 0 } case deployapi.DeploymentStatusComplete: // Check for test deployment and ensure the deployment scale matches if config, err := c.decodeConfig(deployment); err == nil && config.Spec.Test { deploymentScaled = deployment.Spec.Replicas != 0 deployment.Spec.Replicas = 0 } // now list any pods in the namespace that have the specified label deployerPods, err := c.podClient.getDeployerPodsFor(deployment.Namespace, deployment.Name) if err != nil { return fmt.Errorf("couldn't fetch deployer pods for %s after successful completion: %v", deployutil.LabelForDeployment(deployment), err) } if len(deployerPods) > 0 { glog.V(4).Infof("Deleting %d deployer pods for deployment %s", len(deployerPods), deployutil.LabelForDeployment(deployment)) } cleanedAll := true for _, deployerPod := range deployerPods { if err := c.podClient.deletePod(deployerPod.Namespace, deployerPod.Name); err != nil { if !kerrors.IsNotFound(err) { // if the pod deletion failed, then log the error and continue // we will try to delete any remaining deployer pods and return an error later utilruntime.HandleError(fmt.Errorf("couldn't delete completed deployer pod %s/%s for deployment %s: %v", deployment.Namespace, deployerPod.Name, deployutil.LabelForDeployment(deployment), err)) cleanedAll = false } // Already deleted } else { glog.V(4).Infof("Deleted completed deployer pod %s/%s for deployment %s", deployment.Namespace, deployerPod.Name, deployutil.LabelForDeployment(deployment)) } } if !cleanedAll { return fmt.Errorf("couldn't clean up all deployer pods for %s", deployutil.LabelForDeployment(deployment)) } } if currentStatus != nextStatus || deploymentScaled { deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(nextStatus) if _, err := c.deploymentClient.updateDeployment(deployment.Namespace, deployment); err != nil { c.recorder.Eventf(deployment, kapi.EventTypeWarning, "FailedUpdate", "Error updating deployment %s status to %s", deployutil.LabelForDeployment(deployment), nextStatus) return fmt.Errorf("couldn't update deployment %s to status %s: %v", deployutil.LabelForDeployment(deployment), nextStatus, err) } glog.V(4).Infof("Updated deployment %s status from %s to %s (scale: %d)", deployutil.LabelForDeployment(deployment), currentStatus, nextStatus, deployment.Spec.Replicas) } return nil }
// Handle processes deployment and either creates a deployer pod or responds // to a terminal deployment status. Since this controller started using caches, // the provided rc MUST be deep-copied beforehand (see work() in factory.go). func (c *DeploymentController) Handle(deployment *kapi.ReplicationController) error { currentStatus := deployutil.DeploymentStatusFor(deployment) nextStatus := currentStatus deploymentScaled := false deployerPodName := deployutil.DeployerPodNameForDeployment(deployment.Name) deployer, deployerErr := c.getPod(deployment.Namespace, deployerPodName) switch currentStatus { case deployapi.DeploymentStatusNew: // If the deployment has been cancelled, don't create a deployer pod. // Instead try to delete any deployer pods found and transition the // deployment to Pending so that the deployment config controller // continues to see the deployment as in-flight. Eventually the deletion // of the deployer pod should cause a requeue of this deployment and // then it can be transitioned to Failed by this controller. if deployutil.IsDeploymentCancelled(deployment) { nextStatus = deployapi.DeploymentStatusPending if err := c.cleanupDeployerPods(deployment); err != nil { return err } break } // If the pod already exists, it's possible that a previous CreatePod // succeeded but the deployment state update failed and now we're re- // entering. Ensure that the pod is the one we created by verifying the // annotation on it, and throw a retryable error. if deployerErr != nil && !kerrors.IsNotFound(deployerErr) { return fmt.Errorf("couldn't fetch existing deployer pod for %s: %v", deployutil.LabelForDeployment(deployment), deployerErr) } if deployerErr == nil && deployer != nil { // Do a stronger check to validate that the existing deployer pod is // actually for this deployment, and if not, fail this deployment. // // TODO: Investigate checking the container image of the running pod and // comparing with the intended deployer pod image. If we do so, we'll need // to ensure that changes to 'unrelated' pods don't result in updates to // the deployment. So, the image check will have to be done in other areas // of the code as well. if deployutil.DeploymentNameFor(deployer) != deployment.Name { nextStatus = deployapi.DeploymentStatusFailed deployment.Annotations[deployapi.DeploymentStatusReasonAnnotation] = deployapi.DeploymentFailedUnrelatedDeploymentExists c.emitDeploymentEvent(deployment, kapi.EventTypeWarning, "FailedCreate", fmt.Sprintf("Error creating deployer pod since another pod with the same name (%q) exists", deployer.Name)) glog.V(2).Infof("Couldn't create deployer pod for %s since an unrelated pod with the same name (%q) exists", deployutil.LabelForDeployment(deployment), deployer.Name) } else { // Update to pending relative to the existing validated deployer pod. deployment.Annotations[deployapi.DeploymentPodAnnotation] = deployer.Name nextStatus = deployapi.DeploymentStatusPending glog.V(4).Infof("Detected existing deployer pod %s for deployment %s", deployer.Name, deployutil.LabelForDeployment(deployment)) } // Don't try and re-create the deployer pod. break } if _, ok := deployment.Annotations[deployapi.DeploymentIgnorePodAnnotation]; ok { return nil } // Generate a deployer pod spec. deployerPod, err := c.makeDeployerPod(deployment) if err != nil { return fatalError(fmt.Sprintf("couldn't make deployer pod for %s: %v", deployutil.LabelForDeployment(deployment), err)) } // Create the deployer pod. deploymentPod, err := c.pn.Pods(deployment.Namespace).Create(deployerPod) // Retry on error. if err != nil { return actionableError(fmt.Sprintf("couldn't create deployer pod for %s: %v", deployutil.LabelForDeployment(deployment), err)) } deployment.Annotations[deployapi.DeploymentPodAnnotation] = deploymentPod.Name nextStatus = deployapi.DeploymentStatusPending glog.V(4).Infof("Created deployer pod %s for deployment %s", deploymentPod.Name, deployutil.LabelForDeployment(deployment)) case deployapi.DeploymentStatusPending, deployapi.DeploymentStatusRunning: switch { case kerrors.IsNotFound(deployerErr): nextStatus = deployapi.DeploymentStatusFailed // If the deployment is cancelled here then we deleted the deployer in a previous // resync of the deployment. if !deployutil.IsDeploymentCancelled(deployment) { deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(nextStatus) deployment.Annotations[deployapi.DeploymentStatusReasonAnnotation] = deployapi.DeploymentFailedDeployerPodNoLongerExists c.emitDeploymentEvent(deployment, kapi.EventTypeWarning, "Failed", fmt.Sprintf("Deployer pod %q has gone missing", deployerPodName)) glog.V(4).Infof("Failing deployment %q because its deployer pod %q disappeared", deployutil.LabelForDeployment(deployment), deployerPodName) } case deployerErr != nil: // We'll try again later on resync. Continue to process cancellations. glog.V(4).Infof("Error getting deployer pod %s for deployment %s: %v", deployerPodName, deployutil.LabelForDeployment(deployment), deployerErr) default: /* err == nil */ // If the deployment has been cancelled, delete any deployer pods // found and transition the deployment to Pending so that the // deployment config controller continues to see the deployment // as in-flight. Eventually the deletion of the deployer pod should // cause a requeue of this deployment and then it can be transitioned // to Failed by this controller. if deployutil.IsDeploymentCancelled(deployment) { if err := c.cleanupDeployerPods(deployment); err != nil { return err } } } case deployapi.DeploymentStatusFailed: // Check for test deployment and ensure the deployment scale matches if config, err := deployutil.DecodeDeploymentConfig(deployment, c.codec); err == nil && config.Spec.Test { deploymentScaled = deployment.Spec.Replicas != 0 deployment.Spec.Replicas = 0 } // Try to cleanup once more a cancelled deployment in case hook pods // were created just after we issued the first cleanup request. if deployutil.IsDeploymentCancelled(deployment) { if err := c.cleanupDeployerPods(deployment); err != nil { return err } } case deployapi.DeploymentStatusComplete: // Check for test deployment and ensure the deployment scale matches if config, err := deployutil.DecodeDeploymentConfig(deployment, c.codec); err == nil && config.Spec.Test { deploymentScaled = deployment.Spec.Replicas != 0 deployment.Spec.Replicas = 0 } if err := c.cleanupDeployerPods(deployment); err != nil { return err } } if deployutil.CanTransitionPhase(currentStatus, nextStatus) || deploymentScaled { deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(nextStatus) if _, err := c.rn.ReplicationControllers(deployment.Namespace).Update(deployment); err != nil { return fmt.Errorf("couldn't update deployment %s to status %s: %v", deployutil.LabelForDeployment(deployment), nextStatus, err) } glog.V(4).Infof("Updated deployment %s status from %s to %s (scale: %d)", deployutil.LabelForDeployment(deployment), currentStatus, nextStatus, deployment.Spec.Replicas) } return nil }
// TestHandle_createPodOk ensures that a the deployer pod created in response // to a new deployment is valid. func TestHandle_createPodOk(t *testing.T) { var ( updatedDeployment *kapi.ReplicationController createdPod *kapi.Pod expectedContainer = okContainer() ) fake := &ktestclient.Fake{} fake.AddReactor("create", "pods", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) { pod := action.(ktestclient.CreateAction).GetObject().(*kapi.Pod) createdPod = pod return true, pod, nil }) fake.AddReactor("update", "replicationcontrollers", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) { rc := action.(ktestclient.UpdateAction).GetObject().(*kapi.ReplicationController) updatedDeployment = rc return true, rc, nil }) // Verify new -> pending config := deploytest.OkDeploymentConfig(1) config.Spec.Strategy = deploytest.OkCustomStrategy() deployment, _ := deployutil.MakeDeployment(config, codec) deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(deployapi.DeploymentStatusNew) deployment.Spec.Template.Spec.NodeSelector = map[string]string{"labelKey1": "labelValue1", "labelKey2": "labelValue2"} controller := okDeploymentController(fake, nil, nil, true) if err := controller.Handle(deployment); err != nil { t.Fatalf("unexpected error: %v", err) } if updatedDeployment == nil { t.Fatalf("expected an updated deployment") } if e, a := deployapi.DeploymentStatusPending, deployutil.DeploymentStatusFor(updatedDeployment); e != a { t.Fatalf("expected updated deployment status %s, got %s", e, a) } if createdPod == nil { t.Fatalf("expected a pod to be created") } if e := deployutil.DeployerPodNameFor(updatedDeployment); len(e) == 0 { t.Fatalf("missing deployment pod annotation") } if e, a := createdPod.Name, deployutil.DeployerPodNameFor(updatedDeployment); e != a { t.Fatalf("expected deployment pod annotation %s, got %s", e, a) } if e := deployutil.DeploymentNameFor(createdPod); len(e) == 0 { t.Fatalf("missing deployment annotation") } if e, a := updatedDeployment.Name, deployutil.DeploymentNameFor(createdPod); e != a { t.Fatalf("expected pod deployment annotation %s, got %s", e, a) } 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 deployer pod") } if *createdPod.Spec.ActiveDeadlineSeconds != deployapi.MaxDeploymentDurationSeconds { t.Fatalf("expected ActiveDeadlineSeconds on the deployer pod to be set to %d; found: %d", deployapi.MaxDeploymentDurationSeconds, *createdPod.Spec.ActiveDeadlineSeconds) } actualContainer := createdPod.Spec.Containers[0] if e, a := expectedContainer.Image, actualContainer.Image; e != a { t.Fatalf("expected container image %s, got %s", expectedContainer.Image, actualContainer.Image) } if e, a := expectedContainer.Command[0], actualContainer.Command[0]; e != a { t.Fatalf("expected container command %s, got %s", expectedContainer.Command[0], actualContainer.Command[0]) } if e, a := expectedContainer.Env[0].Name, actualContainer.Env[0].Name; e != a { t.Fatalf("expected container env name %s, got %s", expectedContainer.Env[0].Name, actualContainer.Env[0].Name) } if e, a := expectedContainer.Env[0].Value, actualContainer.Env[0].Value; e != a { t.Fatalf("expected container env value %s, got %s", expectedContainer.Env[0].Value, actualContainer.Env[0].Value) } if e, a := expectedContainer.Resources, actualContainer.Resources; !kapi.Semantic.DeepEqual(e, a) { t.Fatalf("expected container resources %v, got %v", expectedContainer.Resources, actualContainer.Resources) } }
// TestHandle_createPodOk ensures that a the deployer pod created in response // to a new deployment is valid. func TestHandle_createPodOk(t *testing.T) { var ( updatedDeployment *kapi.ReplicationController createdPod *kapi.Pod expectedContainer = okContainer() ) 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{ createPodFunc: func(namespace string, pod *kapi.Pod) (*kapi.Pod, error) { createdPod = pod return pod, nil }, }, makeContainer: func(strategy *deployapi.DeploymentStrategy) (*kapi.Container, error) { return expectedContainer, nil }, recorder: &record.FakeRecorder{}, } // Verify new -> pending config := deploytest.OkDeploymentConfig(1) deployment, _ := deployutil.MakeDeployment(config, kapi.Codec) deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(deployapi.DeploymentStatusNew) deployment.Spec.Template.Spec.NodeSelector = map[string]string{"labelKey1": "labelValue1", "labelKey2": "labelValue2"} err := controller.Handle(deployment) if err != nil { t.Fatalf("unexpected error: %v", err) } if updatedDeployment == nil { t.Fatalf("expected an updated deployment") } if e, a := deployapi.DeploymentStatusPending, deployutil.DeploymentStatusFor(updatedDeployment); e != a { t.Fatalf("expected updated deployment status %s, got %s", e, a) } if createdPod == nil { t.Fatalf("expected a pod to be created") } if e := deployutil.DeployerPodNameFor(updatedDeployment); len(e) == 0 { t.Fatalf("missing deployment pod annotation") } if e, a := createdPod.Name, deployutil.DeployerPodNameFor(updatedDeployment); e != a { t.Fatalf("expected deployment pod annotation %s, got %s", e, a) } if e := deployutil.DeploymentNameFor(createdPod); len(e) == 0 { t.Fatalf("missing deployment annotation") } if e, a := updatedDeployment.Name, deployutil.DeploymentNameFor(createdPod); e != a { t.Fatalf("expected pod deployment annotation %s, got %s", e, a) } 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 deployer pod") } if *createdPod.Spec.ActiveDeadlineSeconds != deployapi.MaxDeploymentDurationSeconds { t.Fatalf("expected ActiveDeadlineSeconds on the deployer pod to be set to %d; found: %d", deployapi.MaxDeploymentDurationSeconds, *createdPod.Spec.ActiveDeadlineSeconds) } actualContainer := createdPod.Spec.Containers[0] if e, a := expectedContainer.Image, actualContainer.Image; e != a { t.Fatalf("expected container image %s, got %s", expectedContainer.Image, actualContainer.Image) } if e, a := expectedContainer.Command[0], actualContainer.Command[0]; e != a { t.Fatalf("expected container command %s, got %s", expectedContainer.Command[0], actualContainer.Command[0]) } if e, a := expectedContainer.Env[0].Name, actualContainer.Env[0].Name; e != a { t.Fatalf("expected container env name %s, got %s", expectedContainer.Env[0].Name, actualContainer.Env[0].Name) } if e, a := expectedContainer.Env[0].Value, actualContainer.Env[0].Value; e != a { t.Fatalf("expected container env value %s, got %s", expectedContainer.Env[0].Value, actualContainer.Env[0].Value) } if e, a := expectedContainer.Resources, actualContainer.Resources; !kapi.Semantic.DeepEqual(e, a) { t.Fatalf("expected container resources %v, got %v", expectedContainer.Resources, actualContainer.Resources) } }
// Handle syncs pod's status with any associated deployment. func (c *DeployerPodController) Handle(pod *kapi.Pod) error { // Find the deployment associated with the deployer pod. deploymentName := deployutil.DeploymentNameFor(pod) if len(deploymentName) == 0 { return nil } // Reject updates to anything but the main deployer pod // TODO: Find a way to filter this on the watch side. if pod.Name != deployutil.DeployerPodNameForDeployment(deploymentName) { return nil } deployment, err := c.deploymentClient.getDeployment(pod.Namespace, deploymentName) // If the deployment for this pod has disappeared, we should clean up this // and any other deployer pods, then bail out. if err != nil { // Some retrieval error occured. Retry. if !kerrors.IsNotFound(err) { return fmt.Errorf("couldn't get deployment %s/%s which owns deployer pod %s/%s", pod.Namespace, deploymentName, pod.Name, pod.Namespace) } // Find all the deployer pods for the deployment (including this one). deployers, err := c.deployerPodsFor(pod.Namespace, deploymentName) if err != nil { // Retry. return fmt.Errorf("couldn't get deployer pods for %s: %v", deployutil.LabelForDeployment(deployment), err) } // Delete all deployers. for _, deployer := range deployers.Items { err := c.deletePod(deployer.Namespace, deployer.Name) if err != nil { if !kerrors.IsNotFound(err) { // TODO: Should this fire an event? glog.V(2).Infof("Couldn't delete orphaned deployer pod %s/%s: %v", deployer.Namespace, deployer.Name, err) } } else { // TODO: Should this fire an event? glog.V(2).Infof("Deleted orphaned deployer pod %s/%s", deployer.Namespace, deployer.Name) } } return nil } currentStatus := deployutil.DeploymentStatusFor(deployment) nextStatus := currentStatus switch pod.Status.Phase { case kapi.PodRunning: nextStatus = deployapi.DeploymentStatusRunning case kapi.PodSucceeded: // Detect failure based on the container state nextStatus = deployapi.DeploymentStatusComplete for _, info := range pod.Status.ContainerStatuses { if info.State.Termination != nil && info.State.Termination.ExitCode != 0 { nextStatus = deployapi.DeploymentStatusFailed } } case kapi.PodFailed: // if the deployment is already marked Failed, do not attempt clean up again if currentStatus != deployapi.DeploymentStatusFailed { // clean up will also update the deployment status to Failed // failure to clean up will result in retries and // the deployment will not be marked Failed // Note: this will prevent new deployments from being created for this config err := c.cleanupFailedDeployment(deployment) if err != nil { return transientError(fmt.Sprintf("couldn't clean up failed deployment: %v", err)) } } } if currentStatus != nextStatus { deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(nextStatus) if _, err := c.deploymentClient.updateDeployment(deployment.Namespace, deployment); err != nil { if kerrors.IsNotFound(err) { return nil } return fmt.Errorf("couldn't update Deployment %s to status %s: %v", deployutil.LabelForDeployment(deployment), nextStatus, err) } glog.V(4).Infof("Updated Deployment %s status from %s to %s", deployutil.LabelForDeployment(deployment), currentStatus, nextStatus) } return nil }
func (c *DeploymentController) rcForDeployerPod(pod *kapi.Pod) (*kapi.ReplicationController, error) { key := pod.Namespace + "/" + deployutil.DeploymentNameFor(pod) return c.getByKey(key) }
// Handle syncs pod's status with any associated deployment. func (c *DeployerPodController) Handle(pod *kapi.Pod) error { // Find the deployment associated with the deployer pod. deploymentName := deployutil.DeploymentNameFor(pod) if len(deploymentName) == 0 { return nil } // Reject updates to anything but the main deployer pod // TODO: Find a way to filter this on the watch side. if pod.Name != deployutil.DeployerPodNameForDeployment(deploymentName) { return nil } deployment, err := c.deploymentClient.getDeployment(pod.Namespace, deploymentName) // If the deployment for this pod has disappeared, we should clean up this // and any other deployer pods, then bail out. if err != nil { // Some retrieval error occurred. Retry. if !kerrors.IsNotFound(err) { return fmt.Errorf("couldn't get deployment %s/%s which owns deployer pod %s/%s", pod.Namespace, deploymentName, pod.Name, pod.Namespace) } // Find all the deployer pods for the deployment (including this one). deployers, err := c.deployerPodsFor(pod.Namespace, deploymentName) if err != nil { // Retry. return fmt.Errorf("couldn't get deployer pods for %s: %v", deployutil.LabelForDeployment(deployment), err) } // Delete all deployers. for _, deployer := range deployers.Items { err := c.deletePod(deployer.Namespace, deployer.Name) if err != nil { if !kerrors.IsNotFound(err) { // TODO: Should this fire an event? glog.V(2).Infof("Couldn't delete orphaned deployer pod %s/%s: %v", deployer.Namespace, deployer.Name, err) } } else { // TODO: Should this fire an event? glog.V(2).Infof("Deleted orphaned deployer pod %s/%s", deployer.Namespace, deployer.Name) } } return nil } currentStatus := deployutil.DeploymentStatusFor(deployment) nextStatus := currentStatus switch pod.Status.Phase { case kapi.PodRunning: if !deployutil.IsTerminatedDeployment(deployment) { nextStatus = deployapi.DeploymentStatusRunning } case kapi.PodSucceeded: // Detect failure based on the container state nextStatus = deployapi.DeploymentStatusComplete for _, info := range pod.Status.ContainerStatuses { if info.State.Terminated != nil && info.State.Terminated.ExitCode != 0 { nextStatus = deployapi.DeploymentStatusFailed break } } // Sync the internal replica annotation with the target so that we can // distinguish deployer updates from other scaling events. deployment.Annotations[deployapi.DeploymentReplicasAnnotation] = deployment.Annotations[deployapi.DesiredReplicasAnnotation] if nextStatus == deployapi.DeploymentStatusComplete { delete(deployment.Annotations, deployapi.DesiredReplicasAnnotation) } case kapi.PodFailed: nextStatus = deployapi.DeploymentStatusFailed } if currentStatus != nextStatus { deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(nextStatus) if _, err := c.deploymentClient.updateDeployment(deployment.Namespace, deployment); err != nil { if kerrors.IsNotFound(err) { return nil } return fmt.Errorf("couldn't update Deployment %s to status %s: %v", deployutil.LabelForDeployment(deployment), nextStatus, err) } glog.V(4).Infof("Updated Deployment %s status from %s to %s", deployutil.LabelForDeployment(deployment), currentStatus, nextStatus) } return nil }
// Handle syncs pod's status with any associated deployment. func (c *DeployerPodController) Handle(pod *kapi.Pod) error { // Find the deployment associated with the deployer pod. deploymentName := deployutil.DeploymentNameFor(pod) if len(deploymentName) == 0 { return nil } // Reject updates to anything but the main deployer pod // TODO: Find a way to filter this on the watch side. if pod.Name != deployutil.DeployerPodNameForDeployment(deploymentName) { return nil } deployment := &kapi.ReplicationController{ObjectMeta: kapi.ObjectMeta{Namespace: pod.Namespace, Name: deploymentName}} cached, exists, err := c.store.Get(deployment) if err == nil && exists { // Try to use the cache first. Trust hits and return them. deployment = cached.(*kapi.ReplicationController) } else { // Double-check with the master for cache misses/errors, since those // are rare and API calls are expensive but more reliable. deployment, err = c.kClient.ReplicationControllers(pod.Namespace).Get(deploymentName) } // If the deployment for this pod has disappeared, we should clean up this // and any other deployer pods, then bail out. if err != nil { // Some retrieval error occurred. Retry. if !kerrors.IsNotFound(err) { return fmt.Errorf("couldn't get deployment %s/%s which owns deployer pod %s/%s", pod.Namespace, deploymentName, pod.Name, pod.Namespace) } // Find all the deployer pods for the deployment (including this one). opts := kapi.ListOptions{LabelSelector: deployutil.DeployerPodSelector(deploymentName)} deployers, err := c.kClient.Pods(pod.Namespace).List(opts) if err != nil { // Retry. return fmt.Errorf("couldn't get deployer pods for %s: %v", deployutil.LabelForDeployment(deployment), err) } // Delete all deployers. for _, deployer := range deployers.Items { err := c.kClient.Pods(deployer.Namespace).Delete(deployer.Name, kapi.NewDeleteOptions(0)) if err != nil { if !kerrors.IsNotFound(err) { // TODO: Should this fire an event? glog.V(2).Infof("Couldn't delete orphaned deployer pod %s/%s: %v", deployer.Namespace, deployer.Name, err) } } else { // TODO: Should this fire an event? glog.V(2).Infof("Deleted orphaned deployer pod %s/%s", deployer.Namespace, deployer.Name) } } return nil } currentStatus := deployutil.DeploymentStatusFor(deployment) nextStatus := currentStatus switch pod.Status.Phase { case kapi.PodRunning: nextStatus = deployapi.DeploymentStatusRunning case kapi.PodSucceeded: nextStatus = deployapi.DeploymentStatusComplete config, decodeErr := c.decodeConfig(deployment) // If the deployment was cancelled just prior to the deployer pod succeeding // then we need to remove the cancel annotations from the complete deployment // and emit an event letting users know their cancellation failed. if deployutil.IsDeploymentCancelled(deployment) { delete(deployment.Annotations, deployapi.DeploymentCancelledAnnotation) delete(deployment.Annotations, deployapi.DeploymentStatusReasonAnnotation) if decodeErr == nil { c.recorder.Eventf(config, kapi.EventTypeWarning, "FailedCancellation", "Deployment %q succeeded before cancel recorded", deployutil.LabelForDeployment(deployment)) } else { c.recorder.Event(deployment, kapi.EventTypeWarning, "FailedCancellation", "Succeeded before cancel recorded") } } // Sync the internal replica annotation with the target so that we can // distinguish deployer updates from other scaling events. deployment.Annotations[deployapi.DeploymentReplicasAnnotation] = deployment.Annotations[deployapi.DesiredReplicasAnnotation] if nextStatus == deployapi.DeploymentStatusComplete { delete(deployment.Annotations, deployapi.DesiredReplicasAnnotation) } // reset the size of any test container, since we are the ones updating the RC if decodeErr == nil && config.Spec.Test { deployment.Spec.Replicas = 0 } case kapi.PodFailed: nextStatus = deployapi.DeploymentStatusFailed // reset the size of any test container, since we are the ones updating the RC if config, err := c.decodeConfig(deployment); err == nil && config.Spec.Test { deployment.Spec.Replicas = 0 } } if deployutil.CanTransitionPhase(currentStatus, nextStatus) { deployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(nextStatus) if _, err := c.kClient.ReplicationControllers(deployment.Namespace).Update(deployment); err != nil { if kerrors.IsNotFound(err) { return nil } return fmt.Errorf("couldn't update Deployment %s to status %s: %v", deployutil.LabelForDeployment(deployment), nextStatus, err) } glog.V(4).Infof("Updated deployment %s status from %s to %s (scale: %d)", deployutil.LabelForDeployment(deployment), currentStatus, nextStatus, deployment.Spec.Replicas) // If the deployment was canceled, trigger a reconcilation of its deployment config // so that the latest complete deployment can immediately rollback in place of the // canceled deployment. if nextStatus == deployapi.DeploymentStatusFailed && deployutil.IsDeploymentCancelled(deployment) { // If we are unable to get the deployment config, then the deploymentconfig controller will // perform its duties once the resync interval forces the deploymentconfig to be reconciled. name := deployutil.DeploymentConfigNameFor(deployment) kclient.RetryOnConflict(kclient.DefaultRetry, func() error { config, err := c.client.DeploymentConfigs(deployment.Namespace).Get(name) if err != nil { return err } if config.Annotations == nil { config.Annotations = make(map[string]string) } config.Annotations[deployapi.DeploymentCancelledAnnotation] = strconv.Itoa(config.Status.LatestVersion) _, err = c.client.DeploymentConfigs(config.Namespace).Update(config) return err }) } } return nil }