func updateConditions(config *deployapi.DeploymentConfig, newStatus *deployapi.DeploymentConfigStatus, latestRC *kapi.ReplicationController) { // Availability condition. if newStatus.AvailableReplicas >= config.Spec.Replicas-deployutil.MaxUnavailable(*config) { minAvailability := deployutil.NewDeploymentCondition(deployapi.DeploymentAvailable, kapi.ConditionTrue, "", "Deployment config has minimum availability.") deployutil.SetDeploymentCondition(newStatus, *minAvailability) } else { noMinAvailability := deployutil.NewDeploymentCondition(deployapi.DeploymentAvailable, kapi.ConditionFalse, "", "Deployment config does not have minimum availability.") deployutil.SetDeploymentCondition(newStatus, *noMinAvailability) } // Condition about progress. cond := deployutil.GetDeploymentCondition(*newStatus, deployapi.DeploymentProgressing) if latestRC != nil { switch deployutil.DeploymentStatusFor(latestRC) { case deployapi.DeploymentStatusNew, deployapi.DeploymentStatusPending: msg := fmt.Sprintf("Waiting on deployer pod for %q to be scheduled", latestRC.Name) condition := deployutil.NewDeploymentCondition(deployapi.DeploymentProgressing, kapi.ConditionUnknown, "", msg) deployutil.SetDeploymentCondition(newStatus, *condition) case deployapi.DeploymentStatusRunning: msg := fmt.Sprintf("Replication controller %q is progressing", latestRC.Name) condition := deployutil.NewDeploymentCondition(deployapi.DeploymentProgressing, kapi.ConditionTrue, deployutil.ReplicationControllerUpdatedReason, msg) deployutil.SetDeploymentCondition(newStatus, *condition) case deployapi.DeploymentStatusFailed: if cond != nil && cond.Reason == deployutil.TimedOutReason { break } msg := fmt.Sprintf("Replication controller %q has failed progressing", latestRC.Name) condition := deployutil.NewDeploymentCondition(deployapi.DeploymentProgressing, kapi.ConditionFalse, deployutil.TimedOutReason, msg) deployutil.SetDeploymentCondition(newStatus, *condition) case deployapi.DeploymentStatusComplete: if cond != nil && cond.Reason == deployutil.NewRcAvailableReason { break } msg := fmt.Sprintf("Replication controller %q has completed progressing", latestRC.Name) condition := deployutil.NewDeploymentCondition(deployapi.DeploymentProgressing, kapi.ConditionTrue, deployutil.NewRcAvailableReason, msg) deployutil.SetDeploymentCondition(newStatus, *condition) } } // Pause / resume condition. Since we don't pause running deployments, let's use paused conditions only when a deployment // actually terminates. For now it may be ok to override lack of progress in the conditions, later we may want to separate // paused from the rest of the progressing conditions. if latestRC == nil || deployutil.IsTerminatedDeployment(latestRC) { pausedCondExists := cond != nil && cond.Reason == deployutil.PausedDeployReason if config.Spec.Paused && !pausedCondExists { condition := deployutil.NewDeploymentCondition(deployapi.DeploymentProgressing, kapi.ConditionUnknown, deployutil.PausedDeployReason, "Deployment config is paused") deployutil.SetDeploymentCondition(newStatus, *condition) } else if !config.Spec.Paused && pausedCondExists { condition := deployutil.NewDeploymentCondition(deployapi.DeploymentProgressing, kapi.ConditionUnknown, deployutil.ResumedDeployReason, "Deployment config is resumed") deployutil.SetDeploymentCondition(newStatus, *condition) } } }
func updateConditions(config deployapi.DeploymentConfig, newStatus *deployapi.DeploymentConfigStatus, latestRC *kapi.ReplicationController) { // Availability condition. if newStatus.AvailableReplicas >= config.Spec.Replicas-deployutil.MaxUnavailable(config) && newStatus.AvailableReplicas > 0 { minAvailability := deployutil.NewDeploymentCondition(deployapi.DeploymentAvailable, kapi.ConditionTrue, "", "Deployment config has minimum availability.") deployutil.SetDeploymentCondition(newStatus, *minAvailability) } else { noMinAvailability := deployutil.NewDeploymentCondition(deployapi.DeploymentAvailable, kapi.ConditionFalse, "", "Deployment config does not have minimum availability.") deployutil.SetDeploymentCondition(newStatus, *noMinAvailability) } // Condition about progress. if latestRC != nil { switch deployutil.DeploymentStatusFor(latestRC) { case deployapi.DeploymentStatusPending: msg := fmt.Sprintf("Replication controller %q is waiting for pod %q to run", latestRC.Name, deployutil.DeployerPodNameForDeployment(latestRC.Name)) condition := deployutil.NewDeploymentCondition(deployapi.DeploymentProgressing, kapi.ConditionUnknown, "", msg) deployutil.SetDeploymentCondition(newStatus, *condition) case deployapi.DeploymentStatusRunning: if deployutil.IsProgressing(config, *newStatus) { deployutil.RemoveDeploymentCondition(newStatus, deployapi.DeploymentProgressing) msg := fmt.Sprintf("Replication controller %q is progressing", latestRC.Name) condition := deployutil.NewDeploymentCondition(deployapi.DeploymentProgressing, kapi.ConditionTrue, deployutil.ReplicationControllerUpdatedReason, msg) // TODO: Right now, we use lastTransitionTime for storing the last time we had any progress instead // of the last time the condition transitioned to a new status. We should probably change that. deployutil.SetDeploymentCondition(newStatus, *condition) } case deployapi.DeploymentStatusFailed: msg := fmt.Sprintf("Replication controller %q has failed progressing", latestRC.Name) condition := deployutil.NewDeploymentCondition(deployapi.DeploymentProgressing, kapi.ConditionFalse, deployutil.TimedOutReason, msg) deployutil.SetDeploymentCondition(newStatus, *condition) case deployapi.DeploymentStatusComplete: msg := fmt.Sprintf("Replication controller %q has completed progressing", latestRC.Name) condition := deployutil.NewDeploymentCondition(deployapi.DeploymentProgressing, kapi.ConditionTrue, deployutil.NewRcAvailableReason, msg) deployutil.SetDeploymentCondition(newStatus, *condition) } } }
// Handle implements the loop that processes deployment configs. Since this controller started // using caches, the provided config MUST be deep-copied beforehand (see work() in factory.go). func (c *DeploymentConfigController) Handle(config *deployapi.DeploymentConfig) error { glog.V(5).Infof("Reconciling %s/%s", config.Namespace, config.Name) // There's nothing to reconcile until the version is nonzero. if config.Status.LatestVersion == 0 { return c.updateStatus(config, []kapi.ReplicationController{}) } // Find all deployments owned by the deployment config. selector := deployutil.ConfigSelector(config.Name) existingDeployments, err := c.rcStore.ReplicationControllers(config.Namespace).List(selector) if err != nil { return err } // In case the deployment config has been marked for deletion, merely update its status with // the latest available information. Some deletions make take some time to complete so there // is value in doing this. if config.DeletionTimestamp != nil { return c.updateStatus(config, existingDeployments) } latestIsDeployed, latestDeployment := deployutil.LatestDeploymentInfo(config, existingDeployments) // If the latest deployment doesn't exist yet, cancel any running // deployments to allow them to be superceded by the new config version. awaitingCancellations := false if !latestIsDeployed { for i := range existingDeployments { deployment := existingDeployments[i] // Skip deployments with an outcome. if deployutil.IsTerminatedDeployment(&deployment) { continue } // Cancel running deployments. awaitingCancellations = true if deployutil.IsDeploymentCancelled(&deployment) { continue } // Retry faster on conflicts var updatedDeployment *kapi.ReplicationController if err := kclient.RetryOnConflict(kclient.DefaultBackoff, func() error { rc, err := c.rcStore.ReplicationControllers(deployment.Namespace).Get(deployment.Name) if kapierrors.IsNotFound(err) { return nil } if err != nil { return err } copied, err := deployutil.DeploymentDeepCopy(rc) if err != nil { return err } copied.Annotations[deployapi.DeploymentCancelledAnnotation] = deployapi.DeploymentCancelledAnnotationValue copied.Annotations[deployapi.DeploymentStatusReasonAnnotation] = deployapi.DeploymentCancelledNewerDeploymentExists updatedDeployment, err = c.rn.ReplicationControllers(copied.Namespace).Update(copied) return err }); err != nil { c.recorder.Eventf(config, kapi.EventTypeWarning, "DeploymentCancellationFailed", "Failed to cancel deployment %q superceded by version %d: %s", deployment.Name, config.Status.LatestVersion, err) } else if updatedDeployment != nil { // replace the current deployment with the updated copy so that a future update has a chance at working existingDeployments[i] = *updatedDeployment c.recorder.Eventf(config, kapi.EventTypeNormal, "DeploymentCancelled", "Cancelled deployment %q superceded by version %d", deployment.Name, config.Status.LatestVersion) } } } // Wait for deployment cancellations before reconciling or creating a new // deployment to avoid competing with existing deployment processes. if awaitingCancellations { c.recorder.Eventf(config, kapi.EventTypeNormal, "DeploymentAwaitingCancellation", "Deployment of version %d awaiting cancellation of older running deployments", config.Status.LatestVersion) return fmt.Errorf("found previous inflight deployment for %s - requeuing", deployutil.LabelForDeploymentConfig(config)) } // If the latest deployment already exists, reconcile existing deployments // and return early. if latestIsDeployed { // If the latest deployment is still running, try again later. We don't // want to compete with the deployer. if !deployutil.IsTerminatedDeployment(latestDeployment) { return c.updateStatus(config, existingDeployments) } return c.reconcileDeployments(existingDeployments, config) } // If the config is paused we shouldn't create new deployments for it. if config.Spec.Paused { // in order for revision history limit cleanup to work for paused // deployments, we need to trigger it here if err := c.cleanupOldDeployments(existingDeployments, config); err != nil { c.recorder.Eventf(config, kapi.EventTypeWarning, "DeploymentCleanupFailed", "Couldn't clean up deployments: %v", err) } return c.updateStatus(config, existingDeployments) } // No deployments are running and the latest deployment doesn't exist, so // create the new deployment. deployment, err := deployutil.MakeDeployment(config, c.codec) if err != nil { return fatalError(fmt.Sprintf("couldn't make deployment from (potentially invalid) deployment config %s: %v", deployutil.LabelForDeploymentConfig(config), err)) } created, err := c.rn.ReplicationControllers(config.Namespace).Create(deployment) if err != nil { // If the deployment was already created, just move on. The cache could be // stale, or another process could have already handled this update. if kapierrors.IsAlreadyExists(err) { return c.updateStatus(config, existingDeployments) } c.recorder.Eventf(config, kapi.EventTypeWarning, "DeploymentCreationFailed", "Couldn't deploy version %d: %s", config.Status.LatestVersion, err) // We don't care about this error since we need to report the create failure. cond := deployutil.NewDeploymentCondition(deployapi.DeploymentProgressing, kapi.ConditionFalse, deployutil.FailedRcCreateReason, err.Error()) _ = c.updateStatus(config, existingDeployments, *cond) return fmt.Errorf("couldn't create deployment for deployment config %s: %v", deployutil.LabelForDeploymentConfig(config), err) } msg := fmt.Sprintf("Created new replication controller %q for version %d", created.Name, config.Status.LatestVersion) c.recorder.Eventf(config, kapi.EventTypeNormal, "DeploymentCreated", msg) // As we've just created a new deployment, we need to make sure to clean // up old deployments if we have reached our deployment history quota existingDeployments = append(existingDeployments, *created) if err := c.cleanupOldDeployments(existingDeployments, config); err != nil { c.recorder.Eventf(config, kapi.EventTypeWarning, "DeploymentCleanupFailed", "Couldn't clean up deployments: %v", err) } cond := deployutil.NewDeploymentCondition(deployapi.DeploymentProgressing, kapi.ConditionTrue, deployutil.NewReplicationControllerReason, msg) return c.updateStatus(config, existingDeployments, *cond) }