update vendored dependencies

This commit is contained in:
Suraj Deshmukh
2016-12-16 18:04:46 +05:30
parent 4dd31da6e4
commit 10299eb5dc
134 changed files with 31993 additions and 3700 deletions
+136
View File
@@ -0,0 +1,136 @@
package cmd
import (
"time"
"github.com/golang/glog"
kapi "k8s.io/kubernetes/pkg/api"
kerrors "k8s.io/kubernetes/pkg/api/errors"
kclient "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/kubectl"
kutil "k8s.io/kubernetes/pkg/util"
"k8s.io/kubernetes/pkg/util/wait"
"github.com/openshift/origin/pkg/client"
deployapi "github.com/openshift/origin/pkg/deploy/api"
"github.com/openshift/origin/pkg/deploy/util"
)
// NewDeploymentConfigReaper returns a new reaper for deploymentConfigs
func NewDeploymentConfigReaper(oc client.Interface, kc kclient.Interface) kubectl.Reaper {
return &DeploymentConfigReaper{oc: oc, kc: kc, pollInterval: kubectl.Interval, timeout: kubectl.Timeout}
}
// DeploymentConfigReaper implements the Reaper interface for deploymentConfigs
type DeploymentConfigReaper struct {
oc client.Interface
kc kclient.Interface
pollInterval, timeout time.Duration
}
// pause marks the deployment configuration as paused to avoid triggering new
// deployments.
func (reaper *DeploymentConfigReaper) pause(namespace, name string) (*deployapi.DeploymentConfig, error) {
return client.UpdateConfigWithRetries(reaper.oc, namespace, name, func(d *deployapi.DeploymentConfig) {
d.Spec.RevisionHistoryLimit = kutil.Int32Ptr(0)
d.Spec.Replicas = 0
d.Spec.Paused = true
})
}
// Stop scales a replication controller via its deployment configuration down to
// zero replicas, waits for all of them to get deleted and then deletes both the
// replication controller and its deployment configuration.
func (reaper *DeploymentConfigReaper) Stop(namespace, name string, timeout time.Duration, gracePeriod *kapi.DeleteOptions) error {
// Pause the deployment configuration to prevent the new deployments from
// being triggered.
config, err := reaper.pause(namespace, name)
configNotFound := kerrors.IsNotFound(err)
if err != nil && !configNotFound {
return err
}
var (
isPaused bool
legacy bool
)
// Determine if the deployment config controller noticed the pause.
if !configNotFound {
if err := wait.Poll(1*time.Second, 1*time.Minute, func() (bool, error) {
dc, err := reaper.oc.DeploymentConfigs(namespace).Get(name)
if err != nil {
return false, err
}
isPaused = dc.Spec.Paused
return dc.Status.ObservedGeneration >= config.Generation, nil
}); err != nil {
return err
}
// If we failed to pause the deployment config, it means we are talking to
// old API that does not support pausing. In that case, we delete the
// deployment config to stay backward compatible.
if !isPaused {
if err := reaper.oc.DeploymentConfigs(namespace).Delete(name); err != nil {
return err
}
// Setting this to true avoid deleting the config at the end.
legacy = true
}
}
// Clean up deployments related to the config. Even if the deployment
// configuration has been deleted, we want to sweep the existing replication
// controllers and clean them up.
options := kapi.ListOptions{LabelSelector: util.ConfigSelector(name)}
rcList, err := reaper.kc.ReplicationControllers(namespace).List(options)
if err != nil {
return err
}
rcReaper, err := kubectl.ReaperFor(kapi.Kind("ReplicationController"), reaper.kc)
if err != nil {
return err
}
// If there is neither a config nor any deployments, nor any deployer pods, we can return NotFound.
deployments := rcList.Items
if configNotFound && len(deployments) == 0 {
return kerrors.NewNotFound(kapi.Resource("deploymentconfig"), name)
}
for _, rc := range deployments {
if err = rcReaper.Stop(rc.Namespace, rc.Name, timeout, gracePeriod); err != nil {
// Better not error out here...
glog.Infof("Cannot delete ReplicationController %s/%s for deployment config %s/%s: %v", rc.Namespace, rc.Name, namespace, name, err)
}
// Only remove deployer pods when the deployment was failed. For completed
// deployment the pods should be already deleted.
if !util.IsFailedDeployment(&rc) {
continue
}
// Delete all deployer and hook pods
options = kapi.ListOptions{LabelSelector: util.DeployerPodSelector(rc.Name)}
podList, err := reaper.kc.Pods(rc.Namespace).List(options)
if err != nil {
return err
}
for _, pod := range podList.Items {
err := reaper.kc.Pods(pod.Namespace).Delete(pod.Name, gracePeriod)
if err != nil {
// Better not error out here...
glog.Infof("Cannot delete lifecycle Pod %s/%s for deployment config %s/%s: %v", pod.Namespace, pod.Name, namespace, name, err)
}
}
}
// Nothing to delete or we already deleted the deployment config because we
// failed to pause.
if configNotFound || legacy {
return nil
}
return reaper.oc.DeploymentConfigs(namespace).Delete(name)
}
+3
View File
@@ -0,0 +1,3 @@
// Package cmd contains various interface implementations for command-line tools
// associated with deploymentconfigs.
package cmd
+41
View File
@@ -0,0 +1,41 @@
package cmd
import (
"fmt"
"reflect"
kapi "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/runtime"
deployapi "github.com/openshift/origin/pkg/deploy/api"
)
var basic = kubectl.BasicReplicationController{}
type BasicDeploymentConfigController struct{}
func (BasicDeploymentConfigController) ParamNames() []kubectl.GeneratorParam {
return basic.ParamNames()
}
func (BasicDeploymentConfigController) Generate(genericParams map[string]interface{}) (runtime.Object, error) {
obj, err := basic.Generate(genericParams)
if err != nil {
return nil, err
}
switch t := obj.(type) {
case *kapi.ReplicationController:
obj = &deployapi.DeploymentConfig{
ObjectMeta: t.ObjectMeta,
Spec: deployapi.DeploymentConfigSpec{
Selector: t.Spec.Selector,
Replicas: t.Spec.Replicas,
Template: t.Spec.Template,
},
}
default:
return nil, fmt.Errorf("unrecognized object type: %v", reflect.TypeOf(t))
}
return obj, nil
}
+99
View File
@@ -0,0 +1,99 @@
package cmd
import (
"bytes"
"fmt"
"sort"
"text/tabwriter"
kapi "k8s.io/kubernetes/pkg/api"
kclient "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/kubectl"
"github.com/openshift/origin/pkg/client"
deployapi "github.com/openshift/origin/pkg/deploy/api"
deployutil "github.com/openshift/origin/pkg/deploy/util"
)
func NewDeploymentConfigHistoryViewer(oc client.Interface, kc kclient.Interface) kubectl.HistoryViewer {
return &DeploymentConfigHistoryViewer{dn: oc, rn: kc}
}
// DeploymentConfigHistoryViewer is an implementation of the kubectl HistoryViewer interface
// for deployment configs.
type DeploymentConfigHistoryViewer struct {
rn kclient.ReplicationControllersNamespacer
dn client.DeploymentConfigsNamespacer
}
var _ kubectl.HistoryViewer = &DeploymentConfigHistoryViewer{}
// ViewHistory returns a description of all the history it can find for a deployment config.
func (h *DeploymentConfigHistoryViewer) ViewHistory(namespace, name string, revision int64) (string, error) {
opts := kapi.ListOptions{LabelSelector: deployutil.ConfigSelector(name)}
deploymentList, err := h.rn.ReplicationControllers(namespace).List(opts)
if err != nil {
return "", err
}
history := deploymentList.Items
if len(deploymentList.Items) == 0 {
return "No rollout history found.", nil
}
// Print details of a specific revision
if revision > 0 {
var desired *kapi.PodTemplateSpec
// We could use a binary search here but brute-force is always faster to write
for i := range history {
rc := history[i]
if deployutil.DeploymentVersionFor(&rc) == revision {
desired = rc.Spec.Template
break
}
}
if desired == nil {
return "", fmt.Errorf("unable to find the specified revision")
}
buf := bytes.NewBuffer([]byte{})
kubectl.DescribePodTemplate(desired, buf)
return buf.String(), nil
}
sort.Sort(deployutil.ByLatestVersionAsc(history))
return tabbedString(func(out *tabwriter.Writer) error {
fmt.Fprintf(out, "REVISION\tSTATUS\tCAUSE\n")
for i := range history {
rc := history[i]
rev := deployutil.DeploymentVersionFor(&rc)
status := deployutil.DeploymentStatusFor(&rc)
cause := rc.Annotations[deployapi.DeploymentStatusReasonAnnotation]
if len(cause) == 0 {
cause = "<unknown>"
}
fmt.Fprintf(out, "%d\t%s\t%s\n", rev, status, cause)
}
return nil
})
}
// TODO: Re-use from an utility package
func tabbedString(f func(*tabwriter.Writer) error) (string, error) {
out := new(tabwriter.Writer)
buf := &bytes.Buffer{}
out.Init(buf, 0, 8, 1, '\t', 0)
err := f(out)
if err != nil {
return "", err
}
out.Flush()
str := string(buf.String())
return str, nil
}
+56
View File
@@ -0,0 +1,56 @@
package cmd
import (
"fmt"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/runtime"
"github.com/openshift/origin/pkg/client"
deployapi "github.com/openshift/origin/pkg/deploy/api"
)
func NewDeploymentConfigRollbacker(oc client.Interface) kubectl.Rollbacker {
return &DeploymentConfigRollbacker{dn: oc}
}
// DeploymentConfigRollbacker is an implementation of the kubectl Rollbacker interface
// for deployment configs.
type DeploymentConfigRollbacker struct {
dn client.DeploymentConfigsNamespacer
}
var _ kubectl.Rollbacker = &DeploymentConfigRollbacker{}
// Rollback the provided deployment config to a specific revision. If revision is zero, we will
// rollback to the previous deployment.
func (r *DeploymentConfigRollbacker) Rollback(obj runtime.Object, updatedAnnotations map[string]string, toRevision int64) (string, error) {
config, ok := obj.(*deployapi.DeploymentConfig)
if !ok {
return "", fmt.Errorf("passed object is not a deployment config: %#v", obj)
}
if config.Spec.Paused {
return "", fmt.Errorf("cannot rollback a paused config; resume it first with 'rollout resume dc/%s' and try again", config.Name)
}
rollback := &deployapi.DeploymentConfigRollback{
Name: config.Name,
UpdatedAnnotations: updatedAnnotations,
Spec: deployapi.DeploymentConfigRollbackSpec{
Revision: toRevision,
IncludeTemplate: true,
},
}
rolledback, err := r.dn.DeploymentConfigs(config.Namespace).Rollback(rollback)
if err != nil {
return "", err
}
_, err = r.dn.DeploymentConfigs(config.Namespace).Update(rolledback)
if err != nil {
return "", err
}
return "rolled back", nil
}
+98
View File
@@ -0,0 +1,98 @@
package cmd
import (
"time"
kapi "k8s.io/kubernetes/pkg/api"
kclient "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/util/wait"
"github.com/openshift/origin/pkg/client"
"github.com/openshift/origin/pkg/deploy/util"
)
// NewDeploymentConfigScaler returns a new scaler for deploymentConfigs
func NewDeploymentConfigScaler(oc client.Interface, kc kclient.Interface) kubectl.Scaler {
return &DeploymentConfigScaler{rcClient: kc, dcClient: oc, clientInterface: kc}
}
// DeploymentConfigScaler is a wrapper for the kubectl Scaler client
type DeploymentConfigScaler struct {
rcClient kclient.ReplicationControllersNamespacer
dcClient client.DeploymentConfigsNamespacer
clientInterface kclient.Interface
}
// Scale updates the DeploymentConfig with the provided namespace/name, to a
// new size, with optional precondition check (if preconditions is not nil),
// optional retries (if retry is not nil), and then optionally waits for its
// deployment replica count to reach the new value (if wait is not nil).
func (scaler *DeploymentConfigScaler) Scale(namespace, name string, newSize uint, preconditions *kubectl.ScalePrecondition, retry, waitForReplicas *kubectl.RetryParams) error {
if preconditions == nil {
preconditions = &kubectl.ScalePrecondition{Size: -1, ResourceVersion: ""}
}
if retry == nil {
// Make it try only once, immediately
retry = &kubectl.RetryParams{Interval: time.Millisecond, Timeout: time.Millisecond}
}
cond := kubectl.ScaleCondition(scaler, preconditions, namespace, name, newSize, nil)
if err := wait.Poll(retry.Interval, retry.Timeout, cond); err != nil {
return err
}
// TODO: convert to a watch and use resource version from the ScaleCondition - kubernetes/kubernetes#31051
if waitForReplicas != nil {
dc, err := scaler.dcClient.DeploymentConfigs(namespace).Get(name)
if err != nil {
return err
}
rc, err := scaler.rcClient.ReplicationControllers(namespace).Get(util.LatestDeploymentNameForConfig(dc))
if err != nil {
return err
}
return wait.Poll(waitForReplicas.Interval, waitForReplicas.Timeout, controllerHasSpecifiedReplicas(scaler.clientInterface, rc, dc.Spec.Replicas))
}
return nil
}
// ScaleSimple does a simple one-shot attempt at scaling - not useful on its
// own, but a necessary building block for Scale.
func (scaler *DeploymentConfigScaler) ScaleSimple(namespace, name string, preconditions *kubectl.ScalePrecondition, newSize uint) (string, error) {
scale, err := scaler.dcClient.DeploymentConfigs(namespace).GetScale(name)
if err != nil {
return "", err
}
scale.Spec.Replicas = int32(newSize)
updated, err := scaler.dcClient.DeploymentConfigs(namespace).UpdateScale(scale)
if err != nil {
return "", kubectl.ScaleError{FailureType: kubectl.ScaleUpdateFailure, ResourceVersion: "Unknown", ActualError: err}
}
return updated.ResourceVersion, nil
}
// controllerHasSpecifiedReplicas returns a condition that will be true if and
// only if the specified replica count for a controller's ReplicaSelector
// equals the Replicas count.
//
// This is a slightly modified version of
// unversioned.ControllerHasDesiredReplicas. This is necessary because when
// scaling an RC via a DC, the RC spec replica count is not immediately
// updated to match the owning DC.
func controllerHasSpecifiedReplicas(c kclient.Interface, controller *kapi.ReplicationController, specifiedReplicas int32) wait.ConditionFunc {
// If we're given a controller where the status lags the spec, it either means that the controller is stale,
// or that the rc manager hasn't noticed the update yet. Polling status.Replicas is not safe in the latter case.
desiredGeneration := controller.Generation
return func() (bool, error) {
ctrl, err := c.ReplicationControllers(controller.Namespace).Get(controller.Name)
if err != nil {
return false, err
}
// There's a chance a concurrent update modifies the Spec.Replicas causing this check to pass,
// or, after this check has passed, a modification causes the rc manager to create more pods.
// This will not be an issue once we've implemented graceful delete for rcs, but till then
// concurrent stop operations on the same rc might have unintended side effects.
return ctrl.Status.ObservedGeneration >= desiredGeneration && ctrl.Status.Replicas == specifiedReplicas, nil
}
}
+72
View File
@@ -0,0 +1,72 @@
package cmd
import (
"errors"
"fmt"
"k8s.io/kubernetes/pkg/kubectl"
"github.com/openshift/origin/pkg/client"
deployapi "github.com/openshift/origin/pkg/deploy/api"
deployutil "github.com/openshift/origin/pkg/deploy/util"
)
func NewDeploymentConfigStatusViewer(oc client.Interface) kubectl.StatusViewer {
return &DeploymentConfigStatusViewer{dn: oc}
}
// DeploymentConfigStatusViewer is an implementation of the kubectl StatusViewer interface
// for deployment configs.
type DeploymentConfigStatusViewer struct {
dn client.DeploymentConfigsNamespacer
}
var _ kubectl.StatusViewer = &DeploymentConfigStatusViewer{}
// Status returns a message describing deployment status, and a bool value indicating if the status is considered done
func (s *DeploymentConfigStatusViewer) Status(namespace, name string, desiredRevision int64) (string, bool, error) {
config, err := s.dn.DeploymentConfigs(namespace).Get(name)
if err != nil {
return "", false, err
}
latestRevision := config.Status.LatestVersion
if latestRevision == 0 {
switch {
case deployutil.HasImageChangeTrigger(config):
return fmt.Sprintf("Deployment config %q waiting on image update\n", name), false, nil
case len(config.Spec.Triggers) == 0:
return "", true, fmt.Errorf("Deployment config %q waiting on manual update (use 'oc rollout latest %s')", name, name)
}
}
if desiredRevision > 0 && latestRevision != desiredRevision {
return "", false, fmt.Errorf("desired revision (%d) is different from the running revision (%d)", desiredRevision, latestRevision)
}
cond := deployutil.GetDeploymentCondition(config.Status, deployapi.DeploymentProgressing)
if config.Generation <= config.Status.ObservedGeneration {
switch {
case cond != nil && cond.Reason == deployutil.NewRcAvailableReason:
return fmt.Sprintf("%s\n", cond.Message), true, nil
case cond != nil && cond.Reason == deployutil.TimedOutReason:
return "", true, errors.New(cond.Message)
case cond != nil && cond.Reason == deployutil.PausedDeployReason:
return "", true, fmt.Errorf("Deployment config %q is paused. Resume to continue watching the status of the rollout.\n", config.Name)
case config.Status.UpdatedReplicas < config.Spec.Replicas:
return fmt.Sprintf("Waiting for rollout to finish: %d out of %d new replicas have been updated...\n", config.Status.UpdatedReplicas, config.Spec.Replicas), false, nil
case config.Status.Replicas > config.Status.UpdatedReplicas:
return fmt.Sprintf("Waiting for rollout to finish: %d old replicas are pending termination...\n", config.Status.Replicas-config.Status.UpdatedReplicas), false, nil
case config.Status.AvailableReplicas < config.Status.UpdatedReplicas:
return fmt.Sprintf("Waiting for rollout to finish: %d of %d updated replicas are available...\n", config.Status.AvailableReplicas, config.Status.UpdatedReplicas), false, nil
}
}
return fmt.Sprintf("Waiting for latest deployment config spec to be observed by the controller loop...\n"), false, nil
}
+630
View File
@@ -0,0 +1,630 @@
package util
import (
"errors"
"fmt"
"sort"
"strconv"
"strings"
"time"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/unversioned"
kdeplutil "k8s.io/kubernetes/pkg/controller/deployment/util"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/watch"
deployapi "github.com/openshift/origin/pkg/deploy/api"
"github.com/openshift/origin/pkg/util/namer"
kclient "k8s.io/kubernetes/pkg/client/unversioned"
)
const (
// Reasons for deployment config conditions:
//
// ReplicationControllerUpdatedReason is added in a deployment config when one of its replication
// controllers is updated as part of the rollout process.
ReplicationControllerUpdatedReason = "ReplicationControllerUpdated"
// FailedRcCreateReason is added in a deployment config when it cannot create a new replication
// controller.
FailedRcCreateReason = "ReplicationControllerCreateError"
// NewReplicationControllerReason is added in a deployment config when it creates a new replication
// controller.
NewReplicationControllerReason = "NewReplicationControllerCreated"
// NewRcAvailableReason is added in a deployment config when its newest replication controller is made
// available ie. the number of new pods that have passed readiness checks and run for at least
// minReadySeconds is at least the minimum available pods that need to run for the deployment config.
NewRcAvailableReason = "NewReplicationControllerAvailable"
// TimedOutReason is added in a deployment config when its newest replication controller fails to show
// any progress within the given deadline (progressDeadlineSeconds).
TimedOutReason = "ProgressDeadlineExceeded"
// PausedDeployReason is added in a deployment config when it is paused. Lack of progress shouldn't be
// estimated once a deployment config is paused.
PausedDeployReason = "DeploymentConfigPaused"
// ResumedDeployReason is added in a deployment config when it is resumed. Useful for not failing accidentally
// deployment configs that paused amidst a rollout.
ResumedDeployReason = "DeploymentConfigResumed"
)
// NewDeploymentCondition creates a new deployment condition.
func NewDeploymentCondition(condType deployapi.DeploymentConditionType, status api.ConditionStatus, reason, message string) *deployapi.DeploymentCondition {
return &deployapi.DeploymentCondition{
Type: condType,
Status: status,
LastTransitionTime: unversioned.Now(),
Reason: reason,
Message: message,
}
}
// GetDeploymentCondition returns the condition with the provided type.
func GetDeploymentCondition(status deployapi.DeploymentConfigStatus, condType deployapi.DeploymentConditionType) *deployapi.DeploymentCondition {
for i := range status.Conditions {
c := status.Conditions[i]
if c.Type == condType {
return &c
}
}
return nil
}
// SetDeploymentCondition updates the deployment to include the provided condition. If the condition that
// we are about to add already exists and has the same status and reason then we are not going to update.
func SetDeploymentCondition(status *deployapi.DeploymentConfigStatus, condition deployapi.DeploymentCondition) {
currentCond := GetDeploymentCondition(*status, condition.Type)
if currentCond != nil && currentCond.Status == condition.Status && currentCond.Reason == condition.Reason {
return
}
// Preserve lastTransitionTime if we are not switching between statuses of a condition.
if currentCond != nil && currentCond.Status == condition.Status {
condition.LastTransitionTime = currentCond.LastTransitionTime
}
newConditions := filterOutCondition(status.Conditions, condition.Type)
status.Conditions = append(newConditions, condition)
}
// RemoveDeploymentCondition removes the deployment condition with the provided type.
func RemoveDeploymentCondition(status *deployapi.DeploymentConfigStatus, condType deployapi.DeploymentConditionType) {
status.Conditions = filterOutCondition(status.Conditions, condType)
}
// filterOutCondition returns a new slice of deployment conditions without conditions with the provided type.
func filterOutCondition(conditions []deployapi.DeploymentCondition, condType deployapi.DeploymentConditionType) []deployapi.DeploymentCondition {
var newConditions []deployapi.DeploymentCondition
for _, c := range conditions {
if c.Type == condType {
continue
}
newConditions = append(newConditions, c)
}
return newConditions
}
// LatestDeploymentNameForConfig returns a stable identifier for config based on its version.
func LatestDeploymentNameForConfig(config *deployapi.DeploymentConfig) string {
return fmt.Sprintf("%s-%d", config.Name, config.Status.LatestVersion)
}
// LatestDeploymentInfo returns info about the latest deployment for a config,
// or nil if there is no latest deployment. The latest deployment is not
// always the same as the active deployment.
func LatestDeploymentInfo(config *deployapi.DeploymentConfig, deployments []api.ReplicationController) (bool, *api.ReplicationController) {
if config.Status.LatestVersion == 0 || len(deployments) == 0 {
return false, nil
}
sort.Sort(ByLatestVersionDesc(deployments))
candidate := &deployments[0]
return DeploymentVersionFor(candidate) == config.Status.LatestVersion, candidate
}
// ActiveDeployment returns the latest complete deployment, or nil if there is
// no such deployment. The active deployment is not always the same as the
// latest deployment.
func ActiveDeployment(input []api.ReplicationController) *api.ReplicationController {
var activeDeployment *api.ReplicationController
var lastCompleteDeploymentVersion int64 = 0
for i := range input {
deployment := &input[i]
deploymentVersion := DeploymentVersionFor(deployment)
if IsCompleteDeployment(deployment) && deploymentVersion > lastCompleteDeploymentVersion {
activeDeployment = deployment
lastCompleteDeploymentVersion = deploymentVersion
}
}
return activeDeployment
}
// DeployerPodSuffix is the suffix added to pods created from a deployment
const DeployerPodSuffix = "deploy"
// DeployerPodNameForDeployment returns the name of a pod for a given deployment
func DeployerPodNameForDeployment(deployment string) string {
return namer.GetPodName(deployment, DeployerPodSuffix)
}
// LabelForDeployment builds a string identifier for a Deployment.
func LabelForDeployment(deployment *api.ReplicationController) string {
return fmt.Sprintf("%s/%s", deployment.Namespace, deployment.Name)
}
// LabelForDeploymentConfig builds a string identifier for a DeploymentConfig.
func LabelForDeploymentConfig(config *deployapi.DeploymentConfig) string {
return fmt.Sprintf("%s/%s", config.Namespace, config.Name)
}
// DeploymentNameForConfigVersion returns the name of the version-th deployment
// for the config that has the provided name
func DeploymentNameForConfigVersion(name string, version int64) string {
return fmt.Sprintf("%s-%d", name, version)
}
// ConfigSelector returns a label Selector which can be used to find all
// deployments for a DeploymentConfig.
//
// TODO: Using the annotation constant for now since the value is correct
// but we could consider adding a new constant to the public types.
func ConfigSelector(name string) labels.Selector {
return labels.Set{deployapi.DeploymentConfigAnnotation: name}.AsSelector()
}
// DeployerPodSelector returns a label Selector which can be used to find all
// deployer pods associated with a deployment with name.
func DeployerPodSelector(name string) labels.Selector {
return labels.Set{deployapi.DeployerPodForDeploymentLabel: name}.AsSelector()
}
// AnyDeployerPodSelector returns a label Selector which can be used to find
// all deployer pods across all deployments, including hook and custom
// deployer pods.
func AnyDeployerPodSelector() labels.Selector {
sel, _ := labels.Parse(deployapi.DeployerPodForDeploymentLabel)
return sel
}
// HasChangeTrigger returns whether the provided deployment configuration has
// a config change trigger or not
func HasChangeTrigger(config *deployapi.DeploymentConfig) bool {
for _, trigger := range config.Spec.Triggers {
if trigger.Type == deployapi.DeploymentTriggerOnConfigChange {
return true
}
}
return false
}
// HasImageChangeTrigger returns whether the provided deployment configuration has
// an image change trigger or not.
func HasImageChangeTrigger(config *deployapi.DeploymentConfig) bool {
for _, trigger := range config.Spec.Triggers {
if trigger.Type == deployapi.DeploymentTriggerOnImageChange {
return true
}
}
return false
}
func DeploymentConfigDeepCopy(dc *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error) {
objCopy, err := api.Scheme.DeepCopy(dc)
if err != nil {
return nil, err
}
copied, ok := objCopy.(*deployapi.DeploymentConfig)
if !ok {
return nil, fmt.Errorf("expected DeploymentConfig, got %#v", objCopy)
}
return copied, nil
}
func DeploymentDeepCopy(rc *api.ReplicationController) (*api.ReplicationController, error) {
objCopy, err := api.Scheme.DeepCopy(rc)
if err != nil {
return nil, err
}
copied, ok := objCopy.(*api.ReplicationController)
if !ok {
return nil, fmt.Errorf("expected ReplicationController, got %#v", objCopy)
}
return copied, nil
}
// DecodeDeploymentConfig decodes a DeploymentConfig from controller using codec. An error is returned
// if the controller doesn't contain an encoded config.
func DecodeDeploymentConfig(controller *api.ReplicationController, decoder runtime.Decoder) (*deployapi.DeploymentConfig, error) {
encodedConfig := []byte(EncodedDeploymentConfigFor(controller))
decoded, err := runtime.Decode(decoder, encodedConfig)
if err != nil {
return nil, fmt.Errorf("failed to decode DeploymentConfig from controller: %v", err)
}
config, ok := decoded.(*deployapi.DeploymentConfig)
if !ok {
return nil, fmt.Errorf("decoded object from controller is not a DeploymentConfig")
}
return config, nil
}
// EncodeDeploymentConfig encodes config as a string using codec.
func EncodeDeploymentConfig(config *deployapi.DeploymentConfig, codec runtime.Codec) (string, error) {
bytes, err := runtime.Encode(codec, config)
if err != nil {
return "", err
}
return string(bytes[:]), nil
}
// MakeDeployment creates a deployment represented as a ReplicationController and based on the given
// DeploymentConfig. The controller replica count will be zero.
func MakeDeployment(config *deployapi.DeploymentConfig, codec runtime.Codec) (*api.ReplicationController, error) {
var err error
var encodedConfig string
if encodedConfig, err = EncodeDeploymentConfig(config, codec); err != nil {
return nil, err
}
deploymentName := LatestDeploymentNameForConfig(config)
podSpec := api.PodSpec{}
if err := api.Scheme.Convert(&config.Spec.Template.Spec, &podSpec, nil); err != nil {
return nil, fmt.Errorf("couldn't clone podSpec: %v", err)
}
controllerLabels := make(labels.Set)
for k, v := range config.Labels {
controllerLabels[k] = v
}
// Correlate the deployment with the config.
// TODO: Using the annotation constant for now since the value is correct
// but we could consider adding a new constant to the public types.
controllerLabels[deployapi.DeploymentConfigAnnotation] = config.Name
// Ensure that pods created by this deployment controller can be safely associated back
// to the controller, and that multiple deployment controllers for the same config don't
// manipulate each others' pods.
selector := map[string]string{}
for k, v := range config.Spec.Selector {
selector[k] = v
}
selector[deployapi.DeploymentConfigLabel] = config.Name
selector[deployapi.DeploymentLabel] = deploymentName
podLabels := make(labels.Set)
for k, v := range config.Spec.Template.Labels {
podLabels[k] = v
}
podLabels[deployapi.DeploymentConfigLabel] = config.Name
podLabels[deployapi.DeploymentLabel] = deploymentName
podAnnotations := make(labels.Set)
for k, v := range config.Spec.Template.Annotations {
podAnnotations[k] = v
}
podAnnotations[deployapi.DeploymentAnnotation] = deploymentName
podAnnotations[deployapi.DeploymentConfigAnnotation] = config.Name
podAnnotations[deployapi.DeploymentVersionAnnotation] = strconv.FormatInt(config.Status.LatestVersion, 10)
deployment := &api.ReplicationController{
ObjectMeta: api.ObjectMeta{
Name: deploymentName,
Namespace: config.Namespace,
Annotations: map[string]string{
deployapi.DeploymentConfigAnnotation: config.Name,
deployapi.DeploymentStatusAnnotation: string(deployapi.DeploymentStatusNew),
deployapi.DeploymentEncodedConfigAnnotation: encodedConfig,
deployapi.DeploymentVersionAnnotation: strconv.FormatInt(config.Status.LatestVersion, 10),
// This is the target replica count for the new deployment.
deployapi.DesiredReplicasAnnotation: strconv.Itoa(int(config.Spec.Replicas)),
deployapi.DeploymentReplicasAnnotation: strconv.Itoa(0),
},
Labels: controllerLabels,
},
Spec: api.ReplicationControllerSpec{
// The deployment should be inactive initially
Replicas: 0,
Selector: selector,
Template: &api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: podLabels,
Annotations: podAnnotations,
},
Spec: podSpec,
},
},
}
if config.Status.Details != nil && len(config.Status.Details.Message) > 0 {
deployment.Annotations[deployapi.DeploymentStatusReasonAnnotation] = config.Status.Details.Message
}
if value, ok := config.Annotations[deployapi.DeploymentIgnorePodAnnotation]; ok {
deployment.Annotations[deployapi.DeploymentIgnorePodAnnotation] = value
}
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, time.Now()) {
available++
}
}
return available
}
func DeploymentConfigNameFor(obj runtime.Object) string {
return annotationFor(obj, deployapi.DeploymentConfigAnnotation)
}
func DeploymentNameFor(obj runtime.Object) string {
return annotationFor(obj, deployapi.DeploymentAnnotation)
}
func DeployerPodNameFor(obj runtime.Object) string {
return annotationFor(obj, deployapi.DeploymentPodAnnotation)
}
func DeploymentStatusFor(obj runtime.Object) deployapi.DeploymentStatus {
return deployapi.DeploymentStatus(annotationFor(obj, deployapi.DeploymentStatusAnnotation))
}
func DeploymentStatusReasonFor(obj runtime.Object) string {
return annotationFor(obj, deployapi.DeploymentStatusReasonAnnotation)
}
func DeploymentDesiredReplicas(obj runtime.Object) (int32, bool) {
return int32AnnotationFor(obj, deployapi.DesiredReplicasAnnotation)
}
func DeploymentReplicas(obj runtime.Object) (int32, bool) {
return int32AnnotationFor(obj, deployapi.DeploymentReplicasAnnotation)
}
func EncodedDeploymentConfigFor(obj runtime.Object) string {
return annotationFor(obj, deployapi.DeploymentEncodedConfigAnnotation)
}
func DeploymentVersionFor(obj runtime.Object) int64 {
v, err := strconv.ParseInt(annotationFor(obj, deployapi.DeploymentVersionAnnotation), 10, 64)
if err != nil {
return -1
}
return v
}
func IsDeploymentCancelled(deployment *api.ReplicationController) bool {
value := annotationFor(deployment, deployapi.DeploymentCancelledAnnotation)
return strings.EqualFold(value, deployapi.DeploymentCancelledAnnotationValue)
}
// HasSynced checks if the provided deployment config has been noticed by the deployment
// config controller.
func HasSynced(dc *deployapi.DeploymentConfig, generation int64) bool {
return dc.Status.ObservedGeneration >= generation
}
// IsOwnedByConfig checks whether the provided replication controller is part of a
// deployment configuration.
// TODO: Switch to use owner references once we got those working.
func IsOwnedByConfig(deployment *api.ReplicationController) bool {
_, ok := deployment.Annotations[deployapi.DeploymentConfigAnnotation]
return ok
}
// IsTerminatedDeployment returns true if the passed deployment has terminated (either
// complete or failed).
func IsTerminatedDeployment(deployment *api.ReplicationController) bool {
return IsCompleteDeployment(deployment) || IsFailedDeployment(deployment)
}
// IsCompleteDeployment returns true if the passed deployment failed.
func IsCompleteDeployment(deployment *api.ReplicationController) bool {
current := DeploymentStatusFor(deployment)
return current == deployapi.DeploymentStatusComplete
}
// IsFailedDeployment returns true if the passed deployment failed.
func IsFailedDeployment(deployment *api.ReplicationController) bool {
current := DeploymentStatusFor(deployment)
return current == deployapi.DeploymentStatusFailed
}
// CanTransitionPhase returns whether it is allowed to go from the current to the next phase.
func CanTransitionPhase(current, next deployapi.DeploymentStatus) bool {
switch current {
case deployapi.DeploymentStatusNew:
switch next {
case deployapi.DeploymentStatusPending,
deployapi.DeploymentStatusRunning,
deployapi.DeploymentStatusFailed,
deployapi.DeploymentStatusComplete:
return true
}
case deployapi.DeploymentStatusPending:
switch next {
case deployapi.DeploymentStatusRunning,
deployapi.DeploymentStatusFailed,
deployapi.DeploymentStatusComplete:
return true
}
case deployapi.DeploymentStatusRunning:
switch next {
case deployapi.DeploymentStatusFailed, deployapi.DeploymentStatusComplete:
return true
}
}
return false
}
// IsRollingConfig returns true if the strategy type is a rolling update.
func IsRollingConfig(config *deployapi.DeploymentConfig) bool {
return config.Spec.Strategy.Type == deployapi.DeploymentStrategyTypeRolling
}
// IsProgressing expects a state deployment config and its updated status in order to
// determine if there is any progress.
func IsProgressing(config deployapi.DeploymentConfig, newStatus deployapi.DeploymentConfigStatus) bool {
oldStatusOldReplicas := config.Status.Replicas - config.Status.UpdatedReplicas
newStatusOldReplicas := newStatus.Replicas - newStatus.UpdatedReplicas
return (newStatus.UpdatedReplicas > config.Status.UpdatedReplicas) || (newStatusOldReplicas < oldStatusOldReplicas)
}
// MaxUnavailable returns the maximum unavailable pods a rolling deployment config can take.
func MaxUnavailable(config deployapi.DeploymentConfig) int32 {
if !IsRollingConfig(&config) {
return int32(0)
}
// Error caught by validation
_, maxUnavailable, _ := kdeplutil.ResolveFenceposts(&config.Spec.Strategy.RollingParams.MaxSurge, &config.Spec.Strategy.RollingParams.MaxUnavailable, config.Spec.Replicas)
return maxUnavailable
}
// MaxSurge returns the maximum surge pods a rolling deployment config can take.
func MaxSurge(config deployapi.DeploymentConfig) int32 {
if !IsRollingConfig(&config) {
return int32(0)
}
// Error caught by validation
maxSurge, _, _ := kdeplutil.ResolveFenceposts(&config.Spec.Strategy.RollingParams.MaxSurge, &config.Spec.Strategy.RollingParams.MaxUnavailable, config.Spec.Replicas)
return maxSurge
}
// annotationFor returns the annotation with key for obj.
func annotationFor(obj runtime.Object, key string) string {
meta, err := api.ObjectMetaFor(obj)
if err != nil {
return ""
}
return meta.Annotations[key]
}
func int32AnnotationFor(obj runtime.Object, key string) (int32, bool) {
s := annotationFor(obj, key)
if len(s) == 0 {
return 0, false
}
i, err := strconv.ParseInt(s, 10, 32)
if err != nil {
return 0, false
}
return int32(i), true
}
// DeploymentsForCleanup determines which deployments for a configuration are relevant for the
// revision history limit quota
func DeploymentsForCleanup(configuration *deployapi.DeploymentConfig, deployments []api.ReplicationController) []api.ReplicationController {
// if the past deployment quota has been exceeded, we need to prune the oldest deployments
// until we are not exceeding the quota any longer, so we sort oldest first
sort.Sort(ByLatestVersionAsc(deployments))
relevantDeployments := []api.ReplicationController{}
activeDeployment := ActiveDeployment(deployments)
if activeDeployment == nil {
// if cleanup policy is set but no successful deployments have happened, there will be
// no active deployment. We can consider all of the deployments in this case except for
// the latest one
for i := range deployments {
deployment := &deployments[i]
if DeploymentVersionFor(deployment) != configuration.Status.LatestVersion {
relevantDeployments = append(relevantDeployments, *deployment)
}
}
} else {
// if there is an active deployment, we need to filter out any deployments that we don't
// care about, namely the active deployment and any newer deployments
for i := range deployments {
deployment := &deployments[i]
if deployment != activeDeployment && DeploymentVersionFor(deployment) < DeploymentVersionFor(activeDeployment) {
relevantDeployments = append(relevantDeployments, *deployment)
}
}
}
return relevantDeployments
}
// WaitForRunningDeployerPod waits a given period of time until the deployer pod
// for given replication controller is not running.
func WaitForRunningDeployerPod(podClient kclient.PodsNamespacer, rc *api.ReplicationController, timeout time.Duration) error {
podName := DeployerPodNameForDeployment(rc.Name)
canGetLogs := func(p *api.Pod) bool {
return api.PodSucceeded == p.Status.Phase || api.PodFailed == p.Status.Phase || api.PodRunning == p.Status.Phase
}
pod, err := podClient.Pods(rc.Namespace).Get(podName)
if err == nil && canGetLogs(pod) {
return nil
}
watcher, err := podClient.Pods(rc.Namespace).Watch(
api.ListOptions{
FieldSelector: fields.Set{"metadata.name": podName}.AsSelector(),
},
)
if err != nil {
return err
}
defer watcher.Stop()
if _, err := watch.Until(timeout, watcher, func(e watch.Event) (bool, error) {
if e.Type == watch.Error {
return false, fmt.Errorf("encountered error while watching for pod: %v", e.Object)
}
obj, isPod := e.Object.(*api.Pod)
if !isPod {
return false, errors.New("received unknown object while watching for pods")
}
return canGetLogs(obj), nil
}); err != nil {
return err
}
return nil
}
// ByLatestVersionAsc sorts deployments by LatestVersion ascending.
type ByLatestVersionAsc []api.ReplicationController
func (d ByLatestVersionAsc) Len() int { return len(d) }
func (d ByLatestVersionAsc) Swap(i, j int) { d[i], d[j] = d[j], d[i] }
func (d ByLatestVersionAsc) Less(i, j int) bool {
return DeploymentVersionFor(&d[i]) < DeploymentVersionFor(&d[j])
}
// ByLatestVersionDesc sorts deployments by LatestVersion descending.
type ByLatestVersionDesc []api.ReplicationController
func (d ByLatestVersionDesc) Len() int { return len(d) }
func (d ByLatestVersionDesc) Swap(i, j int) { d[i], d[j] = d[j], d[i] }
func (d ByLatestVersionDesc) Less(i, j int) bool {
return DeploymentVersionFor(&d[j]) < DeploymentVersionFor(&d[i])
}
// ByMostRecent sorts deployments by most recently created.
type ByMostRecent []*api.ReplicationController
func (s ByMostRecent) Len() int { return len(s) }
func (s ByMostRecent) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s ByMostRecent) Less(i, j int) bool {
return !s[i].CreationTimestamp.Before(s[j].CreationTimestamp)
}