Skip to content

Commit

Permalink
deploy: enhance status for deploymentconfigs
Browse files Browse the repository at this point in the history
  • Loading branch information
0xmichalis committed Jun 23, 2016
1 parent a0ac96f commit 0e85054
Show file tree
Hide file tree
Showing 12 changed files with 216 additions and 112 deletions.
18 changes: 7 additions & 11 deletions pkg/cmd/cli/describe/printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"}
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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 {
Expand Down
3 changes: 2 additions & 1 deletion pkg/cmd/server/origin/run_components.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
12 changes: 12 additions & 0 deletions pkg/deploy/api/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
},
},
}
}

Expand Down
17 changes: 13 additions & 4 deletions pkg/deploy/api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
17 changes: 13 additions & 4 deletions pkg/deploy/api/v1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
17 changes: 13 additions & 4 deletions pkg/deploy/api/v1beta3/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
93 changes: 75 additions & 18 deletions pkg/deploy/controller/deploymentconfig/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package deploymentconfig

import (
"fmt"
"reflect"
"strconv"

"github.com/golang/glog"
Expand All @@ -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"
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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 {
Expand Down
15 changes: 14 additions & 1 deletion pkg/deploy/controller/deploymentconfig/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down
Loading

0 comments on commit 0e85054

Please sign in to comment.