// getOrCreateTargetControllerWithClient looks for an existing controller with // sourceId. If found, the existing controller is returned with true // indicating that the controller already exists. If the controller isn't // found, a new one is created and returned along with false indicating the // controller was created. // // Existing controllers are validated to ensure their sourceIdAnnotation // matches sourceId; if there's a mismatch, an error is returned. func (r *RollingUpdater) getOrCreateTargetControllerWithClient(controller *api.ReplicationController, sourceId string) (*api.ReplicationController, bool, error) { existingRc, err := r.existingController(controller) if err != nil { if !errors.IsNotFound(err) { // There was an error trying to find the controller; don't assume we // should create it. return nil, false, err } if controller.Spec.Replicas <= 0 { return nil, false, fmt.Errorf("Invalid controller spec for %s; required: > 0 replicas, actual: %d\n", controller.Name, controller.Spec) } // The controller wasn't found, so create it. if controller.Annotations == nil { controller.Annotations = map[string]string{} } controller.Annotations[desiredReplicasAnnotation] = fmt.Sprintf("%d", controller.Spec.Replicas) controller.Annotations[sourceIdAnnotation] = sourceId controller.Spec.Replicas = 0 newRc, err := r.c.ReplicationControllers(r.ns).Create(controller) return newRc, false, err } // Validate and use the existing controller. annotations := existingRc.Annotations source := annotations[sourceIdAnnotation] _, ok := annotations[desiredReplicasAnnotation] if source != sourceId || !ok { return nil, false, fmt.Errorf("Missing/unexpected annotations for controller %s, expected %s : %s", controller.Name, sourceId, annotations) } return existingRc, true, nil }
func (dc *DeploymentController) updateRCRevision(rc api.ReplicationController, revision string) error { if rc.Annotations == nil { rc.Annotations = make(map[string]string) } rc.Annotations[deploymentutil.RevisionAnnotation] = revision _, err := dc.client.Legacy().ReplicationControllers(rc.ObjectMeta.Namespace).Update(&rc) return err }
// setNewRCAnnotations sets new rc's annotations appropriately by updating its revision and // copying required deployment annotations to it; it returns true if rc's annotation is changed. func setNewRCAnnotations(deployment *extensions.Deployment, rc *api.ReplicationController, newRevision string) bool { // First, copy deployment's annotations annotationChanged := copyDeploymentAnnotationsToRC(deployment, rc) // Then, update RC's revision annotation if rc.Annotations == nil { rc.Annotations = make(map[string]string) } if rc.Annotations[deploymentutil.RevisionAnnotation] != newRevision { rc.Annotations[deploymentutil.RevisionAnnotation] = newRevision annotationChanged = true glog.V(4).Infof("updating RC %q's revision to %s - %+v\n", rc.Name, newRevision) } return annotationChanged }
// copyDeploymentAnnotationsToRC copies deployment's annotations to rc's annotations, // and returns true if rc's annotation is changed func copyDeploymentAnnotationsToRC(deployment *extensions.Deployment, rc *api.ReplicationController) bool { rcAnnotationsChanged := false if rc.Annotations == nil { rc.Annotations = make(map[string]string) } for k, v := range deployment.Annotations { // Skip apply annotations // TODO: How to decide which annotations should / should not be copied? // See https://github.com/kubernetes/kubernetes/pull/20035#issuecomment-179558615 if k == kubectl.LastAppliedConfigAnnotation || rc.Annotations[k] == v { continue } rc.Annotations[k] = v rcAnnotationsChanged = true } return rcAnnotationsChanged }
func SetNextControllerAnnotation(rc *api.ReplicationController, name string) { if rc.Annotations == nil { rc.Annotations = map[string]string{} } rc.Annotations[nextControllerAnnotation] = name }
// 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 { // Copy all the annotations from the deployment. updatedAnnotations := make(map[string]string) for key, value := range deployment.Annotations { updatedAnnotations[key] = value } currentStatus := deployutil.DeploymentStatusFor(deployment) nextStatus := currentStatus deployerPodName := deployutil.DeployerPodNameForDeployment(deployment.Name) deployer, deployerErr := c.getPod(deployment.Namespace, deployerPodName) if deployerErr == nil { nextStatus = c.nextStatus(deployer, deployment, updatedAnnotations) } 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 updatedAnnotations[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 or to the appropriate status relative to the existing validated deployer pod. updatedAnnotations[deployapi.DeploymentPodAnnotation] = deployer.Name nextStatus = nextStatusComp(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)) } updatedAnnotations[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) { updatedAnnotations[deployapi.DeploymentStatusReasonAnnotation] = deployapi.DeploymentFailedDeployerPodNoLongerExists c.emitDeploymentEvent(deployment, kapi.EventTypeWarning, "Failed", fmt.Sprintf("Deployer pod %q has gone missing", deployerPodName)) deployerErr = fmt.Errorf("Failing deployment %q because its deployer pod %q disappeared", deployutil.LabelForDeployment(deployment), deployerPodName) utilruntime.HandleError(deployerErr) } case deployerErr != nil: // We'll try again later on resync. Continue to process cancellations. deployerErr = fmt.Errorf("Error getting deployer pod %q for deployment %q: %v", deployerPodName, deployutil.LabelForDeployment(deployment), deployerErr) utilruntime.HandleError(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: // 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: if err := c.cleanupDeployerPods(deployment); err != nil { return err } } // Update only if we need to transition to a new phase. if deployutil.CanTransitionPhase(currentStatus, nextStatus) { deployment, err := deployutil.DeploymentDeepCopy(deployment) if err != nil { return err } updatedAnnotations[deployapi.DeploymentStatusAnnotation] = string(nextStatus) deployment.Annotations = updatedAnnotations // if we are going to transition to failed or complete and scale is non-zero, we'll check one more // time to see if we are a test deployment to guarantee that we maintain the test invariant. if deployment.Spec.Replicas != 0 && deployutil.IsTerminatedDeployment(deployment) { if config, err := deployutil.DecodeDeploymentConfig(deployment, c.codec); err == nil && config.Spec.Test { deployment.Spec.Replicas = 0 } } 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 }