diff --git a/api/swagger-spec/oapi-v1.json b/api/swagger-spec/oapi-v1.json index bc3b2dd4c355..1f0a9a5e1da1 100644 --- a/api/swagger-spec/oapi-v1.json +++ b/api/swagger-spec/oapi-v1.json @@ -21797,16 +21797,36 @@ "latestVersion": { "type": "integer", "format": "int64", - "description": "LatestVersion is used to determine whether the current deployment associated with a DeploymentConfig is out of sync." - }, - "details": { - "$ref": "v1.DeploymentDetails", - "description": "Details are the reasons for the update to this deployment config. This could be based on a change made by the user or caused by an automatic trigger" + "description": "LatestVersion is used to determine whether the current deployment associated with a deployment config is out of sync." }, "observedGeneration": { "type": "integer", "format": "int64", - "description": "ObservedGeneration is the most recent generation observed by the controller." + "description": "ObservedGeneration is the most recent generation observed by the deployment config controller." + }, + "replicas": { + "type": "integer", + "format": "int32", + "description": "Replicas is the total number of pods targeted by this deployment config." + }, + "updatedReplicas": { + "type": "integer", + "format": "int32", + "description": "UpdatedReplicas is the total number of non-terminated pods targeted by this deployment config that have the desired template spec." + }, + "availableReplicas": { + "type": "integer", + "format": "int32", + "description": "AvailableReplicas is the total number of available pods targeted by this deployment config." + }, + "unavailableReplicas": { + "type": "integer", + "format": "int32", + "description": "UnavailableReplicas is the total number of unavailable pods targeted by this deployment config." + }, + "details": { + "$ref": "v1.DeploymentDetails", + "description": "Details are the reasons for the update to this deployment config. This could be based on a change made by the user or caused by an automatic trigger" } } }, diff --git a/pkg/cmd/cli/describe/printer.go b/pkg/cmd/cli/describe/printer.go index 3efdcf6c7627..f3f411bb54f2 100644 --- a/pkg/cmd/cli/describe/printer.go +++ b/pkg/cmd/cli/describe/printer.go @@ -36,8 +36,7 @@ var ( imageStreamColumns = []string{"NAME", "DOCKER REPO", "TAGS", "UPDATED"} projectColumns = []string{"NAME", "DISPLAY NAME", "STATUS"} routeColumns = []string{"NAME", "HOST/PORT", "PATH", "SERVICE", "TERMINATION", "LABELS"} - deploymentColumns = []string{"NAME", "STATUS", "CAUSE"} - deploymentConfigColumns = []string{"NAME", "REVISION", "REPLICAS", "TRIGGERED BY"} + deploymentConfigColumns = []string{"NAME", "REVISION", "DESIRED", "CURRENT", "TRIGGERED BY"} templateColumns = []string{"NAME", "DESCRIPTION", "PARAMETERS", "OBJECTS"} policyColumns = []string{"NAME", "ROLES", "LAST MODIFIED"} policyBindingColumns = []string{"NAME", "ROLE BINDINGS", "LAST MODIFIED"} @@ -533,11 +532,11 @@ func printRouteList(routeList *routeapi.RouteList, w io.Writer, opts kctl.PrintO } func printDeploymentConfig(dc *deployapi.DeploymentConfig, w io.Writer, opts kctl.PrintOptions) error { - var scale string + var desired string if dc.Spec.Test { - scale = fmt.Sprintf("%d (during test)", dc.Spec.Replicas) + desired = fmt.Sprintf("%d (during test)", dc.Spec.Replicas) } else { - scale = fmt.Sprintf("%d", dc.Spec.Replicas) + desired = fmt.Sprintf("%d", dc.Spec.Replicas) } containers := sets.NewString() @@ -580,14 +579,11 @@ func printDeploymentConfig(dc *deployapi.DeploymentConfig, w io.Writer, opts kct return err } } - if _, err := fmt.Fprintf(w, "%s\t%v\t%s\t%s", dc.Name, dc.Status.LatestVersion, scale, trigger); err != nil { + if _, err := fmt.Fprintf(w, "%s\t%d\t%s\t%d\t%s", dc.Name, dc.Status.LatestVersion, desired, dc.Status.UpdatedReplicas, trigger); err != nil { return err } - if err := appendItemLabels(dc.Labels, w, opts.ColumnLabels, opts.ShowLabels); err != nil { - return err - } - - return nil + err := appendItemLabels(dc.Labels, w, opts.ColumnLabels, opts.ShowLabels) + return err } func printDeploymentConfigList(list *deployapi.DeploymentConfigList, w io.Writer, opts kctl.PrintOptions) error { diff --git a/pkg/cmd/server/origin/run_components.go b/pkg/cmd/server/origin/run_components.go index 41d35da588a4..97ad25ba65ce 100644 --- a/pkg/cmd/server/origin/run_components.go +++ b/pkg/cmd/server/origin/run_components.go @@ -344,9 +344,10 @@ func (c *MasterConfig) RunDeployerPodController() { func (c *MasterConfig) RunDeploymentConfigController() { dcInfomer := c.Informers.DeploymentConfigs().Informer() rcInformer := c.Informers.ReplicationControllers().Informer() + podInformer := c.Informers.Pods().Informer() osclient, kclient := c.DeploymentConfigControllerClients() - controller := deployconfigcontroller.NewDeploymentConfigController(dcInfomer, rcInformer, osclient, kclient, c.EtcdHelper.Codec()) + controller := deployconfigcontroller.NewDeploymentConfigController(dcInfomer, rcInformer, podInformer, osclient, kclient, c.EtcdHelper.Codec()) // TODO: Make the stop channel actually work. stopCh := make(chan struct{}) // TODO: Make the number of workers configurable. diff --git a/pkg/deploy/api/deep_copy_generated.go b/pkg/deploy/api/deep_copy_generated.go index 65da1d2d3690..f4db5804c59c 100644 --- a/pkg/deploy/api/deep_copy_generated.go +++ b/pkg/deploy/api/deep_copy_generated.go @@ -184,6 +184,11 @@ func DeepCopy_api_DeploymentConfigSpec(in DeploymentConfigSpec, out *DeploymentC func DeepCopy_api_DeploymentConfigStatus(in DeploymentConfigStatus, out *DeploymentConfigStatus, c *conversion.Cloner) error { out.LatestVersion = in.LatestVersion + out.ObservedGeneration = in.ObservedGeneration + out.Replicas = in.Replicas + out.UpdatedReplicas = in.UpdatedReplicas + out.AvailableReplicas = in.AvailableReplicas + out.UnavailableReplicas = in.UnavailableReplicas if in.Details != nil { in, out := in.Details, &out.Details *out = new(DeploymentDetails) @@ -193,7 +198,6 @@ func DeepCopy_api_DeploymentConfigStatus(in DeploymentConfigStatus, out *Deploym } else { out.Details = nil } - out.ObservedGeneration = in.ObservedGeneration return nil } diff --git a/pkg/deploy/api/helpers.go b/pkg/deploy/api/helpers.go index f8d86a0c3d7c..4b222b197d5a 100644 --- a/pkg/deploy/api/helpers.go +++ b/pkg/deploy/api/helpers.go @@ -4,6 +4,7 @@ import ( "fmt" kapi "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/apis/extensions" imageapi "github.com/openshift/origin/pkg/image/api" @@ -32,8 +33,19 @@ func ScaleFromConfig(dc *DeploymentConfig) *extensions.Scale { ObjectMeta: kapi.ObjectMeta{ Name: dc.Name, Namespace: dc.Namespace, + UID: dc.UID, + ResourceVersion: dc.ResourceVersion, CreationTimestamp: dc.CreationTimestamp, }, + Spec: extensions.ScaleSpec{ + Replicas: dc.Spec.Replicas, + }, + Status: extensions.ScaleStatus{ + Replicas: dc.Status.Replicas, + Selector: &unversioned.LabelSelector{ + MatchLabels: dc.Spec.Selector, + }, + }, } } diff --git a/pkg/deploy/api/types.go b/pkg/deploy/api/types.go index 52a577ad3d2b..916965d1e553 100644 --- a/pkg/deploy/api/types.go +++ b/pkg/deploy/api/types.go @@ -325,14 +325,23 @@ type DeploymentConfigSpec struct { // DeploymentConfigStatus represents the current deployment state. type DeploymentConfigStatus struct { - // LatestVersion is used to determine whether the current deployment associated with a DeploymentConfig - // is out of sync. + // LatestVersion is used to determine whether the current deployment associated with a deployment + // config is out of sync. LatestVersion int64 + // ObservedGeneration is the most recent generation observed by the deployment config controller. + ObservedGeneration int64 + // Replicas is the total number of pods targeted by this deployment config. + Replicas int32 + // UpdatedReplicas is the total number of non-terminated pods targeted by this deployment config + // that have the desired template spec. + UpdatedReplicas int32 + // AvailableReplicas is the total number of available pods targeted by this deployment config. + AvailableReplicas int32 + // UnavailableReplicas is the total number of unavailable pods targeted by this deployment config. + UnavailableReplicas int32 // Details are the reasons for the update to this deployment config. // This could be based on a change made by the user or caused by an automatic trigger Details *DeploymentDetails - // ObservedGeneration is the most recent generation observed by the controller. - ObservedGeneration int64 } // DeploymentTriggerPolicy describes a policy for a single trigger that results in a new deployment. diff --git a/pkg/deploy/api/v1/conversion_generated.go b/pkg/deploy/api/v1/conversion_generated.go index b998dd6fc63d..5b7649abf795 100644 --- a/pkg/deploy/api/v1/conversion_generated.go +++ b/pkg/deploy/api/v1/conversion_generated.go @@ -420,6 +420,11 @@ func Convert_api_DeploymentConfigSpec_To_v1_DeploymentConfigSpec(in *deploy_api. func autoConvert_v1_DeploymentConfigStatus_To_api_DeploymentConfigStatus(in *DeploymentConfigStatus, out *deploy_api.DeploymentConfigStatus, s conversion.Scope) error { out.LatestVersion = in.LatestVersion + out.ObservedGeneration = in.ObservedGeneration + out.Replicas = in.Replicas + out.UpdatedReplicas = in.UpdatedReplicas + out.AvailableReplicas = in.AvailableReplicas + out.UnavailableReplicas = in.UnavailableReplicas if in.Details != nil { in, out := &in.Details, &out.Details *out = new(deploy_api.DeploymentDetails) @@ -429,7 +434,6 @@ func autoConvert_v1_DeploymentConfigStatus_To_api_DeploymentConfigStatus(in *Dep } else { out.Details = nil } - out.ObservedGeneration = in.ObservedGeneration return nil } @@ -439,6 +443,11 @@ func Convert_v1_DeploymentConfigStatus_To_api_DeploymentConfigStatus(in *Deploym func autoConvert_api_DeploymentConfigStatus_To_v1_DeploymentConfigStatus(in *deploy_api.DeploymentConfigStatus, out *DeploymentConfigStatus, s conversion.Scope) error { out.LatestVersion = in.LatestVersion + out.ObservedGeneration = in.ObservedGeneration + out.Replicas = in.Replicas + out.UpdatedReplicas = in.UpdatedReplicas + out.AvailableReplicas = in.AvailableReplicas + out.UnavailableReplicas = in.UnavailableReplicas if in.Details != nil { in, out := &in.Details, &out.Details *out = new(DeploymentDetails) @@ -448,7 +457,6 @@ func autoConvert_api_DeploymentConfigStatus_To_v1_DeploymentConfigStatus(in *dep } else { out.Details = nil } - out.ObservedGeneration = in.ObservedGeneration return nil } diff --git a/pkg/deploy/api/v1/deep_copy_generated.go b/pkg/deploy/api/v1/deep_copy_generated.go index 0a9b1e57a71c..0ce6b9ab55f5 100644 --- a/pkg/deploy/api/v1/deep_copy_generated.go +++ b/pkg/deploy/api/v1/deep_copy_generated.go @@ -183,6 +183,11 @@ func DeepCopy_v1_DeploymentConfigSpec(in DeploymentConfigSpec, out *DeploymentCo func DeepCopy_v1_DeploymentConfigStatus(in DeploymentConfigStatus, out *DeploymentConfigStatus, c *conversion.Cloner) error { out.LatestVersion = in.LatestVersion + out.ObservedGeneration = in.ObservedGeneration + out.Replicas = in.Replicas + out.UpdatedReplicas = in.UpdatedReplicas + out.AvailableReplicas = in.AvailableReplicas + out.UnavailableReplicas = in.UnavailableReplicas if in.Details != nil { in, out := in.Details, &out.Details *out = new(DeploymentDetails) @@ -192,7 +197,6 @@ func DeepCopy_v1_DeploymentConfigStatus(in DeploymentConfigStatus, out *Deployme } else { out.Details = nil } - out.ObservedGeneration = in.ObservedGeneration return nil } diff --git a/pkg/deploy/api/v1/swagger_doc.go b/pkg/deploy/api/v1/swagger_doc.go index 2fcc5f3fd90a..f785780ff1c9 100644 --- a/pkg/deploy/api/v1/swagger_doc.go +++ b/pkg/deploy/api/v1/swagger_doc.go @@ -94,10 +94,14 @@ func (DeploymentConfigSpec) SwaggerDoc() map[string]string { } var map_DeploymentConfigStatus = map[string]string{ - "": "DeploymentConfigStatus represents the current deployment state.", - "latestVersion": "LatestVersion is used to determine whether the current deployment associated with a DeploymentConfig is out of sync.", - "details": "Details are the reasons for the update to this deployment config. This could be based on a change made by the user or caused by an automatic trigger", - "observedGeneration": "ObservedGeneration is the most recent generation observed by the controller.", + "": "DeploymentConfigStatus represents the current deployment state.", + "latestVersion": "LatestVersion is used to determine whether the current deployment associated with a deployment config is out of sync.", + "observedGeneration": "ObservedGeneration is the most recent generation observed by the deployment config controller.", + "replicas": "Replicas is the total number of pods targeted by this deployment config.", + "updatedReplicas": "UpdatedReplicas is the total number of non-terminated pods targeted by this deployment config that have the desired template spec.", + "availableReplicas": "AvailableReplicas is the total number of available pods targeted by this deployment config.", + "unavailableReplicas": "UnavailableReplicas is the total number of unavailable pods targeted by this deployment config.", + "details": "Details are the reasons for the update to this deployment config. This could be based on a change made by the user or caused by an automatic trigger", } func (DeploymentConfigStatus) SwaggerDoc() map[string]string { diff --git a/pkg/deploy/api/v1/types.go b/pkg/deploy/api/v1/types.go index ba8b29da45d2..7af18d3073ab 100644 --- a/pkg/deploy/api/v1/types.go +++ b/pkg/deploy/api/v1/types.go @@ -282,14 +282,23 @@ type DeploymentConfigSpec struct { // DeploymentConfigStatus represents the current deployment state. type DeploymentConfigStatus struct { - // LatestVersion is used to determine whether the current deployment associated with a DeploymentConfig - // is out of sync. + // LatestVersion is used to determine whether the current deployment associated with a deployment + // config is out of sync. LatestVersion int64 `json:"latestVersion,omitempty"` + // ObservedGeneration is the most recent generation observed by the deployment config controller. + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + // Replicas is the total number of pods targeted by this deployment config. + Replicas int32 `json:"replicas,omitempty"` + // UpdatedReplicas is the total number of non-terminated pods targeted by this deployment config + // that have the desired template spec. + UpdatedReplicas int32 `json:"updatedReplicas,omitempty"` + // AvailableReplicas is the total number of available pods targeted by this deployment config. + AvailableReplicas int32 `json:"availableReplicas,omitempty"` + // UnavailableReplicas is the total number of unavailable pods targeted by this deployment config. + UnavailableReplicas int32 `json:"unavailableReplicas,omitempty"` // Details are the reasons for the update to this deployment config. // This could be based on a change made by the user or caused by an automatic trigger Details *DeploymentDetails `json:"details,omitempty"` - // ObservedGeneration is the most recent generation observed by the controller. - ObservedGeneration int64 `json:"observedGeneration,omitempty"` } // DeploymentTriggerPolicy describes a policy for a single trigger that results in a new deployment. diff --git a/pkg/deploy/api/v1beta3/types.go b/pkg/deploy/api/v1beta3/types.go index 69c513410ccc..4385a863ca19 100644 --- a/pkg/deploy/api/v1beta3/types.go +++ b/pkg/deploy/api/v1beta3/types.go @@ -297,14 +297,23 @@ type DeploymentConfigSpec struct { } type DeploymentConfigStatus struct { - // LatestVersion is used to determine whether the current deployment associated with a DeploymentConfig - // is out of sync. + // LatestVersion is used to determine whether the current deployment associated with a deployment + // config is out of sync. LatestVersion int64 `json:"latestVersion,omitempty"` + // ObservedGeneration is the most recent generation observed by the deployment config controller. + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + // Replicas is the total number of pods targeted by this deployment config. + Replicas int32 `json:"replicas,omitempty"` + // UpdatedReplicas is the total number of non-terminated pods targeted by this deployment config + // that have the desired template spec. + UpdatedReplicas int32 `json:"updatedReplicas,omitempty"` + // AvailableReplicas is the total number of available pods targeted by this deployment config. + AvailableReplicas int32 `json:"availableReplicas,omitempty"` + // UnavailableReplicas is the total number of unavailable pods targeted by this deployment config. + UnavailableReplicas int32 `json:"unavailableReplicas,omitempty"` // The reasons for the update to this deployment config. // This could be based on a change made by the user or caused by an automatic trigger Details *DeploymentDetails `json:"details,omitempty"` - // ObservedGeneration is the most recent generation observed by the controller. - ObservedGeneration int64 `json:"observedGeneration,omitempty"` } // DeploymentTriggerPolicy describes a policy for a single trigger that results in a new deployment. diff --git a/pkg/deploy/controller/deploymentconfig/controller.go b/pkg/deploy/controller/deploymentconfig/controller.go index 8b536705dc4c..49519603da8a 100644 --- a/pkg/deploy/controller/deploymentconfig/controller.go +++ b/pkg/deploy/controller/deploymentconfig/controller.go @@ -2,6 +2,7 @@ package deploymentconfig import ( "fmt" + "reflect" "strconv" "github.com/golang/glog" @@ -11,6 +12,7 @@ import ( "k8s.io/kubernetes/pkg/client/cache" "k8s.io/kubernetes/pkg/client/record" kclient "k8s.io/kubernetes/pkg/client/unversioned" + "k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/runtime" utilruntime "k8s.io/kubernetes/pkg/util/runtime" "k8s.io/kubernetes/pkg/util/workqueue" @@ -55,10 +57,15 @@ type DeploymentConfigController struct { dcStore oscache.StoreToDeploymentConfigLister // rcStore provides a local cache for replication controllers. rcStore cache.StoreToReplicationControllerLister + // podStore provides a local cache for pods. + podStore cache.StoreToPodLister + // dcStoreSynced makes sure the dc store is synced before reconcling any deployment config. dcStoreSynced func() bool // rcStoreSynced makes sure the rc store is synced before reconcling any deployment config. rcStoreSynced func() bool + // podStoreSynced makes sure the pod store is synced before reconcling any deployment config. + podStoreSynced func() bool // codec is used to build deployments from configs. codec runtime.Codec @@ -69,10 +76,9 @@ type DeploymentConfigController struct { // 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 { - // There's nothing to reconcile until the version is nonzero or when the - // deployment config has been marked for deletion. - if config.Status.LatestVersion == 0 || config.DeletionTimestamp != nil { - return c.updateStatus(config) + // 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. @@ -82,6 +88,13 @@ func (c *DeploymentConfigController) Handle(config *deployapi.DeploymentConfig) 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. @@ -118,14 +131,14 @@ func (c *DeploymentConfigController) Handle(config *deployapi.DeploymentConfig) // 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) + return c.updateStatus(config, existingDeployments) } return c.reconcileDeployments(existingDeployments, config) } // If the config is paused we shouldn't create new deployments for it. // TODO: Make sure cleanup policy will work for paused configs. if config.Spec.Paused { - return c.updateStatus(config) + return c.updateStatus(config, existingDeployments) } // No deployments are running and the latest deployment doesn't exist, so // create the new deployment. @@ -138,14 +151,14 @@ func (c *DeploymentConfigController) Handle(config *deployapi.DeploymentConfig) // If the deployment was already created, just move on. The cache could be // stale, or another process could have already handled this update. if errors.IsAlreadyExists(err) { - return c.updateStatus(config) + return c.updateStatus(config, existingDeployments) } c.recorder.Eventf(config, kapi.EventTypeWarning, "DeploymentCreationFailed", "Couldn't deploy version %d: %s", config.Status.LatestVersion, err) return fmt.Errorf("couldn't create deployment for deployment config %s: %v", deployutil.LabelForDeploymentConfig(config), err) } c.recorder.Eventf(config, kapi.EventTypeNormal, "DeploymentCreated", "Created new deployment %q for version %d", created.Name, config.Status.LatestVersion) - return c.updateStatus(config) + return c.updateStatus(config, existingDeployments) } // reconcileDeployments reconciles existing deployment replica counts which @@ -166,7 +179,7 @@ func (c *DeploymentConfigController) reconcileDeployments(existingDeployments [] // We shouldn't be reconciling if the latest deployment hasn't been // created; this is enforced on the calling side, but double checking // can't hurt. - return nil + return c.updateStatus(config, existingDeployments) } activeDeployment := deployutil.ActiveDeployment(config, existingDeployments) // Compute the replica count for the active deployment (even if the active @@ -238,7 +251,11 @@ func (c *DeploymentConfigController) reconcileDeployments(existingDeployments [] // Reconcile deployments. The active deployment follows the config, and all // other deployments should be scaled to zero. - for _, deployment := range existingDeployments { + var updatedDeployments []kapi.ReplicationController + for i := range existingDeployments { + deployment := existingDeployments[i] + toAppend := deployment + isActiveDeployment := activeDeployment != nil && deployment.Name == activeDeployment.Name oldReplicaCount := deployment.Spec.Replicas @@ -274,19 +291,29 @@ func (c *DeploymentConfigController) reconcileDeployments(existingDeployments [] } else { glog.V(4).Infof("Updated deployment %q replica annotation to match current replica count %d", deployutil.LabelForDeployment(copied), newReplicaCount) } + toAppend = *copied } + + updatedDeployments = append(updatedDeployments, toAppend) } - return c.updateStatus(config) + return c.updateStatus(config, updatedDeployments) } -func (c *DeploymentConfigController) updateStatus(config *deployapi.DeploymentConfig) error { +func (c *DeploymentConfigController) updateStatus(config *deployapi.DeploymentConfig, deployments []kapi.ReplicationController) error { + newStatus, err := c.calculateStatus(*config, deployments) + if err != nil { + glog.V(2).Infof("Cannot calculate the status for %q: %v", deployutil.LabelForDeploymentConfig(config), err) + return err + } + // NOTE: We should update the status of the deployment config only if we need to, otherwise // we hotloop between updates. - if !needsUpdate(config) { + if reflect.DeepEqual(newStatus, config.Status) { return nil } - config.Status.ObservedGeneration = config.Generation + + config.Status = newStatus if _, err := c.dn.DeploymentConfigs(config.Namespace).UpdateStatus(config); err != nil { glog.V(2).Infof("Cannot update the status for %q: %v", deployutil.LabelForDeploymentConfig(config), err) return err @@ -295,6 +322,40 @@ func (c *DeploymentConfigController) updateStatus(config *deployapi.DeploymentCo return nil } +func (c *DeploymentConfigController) calculateStatus(config deployapi.DeploymentConfig, deployments []kapi.ReplicationController) (deployapi.DeploymentConfigStatus, error) { + // TODO: Implement MinReadySeconds for deploymentconfigs: https://github.com/openshift/origin/issues/7114 + minReadSeconds := int32(0) + selector := labels.Set(config.Spec.Selector).AsSelector() + pods, err := c.podStore.Pods(config.Namespace).List(selector) + if err != nil { + return config.Status, err + } + available := deployutil.GetAvailablePods(pods.Items, minReadSeconds) + + // UpdatedReplicas represents the replicas that use the deployment config template which means + // we should inform about the replicas of the latest deployment and not the active. + latestReplicas := int32(0) + for _, deployment := range deployments { + if deployment.Name == deployutil.LatestDeploymentNameForConfig(&config) { + updatedDeployment := []kapi.ReplicationController{deployment} + latestReplicas = deployutil.GetStatusReplicaCountForDeployments(updatedDeployment) + break + } + } + + total := deployutil.GetReplicaCountForDeployments(deployments) + + return deployapi.DeploymentConfigStatus{ + LatestVersion: config.Status.LatestVersion, + Details: config.Status.Details, + ObservedGeneration: config.Generation, + Replicas: deployutil.GetStatusReplicaCountForDeployments(deployments), + UpdatedReplicas: latestReplicas, + AvailableReplicas: available, + UnavailableReplicas: total - available, + }, nil +} + func (c *DeploymentConfigController) handleErr(err error, key interface{}) { if err == nil { return @@ -313,10 +374,6 @@ func (c *DeploymentConfigController) handleErr(err error, key interface{}) { } } -func needsUpdate(config *deployapi.DeploymentConfig) bool { - return config.Generation > config.Status.ObservedGeneration -} - func deploymentCopy(rc *kapi.ReplicationController) (*kapi.ReplicationController, error) { objCopy, err := kapi.Scheme.DeepCopy(rc) if err != nil { diff --git a/pkg/deploy/controller/deploymentconfig/controller_test.go b/pkg/deploy/controller/deploymentconfig/controller_test.go index d23d0c7315d3..395166fd4559 100644 --- a/pkg/deploy/controller/deploymentconfig/controller_test.go +++ b/pkg/deploy/controller/deploymentconfig/controller_test.go @@ -670,7 +670,20 @@ func TestHandleScenarios(t *testing.T) { 2*time.Minute, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, ) - c := NewDeploymentConfigController(dcInformer, rcInformer, oc, kc, codec) + podInformer := framework.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options kapi.ListOptions) (runtime.Object, error) { + return kc.Pods(kapi.NamespaceAll).List(options) + }, + WatchFunc: func(options kapi.ListOptions) (watch.Interface, error) { + return kc.Pods(kapi.NamespaceAll).Watch(options) + }, + }, + &kapi.Pod{}, + 2*time.Minute, + cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, + ) + c := NewDeploymentConfigController(dcInformer, rcInformer, podInformer, oc, kc, codec) for i := range toStore { c.rcStore.Add(&toStore[i]) diff --git a/pkg/deploy/controller/deploymentconfig/factory.go b/pkg/deploy/controller/deploymentconfig/factory.go index e59baf63add6..fa04017e2ccb 100644 --- a/pkg/deploy/controller/deploymentconfig/factory.go +++ b/pkg/deploy/controller/deploymentconfig/factory.go @@ -27,7 +27,7 @@ const ( ) // NewDeploymentConfigController creates a new DeploymentConfigController. -func NewDeploymentConfigController(dcInformer, rcInformer framework.SharedIndexInformer, oc osclient.Interface, kc kclient.Interface, codec runtime.Codec) *DeploymentConfigController { +func NewDeploymentConfigController(dcInformer, rcInformer, podInformer framework.SharedIndexInformer, oc osclient.Interface, kc kclient.Interface, codec runtime.Codec) *DeploymentConfigController { eventBroadcaster := record.NewBroadcaster() eventBroadcaster.StartRecordingToSink(kc.Events("")) recorder := eventBroadcaster.NewRecorder(kapi.EventSource{Component: "deploymentconfig-controller"}) @@ -48,16 +48,17 @@ func NewDeploymentConfigController(dcInformer, rcInformer framework.SharedIndexI UpdateFunc: c.updateDeploymentConfig, DeleteFunc: c.deleteDeploymentConfig, }) - c.rcStore.Indexer = rcInformer.GetIndexer() rcInformer.AddEventHandler(framework.ResourceEventHandlerFuncs{ AddFunc: c.addReplicationController, UpdateFunc: c.updateReplicationController, DeleteFunc: c.deleteReplicationController, }) + c.podStore.Indexer = podInformer.GetIndexer() c.dcStoreSynced = dcInformer.HasSynced c.rcStoreSynced = rcInformer.HasSynced + c.podStoreSynced = podInformer.HasSynced return c } @@ -87,8 +88,8 @@ func (c *DeploymentConfigController) Run(workers int, stopCh <-chan struct{}) { func (c *DeploymentConfigController) waitForSyncedStores(ready chan struct{}) { defer utilruntime.HandleCrash() - for !c.dcStoreSynced() || !c.rcStoreSynced() { - glog.V(4).Infof("Waiting for the dc and rc controllers to sync before starting the deployment config controller workers") + for !c.dcStoreSynced() || !c.rcStoreSynced() || !c.podStoreSynced() { + glog.V(4).Infof("Waiting for the dc, rc, and pod caches to sync before starting the deployment config controller workers") time.Sleep(StoreSyncedPollPeriod) } close(ready) diff --git a/pkg/deploy/registry/deployconfig/etcd/etcd.go b/pkg/deploy/registry/deployconfig/etcd/etcd.go index 32577c552605..00af9b605907 100644 --- a/pkg/deploy/registry/deployconfig/etcd/etcd.go +++ b/pkg/deploy/registry/deployconfig/etcd/etcd.go @@ -6,7 +6,6 @@ import ( kapi "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/rest" - "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/apis/extensions" kclient "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/fields" @@ -17,7 +16,6 @@ import ( "github.com/openshift/origin/pkg/deploy/api" "github.com/openshift/origin/pkg/deploy/registry/deployconfig" - "github.com/openshift/origin/pkg/deploy/util" "github.com/openshift/origin/pkg/util/restoptions" extvalidation "k8s.io/kubernetes/pkg/apis/extensions/validation" ) @@ -91,34 +89,11 @@ func (r *ScaleREST) Get(ctx kapi.Context, name string) (runtime.Object, error) { return nil, err } - // TODO(directxman12): this is going to be a bit out of sync, since we are calculating it - // here and not as part of the deploymentconfig loop -- is there a better way of doing it? - totalReplicas, err := r.replicasForDeploymentConfig(deploymentConfig.Namespace, deploymentConfig.Name) - if err != nil { - return nil, err - } - - return &extensions.Scale{ - ObjectMeta: kapi.ObjectMeta{ - Name: name, - Namespace: deploymentConfig.Namespace, - CreationTimestamp: deploymentConfig.CreationTimestamp, - }, - Spec: extensions.ScaleSpec{ - Replicas: int32(deploymentConfig.Spec.Replicas), - }, - Status: extensions.ScaleStatus{ - Replicas: int32(totalReplicas), - Selector: &unversioned.LabelSelector{MatchLabels: deploymentConfig.Spec.Selector}, - }, - }, nil + return api.ScaleFromConfig(deploymentConfig), nil } // Update scales the DeploymentConfig for the given Scale subresource, returning the updated Scale. func (r *ScaleREST) Update(ctx kapi.Context, obj runtime.Object) (runtime.Object, bool, error) { - if obj == nil { - return nil, false, errors.NewBadRequest(fmt.Sprintf("nil update passed to Scale")) - } scale, ok := obj.(*extensions.Scale) if !ok { return nil, false, errors.NewBadRequest(fmt.Sprintf("wrong object passed to Scale update: %v", obj)) @@ -133,50 +108,12 @@ func (r *ScaleREST) Update(ctx kapi.Context, obj runtime.Object) (runtime.Object return nil, false, errors.NewNotFound(extensions.Resource("scale"), scale.Name) } - scaleRet := &extensions.Scale{ - ObjectMeta: kapi.ObjectMeta{ - Name: deploymentConfig.Name, - Namespace: deploymentConfig.Namespace, - CreationTimestamp: deploymentConfig.CreationTimestamp, - }, - Spec: extensions.ScaleSpec{ - Replicas: scale.Spec.Replicas, - }, - Status: extensions.ScaleStatus{ - Selector: &unversioned.LabelSelector{MatchLabels: deploymentConfig.Spec.Selector}, - }, - } - - // TODO(directxman12): this is going to be a bit out of sync, since we are calculating it - // here and not as part of the deploymentconfig loop -- is there a better way of doing it? - totalReplicas, err := r.replicasForDeploymentConfig(deploymentConfig.Namespace, deploymentConfig.Name) - if err != nil { - return nil, false, err - } - - oldReplicas := deploymentConfig.Spec.Replicas deploymentConfig.Spec.Replicas = scale.Spec.Replicas if err := r.registry.UpdateDeploymentConfig(ctx, deploymentConfig); err != nil { return nil, false, err } - scaleRet.Status.Replicas = totalReplicas + (scale.Spec.Replicas - oldReplicas) - - return scaleRet, false, nil -} - -func (r *ScaleREST) replicasForDeploymentConfig(namespace, configName string) (int32, error) { - options := kapi.ListOptions{LabelSelector: util.ConfigSelector(configName)} - rcList, err := r.rcNamespacer.ReplicationControllers(namespace).List(options) - if err != nil { - return 0, err - } - - replicas := int32(0) - for _, rc := range rcList.Items { - replicas += rc.Spec.Replicas - } - return replicas, nil + return api.ScaleFromConfig(deploymentConfig), false, nil } // StatusREST implements the REST endpoint for changing the status of a DeploymentConfig. diff --git a/pkg/deploy/util/util.go b/pkg/deploy/util/util.go index 3894ecc13a06..ff223235dc3c 100644 --- a/pkg/deploy/util/util.go +++ b/pkg/deploy/util/util.go @@ -9,6 +9,7 @@ import ( "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/runtime" + kdeplutil "k8s.io/kubernetes/pkg/util/deployment" deployapi "github.com/openshift/origin/pkg/deploy/api" "github.com/openshift/origin/pkg/util/namer" @@ -212,6 +213,38 @@ func MakeDeployment(config *deployapi.DeploymentConfig, codec runtime.Codec) (*a return deployment, nil } +// GetReplicaCountForDeployments returns the sum of all replicas for the +// given deployments. +func GetReplicaCountForDeployments(deployments []api.ReplicationController) int32 { + totalReplicaCount := int32(0) + for _, deployment := range deployments { + totalReplicaCount += deployment.Spec.Replicas + } + return totalReplicaCount +} + +// GetStatusReplicaCountForDeployments returns the sum of the replicas reported in the +// status of the given deployments. +func GetStatusReplicaCountForDeployments(deployments []api.ReplicationController) int32 { + totalReplicaCount := int32(0) + for _, deployment := range deployments { + totalReplicaCount += deployment.Status.Replicas + } + return totalReplicaCount +} + +// GetAvailablePods returns all the available pods from the provided pod list. +func GetAvailablePods(pods []api.Pod, minReadySeconds int32) int32 { + available := int32(0) + for i := range pods { + pod := pods[i] + if kdeplutil.IsPodAvailable(&pod, minReadySeconds) { + available++ + } + } + return available +} + func DeploymentConfigNameFor(obj runtime.Object) string { return annotationFor(obj, deployapi.DeploymentConfigAnnotation) } diff --git a/test/extended/deployments/deployments.go b/test/extended/deployments/deployments.go index a0531d33885b..a1033d9d0986 100644 --- a/test/extended/deployments/deployments.go +++ b/test/extended/deployments/deployments.go @@ -189,6 +189,33 @@ var _ = g.Describe("deploymentconfigs", func() { }) }) + g.Describe("with enhanced status", func() { + g.It("should include various info in status [Conformance]", func() { + resource, name, err := createFixture(oc, simpleDeploymentFixture) + o.Expect(err).NotTo(o.HaveOccurred()) + + g.By("verifying the deployment is marked complete") + o.Expect(waitForLatestCondition(oc, name, deploymentRunTimeout, deploymentReachedCompletion)).NotTo(o.HaveOccurred()) + + g.By("verifying that status.replicas is set") + replicas, err := oc.Run("get").Args(resource, "--output=jsonpath=\"{.status.replicas}\"").Output() + o.Expect(err).NotTo(o.HaveOccurred()) + o.Expect(replicas).To(o.ContainSubstring("2")) + g.By("verifying that status.updatedReplicas is set") + updatedReplicas, err := oc.Run("get").Args(resource, "--output=jsonpath=\"{.status.updatedReplicas}\"").Output() + o.Expect(err).NotTo(o.HaveOccurred()) + o.Expect(updatedReplicas).To(o.ContainSubstring("2")) + g.By("verifying that status.availableReplicas is set") + availableReplicas, err := oc.Run("get").Args(resource, "--output=jsonpath=\"{.status.availableReplicas}\"").Output() + o.Expect(err).NotTo(o.HaveOccurred()) + o.Expect(availableReplicas).To(o.ContainSubstring("2")) + g.By("verifying that status.unavailableReplicas is set") + unavailableReplicas, err := oc.Run("get").Args(resource, "--output=jsonpath=\"{.status.unavailableReplicas}\"").Output() + o.Expect(err).NotTo(o.HaveOccurred()) + o.Expect(unavailableReplicas).To(o.ContainSubstring("0")) + }) + }) + g.Describe("with custom deployments", func() { g.It("should run the custom deployment steps [Conformance]", func() { out, err := oc.Run("create").Args("-f", customDeploymentFixture).Output()