mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-13 10:33:35 +00:00
vendor cleanup: remove unused,non-go and test files
This commit is contained in:
105
vendor/k8s.io/kubernetes/pkg/controller/deployment/BUILD
generated
vendored
105
vendor/k8s.io/kubernetes/pkg/controller/deployment/BUILD
generated
vendored
@ -1,105 +0,0 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"deployment_controller.go",
|
||||
"progress.go",
|
||||
"recreate.go",
|
||||
"rollback.go",
|
||||
"rolling.go",
|
||||
"sync.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/pkg/controller/deployment",
|
||||
deps = [
|
||||
"//pkg/controller:go_default_library",
|
||||
"//pkg/controller/deployment/util:go_default_library",
|
||||
"//pkg/util/labels:go_default_library",
|
||||
"//pkg/util/metrics:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/k8s.io/api/apps/v1:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/api/extensions/v1beta1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/rand:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||
"//vendor/k8s.io/client-go/informers/apps/v1:go_default_library",
|
||||
"//vendor/k8s.io/client-go/informers/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/client-go/listers/apps/v1:go_default_library",
|
||||
"//vendor/k8s.io/client-go/listers/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/cache:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/record:go_default_library",
|
||||
"//vendor/k8s.io/client-go/util/integer:go_default_library",
|
||||
"//vendor/k8s.io/client-go/util/workqueue:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"deployment_controller_test.go",
|
||||
"progress_test.go",
|
||||
"recreate_test.go",
|
||||
"rolling_test.go",
|
||||
"sync_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//pkg/apis/apps/install:go_default_library",
|
||||
"//pkg/apis/authentication/install:go_default_library",
|
||||
"//pkg/apis/authorization/install:go_default_library",
|
||||
"//pkg/apis/autoscaling/install:go_default_library",
|
||||
"//pkg/apis/batch/install:go_default_library",
|
||||
"//pkg/apis/certificates/install:go_default_library",
|
||||
"//pkg/apis/core/install:go_default_library",
|
||||
"//pkg/apis/policy/install:go_default_library",
|
||||
"//pkg/apis/rbac/install:go_default_library",
|
||||
"//pkg/apis/settings/install:go_default_library",
|
||||
"//pkg/apis/storage/install:go_default_library",
|
||||
"//pkg/controller:go_default_library",
|
||||
"//pkg/controller/deployment/util:go_default_library",
|
||||
"//vendor/k8s.io/api/apps/v1:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/api/extensions/v1beta1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library",
|
||||
"//vendor/k8s.io/client-go/informers:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes/fake:go_default_library",
|
||||
"//vendor/k8s.io/client-go/testing:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/record:go_default_library",
|
||||
"//vendor/k8s.io/client-go/util/workqueue:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//pkg/controller/deployment/util:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
)
|
11
vendor/k8s.io/kubernetes/pkg/controller/deployment/OWNERS
generated
vendored
11
vendor/k8s.io/kubernetes/pkg/controller/deployment/OWNERS
generated
vendored
@ -1,11 +0,0 @@
|
||||
approvers:
|
||||
- janetkuo
|
||||
- nikhiljindal
|
||||
- kargakis
|
||||
- mfojtik
|
||||
reviewers:
|
||||
- janetkuo
|
||||
- nikhiljindal
|
||||
- kargakis
|
||||
- mfojtik
|
||||
- tnozicka
|
647
vendor/k8s.io/kubernetes/pkg/controller/deployment/deployment_controller.go
generated
vendored
647
vendor/k8s.io/kubernetes/pkg/controller/deployment/deployment_controller.go
generated
vendored
@ -1,647 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package deployment contains all the logic for handling Kubernetes Deployments.
|
||||
// It implements a set of strategies (rolling, recreate) for deploying an application,
|
||||
// the means to rollback to previous versions, proportional scaling for mitigating
|
||||
// risk, cleanup policy, and other useful features of Deployments.
|
||||
package deployment
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
apps "k8s.io/api/apps/v1"
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
appsinformers "k8s.io/client-go/informers/apps/v1"
|
||||
coreinformers "k8s.io/client-go/informers/core/v1"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
v1core "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
appslisters "k8s.io/client-go/listers/apps/v1"
|
||||
corelisters "k8s.io/client-go/listers/core/v1"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
"k8s.io/kubernetes/pkg/controller"
|
||||
"k8s.io/kubernetes/pkg/controller/deployment/util"
|
||||
"k8s.io/kubernetes/pkg/util/metrics"
|
||||
)
|
||||
|
||||
const (
|
||||
// maxRetries is the number of times a deployment will be retried before it is dropped out of the queue.
|
||||
// With the current rate-limiter in use (5ms*2^(maxRetries-1)) the following numbers represent the times
|
||||
// a deployment is going to be requeued:
|
||||
//
|
||||
// 5ms, 10ms, 20ms, 40ms, 80ms, 160ms, 320ms, 640ms, 1.3s, 2.6s, 5.1s, 10.2s, 20.4s, 41s, 82s
|
||||
maxRetries = 15
|
||||
)
|
||||
|
||||
// controllerKind contains the schema.GroupVersionKind for this controller type.
|
||||
var controllerKind = apps.SchemeGroupVersion.WithKind("Deployment")
|
||||
|
||||
// DeploymentController is responsible for synchronizing Deployment objects stored
|
||||
// in the system with actual running replica sets and pods.
|
||||
type DeploymentController struct {
|
||||
// rsControl is used for adopting/releasing replica sets.
|
||||
rsControl controller.RSControlInterface
|
||||
client clientset.Interface
|
||||
eventRecorder record.EventRecorder
|
||||
|
||||
// To allow injection of syncDeployment for testing.
|
||||
syncHandler func(dKey string) error
|
||||
// used for unit testing
|
||||
enqueueDeployment func(deployment *apps.Deployment)
|
||||
|
||||
// dLister can list/get deployments from the shared informer's store
|
||||
dLister appslisters.DeploymentLister
|
||||
// rsLister can list/get replica sets from the shared informer's store
|
||||
rsLister appslisters.ReplicaSetLister
|
||||
// podLister can list/get pods from the shared informer's store
|
||||
podLister corelisters.PodLister
|
||||
|
||||
// dListerSynced returns true if the Deployment store has been synced at least once.
|
||||
// Added as a member to the struct to allow injection for testing.
|
||||
dListerSynced cache.InformerSynced
|
||||
// rsListerSynced returns true if the ReplicaSet store has been synced at least once.
|
||||
// Added as a member to the struct to allow injection for testing.
|
||||
rsListerSynced cache.InformerSynced
|
||||
// podListerSynced returns true if the pod store has been synced at least once.
|
||||
// Added as a member to the struct to allow injection for testing.
|
||||
podListerSynced cache.InformerSynced
|
||||
|
||||
// Deployments that need to be synced
|
||||
queue workqueue.RateLimitingInterface
|
||||
}
|
||||
|
||||
// NewDeploymentController creates a new DeploymentController.
|
||||
func NewDeploymentController(dInformer appsinformers.DeploymentInformer, rsInformer appsinformers.ReplicaSetInformer, podInformer coreinformers.PodInformer, client clientset.Interface) (*DeploymentController, error) {
|
||||
eventBroadcaster := record.NewBroadcaster()
|
||||
eventBroadcaster.StartLogging(glog.Infof)
|
||||
eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: client.CoreV1().Events("")})
|
||||
|
||||
if client != nil && client.CoreV1().RESTClient().GetRateLimiter() != nil {
|
||||
if err := metrics.RegisterMetricAndTrackRateLimiterUsage("deployment_controller", client.CoreV1().RESTClient().GetRateLimiter()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
dc := &DeploymentController{
|
||||
client: client,
|
||||
eventRecorder: eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "deployment-controller"}),
|
||||
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "deployment"),
|
||||
}
|
||||
dc.rsControl = controller.RealRSControl{
|
||||
KubeClient: client,
|
||||
Recorder: dc.eventRecorder,
|
||||
}
|
||||
|
||||
dInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: dc.addDeployment,
|
||||
UpdateFunc: dc.updateDeployment,
|
||||
// This will enter the sync loop and no-op, because the deployment has been deleted from the store.
|
||||
DeleteFunc: dc.deleteDeployment,
|
||||
})
|
||||
rsInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: dc.addReplicaSet,
|
||||
UpdateFunc: dc.updateReplicaSet,
|
||||
DeleteFunc: dc.deleteReplicaSet,
|
||||
})
|
||||
podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
DeleteFunc: dc.deletePod,
|
||||
})
|
||||
|
||||
dc.syncHandler = dc.syncDeployment
|
||||
dc.enqueueDeployment = dc.enqueue
|
||||
|
||||
dc.dLister = dInformer.Lister()
|
||||
dc.rsLister = rsInformer.Lister()
|
||||
dc.podLister = podInformer.Lister()
|
||||
dc.dListerSynced = dInformer.Informer().HasSynced
|
||||
dc.rsListerSynced = rsInformer.Informer().HasSynced
|
||||
dc.podListerSynced = podInformer.Informer().HasSynced
|
||||
return dc, nil
|
||||
}
|
||||
|
||||
// Run begins watching and syncing.
|
||||
func (dc *DeploymentController) Run(workers int, stopCh <-chan struct{}) {
|
||||
defer utilruntime.HandleCrash()
|
||||
defer dc.queue.ShutDown()
|
||||
|
||||
glog.Infof("Starting deployment controller")
|
||||
defer glog.Infof("Shutting down deployment controller")
|
||||
|
||||
if !controller.WaitForCacheSync("deployment", stopCh, dc.dListerSynced, dc.rsListerSynced, dc.podListerSynced) {
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < workers; i++ {
|
||||
go wait.Until(dc.worker, time.Second, stopCh)
|
||||
}
|
||||
|
||||
<-stopCh
|
||||
}
|
||||
|
||||
func (dc *DeploymentController) addDeployment(obj interface{}) {
|
||||
d := obj.(*apps.Deployment)
|
||||
glog.V(4).Infof("Adding deployment %s", d.Name)
|
||||
dc.enqueueDeployment(d)
|
||||
}
|
||||
|
||||
func (dc *DeploymentController) updateDeployment(old, cur interface{}) {
|
||||
oldD := old.(*apps.Deployment)
|
||||
curD := cur.(*apps.Deployment)
|
||||
glog.V(4).Infof("Updating deployment %s", oldD.Name)
|
||||
dc.enqueueDeployment(curD)
|
||||
}
|
||||
|
||||
func (dc *DeploymentController) deleteDeployment(obj interface{}) {
|
||||
d, ok := obj.(*apps.Deployment)
|
||||
if !ok {
|
||||
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
|
||||
if !ok {
|
||||
utilruntime.HandleError(fmt.Errorf("Couldn't get object from tombstone %#v", obj))
|
||||
return
|
||||
}
|
||||
d, ok = tombstone.Obj.(*apps.Deployment)
|
||||
if !ok {
|
||||
utilruntime.HandleError(fmt.Errorf("Tombstone contained object that is not a Deployment %#v", obj))
|
||||
return
|
||||
}
|
||||
}
|
||||
glog.V(4).Infof("Deleting deployment %s", d.Name)
|
||||
dc.enqueueDeployment(d)
|
||||
}
|
||||
|
||||
// addReplicaSet enqueues the deployment that manages a ReplicaSet when the ReplicaSet is created.
|
||||
func (dc *DeploymentController) addReplicaSet(obj interface{}) {
|
||||
rs := obj.(*apps.ReplicaSet)
|
||||
|
||||
if rs.DeletionTimestamp != nil {
|
||||
// On a restart of the controller manager, it's possible for an object to
|
||||
// show up in a state that is already pending deletion.
|
||||
dc.deleteReplicaSet(rs)
|
||||
return
|
||||
}
|
||||
|
||||
// If it has a ControllerRef, that's all that matters.
|
||||
if controllerRef := metav1.GetControllerOf(rs); controllerRef != nil {
|
||||
d := dc.resolveControllerRef(rs.Namespace, controllerRef)
|
||||
if d == nil {
|
||||
return
|
||||
}
|
||||
glog.V(4).Infof("ReplicaSet %s added.", rs.Name)
|
||||
dc.enqueueDeployment(d)
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, it's an orphan. Get a list of all matching Deployments and sync
|
||||
// them to see if anyone wants to adopt it.
|
||||
ds := dc.getDeploymentsForReplicaSet(rs)
|
||||
if len(ds) == 0 {
|
||||
return
|
||||
}
|
||||
glog.V(4).Infof("Orphan ReplicaSet %s added.", rs.Name)
|
||||
for _, d := range ds {
|
||||
dc.enqueueDeployment(d)
|
||||
}
|
||||
}
|
||||
|
||||
// getDeploymentsForReplicaSet returns a list of Deployments that potentially
|
||||
// match a ReplicaSet.
|
||||
func (dc *DeploymentController) getDeploymentsForReplicaSet(rs *apps.ReplicaSet) []*apps.Deployment {
|
||||
deployments, err := dc.dLister.GetDeploymentsForReplicaSet(rs)
|
||||
if err != nil || len(deployments) == 0 {
|
||||
return nil
|
||||
}
|
||||
// Because all ReplicaSet's belonging to a deployment should have a unique label key,
|
||||
// there should never be more than one deployment returned by the above method.
|
||||
// If that happens we should probably dynamically repair the situation by ultimately
|
||||
// trying to clean up one of the controllers, for now we just return the older one
|
||||
if len(deployments) > 1 {
|
||||
// ControllerRef will ensure we don't do anything crazy, but more than one
|
||||
// item in this list nevertheless constitutes user error.
|
||||
glog.V(4).Infof("user error! more than one deployment is selecting replica set %s/%s with labels: %#v, returning %s/%s",
|
||||
rs.Namespace, rs.Name, rs.Labels, deployments[0].Namespace, deployments[0].Name)
|
||||
}
|
||||
return deployments
|
||||
}
|
||||
|
||||
// updateReplicaSet figures out what deployment(s) manage a ReplicaSet when the ReplicaSet
|
||||
// is updated and wake them up. If the anything of the ReplicaSets have changed, we need to
|
||||
// awaken both the old and new deployments. old and cur must be *apps.ReplicaSet
|
||||
// types.
|
||||
func (dc *DeploymentController) updateReplicaSet(old, cur interface{}) {
|
||||
curRS := cur.(*apps.ReplicaSet)
|
||||
oldRS := old.(*apps.ReplicaSet)
|
||||
if curRS.ResourceVersion == oldRS.ResourceVersion {
|
||||
// Periodic resync will send update events for all known replica sets.
|
||||
// Two different versions of the same replica set will always have different RVs.
|
||||
return
|
||||
}
|
||||
|
||||
curControllerRef := metav1.GetControllerOf(curRS)
|
||||
oldControllerRef := metav1.GetControllerOf(oldRS)
|
||||
controllerRefChanged := !reflect.DeepEqual(curControllerRef, oldControllerRef)
|
||||
if controllerRefChanged && oldControllerRef != nil {
|
||||
// The ControllerRef was changed. Sync the old controller, if any.
|
||||
if d := dc.resolveControllerRef(oldRS.Namespace, oldControllerRef); d != nil {
|
||||
dc.enqueueDeployment(d)
|
||||
}
|
||||
}
|
||||
|
||||
// If it has a ControllerRef, that's all that matters.
|
||||
if curControllerRef != nil {
|
||||
d := dc.resolveControllerRef(curRS.Namespace, curControllerRef)
|
||||
if d == nil {
|
||||
return
|
||||
}
|
||||
glog.V(4).Infof("ReplicaSet %s updated.", curRS.Name)
|
||||
dc.enqueueDeployment(d)
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, it's an orphan. If anything changed, sync matching controllers
|
||||
// to see if anyone wants to adopt it now.
|
||||
labelChanged := !reflect.DeepEqual(curRS.Labels, oldRS.Labels)
|
||||
if labelChanged || controllerRefChanged {
|
||||
ds := dc.getDeploymentsForReplicaSet(curRS)
|
||||
if len(ds) == 0 {
|
||||
return
|
||||
}
|
||||
glog.V(4).Infof("Orphan ReplicaSet %s updated.", curRS.Name)
|
||||
for _, d := range ds {
|
||||
dc.enqueueDeployment(d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// deleteReplicaSet enqueues the deployment that manages a ReplicaSet when
|
||||
// the ReplicaSet is deleted. obj could be an *apps.ReplicaSet, or
|
||||
// a DeletionFinalStateUnknown marker item.
|
||||
func (dc *DeploymentController) deleteReplicaSet(obj interface{}) {
|
||||
rs, ok := obj.(*apps.ReplicaSet)
|
||||
|
||||
// When a delete is dropped, the relist will notice a pod in the store not
|
||||
// in the list, leading to the insertion of a tombstone object which contains
|
||||
// the deleted key/value. Note that this value might be stale. If the ReplicaSet
|
||||
// changed labels the new deployment will not be woken up till the periodic resync.
|
||||
if !ok {
|
||||
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
|
||||
if !ok {
|
||||
utilruntime.HandleError(fmt.Errorf("Couldn't get object from tombstone %#v", obj))
|
||||
return
|
||||
}
|
||||
rs, ok = tombstone.Obj.(*apps.ReplicaSet)
|
||||
if !ok {
|
||||
utilruntime.HandleError(fmt.Errorf("Tombstone contained object that is not a ReplicaSet %#v", obj))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
controllerRef := metav1.GetControllerOf(rs)
|
||||
if controllerRef == nil {
|
||||
// No controller should care about orphans being deleted.
|
||||
return
|
||||
}
|
||||
d := dc.resolveControllerRef(rs.Namespace, controllerRef)
|
||||
if d == nil {
|
||||
return
|
||||
}
|
||||
glog.V(4).Infof("ReplicaSet %s deleted.", rs.Name)
|
||||
dc.enqueueDeployment(d)
|
||||
}
|
||||
|
||||
// deletePod will enqueue a Recreate Deployment once all of its pods have stopped running.
|
||||
func (dc *DeploymentController) deletePod(obj interface{}) {
|
||||
pod, ok := obj.(*v1.Pod)
|
||||
|
||||
// When a delete is dropped, the relist will notice a pod in the store not
|
||||
// in the list, leading to the insertion of a tombstone object which contains
|
||||
// the deleted key/value. Note that this value might be stale. If the Pod
|
||||
// changed labels the new deployment will not be woken up till the periodic resync.
|
||||
if !ok {
|
||||
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
|
||||
if !ok {
|
||||
utilruntime.HandleError(fmt.Errorf("Couldn't get object from tombstone %#v", obj))
|
||||
return
|
||||
}
|
||||
pod, ok = tombstone.Obj.(*v1.Pod)
|
||||
if !ok {
|
||||
utilruntime.HandleError(fmt.Errorf("Tombstone contained object that is not a pod %#v", obj))
|
||||
return
|
||||
}
|
||||
}
|
||||
glog.V(4).Infof("Pod %s deleted.", pod.Name)
|
||||
if d := dc.getDeploymentForPod(pod); d != nil && d.Spec.Strategy.Type == apps.RecreateDeploymentStrategyType {
|
||||
// Sync if this Deployment now has no more Pods.
|
||||
rsList, err := util.ListReplicaSets(d, util.RsListFromClient(dc.client.AppsV1()))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
podMap, err := dc.getPodMapForDeployment(d, rsList)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
numPods := 0
|
||||
for _, podList := range podMap {
|
||||
numPods += len(podList.Items)
|
||||
}
|
||||
if numPods == 0 {
|
||||
dc.enqueueDeployment(d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (dc *DeploymentController) enqueue(deployment *apps.Deployment) {
|
||||
key, err := controller.KeyFunc(deployment)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("Couldn't get key for object %#v: %v", deployment, err))
|
||||
return
|
||||
}
|
||||
|
||||
dc.queue.Add(key)
|
||||
}
|
||||
|
||||
func (dc *DeploymentController) enqueueRateLimited(deployment *apps.Deployment) {
|
||||
key, err := controller.KeyFunc(deployment)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("Couldn't get key for object %#v: %v", deployment, err))
|
||||
return
|
||||
}
|
||||
|
||||
dc.queue.AddRateLimited(key)
|
||||
}
|
||||
|
||||
// enqueueAfter will enqueue a deployment after the provided amount of time.
|
||||
func (dc *DeploymentController) enqueueAfter(deployment *apps.Deployment, after time.Duration) {
|
||||
key, err := controller.KeyFunc(deployment)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("Couldn't get key for object %#v: %v", deployment, err))
|
||||
return
|
||||
}
|
||||
|
||||
dc.queue.AddAfter(key, after)
|
||||
}
|
||||
|
||||
// getDeploymentForPod returns the deployment managing the given Pod.
|
||||
func (dc *DeploymentController) getDeploymentForPod(pod *v1.Pod) *apps.Deployment {
|
||||
// Find the owning replica set
|
||||
var rs *apps.ReplicaSet
|
||||
var err error
|
||||
controllerRef := metav1.GetControllerOf(pod)
|
||||
if controllerRef == nil {
|
||||
// No controller owns this Pod.
|
||||
return nil
|
||||
}
|
||||
if controllerRef.Kind != apps.SchemeGroupVersion.WithKind("ReplicaSet").Kind {
|
||||
// Not a pod owned by a replica set.
|
||||
return nil
|
||||
}
|
||||
rs, err = dc.rsLister.ReplicaSets(pod.Namespace).Get(controllerRef.Name)
|
||||
if err != nil || rs.UID != controllerRef.UID {
|
||||
glog.V(4).Infof("Cannot get replicaset %q for pod %q: %v", controllerRef.Name, pod.Name, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Now find the Deployment that owns that ReplicaSet.
|
||||
controllerRef = metav1.GetControllerOf(rs)
|
||||
if controllerRef == nil {
|
||||
return nil
|
||||
}
|
||||
return dc.resolveControllerRef(rs.Namespace, controllerRef)
|
||||
}
|
||||
|
||||
// resolveControllerRef returns the controller referenced by a ControllerRef,
|
||||
// or nil if the ControllerRef could not be resolved to a matching controller
|
||||
// of the correct Kind.
|
||||
func (dc *DeploymentController) resolveControllerRef(namespace string, controllerRef *metav1.OwnerReference) *apps.Deployment {
|
||||
// We can't look up by UID, so look up by Name and then verify UID.
|
||||
// Don't even try to look up by Name if it's the wrong Kind.
|
||||
if controllerRef.Kind != controllerKind.Kind {
|
||||
return nil
|
||||
}
|
||||
d, err := dc.dLister.Deployments(namespace).Get(controllerRef.Name)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if d.UID != controllerRef.UID {
|
||||
// The controller we found with this Name is not the same one that the
|
||||
// ControllerRef points to.
|
||||
return nil
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// worker runs a worker thread that just dequeues items, processes them, and marks them done.
|
||||
// It enforces that the syncHandler is never invoked concurrently with the same key.
|
||||
func (dc *DeploymentController) worker() {
|
||||
for dc.processNextWorkItem() {
|
||||
}
|
||||
}
|
||||
|
||||
func (dc *DeploymentController) processNextWorkItem() bool {
|
||||
key, quit := dc.queue.Get()
|
||||
if quit {
|
||||
return false
|
||||
}
|
||||
defer dc.queue.Done(key)
|
||||
|
||||
err := dc.syncHandler(key.(string))
|
||||
dc.handleErr(err, key)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (dc *DeploymentController) handleErr(err error, key interface{}) {
|
||||
if err == nil {
|
||||
dc.queue.Forget(key)
|
||||
return
|
||||
}
|
||||
|
||||
if dc.queue.NumRequeues(key) < maxRetries {
|
||||
glog.V(2).Infof("Error syncing deployment %v: %v", key, err)
|
||||
dc.queue.AddRateLimited(key)
|
||||
return
|
||||
}
|
||||
|
||||
utilruntime.HandleError(err)
|
||||
glog.V(2).Infof("Dropping deployment %q out of the queue: %v", key, err)
|
||||
dc.queue.Forget(key)
|
||||
}
|
||||
|
||||
// getReplicaSetsForDeployment uses ControllerRefManager to reconcile
|
||||
// ControllerRef by adopting and orphaning.
|
||||
// It returns the list of ReplicaSets that this Deployment should manage.
|
||||
func (dc *DeploymentController) getReplicaSetsForDeployment(d *apps.Deployment) ([]*apps.ReplicaSet, error) {
|
||||
// List all ReplicaSets to find those we own but that no longer match our
|
||||
// selector. They will be orphaned by ClaimReplicaSets().
|
||||
rsList, err := dc.rsLister.ReplicaSets(d.Namespace).List(labels.Everything())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
deploymentSelector, err := metav1.LabelSelectorAsSelector(d.Spec.Selector)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("deployment %s/%s has invalid label selector: %v", d.Namespace, d.Name, err)
|
||||
}
|
||||
// If any adoptions are attempted, we should first recheck for deletion with
|
||||
// an uncached quorum read sometime after listing ReplicaSets (see #42639).
|
||||
canAdoptFunc := controller.RecheckDeletionTimestamp(func() (metav1.Object, error) {
|
||||
fresh, err := dc.client.AppsV1().Deployments(d.Namespace).Get(d.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if fresh.UID != d.UID {
|
||||
return nil, fmt.Errorf("original Deployment %v/%v is gone: got uid %v, wanted %v", d.Namespace, d.Name, fresh.UID, d.UID)
|
||||
}
|
||||
return fresh, nil
|
||||
})
|
||||
cm := controller.NewReplicaSetControllerRefManager(dc.rsControl, d, deploymentSelector, controllerKind, canAdoptFunc)
|
||||
return cm.ClaimReplicaSets(rsList)
|
||||
}
|
||||
|
||||
// getPodMapForDeployment returns the Pods managed by a Deployment.
|
||||
//
|
||||
// It returns a map from ReplicaSet UID to a list of Pods controlled by that RS,
|
||||
// according to the Pod's ControllerRef.
|
||||
func (dc *DeploymentController) getPodMapForDeployment(d *apps.Deployment, rsList []*apps.ReplicaSet) (map[types.UID]*v1.PodList, error) {
|
||||
// Get all Pods that potentially belong to this Deployment.
|
||||
selector, err := metav1.LabelSelectorAsSelector(d.Spec.Selector)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pods, err := dc.podLister.Pods(d.Namespace).List(selector)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Group Pods by their controller (if it's in rsList).
|
||||
podMap := make(map[types.UID]*v1.PodList, len(rsList))
|
||||
for _, rs := range rsList {
|
||||
podMap[rs.UID] = &v1.PodList{}
|
||||
}
|
||||
for _, pod := range pods {
|
||||
// Do not ignore inactive Pods because Recreate Deployments need to verify that no
|
||||
// Pods from older versions are running before spinning up new Pods.
|
||||
controllerRef := metav1.GetControllerOf(pod)
|
||||
if controllerRef == nil {
|
||||
continue
|
||||
}
|
||||
// Only append if we care about this UID.
|
||||
if podList, ok := podMap[controllerRef.UID]; ok {
|
||||
podList.Items = append(podList.Items, *pod)
|
||||
}
|
||||
}
|
||||
return podMap, nil
|
||||
}
|
||||
|
||||
// syncDeployment will sync the deployment with the given key.
|
||||
// This function is not meant to be invoked concurrently with the same key.
|
||||
func (dc *DeploymentController) syncDeployment(key string) error {
|
||||
startTime := time.Now()
|
||||
glog.V(4).Infof("Started syncing deployment %q (%v)", key, startTime)
|
||||
defer func() {
|
||||
glog.V(4).Infof("Finished syncing deployment %q (%v)", key, time.Since(startTime))
|
||||
}()
|
||||
|
||||
namespace, name, err := cache.SplitMetaNamespaceKey(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
deployment, err := dc.dLister.Deployments(namespace).Get(name)
|
||||
if errors.IsNotFound(err) {
|
||||
glog.V(2).Infof("Deployment %v has been deleted", key)
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Deep-copy otherwise we are mutating our cache.
|
||||
// TODO: Deep-copy only when needed.
|
||||
d := deployment.DeepCopy()
|
||||
|
||||
everything := metav1.LabelSelector{}
|
||||
if reflect.DeepEqual(d.Spec.Selector, &everything) {
|
||||
dc.eventRecorder.Eventf(d, v1.EventTypeWarning, "SelectingAll", "This deployment is selecting all pods. A non-empty selector is required.")
|
||||
if d.Status.ObservedGeneration < d.Generation {
|
||||
d.Status.ObservedGeneration = d.Generation
|
||||
dc.client.AppsV1().Deployments(d.Namespace).UpdateStatus(d)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// List ReplicaSets owned by this Deployment, while reconciling ControllerRef
|
||||
// through adoption/orphaning.
|
||||
rsList, err := dc.getReplicaSetsForDeployment(d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// List all Pods owned by this Deployment, grouped by their ReplicaSet.
|
||||
// Current uses of the podMap are:
|
||||
//
|
||||
// * check if a Pod is labeled correctly with the pod-template-hash label.
|
||||
// * check that no old Pods are running in the middle of Recreate Deployments.
|
||||
podMap, err := dc.getPodMapForDeployment(d, rsList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if d.DeletionTimestamp != nil {
|
||||
return dc.syncStatusOnly(d, rsList, podMap)
|
||||
}
|
||||
|
||||
// Update deployment conditions with an Unknown condition when pausing/resuming
|
||||
// a deployment. In this way, we can be sure that we won't timeout when a user
|
||||
// resumes a Deployment with a set progressDeadlineSeconds.
|
||||
if err = dc.checkPausedConditions(d); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if d.Spec.Paused {
|
||||
return dc.sync(d, rsList, podMap)
|
||||
}
|
||||
|
||||
// rollback is not re-entrant in case the underlying replica sets are updated with a new
|
||||
// revision so we should ensure that we won't proceed to update replica sets until we
|
||||
// make sure that the deployment has cleaned up its rollback spec in subsequent enqueues.
|
||||
if getRollbackTo(d) != nil {
|
||||
return dc.rollback(d, rsList, podMap)
|
||||
}
|
||||
|
||||
scalingEvent, err := dc.isScalingEvent(d, rsList, podMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if scalingEvent {
|
||||
return dc.sync(d, rsList, podMap)
|
||||
}
|
||||
|
||||
switch d.Spec.Strategy.Type {
|
||||
case apps.RecreateDeploymentStrategyType:
|
||||
return dc.rolloutRecreate(d, rsList, podMap)
|
||||
case apps.RollingUpdateDeploymentStrategyType:
|
||||
return dc.rolloutRolling(d, rsList, podMap)
|
||||
}
|
||||
return fmt.Errorf("unexpected deployment strategy type: %s", d.Spec.Strategy.Type)
|
||||
}
|
987
vendor/k8s.io/kubernetes/pkg/controller/deployment/deployment_controller_test.go
generated
vendored
987
vendor/k8s.io/kubernetes/pkg/controller/deployment/deployment_controller_test.go
generated
vendored
@ -1,987 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package deployment
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
apps "k8s.io/api/apps/v1"
|
||||
"k8s.io/api/core/v1"
|
||||
extensions "k8s.io/api/extensions/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/apimachinery/pkg/util/uuid"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
core "k8s.io/client-go/testing"
|
||||
"k8s.io/client-go/tools/record"
|
||||
_ "k8s.io/kubernetes/pkg/apis/apps/install"
|
||||
_ "k8s.io/kubernetes/pkg/apis/authentication/install"
|
||||
_ "k8s.io/kubernetes/pkg/apis/authorization/install"
|
||||
_ "k8s.io/kubernetes/pkg/apis/autoscaling/install"
|
||||
_ "k8s.io/kubernetes/pkg/apis/batch/install"
|
||||
_ "k8s.io/kubernetes/pkg/apis/certificates/install"
|
||||
_ "k8s.io/kubernetes/pkg/apis/core/install"
|
||||
_ "k8s.io/kubernetes/pkg/apis/policy/install"
|
||||
_ "k8s.io/kubernetes/pkg/apis/rbac/install"
|
||||
_ "k8s.io/kubernetes/pkg/apis/settings/install"
|
||||
_ "k8s.io/kubernetes/pkg/apis/storage/install"
|
||||
"k8s.io/kubernetes/pkg/controller"
|
||||
"k8s.io/kubernetes/pkg/controller/deployment/util"
|
||||
)
|
||||
|
||||
var (
|
||||
alwaysReady = func() bool { return true }
|
||||
noTimestamp = metav1.Time{}
|
||||
)
|
||||
|
||||
func rs(name string, replicas int, selector map[string]string, timestamp metav1.Time) *apps.ReplicaSet {
|
||||
return &apps.ReplicaSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
CreationTimestamp: timestamp,
|
||||
Namespace: metav1.NamespaceDefault,
|
||||
},
|
||||
Spec: apps.ReplicaSetSpec{
|
||||
Replicas: func() *int32 { i := int32(replicas); return &i }(),
|
||||
Selector: &metav1.LabelSelector{MatchLabels: selector},
|
||||
Template: v1.PodTemplateSpec{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newRSWithStatus(name string, specReplicas, statusReplicas int, selector map[string]string) *apps.ReplicaSet {
|
||||
rs := rs(name, specReplicas, selector, noTimestamp)
|
||||
rs.Status = apps.ReplicaSetStatus{
|
||||
Replicas: int32(statusReplicas),
|
||||
}
|
||||
return rs
|
||||
}
|
||||
|
||||
func newDeployment(name string, replicas int, revisionHistoryLimit *int32, maxSurge, maxUnavailable *intstr.IntOrString, selector map[string]string) *apps.Deployment {
|
||||
d := apps.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: "apps/v1"},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
UID: uuid.NewUUID(),
|
||||
Name: name,
|
||||
Namespace: metav1.NamespaceDefault,
|
||||
Annotations: make(map[string]string),
|
||||
},
|
||||
Spec: apps.DeploymentSpec{
|
||||
Strategy: apps.DeploymentStrategy{
|
||||
Type: apps.RollingUpdateDeploymentStrategyType,
|
||||
RollingUpdate: &apps.RollingUpdateDeployment{
|
||||
MaxUnavailable: func() *intstr.IntOrString { i := intstr.FromInt(0); return &i }(),
|
||||
MaxSurge: func() *intstr.IntOrString { i := intstr.FromInt(0); return &i }(),
|
||||
},
|
||||
},
|
||||
Replicas: func() *int32 { i := int32(replicas); return &i }(),
|
||||
Selector: &metav1.LabelSelector{MatchLabels: selector},
|
||||
Template: v1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: selector,
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Image: "foo/bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
RevisionHistoryLimit: revisionHistoryLimit,
|
||||
},
|
||||
}
|
||||
if maxSurge != nil {
|
||||
d.Spec.Strategy.RollingUpdate.MaxSurge = maxSurge
|
||||
}
|
||||
if maxUnavailable != nil {
|
||||
d.Spec.Strategy.RollingUpdate.MaxUnavailable = maxUnavailable
|
||||
}
|
||||
return &d
|
||||
}
|
||||
|
||||
func newReplicaSet(d *apps.Deployment, name string, replicas int) *apps.ReplicaSet {
|
||||
return &apps.ReplicaSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
UID: uuid.NewUUID(),
|
||||
Namespace: metav1.NamespaceDefault,
|
||||
Labels: d.Spec.Selector.MatchLabels,
|
||||
OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(d, controllerKind)},
|
||||
},
|
||||
Spec: apps.ReplicaSetSpec{
|
||||
Selector: d.Spec.Selector,
|
||||
Replicas: func() *int32 { i := int32(replicas); return &i }(),
|
||||
Template: d.Spec.Template,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getKey(d *apps.Deployment, t *testing.T) string {
|
||||
if key, err := controller.KeyFunc(d); err != nil {
|
||||
t.Errorf("Unexpected error getting key for deployment %v: %v", d.Name, err)
|
||||
return ""
|
||||
} else {
|
||||
return key
|
||||
}
|
||||
}
|
||||
|
||||
type fixture struct {
|
||||
t *testing.T
|
||||
|
||||
client *fake.Clientset
|
||||
// Objects to put in the store.
|
||||
dLister []*apps.Deployment
|
||||
rsLister []*apps.ReplicaSet
|
||||
podLister []*v1.Pod
|
||||
|
||||
// Actions expected to happen on the client. Objects from here are also
|
||||
// preloaded into NewSimpleFake.
|
||||
actions []core.Action
|
||||
objects []runtime.Object
|
||||
}
|
||||
|
||||
func (f *fixture) expectGetDeploymentAction(d *apps.Deployment) {
|
||||
action := core.NewGetAction(schema.GroupVersionResource{Resource: "deployments"}, d.Namespace, d.Name)
|
||||
f.actions = append(f.actions, action)
|
||||
}
|
||||
|
||||
func (f *fixture) expectUpdateDeploymentStatusAction(d *apps.Deployment) {
|
||||
action := core.NewUpdateAction(schema.GroupVersionResource{Resource: "deployments"}, d.Namespace, d)
|
||||
action.Subresource = "status"
|
||||
f.actions = append(f.actions, action)
|
||||
}
|
||||
|
||||
func (f *fixture) expectUpdateDeploymentAction(d *apps.Deployment) {
|
||||
action := core.NewUpdateAction(schema.GroupVersionResource{Resource: "deployments"}, d.Namespace, d)
|
||||
f.actions = append(f.actions, action)
|
||||
}
|
||||
|
||||
func (f *fixture) expectCreateRSAction(rs *apps.ReplicaSet) {
|
||||
f.actions = append(f.actions, core.NewCreateAction(schema.GroupVersionResource{Resource: "replicasets"}, rs.Namespace, rs))
|
||||
}
|
||||
|
||||
func newFixture(t *testing.T) *fixture {
|
||||
f := &fixture{}
|
||||
f.t = t
|
||||
f.objects = []runtime.Object{}
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *fixture) newController() (*DeploymentController, informers.SharedInformerFactory, error) {
|
||||
f.client = fake.NewSimpleClientset(f.objects...)
|
||||
informers := informers.NewSharedInformerFactory(f.client, controller.NoResyncPeriodFunc())
|
||||
c, err := NewDeploymentController(informers.Apps().V1().Deployments(), informers.Apps().V1().ReplicaSets(), informers.Core().V1().Pods(), f.client)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
c.eventRecorder = &record.FakeRecorder{}
|
||||
c.dListerSynced = alwaysReady
|
||||
c.rsListerSynced = alwaysReady
|
||||
c.podListerSynced = alwaysReady
|
||||
for _, d := range f.dLister {
|
||||
informers.Apps().V1().Deployments().Informer().GetIndexer().Add(d)
|
||||
}
|
||||
for _, rs := range f.rsLister {
|
||||
informers.Apps().V1().ReplicaSets().Informer().GetIndexer().Add(rs)
|
||||
}
|
||||
for _, pod := range f.podLister {
|
||||
informers.Core().V1().Pods().Informer().GetIndexer().Add(pod)
|
||||
}
|
||||
return c, informers, nil
|
||||
}
|
||||
|
||||
func (f *fixture) runExpectError(deploymentName string, startInformers bool) {
|
||||
f.run_(deploymentName, startInformers, true)
|
||||
}
|
||||
|
||||
func (f *fixture) run(deploymentName string) {
|
||||
f.run_(deploymentName, true, false)
|
||||
}
|
||||
|
||||
func (f *fixture) run_(deploymentName string, startInformers bool, expectError bool) {
|
||||
c, informers, err := f.newController()
|
||||
if err != nil {
|
||||
f.t.Fatalf("error creating Deployment controller: %v", err)
|
||||
}
|
||||
if startInformers {
|
||||
stopCh := make(chan struct{})
|
||||
defer close(stopCh)
|
||||
informers.Start(stopCh)
|
||||
}
|
||||
|
||||
err = c.syncDeployment(deploymentName)
|
||||
if !expectError && err != nil {
|
||||
f.t.Errorf("error syncing deployment: %v", err)
|
||||
} else if expectError && err == nil {
|
||||
f.t.Error("expected error syncing deployment, got nil")
|
||||
}
|
||||
|
||||
actions := filterInformerActions(f.client.Actions())
|
||||
for i, action := range actions {
|
||||
if len(f.actions) < i+1 {
|
||||
f.t.Errorf("%d unexpected actions: %+v", len(actions)-len(f.actions), actions[i:])
|
||||
break
|
||||
}
|
||||
|
||||
expectedAction := f.actions[i]
|
||||
if !(expectedAction.Matches(action.GetVerb(), action.GetResource().Resource) && action.GetSubresource() == expectedAction.GetSubresource()) {
|
||||
f.t.Errorf("Expected\n\t%#v\ngot\n\t%#v", expectedAction, action)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if len(f.actions) > len(actions) {
|
||||
f.t.Errorf("%d additional expected actions:%+v", len(f.actions)-len(actions), f.actions[len(actions):])
|
||||
}
|
||||
}
|
||||
|
||||
func filterInformerActions(actions []core.Action) []core.Action {
|
||||
ret := []core.Action{}
|
||||
for _, action := range actions {
|
||||
if len(action.GetNamespace()) == 0 &&
|
||||
(action.Matches("list", "pods") ||
|
||||
action.Matches("list", "deployments") ||
|
||||
action.Matches("list", "replicasets") ||
|
||||
action.Matches("watch", "pods") ||
|
||||
action.Matches("watch", "deployments") ||
|
||||
action.Matches("watch", "replicasets")) {
|
||||
continue
|
||||
}
|
||||
ret = append(ret, action)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func TestSyncDeploymentCreatesReplicaSet(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
|
||||
d := newDeployment("foo", 1, nil, nil, nil, map[string]string{"foo": "bar"})
|
||||
f.dLister = append(f.dLister, d)
|
||||
f.objects = append(f.objects, d)
|
||||
|
||||
rs := newReplicaSet(d, "deploymentrs-4186632231", 1)
|
||||
|
||||
f.expectCreateRSAction(rs)
|
||||
f.expectUpdateDeploymentStatusAction(d)
|
||||
f.expectUpdateDeploymentStatusAction(d)
|
||||
|
||||
f.run(getKey(d, t))
|
||||
}
|
||||
|
||||
func TestSyncDeploymentDontDoAnythingDuringDeletion(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
|
||||
d := newDeployment("foo", 1, nil, nil, nil, map[string]string{"foo": "bar"})
|
||||
now := metav1.Now()
|
||||
d.DeletionTimestamp = &now
|
||||
f.dLister = append(f.dLister, d)
|
||||
f.objects = append(f.objects, d)
|
||||
|
||||
f.expectUpdateDeploymentStatusAction(d)
|
||||
f.run(getKey(d, t))
|
||||
}
|
||||
|
||||
func TestSyncDeploymentDeletionRace(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
|
||||
d := newDeployment("foo", 1, nil, nil, nil, map[string]string{"foo": "bar"})
|
||||
d2 := *d
|
||||
// Lister (cache) says NOT deleted.
|
||||
f.dLister = append(f.dLister, d)
|
||||
// Bare client says it IS deleted. This should be presumed more up-to-date.
|
||||
now := metav1.Now()
|
||||
d2.DeletionTimestamp = &now
|
||||
f.objects = append(f.objects, &d2)
|
||||
|
||||
// The recheck is only triggered if a matching orphan exists.
|
||||
rs := newReplicaSet(d, "rs1", 1)
|
||||
rs.OwnerReferences = nil
|
||||
f.objects = append(f.objects, rs)
|
||||
f.rsLister = append(f.rsLister, rs)
|
||||
|
||||
// Expect to only recheck DeletionTimestamp.
|
||||
f.expectGetDeploymentAction(d)
|
||||
// Sync should fail and requeue to let cache catch up.
|
||||
// Don't start informers, since we don't want cache to catch up for this test.
|
||||
f.runExpectError(getKey(d, t), false)
|
||||
}
|
||||
|
||||
// issue: https://github.com/kubernetes/kubernetes/issues/23218
|
||||
func TestDontSyncDeploymentsWithEmptyPodSelector(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
|
||||
d := newDeployment("foo", 1, nil, nil, nil, map[string]string{"foo": "bar"})
|
||||
d.Spec.Selector = &metav1.LabelSelector{}
|
||||
f.dLister = append(f.dLister, d)
|
||||
f.objects = append(f.objects, d)
|
||||
|
||||
// Normally there should be a status update to sync observedGeneration but the fake
|
||||
// deployment has no generation set so there is no action happpening here.
|
||||
f.run(getKey(d, t))
|
||||
}
|
||||
|
||||
func TestReentrantRollback(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
|
||||
d := newDeployment("foo", 1, nil, nil, nil, map[string]string{"foo": "bar"})
|
||||
d.Annotations = map[string]string{util.RevisionAnnotation: "2"}
|
||||
setRollbackTo(d, &extensions.RollbackConfig{Revision: 0})
|
||||
f.dLister = append(f.dLister, d)
|
||||
|
||||
rs1 := newReplicaSet(d, "deploymentrs-old", 0)
|
||||
rs1.Annotations = map[string]string{util.RevisionAnnotation: "1"}
|
||||
one := int64(1)
|
||||
rs1.Spec.Template.Spec.TerminationGracePeriodSeconds = &one
|
||||
rs1.Spec.Selector.MatchLabels[apps.DefaultDeploymentUniqueLabelKey] = "hash"
|
||||
|
||||
rs2 := newReplicaSet(d, "deploymentrs-new", 1)
|
||||
rs2.Annotations = map[string]string{util.RevisionAnnotation: "2"}
|
||||
rs2.Spec.Selector.MatchLabels[apps.DefaultDeploymentUniqueLabelKey] = "hash"
|
||||
|
||||
f.rsLister = append(f.rsLister, rs1, rs2)
|
||||
f.objects = append(f.objects, d, rs1, rs2)
|
||||
|
||||
// Rollback is done here
|
||||
f.expectUpdateDeploymentAction(d)
|
||||
// Expect no update on replica sets though
|
||||
f.run(getKey(d, t))
|
||||
}
|
||||
|
||||
// TestPodDeletionEnqueuesRecreateDeployment ensures that the deletion of a pod
|
||||
// will requeue a Recreate deployment iff there is no other pod returned from the
|
||||
// client.
|
||||
func TestPodDeletionEnqueuesRecreateDeployment(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
|
||||
foo := newDeployment("foo", 1, nil, nil, nil, map[string]string{"foo": "bar"})
|
||||
foo.Spec.Strategy.Type = apps.RecreateDeploymentStrategyType
|
||||
rs := newReplicaSet(foo, "foo-1", 1)
|
||||
pod := generatePodFromRS(rs)
|
||||
|
||||
f.dLister = append(f.dLister, foo)
|
||||
f.rsLister = append(f.rsLister, rs)
|
||||
f.objects = append(f.objects, foo, rs)
|
||||
|
||||
c, _, err := f.newController()
|
||||
if err != nil {
|
||||
t.Fatalf("error creating Deployment controller: %v", err)
|
||||
}
|
||||
enqueued := false
|
||||
c.enqueueDeployment = func(d *apps.Deployment) {
|
||||
if d.Name == "foo" {
|
||||
enqueued = true
|
||||
}
|
||||
}
|
||||
|
||||
c.deletePod(pod)
|
||||
|
||||
if !enqueued {
|
||||
t.Errorf("expected deployment %q to be queued after pod deletion", foo.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// TestPodDeletionDoesntEnqueueRecreateDeployment ensures that the deletion of a pod
|
||||
// will not requeue a Recreate deployment iff there are other pods returned from the
|
||||
// client.
|
||||
func TestPodDeletionDoesntEnqueueRecreateDeployment(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
|
||||
foo := newDeployment("foo", 1, nil, nil, nil, map[string]string{"foo": "bar"})
|
||||
foo.Spec.Strategy.Type = apps.RecreateDeploymentStrategyType
|
||||
rs1 := newReplicaSet(foo, "foo-1", 1)
|
||||
rs2 := newReplicaSet(foo, "foo-1", 1)
|
||||
pod1 := generatePodFromRS(rs1)
|
||||
pod2 := generatePodFromRS(rs2)
|
||||
|
||||
f.dLister = append(f.dLister, foo)
|
||||
// Let's pretend this is a different pod. The gist is that the pod lister needs to
|
||||
// return a non-empty list.
|
||||
f.podLister = append(f.podLister, pod1, pod2)
|
||||
|
||||
c, _, err := f.newController()
|
||||
if err != nil {
|
||||
t.Fatalf("error creating Deployment controller: %v", err)
|
||||
}
|
||||
enqueued := false
|
||||
c.enqueueDeployment = func(d *apps.Deployment) {
|
||||
if d.Name == "foo" {
|
||||
enqueued = true
|
||||
}
|
||||
}
|
||||
|
||||
c.deletePod(pod1)
|
||||
|
||||
if enqueued {
|
||||
t.Errorf("expected deployment %q not to be queued after pod deletion", foo.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// TestPodDeletionPartialReplicaSetOwnershipEnqueueRecreateDeployment ensures that
|
||||
// the deletion of a pod will requeue a Recreate deployment iff there is no other
|
||||
// pod returned from the client in the case where a deployment has multiple replica
|
||||
// sets, some of which have empty owner references.
|
||||
func TestPodDeletionPartialReplicaSetOwnershipEnqueueRecreateDeployment(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
|
||||
foo := newDeployment("foo", 1, nil, nil, nil, map[string]string{"foo": "bar"})
|
||||
foo.Spec.Strategy.Type = apps.RecreateDeploymentStrategyType
|
||||
rs1 := newReplicaSet(foo, "foo-1", 1)
|
||||
rs2 := newReplicaSet(foo, "foo-2", 2)
|
||||
rs2.OwnerReferences = nil
|
||||
pod := generatePodFromRS(rs1)
|
||||
|
||||
f.dLister = append(f.dLister, foo)
|
||||
f.rsLister = append(f.rsLister, rs1, rs2)
|
||||
f.objects = append(f.objects, foo, rs1, rs2)
|
||||
|
||||
c, _, err := f.newController()
|
||||
if err != nil {
|
||||
t.Fatalf("error creating Deployment controller: %v", err)
|
||||
}
|
||||
enqueued := false
|
||||
c.enqueueDeployment = func(d *apps.Deployment) {
|
||||
if d.Name == "foo" {
|
||||
enqueued = true
|
||||
}
|
||||
}
|
||||
|
||||
c.deletePod(pod)
|
||||
|
||||
if !enqueued {
|
||||
t.Errorf("expected deployment %q to be queued after pod deletion", foo.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// TestPodDeletionPartialReplicaSetOwnershipDoesntEnqueueRecreateDeployment that the
|
||||
// deletion of a pod will not requeue a Recreate deployment iff there are other pods
|
||||
// returned from the client in the case where a deployment has multiple replica sets,
|
||||
// some of which have empty owner references.
|
||||
func TestPodDeletionPartialReplicaSetOwnershipDoesntEnqueueRecreateDeployment(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
|
||||
foo := newDeployment("foo", 1, nil, nil, nil, map[string]string{"foo": "bar"})
|
||||
foo.Spec.Strategy.Type = apps.RecreateDeploymentStrategyType
|
||||
rs1 := newReplicaSet(foo, "foo-1", 1)
|
||||
rs2 := newReplicaSet(foo, "foo-2", 2)
|
||||
rs2.OwnerReferences = nil
|
||||
pod := generatePodFromRS(rs1)
|
||||
|
||||
f.dLister = append(f.dLister, foo)
|
||||
f.rsLister = append(f.rsLister, rs1, rs2)
|
||||
f.objects = append(f.objects, foo, rs1, rs2)
|
||||
// Let's pretend this is a different pod. The gist is that the pod lister needs to
|
||||
// return a non-empty list.
|
||||
f.podLister = append(f.podLister, pod)
|
||||
|
||||
c, _, err := f.newController()
|
||||
if err != nil {
|
||||
t.Fatalf("error creating Deployment controller: %v", err)
|
||||
}
|
||||
enqueued := false
|
||||
c.enqueueDeployment = func(d *apps.Deployment) {
|
||||
if d.Name == "foo" {
|
||||
enqueued = true
|
||||
}
|
||||
}
|
||||
|
||||
c.deletePod(pod)
|
||||
|
||||
if enqueued {
|
||||
t.Errorf("expected deployment %q not to be queued after pod deletion", foo.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetReplicaSetsForDeployment(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
|
||||
// Two Deployments with same labels.
|
||||
d1 := newDeployment("foo", 1, nil, nil, nil, map[string]string{"foo": "bar"})
|
||||
d2 := newDeployment("bar", 1, nil, nil, nil, map[string]string{"foo": "bar"})
|
||||
|
||||
// Two ReplicaSets that match labels for both Deployments,
|
||||
// but have ControllerRefs to make ownership explicit.
|
||||
rs1 := newReplicaSet(d1, "rs1", 1)
|
||||
rs2 := newReplicaSet(d2, "rs2", 1)
|
||||
|
||||
f.dLister = append(f.dLister, d1, d2)
|
||||
f.rsLister = append(f.rsLister, rs1, rs2)
|
||||
f.objects = append(f.objects, d1, d2, rs1, rs2)
|
||||
|
||||
// Start the fixture.
|
||||
c, informers, err := f.newController()
|
||||
if err != nil {
|
||||
t.Fatalf("error creating Deployment controller: %v", err)
|
||||
}
|
||||
stopCh := make(chan struct{})
|
||||
defer close(stopCh)
|
||||
informers.Start(stopCh)
|
||||
|
||||
rsList, err := c.getReplicaSetsForDeployment(d1)
|
||||
if err != nil {
|
||||
t.Fatalf("getReplicaSetsForDeployment() error: %v", err)
|
||||
}
|
||||
rsNames := []string{}
|
||||
for _, rs := range rsList {
|
||||
rsNames = append(rsNames, rs.Name)
|
||||
}
|
||||
if len(rsNames) != 1 || rsNames[0] != rs1.Name {
|
||||
t.Errorf("getReplicaSetsForDeployment() = %v, want [%v]", rsNames, rs1.Name)
|
||||
}
|
||||
|
||||
rsList, err = c.getReplicaSetsForDeployment(d2)
|
||||
if err != nil {
|
||||
t.Fatalf("getReplicaSetsForDeployment() error: %v", err)
|
||||
}
|
||||
rsNames = []string{}
|
||||
for _, rs := range rsList {
|
||||
rsNames = append(rsNames, rs.Name)
|
||||
}
|
||||
if len(rsNames) != 1 || rsNames[0] != rs2.Name {
|
||||
t.Errorf("getReplicaSetsForDeployment() = %v, want [%v]", rsNames, rs2.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetReplicaSetsForDeploymentAdoptRelease(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
|
||||
d := newDeployment("foo", 1, nil, nil, nil, map[string]string{"foo": "bar"})
|
||||
|
||||
// RS with matching labels, but orphaned. Should be adopted and returned.
|
||||
rsAdopt := newReplicaSet(d, "rsAdopt", 1)
|
||||
rsAdopt.OwnerReferences = nil
|
||||
// RS with matching ControllerRef, but wrong labels. Should be released.
|
||||
rsRelease := newReplicaSet(d, "rsRelease", 1)
|
||||
rsRelease.Labels = map[string]string{"foo": "notbar"}
|
||||
|
||||
f.dLister = append(f.dLister, d)
|
||||
f.rsLister = append(f.rsLister, rsAdopt, rsRelease)
|
||||
f.objects = append(f.objects, d, rsAdopt, rsRelease)
|
||||
|
||||
// Start the fixture.
|
||||
c, informers, err := f.newController()
|
||||
if err != nil {
|
||||
t.Fatalf("error creating Deployment controller: %v", err)
|
||||
}
|
||||
stopCh := make(chan struct{})
|
||||
defer close(stopCh)
|
||||
informers.Start(stopCh)
|
||||
|
||||
rsList, err := c.getReplicaSetsForDeployment(d)
|
||||
if err != nil {
|
||||
t.Fatalf("getReplicaSetsForDeployment() error: %v", err)
|
||||
}
|
||||
rsNames := []string{}
|
||||
for _, rs := range rsList {
|
||||
rsNames = append(rsNames, rs.Name)
|
||||
}
|
||||
if len(rsNames) != 1 || rsNames[0] != rsAdopt.Name {
|
||||
t.Errorf("getReplicaSetsForDeployment() = %v, want [%v]", rsNames, rsAdopt.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPodMapForReplicaSets(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
|
||||
d := newDeployment("foo", 1, nil, nil, nil, map[string]string{"foo": "bar"})
|
||||
|
||||
rs1 := newReplicaSet(d, "rs1", 1)
|
||||
rs2 := newReplicaSet(d, "rs2", 1)
|
||||
|
||||
// Add a Pod for each ReplicaSet.
|
||||
pod1 := generatePodFromRS(rs1)
|
||||
pod2 := generatePodFromRS(rs2)
|
||||
// Add a Pod that has matching labels, but no ControllerRef.
|
||||
pod3 := generatePodFromRS(rs1)
|
||||
pod3.Name = "pod3"
|
||||
pod3.OwnerReferences = nil
|
||||
// Add a Pod that has matching labels and ControllerRef, but is inactive.
|
||||
pod4 := generatePodFromRS(rs1)
|
||||
pod4.Name = "pod4"
|
||||
pod4.Status.Phase = v1.PodFailed
|
||||
|
||||
f.dLister = append(f.dLister, d)
|
||||
f.rsLister = append(f.rsLister, rs1, rs2)
|
||||
f.podLister = append(f.podLister, pod1, pod2, pod3, pod4)
|
||||
f.objects = append(f.objects, d, rs1, rs2, pod1, pod2, pod3, pod4)
|
||||
|
||||
// Start the fixture.
|
||||
c, informers, err := f.newController()
|
||||
if err != nil {
|
||||
t.Fatalf("error creating Deployment controller: %v", err)
|
||||
}
|
||||
stopCh := make(chan struct{})
|
||||
defer close(stopCh)
|
||||
informers.Start(stopCh)
|
||||
|
||||
podMap, err := c.getPodMapForDeployment(d, f.rsLister)
|
||||
if err != nil {
|
||||
t.Fatalf("getPodMapForDeployment() error: %v", err)
|
||||
}
|
||||
podCount := 0
|
||||
for _, podList := range podMap {
|
||||
podCount += len(podList.Items)
|
||||
}
|
||||
if got, want := podCount, 3; got != want {
|
||||
t.Errorf("podCount = %v, want %v", got, want)
|
||||
}
|
||||
|
||||
if got, want := len(podMap), 2; got != want {
|
||||
t.Errorf("len(podMap) = %v, want %v", got, want)
|
||||
}
|
||||
if got, want := len(podMap[rs1.UID].Items), 2; got != want {
|
||||
t.Errorf("len(podMap[rs1]) = %v, want %v", got, want)
|
||||
}
|
||||
expect := map[string]struct{}{"rs1-pod": {}, "pod4": {}}
|
||||
for _, pod := range podMap[rs1.UID].Items {
|
||||
if _, ok := expect[pod.Name]; !ok {
|
||||
t.Errorf("unexpected pod name for rs1: %s", pod.Name)
|
||||
}
|
||||
}
|
||||
if got, want := len(podMap[rs2.UID].Items), 1; got != want {
|
||||
t.Errorf("len(podMap[rs2]) = %v, want %v", got, want)
|
||||
}
|
||||
if got, want := podMap[rs2.UID].Items[0].Name, "rs2-pod"; got != want {
|
||||
t.Errorf("podMap[rs2] = [%v], want [%v]", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddReplicaSet(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
|
||||
d1 := newDeployment("d1", 1, nil, nil, nil, map[string]string{"foo": "bar"})
|
||||
d2 := newDeployment("d2", 1, nil, nil, nil, map[string]string{"foo": "bar"})
|
||||
|
||||
// Two ReplicaSets that match labels for both Deployments,
|
||||
// but have ControllerRefs to make ownership explicit.
|
||||
rs1 := newReplicaSet(d1, "rs1", 1)
|
||||
rs2 := newReplicaSet(d2, "rs2", 1)
|
||||
|
||||
f.dLister = append(f.dLister, d1, d2)
|
||||
f.objects = append(f.objects, d1, d2, rs1, rs2)
|
||||
|
||||
// Create the fixture but don't start it,
|
||||
// so nothing happens in the background.
|
||||
dc, _, err := f.newController()
|
||||
if err != nil {
|
||||
t.Fatalf("error creating Deployment controller: %v", err)
|
||||
}
|
||||
|
||||
dc.addReplicaSet(rs1)
|
||||
if got, want := dc.queue.Len(), 1; got != want {
|
||||
t.Fatalf("queue.Len() = %v, want %v", got, want)
|
||||
}
|
||||
key, done := dc.queue.Get()
|
||||
if key == nil || done {
|
||||
t.Fatalf("failed to enqueue controller for rs %v", rs1.Name)
|
||||
}
|
||||
expectedKey, _ := controller.KeyFunc(d1)
|
||||
if got, want := key.(string), expectedKey; got != want {
|
||||
t.Errorf("queue.Get() = %v, want %v", got, want)
|
||||
}
|
||||
|
||||
dc.addReplicaSet(rs2)
|
||||
if got, want := dc.queue.Len(), 1; got != want {
|
||||
t.Fatalf("queue.Len() = %v, want %v", got, want)
|
||||
}
|
||||
key, done = dc.queue.Get()
|
||||
if key == nil || done {
|
||||
t.Fatalf("failed to enqueue controller for rs %v", rs2.Name)
|
||||
}
|
||||
expectedKey, _ = controller.KeyFunc(d2)
|
||||
if got, want := key.(string), expectedKey; got != want {
|
||||
t.Errorf("queue.Get() = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddReplicaSetOrphan(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
|
||||
// 2 will match the RS, 1 won't.
|
||||
d1 := newDeployment("d1", 1, nil, nil, nil, map[string]string{"foo": "bar"})
|
||||
d2 := newDeployment("d2", 1, nil, nil, nil, map[string]string{"foo": "bar"})
|
||||
d3 := newDeployment("d3", 1, nil, nil, nil, map[string]string{"foo": "bar"})
|
||||
d3.Spec.Selector.MatchLabels = map[string]string{"foo": "notbar"}
|
||||
|
||||
// Make the RS an orphan. Expect matching Deployments to be queued.
|
||||
rs := newReplicaSet(d1, "rs1", 1)
|
||||
rs.OwnerReferences = nil
|
||||
|
||||
f.dLister = append(f.dLister, d1, d2, d3)
|
||||
f.objects = append(f.objects, d1, d2, d3)
|
||||
|
||||
// Create the fixture but don't start it,
|
||||
// so nothing happens in the background.
|
||||
dc, _, err := f.newController()
|
||||
if err != nil {
|
||||
t.Fatalf("error creating Deployment controller: %v", err)
|
||||
}
|
||||
|
||||
dc.addReplicaSet(rs)
|
||||
if got, want := dc.queue.Len(), 2; got != want {
|
||||
t.Fatalf("queue.Len() = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateReplicaSet(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
|
||||
d1 := newDeployment("d1", 1, nil, nil, nil, map[string]string{"foo": "bar"})
|
||||
d2 := newDeployment("d2", 1, nil, nil, nil, map[string]string{"foo": "bar"})
|
||||
|
||||
// Two ReplicaSets that match labels for both Deployments,
|
||||
// but have ControllerRefs to make ownership explicit.
|
||||
rs1 := newReplicaSet(d1, "rs1", 1)
|
||||
rs2 := newReplicaSet(d2, "rs2", 1)
|
||||
|
||||
f.dLister = append(f.dLister, d1, d2)
|
||||
f.rsLister = append(f.rsLister, rs1, rs2)
|
||||
f.objects = append(f.objects, d1, d2, rs1, rs2)
|
||||
|
||||
// Create the fixture but don't start it,
|
||||
// so nothing happens in the background.
|
||||
dc, _, err := f.newController()
|
||||
if err != nil {
|
||||
t.Fatalf("error creating Deployment controller: %v", err)
|
||||
}
|
||||
|
||||
prev := *rs1
|
||||
next := *rs1
|
||||
bumpResourceVersion(&next)
|
||||
dc.updateReplicaSet(&prev, &next)
|
||||
if got, want := dc.queue.Len(), 1; got != want {
|
||||
t.Fatalf("queue.Len() = %v, want %v", got, want)
|
||||
}
|
||||
key, done := dc.queue.Get()
|
||||
if key == nil || done {
|
||||
t.Fatalf("failed to enqueue controller for rs %v", rs1.Name)
|
||||
}
|
||||
expectedKey, _ := controller.KeyFunc(d1)
|
||||
if got, want := key.(string), expectedKey; got != want {
|
||||
t.Errorf("queue.Get() = %v, want %v", got, want)
|
||||
}
|
||||
|
||||
prev = *rs2
|
||||
next = *rs2
|
||||
bumpResourceVersion(&next)
|
||||
dc.updateReplicaSet(&prev, &next)
|
||||
if got, want := dc.queue.Len(), 1; got != want {
|
||||
t.Fatalf("queue.Len() = %v, want %v", got, want)
|
||||
}
|
||||
key, done = dc.queue.Get()
|
||||
if key == nil || done {
|
||||
t.Fatalf("failed to enqueue controller for rs %v", rs2.Name)
|
||||
}
|
||||
expectedKey, _ = controller.KeyFunc(d2)
|
||||
if got, want := key.(string), expectedKey; got != want {
|
||||
t.Errorf("queue.Get() = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateReplicaSetOrphanWithNewLabels(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
|
||||
d1 := newDeployment("d1", 1, nil, nil, nil, map[string]string{"foo": "bar"})
|
||||
d2 := newDeployment("d2", 1, nil, nil, nil, map[string]string{"foo": "bar"})
|
||||
|
||||
// RS matches both, but is an orphan.
|
||||
rs := newReplicaSet(d1, "rs1", 1)
|
||||
rs.OwnerReferences = nil
|
||||
|
||||
f.dLister = append(f.dLister, d1, d2)
|
||||
f.rsLister = append(f.rsLister, rs)
|
||||
f.objects = append(f.objects, d1, d2, rs)
|
||||
|
||||
// Create the fixture but don't start it,
|
||||
// so nothing happens in the background.
|
||||
dc, _, err := f.newController()
|
||||
if err != nil {
|
||||
t.Fatalf("error creating Deployment controller: %v", err)
|
||||
}
|
||||
|
||||
// Change labels and expect all matching controllers to queue.
|
||||
prev := *rs
|
||||
prev.Labels = map[string]string{"foo": "notbar"}
|
||||
next := *rs
|
||||
bumpResourceVersion(&next)
|
||||
dc.updateReplicaSet(&prev, &next)
|
||||
if got, want := dc.queue.Len(), 2; got != want {
|
||||
t.Fatalf("queue.Len() = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateReplicaSetChangeControllerRef(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
|
||||
d1 := newDeployment("d1", 1, nil, nil, nil, map[string]string{"foo": "bar"})
|
||||
d2 := newDeployment("d2", 1, nil, nil, nil, map[string]string{"foo": "bar"})
|
||||
|
||||
rs := newReplicaSet(d1, "rs1", 1)
|
||||
|
||||
f.dLister = append(f.dLister, d1, d2)
|
||||
f.rsLister = append(f.rsLister, rs)
|
||||
f.objects = append(f.objects, d1, d2, rs)
|
||||
|
||||
// Create the fixture but don't start it,
|
||||
// so nothing happens in the background.
|
||||
dc, _, err := f.newController()
|
||||
if err != nil {
|
||||
t.Fatalf("error creating Deployment controller: %v", err)
|
||||
}
|
||||
|
||||
// Change ControllerRef and expect both old and new to queue.
|
||||
prev := *rs
|
||||
prev.OwnerReferences = []metav1.OwnerReference{*metav1.NewControllerRef(d2, controllerKind)}
|
||||
next := *rs
|
||||
bumpResourceVersion(&next)
|
||||
dc.updateReplicaSet(&prev, &next)
|
||||
if got, want := dc.queue.Len(), 2; got != want {
|
||||
t.Fatalf("queue.Len() = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateReplicaSetRelease(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
|
||||
d1 := newDeployment("d1", 1, nil, nil, nil, map[string]string{"foo": "bar"})
|
||||
d2 := newDeployment("d2", 1, nil, nil, nil, map[string]string{"foo": "bar"})
|
||||
|
||||
rs := newReplicaSet(d1, "rs1", 1)
|
||||
|
||||
f.dLister = append(f.dLister, d1, d2)
|
||||
f.rsLister = append(f.rsLister, rs)
|
||||
f.objects = append(f.objects, d1, d2, rs)
|
||||
|
||||
// Create the fixture but don't start it,
|
||||
// so nothing happens in the background.
|
||||
dc, _, err := f.newController()
|
||||
if err != nil {
|
||||
t.Fatalf("error creating Deployment controller: %v", err)
|
||||
}
|
||||
|
||||
// Remove ControllerRef and expect all matching controller to sync orphan.
|
||||
prev := *rs
|
||||
next := *rs
|
||||
next.OwnerReferences = nil
|
||||
bumpResourceVersion(&next)
|
||||
dc.updateReplicaSet(&prev, &next)
|
||||
if got, want := dc.queue.Len(), 2; got != want {
|
||||
t.Fatalf("queue.Len() = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteReplicaSet(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
|
||||
d1 := newDeployment("d1", 1, nil, nil, nil, map[string]string{"foo": "bar"})
|
||||
d2 := newDeployment("d2", 1, nil, nil, nil, map[string]string{"foo": "bar"})
|
||||
|
||||
// Two ReplicaSets that match labels for both Deployments,
|
||||
// but have ControllerRefs to make ownership explicit.
|
||||
rs1 := newReplicaSet(d1, "rs1", 1)
|
||||
rs2 := newReplicaSet(d2, "rs2", 1)
|
||||
|
||||
f.dLister = append(f.dLister, d1, d2)
|
||||
f.rsLister = append(f.rsLister, rs1, rs2)
|
||||
f.objects = append(f.objects, d1, d2, rs1, rs2)
|
||||
|
||||
// Create the fixture but don't start it,
|
||||
// so nothing happens in the background.
|
||||
dc, _, err := f.newController()
|
||||
if err != nil {
|
||||
t.Fatalf("error creating Deployment controller: %v", err)
|
||||
}
|
||||
|
||||
dc.deleteReplicaSet(rs1)
|
||||
if got, want := dc.queue.Len(), 1; got != want {
|
||||
t.Fatalf("queue.Len() = %v, want %v", got, want)
|
||||
}
|
||||
key, done := dc.queue.Get()
|
||||
if key == nil || done {
|
||||
t.Fatalf("failed to enqueue controller for rs %v", rs1.Name)
|
||||
}
|
||||
expectedKey, _ := controller.KeyFunc(d1)
|
||||
if got, want := key.(string), expectedKey; got != want {
|
||||
t.Errorf("queue.Get() = %v, want %v", got, want)
|
||||
}
|
||||
|
||||
dc.deleteReplicaSet(rs2)
|
||||
if got, want := dc.queue.Len(), 1; got != want {
|
||||
t.Fatalf("queue.Len() = %v, want %v", got, want)
|
||||
}
|
||||
key, done = dc.queue.Get()
|
||||
if key == nil || done {
|
||||
t.Fatalf("failed to enqueue controller for rs %v", rs2.Name)
|
||||
}
|
||||
expectedKey, _ = controller.KeyFunc(d2)
|
||||
if got, want := key.(string), expectedKey; got != want {
|
||||
t.Errorf("queue.Get() = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteReplicaSetOrphan(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
|
||||
d1 := newDeployment("d1", 1, nil, nil, nil, map[string]string{"foo": "bar"})
|
||||
d2 := newDeployment("d2", 1, nil, nil, nil, map[string]string{"foo": "bar"})
|
||||
|
||||
// Make the RS an orphan. Expect matching Deployments to be queued.
|
||||
rs := newReplicaSet(d1, "rs1", 1)
|
||||
rs.OwnerReferences = nil
|
||||
|
||||
f.dLister = append(f.dLister, d1, d2)
|
||||
f.rsLister = append(f.rsLister, rs)
|
||||
f.objects = append(f.objects, d1, d2, rs)
|
||||
|
||||
// Create the fixture but don't start it,
|
||||
// so nothing happens in the background.
|
||||
dc, _, err := f.newController()
|
||||
if err != nil {
|
||||
t.Fatalf("error creating Deployment controller: %v", err)
|
||||
}
|
||||
|
||||
dc.deleteReplicaSet(rs)
|
||||
if got, want := dc.queue.Len(), 0; got != want {
|
||||
t.Fatalf("queue.Len() = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func bumpResourceVersion(obj metav1.Object) {
|
||||
ver, _ := strconv.ParseInt(obj.GetResourceVersion(), 10, 32)
|
||||
obj.SetResourceVersion(strconv.FormatInt(ver+1, 10))
|
||||
}
|
||||
|
||||
// generatePodFromRS creates a pod, with the input ReplicaSet's selector and its template
|
||||
func generatePodFromRS(rs *apps.ReplicaSet) *v1.Pod {
|
||||
trueVar := true
|
||||
return &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: rs.Name + "-pod",
|
||||
Namespace: rs.Namespace,
|
||||
Labels: rs.Spec.Selector.MatchLabels,
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{UID: rs.UID, APIVersion: "v1beta1", Kind: "ReplicaSet", Name: rs.Name, Controller: &trueVar},
|
||||
},
|
||||
},
|
||||
Spec: rs.Spec.Template.Spec,
|
||||
}
|
||||
}
|
198
vendor/k8s.io/kubernetes/pkg/controller/deployment/progress.go
generated
vendored
198
vendor/k8s.io/kubernetes/pkg/controller/deployment/progress.go
generated
vendored
@ -1,198 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package deployment
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
apps "k8s.io/api/apps/v1"
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/kubernetes/pkg/controller/deployment/util"
|
||||
)
|
||||
|
||||
// syncRolloutStatus updates the status of a deployment during a rollout. There are
|
||||
// cases this helper will run that cannot be prevented from the scaling detection,
|
||||
// for example a resync of the deployment after it was scaled up. In those cases,
|
||||
// we shouldn't try to estimate any progress.
|
||||
func (dc *DeploymentController) syncRolloutStatus(allRSs []*apps.ReplicaSet, newRS *apps.ReplicaSet, d *apps.Deployment) error {
|
||||
newStatus := calculateStatus(allRSs, newRS, d)
|
||||
|
||||
// If there is no progressDeadlineSeconds set, remove any Progressing condition.
|
||||
if d.Spec.ProgressDeadlineSeconds == nil {
|
||||
util.RemoveDeploymentCondition(&newStatus, apps.DeploymentProgressing)
|
||||
}
|
||||
|
||||
// If there is only one replica set that is active then that means we are not running
|
||||
// a new rollout and this is a resync where we don't need to estimate any progress.
|
||||
// In such a case, we should simply not estimate any progress for this deployment.
|
||||
currentCond := util.GetDeploymentCondition(d.Status, apps.DeploymentProgressing)
|
||||
isCompleteDeployment := newStatus.Replicas == newStatus.UpdatedReplicas && currentCond != nil && currentCond.Reason == util.NewRSAvailableReason
|
||||
// Check for progress only if there is a progress deadline set and the latest rollout
|
||||
// hasn't completed yet.
|
||||
if d.Spec.ProgressDeadlineSeconds != nil && !isCompleteDeployment {
|
||||
switch {
|
||||
case util.DeploymentComplete(d, &newStatus):
|
||||
// Update the deployment conditions with a message for the new replica set that
|
||||
// was successfully deployed. If the condition already exists, we ignore this update.
|
||||
msg := fmt.Sprintf("Deployment %q has successfully progressed.", d.Name)
|
||||
if newRS != nil {
|
||||
msg = fmt.Sprintf("ReplicaSet %q has successfully progressed.", newRS.Name)
|
||||
}
|
||||
condition := util.NewDeploymentCondition(apps.DeploymentProgressing, v1.ConditionTrue, util.NewRSAvailableReason, msg)
|
||||
util.SetDeploymentCondition(&newStatus, *condition)
|
||||
|
||||
case util.DeploymentProgressing(d, &newStatus):
|
||||
// If there is any progress made, continue by not checking if the deployment failed. This
|
||||
// behavior emulates the rolling updater progressDeadline check.
|
||||
msg := fmt.Sprintf("Deployment %q is progressing.", d.Name)
|
||||
if newRS != nil {
|
||||
msg = fmt.Sprintf("ReplicaSet %q is progressing.", newRS.Name)
|
||||
}
|
||||
condition := util.NewDeploymentCondition(apps.DeploymentProgressing, v1.ConditionTrue, util.ReplicaSetUpdatedReason, msg)
|
||||
// Update the current Progressing condition or add a new one if it doesn't exist.
|
||||
// If a Progressing condition with status=true already exists, we should update
|
||||
// everything but lastTransitionTime. SetDeploymentCondition already does that but
|
||||
// it also is not updating conditions when the reason of the new condition is the
|
||||
// same as the old. The Progressing condition is a special case because we want to
|
||||
// update with the same reason and change just lastUpdateTime iff we notice any
|
||||
// progress. That's why we handle it here.
|
||||
if currentCond != nil {
|
||||
if currentCond.Status == v1.ConditionTrue {
|
||||
condition.LastTransitionTime = currentCond.LastTransitionTime
|
||||
}
|
||||
util.RemoveDeploymentCondition(&newStatus, apps.DeploymentProgressing)
|
||||
}
|
||||
util.SetDeploymentCondition(&newStatus, *condition)
|
||||
|
||||
case util.DeploymentTimedOut(d, &newStatus):
|
||||
// Update the deployment with a timeout condition. If the condition already exists,
|
||||
// we ignore this update.
|
||||
msg := fmt.Sprintf("Deployment %q has timed out progressing.", d.Name)
|
||||
if newRS != nil {
|
||||
msg = fmt.Sprintf("ReplicaSet %q has timed out progressing.", newRS.Name)
|
||||
}
|
||||
condition := util.NewDeploymentCondition(apps.DeploymentProgressing, v1.ConditionFalse, util.TimedOutReason, msg)
|
||||
util.SetDeploymentCondition(&newStatus, *condition)
|
||||
}
|
||||
}
|
||||
|
||||
// Move failure conditions of all replica sets in deployment conditions. For now,
|
||||
// only one failure condition is returned from getReplicaFailures.
|
||||
if replicaFailureCond := dc.getReplicaFailures(allRSs, newRS); len(replicaFailureCond) > 0 {
|
||||
// There will be only one ReplicaFailure condition on the replica set.
|
||||
util.SetDeploymentCondition(&newStatus, replicaFailureCond[0])
|
||||
} else {
|
||||
util.RemoveDeploymentCondition(&newStatus, apps.DeploymentReplicaFailure)
|
||||
}
|
||||
|
||||
// Do not update if there is nothing new to add.
|
||||
if reflect.DeepEqual(d.Status, newStatus) {
|
||||
// Requeue the deployment if required.
|
||||
dc.requeueStuckDeployment(d, newStatus)
|
||||
return nil
|
||||
}
|
||||
|
||||
newDeployment := d
|
||||
newDeployment.Status = newStatus
|
||||
_, err := dc.client.AppsV1().Deployments(newDeployment.Namespace).UpdateStatus(newDeployment)
|
||||
return err
|
||||
}
|
||||
|
||||
// getReplicaFailures will convert replica failure conditions from replica sets
|
||||
// to deployment conditions.
|
||||
func (dc *DeploymentController) getReplicaFailures(allRSs []*apps.ReplicaSet, newRS *apps.ReplicaSet) []apps.DeploymentCondition {
|
||||
var conditions []apps.DeploymentCondition
|
||||
if newRS != nil {
|
||||
for _, c := range newRS.Status.Conditions {
|
||||
if c.Type != apps.ReplicaSetReplicaFailure {
|
||||
continue
|
||||
}
|
||||
conditions = append(conditions, util.ReplicaSetToDeploymentCondition(c))
|
||||
}
|
||||
}
|
||||
|
||||
// Return failures for the new replica set over failures from old replica sets.
|
||||
if len(conditions) > 0 {
|
||||
return conditions
|
||||
}
|
||||
|
||||
for i := range allRSs {
|
||||
rs := allRSs[i]
|
||||
if rs == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, c := range rs.Status.Conditions {
|
||||
if c.Type != apps.ReplicaSetReplicaFailure {
|
||||
continue
|
||||
}
|
||||
conditions = append(conditions, util.ReplicaSetToDeploymentCondition(c))
|
||||
}
|
||||
}
|
||||
return conditions
|
||||
}
|
||||
|
||||
// used for unit testing
|
||||
var nowFn = func() time.Time { return time.Now() }
|
||||
|
||||
// requeueStuckDeployment checks whether the provided deployment needs to be synced for a progress
|
||||
// check. It returns the time after the deployment will be requeued for the progress check, 0 if it
|
||||
// will be requeued now, or -1 if it does not need to be requeued.
|
||||
func (dc *DeploymentController) requeueStuckDeployment(d *apps.Deployment, newStatus apps.DeploymentStatus) time.Duration {
|
||||
currentCond := util.GetDeploymentCondition(d.Status, apps.DeploymentProgressing)
|
||||
// Can't estimate progress if there is no deadline in the spec or progressing condition in the current status.
|
||||
if d.Spec.ProgressDeadlineSeconds == nil || currentCond == nil {
|
||||
return time.Duration(-1)
|
||||
}
|
||||
// No need to estimate progress if the rollout is complete or already timed out.
|
||||
if util.DeploymentComplete(d, &newStatus) || currentCond.Reason == util.TimedOutReason {
|
||||
return time.Duration(-1)
|
||||
}
|
||||
// If there is no sign of progress at this point then there is a high chance that the
|
||||
// deployment is stuck. We should resync this deployment at some point in the future[1]
|
||||
// and check whether it has timed out. We definitely need this, otherwise we depend on the
|
||||
// controller resync interval. See https://github.com/kubernetes/kubernetes/issues/34458.
|
||||
//
|
||||
// [1] ProgressingCondition.LastUpdatedTime + progressDeadlineSeconds - time.Now()
|
||||
//
|
||||
// For example, if a Deployment updated its Progressing condition 3 minutes ago and has a
|
||||
// deadline of 10 minutes, it would need to be resynced for a progress check after 7 minutes.
|
||||
//
|
||||
// lastUpdated: 00:00:00
|
||||
// now: 00:03:00
|
||||
// progressDeadlineSeconds: 600 (10 minutes)
|
||||
//
|
||||
// lastUpdated + progressDeadlineSeconds - now => 00:00:00 + 00:10:00 - 00:03:00 => 07:00
|
||||
after := currentCond.LastUpdateTime.Time.Add(time.Duration(*d.Spec.ProgressDeadlineSeconds) * time.Second).Sub(nowFn())
|
||||
// If the remaining time is less than a second, then requeue the deployment immediately.
|
||||
// Make it ratelimited so we stay on the safe side, eventually the Deployment should
|
||||
// transition either to a Complete or to a TimedOut condition.
|
||||
if after < time.Second {
|
||||
glog.V(4).Infof("Queueing up deployment %q for a progress check now", d.Name)
|
||||
dc.enqueueRateLimited(d)
|
||||
return time.Duration(0)
|
||||
}
|
||||
glog.V(4).Infof("Queueing up deployment %q for a progress check after %ds", d.Name, int(after.Seconds()))
|
||||
// Add a second to avoid milliseconds skew in AddAfter.
|
||||
// See https://github.com/kubernetes/kubernetes/issues/39785#issuecomment-279959133 for more info.
|
||||
dc.enqueueAfter(d, after+time.Second)
|
||||
return after
|
||||
}
|
345
vendor/k8s.io/kubernetes/pkg/controller/deployment/progress_test.go
generated
vendored
345
vendor/k8s.io/kubernetes/pkg/controller/deployment/progress_test.go
generated
vendored
@ -1,345 +0,0 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package deployment
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
apps "k8s.io/api/apps/v1"
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
"k8s.io/kubernetes/pkg/controller/deployment/util"
|
||||
)
|
||||
|
||||
func newDeploymentStatus(replicas, updatedReplicas, availableReplicas int32) apps.DeploymentStatus {
|
||||
return apps.DeploymentStatus{
|
||||
Replicas: replicas,
|
||||
UpdatedReplicas: updatedReplicas,
|
||||
AvailableReplicas: availableReplicas,
|
||||
}
|
||||
}
|
||||
|
||||
// assumes the retuned deployment is always observed - not needed to be tested here.
|
||||
func currentDeployment(pds *int32, replicas, statusReplicas, updatedReplicas, availableReplicas int32, conditions []apps.DeploymentCondition) *apps.Deployment {
|
||||
d := &apps.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "progress-test",
|
||||
},
|
||||
Spec: apps.DeploymentSpec{
|
||||
ProgressDeadlineSeconds: pds,
|
||||
Replicas: &replicas,
|
||||
Strategy: apps.DeploymentStrategy{
|
||||
Type: apps.RecreateDeploymentStrategyType,
|
||||
},
|
||||
},
|
||||
Status: newDeploymentStatus(statusReplicas, updatedReplicas, availableReplicas),
|
||||
}
|
||||
d.Status.Conditions = conditions
|
||||
return d
|
||||
}
|
||||
|
||||
// helper to create RS with given availableReplicas
|
||||
func newRSWithAvailable(name string, specReplicas, statusReplicas, availableReplicas int) *apps.ReplicaSet {
|
||||
rs := rs(name, specReplicas, nil, metav1.Time{})
|
||||
rs.Status = apps.ReplicaSetStatus{
|
||||
Replicas: int32(statusReplicas),
|
||||
AvailableReplicas: int32(availableReplicas),
|
||||
}
|
||||
return rs
|
||||
}
|
||||
|
||||
func TestRequeueStuckDeployment(t *testing.T) {
|
||||
pds := int32(60)
|
||||
failed := []apps.DeploymentCondition{
|
||||
{
|
||||
Type: apps.DeploymentProgressing,
|
||||
Status: v1.ConditionFalse,
|
||||
Reason: util.TimedOutReason,
|
||||
},
|
||||
}
|
||||
stuck := []apps.DeploymentCondition{
|
||||
{
|
||||
Type: apps.DeploymentProgressing,
|
||||
Status: v1.ConditionTrue,
|
||||
LastUpdateTime: metav1.Date(2017, 2, 15, 18, 49, 00, 00, time.UTC),
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
d *apps.Deployment
|
||||
status apps.DeploymentStatus
|
||||
nowFn func() time.Time
|
||||
expected time.Duration
|
||||
}{
|
||||
{
|
||||
name: "no progressDeadlineSeconds specified",
|
||||
d: currentDeployment(nil, 4, 3, 3, 2, nil),
|
||||
status: newDeploymentStatus(3, 3, 2),
|
||||
expected: time.Duration(-1),
|
||||
},
|
||||
{
|
||||
name: "no progressing condition found",
|
||||
d: currentDeployment(&pds, 4, 3, 3, 2, nil),
|
||||
status: newDeploymentStatus(3, 3, 2),
|
||||
expected: time.Duration(-1),
|
||||
},
|
||||
{
|
||||
name: "complete deployment does not need to be requeued",
|
||||
d: currentDeployment(&pds, 3, 3, 3, 3, nil),
|
||||
status: newDeploymentStatus(3, 3, 3),
|
||||
expected: time.Duration(-1),
|
||||
},
|
||||
{
|
||||
name: "already failed deployment does not need to be requeued",
|
||||
d: currentDeployment(&pds, 3, 3, 3, 0, failed),
|
||||
status: newDeploymentStatus(3, 3, 0),
|
||||
expected: time.Duration(-1),
|
||||
},
|
||||
{
|
||||
name: "stuck deployment - 30s",
|
||||
d: currentDeployment(&pds, 3, 3, 3, 1, stuck),
|
||||
status: newDeploymentStatus(3, 3, 1),
|
||||
nowFn: func() time.Time { return metav1.Date(2017, 2, 15, 18, 49, 30, 00, time.UTC).Time },
|
||||
expected: 30 * time.Second,
|
||||
},
|
||||
{
|
||||
name: "stuck deployment - 1s",
|
||||
d: currentDeployment(&pds, 3, 3, 3, 1, stuck),
|
||||
status: newDeploymentStatus(3, 3, 1),
|
||||
nowFn: func() time.Time { return metav1.Date(2017, 2, 15, 18, 49, 59, 00, time.UTC).Time },
|
||||
expected: 1 * time.Second,
|
||||
},
|
||||
{
|
||||
name: "failed deployment - less than a second => now",
|
||||
d: currentDeployment(&pds, 3, 3, 3, 1, stuck),
|
||||
status: newDeploymentStatus(3, 3, 1),
|
||||
nowFn: func() time.Time { return metav1.Date(2017, 2, 15, 18, 49, 59, 1, time.UTC).Time },
|
||||
expected: time.Duration(0),
|
||||
},
|
||||
{
|
||||
name: "failed deployment - now",
|
||||
d: currentDeployment(&pds, 3, 3, 3, 1, stuck),
|
||||
status: newDeploymentStatus(3, 3, 1),
|
||||
nowFn: func() time.Time { return metav1.Date(2017, 2, 15, 18, 50, 00, 00, time.UTC).Time },
|
||||
expected: time.Duration(0),
|
||||
},
|
||||
{
|
||||
name: "failed deployment - 1s after deadline",
|
||||
d: currentDeployment(&pds, 3, 3, 3, 1, stuck),
|
||||
status: newDeploymentStatus(3, 3, 1),
|
||||
nowFn: func() time.Time { return metav1.Date(2017, 2, 15, 18, 50, 01, 00, time.UTC).Time },
|
||||
expected: time.Duration(0),
|
||||
},
|
||||
{
|
||||
name: "failed deployment - 60s after deadline",
|
||||
d: currentDeployment(&pds, 3, 3, 3, 1, stuck),
|
||||
status: newDeploymentStatus(3, 3, 1),
|
||||
nowFn: func() time.Time { return metav1.Date(2017, 2, 15, 18, 51, 00, 00, time.UTC).Time },
|
||||
expected: time.Duration(0),
|
||||
},
|
||||
}
|
||||
|
||||
dc := &DeploymentController{
|
||||
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "doesnt-matter"),
|
||||
}
|
||||
dc.enqueueDeployment = dc.enqueue
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if test.nowFn != nil {
|
||||
nowFn = test.nowFn
|
||||
}
|
||||
got := dc.requeueStuckDeployment(test.d, test.status)
|
||||
if got != test.expected {
|
||||
t.Errorf("%s: got duration: %v, expected duration: %v", test.name, got, test.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncRolloutStatus(t *testing.T) {
|
||||
pds := int32(60)
|
||||
testTime := metav1.Date(2017, 2, 15, 18, 49, 00, 00, time.UTC)
|
||||
failedTimedOut := apps.DeploymentCondition{
|
||||
Type: apps.DeploymentProgressing,
|
||||
Status: v1.ConditionFalse,
|
||||
Reason: util.TimedOutReason,
|
||||
}
|
||||
newRSAvailable := apps.DeploymentCondition{
|
||||
Type: apps.DeploymentProgressing,
|
||||
Status: v1.ConditionTrue,
|
||||
Reason: util.NewRSAvailableReason,
|
||||
LastUpdateTime: testTime,
|
||||
LastTransitionTime: testTime,
|
||||
}
|
||||
replicaSetUpdated := apps.DeploymentCondition{
|
||||
Type: apps.DeploymentProgressing,
|
||||
Status: v1.ConditionTrue,
|
||||
Reason: util.ReplicaSetUpdatedReason,
|
||||
LastUpdateTime: testTime,
|
||||
LastTransitionTime: testTime,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
d *apps.Deployment
|
||||
allRSs []*apps.ReplicaSet
|
||||
newRS *apps.ReplicaSet
|
||||
conditionType apps.DeploymentConditionType
|
||||
conditionStatus v1.ConditionStatus
|
||||
conditionReason string
|
||||
lastUpdate metav1.Time
|
||||
lastTransition metav1.Time
|
||||
}{
|
||||
{
|
||||
name: "General: remove Progressing condition and do not estimate progress if deployment has no Progress Deadline",
|
||||
d: currentDeployment(nil, 3, 2, 2, 2, []apps.DeploymentCondition{replicaSetUpdated}),
|
||||
allRSs: []*apps.ReplicaSet{newRSWithAvailable("bar", 0, 1, 1)},
|
||||
newRS: newRSWithAvailable("foo", 3, 2, 2),
|
||||
},
|
||||
{
|
||||
name: "General: do not estimate progress of deployment with only one active ReplicaSet",
|
||||
d: currentDeployment(&pds, 3, 3, 3, 3, []apps.DeploymentCondition{newRSAvailable}),
|
||||
allRSs: []*apps.ReplicaSet{newRSWithAvailable("bar", 3, 3, 3)},
|
||||
conditionType: apps.DeploymentProgressing,
|
||||
conditionStatus: v1.ConditionTrue,
|
||||
conditionReason: util.NewRSAvailableReason,
|
||||
lastUpdate: testTime,
|
||||
lastTransition: testTime,
|
||||
},
|
||||
{
|
||||
name: "DeploymentProgressing: dont update lastTransitionTime if deployment already has Progressing=True",
|
||||
d: currentDeployment(&pds, 3, 2, 2, 2, []apps.DeploymentCondition{replicaSetUpdated}),
|
||||
allRSs: []*apps.ReplicaSet{newRSWithAvailable("bar", 0, 1, 1)},
|
||||
newRS: newRSWithAvailable("foo", 3, 2, 2),
|
||||
conditionType: apps.DeploymentProgressing,
|
||||
conditionStatus: v1.ConditionTrue,
|
||||
conditionReason: util.ReplicaSetUpdatedReason,
|
||||
lastTransition: testTime,
|
||||
},
|
||||
{
|
||||
name: "DeploymentProgressing: update everything if deployment has Progressing=False",
|
||||
d: currentDeployment(&pds, 3, 2, 2, 2, []apps.DeploymentCondition{failedTimedOut}),
|
||||
allRSs: []*apps.ReplicaSet{newRSWithAvailable("bar", 0, 1, 1)},
|
||||
newRS: newRSWithAvailable("foo", 3, 2, 2),
|
||||
conditionType: apps.DeploymentProgressing,
|
||||
conditionStatus: v1.ConditionTrue,
|
||||
conditionReason: util.ReplicaSetUpdatedReason,
|
||||
},
|
||||
{
|
||||
name: "DeploymentProgressing: create Progressing condition if it does not exist",
|
||||
d: currentDeployment(&pds, 3, 2, 2, 2, []apps.DeploymentCondition{}),
|
||||
allRSs: []*apps.ReplicaSet{newRSWithAvailable("bar", 0, 1, 1)},
|
||||
newRS: newRSWithAvailable("foo", 3, 2, 2),
|
||||
conditionType: apps.DeploymentProgressing,
|
||||
conditionStatus: v1.ConditionTrue,
|
||||
conditionReason: util.ReplicaSetUpdatedReason,
|
||||
},
|
||||
{
|
||||
name: "DeploymentComplete: dont update lastTransitionTime if deployment already has Progressing=True",
|
||||
d: currentDeployment(&pds, 3, 3, 3, 3, []apps.DeploymentCondition{replicaSetUpdated}),
|
||||
allRSs: []*apps.ReplicaSet{},
|
||||
newRS: newRSWithAvailable("foo", 3, 3, 3),
|
||||
conditionType: apps.DeploymentProgressing,
|
||||
conditionStatus: v1.ConditionTrue,
|
||||
conditionReason: util.NewRSAvailableReason,
|
||||
lastTransition: testTime,
|
||||
},
|
||||
{
|
||||
name: "DeploymentComplete: update everything if deployment has Progressing=False",
|
||||
d: currentDeployment(&pds, 3, 3, 3, 3, []apps.DeploymentCondition{failedTimedOut}),
|
||||
allRSs: []*apps.ReplicaSet{},
|
||||
newRS: newRSWithAvailable("foo", 3, 3, 3),
|
||||
conditionType: apps.DeploymentProgressing,
|
||||
conditionStatus: v1.ConditionTrue,
|
||||
conditionReason: util.NewRSAvailableReason,
|
||||
},
|
||||
{
|
||||
name: "DeploymentComplete: create Progressing condition if it does not exist",
|
||||
d: currentDeployment(&pds, 3, 3, 3, 3, []apps.DeploymentCondition{}),
|
||||
allRSs: []*apps.ReplicaSet{},
|
||||
newRS: newRSWithAvailable("foo", 3, 3, 3),
|
||||
conditionType: apps.DeploymentProgressing,
|
||||
conditionStatus: v1.ConditionTrue,
|
||||
conditionReason: util.NewRSAvailableReason,
|
||||
},
|
||||
{
|
||||
name: "DeploymentComplete: defend against NPE when newRS=nil",
|
||||
d: currentDeployment(&pds, 0, 3, 3, 3, []apps.DeploymentCondition{replicaSetUpdated}),
|
||||
allRSs: []*apps.ReplicaSet{newRSWithAvailable("foo", 0, 0, 0)},
|
||||
conditionType: apps.DeploymentProgressing,
|
||||
conditionStatus: v1.ConditionTrue,
|
||||
conditionReason: util.NewRSAvailableReason,
|
||||
},
|
||||
{
|
||||
name: "DeploymentTimedOut: update status if rollout exceeds Progress Deadline",
|
||||
d: currentDeployment(&pds, 3, 2, 2, 2, []apps.DeploymentCondition{replicaSetUpdated}),
|
||||
allRSs: []*apps.ReplicaSet{},
|
||||
newRS: newRSWithAvailable("foo", 3, 2, 2),
|
||||
conditionType: apps.DeploymentProgressing,
|
||||
conditionStatus: v1.ConditionFalse,
|
||||
conditionReason: util.TimedOutReason,
|
||||
},
|
||||
{
|
||||
name: "DeploymentTimedOut: do not update status if deployment has existing timedOut condition",
|
||||
d: currentDeployment(&pds, 3, 2, 2, 2, []apps.DeploymentCondition{failedTimedOut}),
|
||||
allRSs: []*apps.ReplicaSet{},
|
||||
newRS: newRSWithAvailable("foo", 3, 2, 2),
|
||||
conditionType: apps.DeploymentProgressing,
|
||||
conditionStatus: v1.ConditionFalse,
|
||||
conditionReason: util.TimedOutReason,
|
||||
lastUpdate: testTime,
|
||||
lastTransition: testTime,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
fake := fake.Clientset{}
|
||||
dc := &DeploymentController{
|
||||
client: &fake,
|
||||
}
|
||||
|
||||
if test.newRS != nil {
|
||||
test.allRSs = append(test.allRSs, test.newRS)
|
||||
}
|
||||
|
||||
err := dc.syncRolloutStatus(test.allRSs, test.newRS, test.d)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
newCond := util.GetDeploymentCondition(test.d.Status, test.conditionType)
|
||||
switch {
|
||||
case newCond == nil:
|
||||
if test.d.Spec.ProgressDeadlineSeconds != nil {
|
||||
t.Errorf("%s: expected deployment condition: %s", test.name, test.conditionType)
|
||||
}
|
||||
case newCond.Status != test.conditionStatus || newCond.Reason != test.conditionReason:
|
||||
t.Errorf("%s: DeploymentProgressing has status %s with reason %s. Expected %s with %s.", test.name, newCond.Status, newCond.Reason, test.conditionStatus, test.conditionReason)
|
||||
case !test.lastUpdate.IsZero() && test.lastUpdate != testTime:
|
||||
t.Errorf("%s: LastUpdateTime was changed to %s but expected %s;", test.name, test.lastUpdate, testTime)
|
||||
case !test.lastTransition.IsZero() && test.lastTransition != testTime:
|
||||
t.Errorf("%s: LastTransitionTime was changed to %s but expected %s;", test.name, test.lastTransition, testTime)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
129
vendor/k8s.io/kubernetes/pkg/controller/deployment/recreate.go
generated
vendored
129
vendor/k8s.io/kubernetes/pkg/controller/deployment/recreate.go
generated
vendored
@ -1,129 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package deployment
|
||||
|
||||
import (
|
||||
apps "k8s.io/api/apps/v1"
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/kubernetes/pkg/controller"
|
||||
"k8s.io/kubernetes/pkg/controller/deployment/util"
|
||||
)
|
||||
|
||||
// rolloutRecreate implements the logic for recreating a replica set.
|
||||
func (dc *DeploymentController) rolloutRecreate(d *apps.Deployment, rsList []*apps.ReplicaSet, podMap map[types.UID]*v1.PodList) error {
|
||||
// Don't create a new RS if not already existed, so that we avoid scaling up before scaling down.
|
||||
newRS, oldRSs, err := dc.getAllReplicaSetsAndSyncRevision(d, rsList, podMap, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
allRSs := append(oldRSs, newRS)
|
||||
activeOldRSs := controller.FilterActiveReplicaSets(oldRSs)
|
||||
|
||||
// scale down old replica sets.
|
||||
scaledDown, err := dc.scaleDownOldReplicaSetsForRecreate(activeOldRSs, d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if scaledDown {
|
||||
// Update DeploymentStatus.
|
||||
return dc.syncRolloutStatus(allRSs, newRS, d)
|
||||
}
|
||||
|
||||
// Do not process a deployment when it has old pods running.
|
||||
if oldPodsRunning(newRS, oldRSs, podMap) {
|
||||
return dc.syncRolloutStatus(allRSs, newRS, d)
|
||||
}
|
||||
|
||||
// If we need to create a new RS, create it now.
|
||||
if newRS == nil {
|
||||
newRS, oldRSs, err = dc.getAllReplicaSetsAndSyncRevision(d, rsList, podMap, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
allRSs = append(oldRSs, newRS)
|
||||
}
|
||||
|
||||
// scale up new replica set.
|
||||
if _, err := dc.scaleUpNewReplicaSetForRecreate(newRS, d); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if util.DeploymentComplete(d, &d.Status) {
|
||||
if err := dc.cleanupDeployment(oldRSs, d); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Sync deployment status.
|
||||
return dc.syncRolloutStatus(allRSs, newRS, d)
|
||||
}
|
||||
|
||||
// scaleDownOldReplicaSetsForRecreate scales down old replica sets when deployment strategy is "Recreate".
|
||||
func (dc *DeploymentController) scaleDownOldReplicaSetsForRecreate(oldRSs []*apps.ReplicaSet, deployment *apps.Deployment) (bool, error) {
|
||||
scaled := false
|
||||
for i := range oldRSs {
|
||||
rs := oldRSs[i]
|
||||
// Scaling not required.
|
||||
if *(rs.Spec.Replicas) == 0 {
|
||||
continue
|
||||
}
|
||||
scaledRS, updatedRS, err := dc.scaleReplicaSetAndRecordEvent(rs, 0, deployment)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if scaledRS {
|
||||
oldRSs[i] = updatedRS
|
||||
scaled = true
|
||||
}
|
||||
}
|
||||
return scaled, nil
|
||||
}
|
||||
|
||||
// oldPodsRunning returns whether there are old pods running or any of the old ReplicaSets thinks that it runs pods.
|
||||
func oldPodsRunning(newRS *apps.ReplicaSet, oldRSs []*apps.ReplicaSet, podMap map[types.UID]*v1.PodList) bool {
|
||||
if oldPods := util.GetActualReplicaCountForReplicaSets(oldRSs); oldPods > 0 {
|
||||
return true
|
||||
}
|
||||
for rsUID, podList := range podMap {
|
||||
// If the pods belong to the new ReplicaSet, ignore.
|
||||
if newRS != nil && newRS.UID == rsUID {
|
||||
continue
|
||||
}
|
||||
for _, pod := range podList.Items {
|
||||
switch pod.Status.Phase {
|
||||
case v1.PodFailed, v1.PodSucceeded:
|
||||
// Don't count pods in terminal state.
|
||||
continue
|
||||
case v1.PodUnknown:
|
||||
// This happens in situation like when the node is temporarily disconnected from the cluster.
|
||||
// If we can't be sure that the pod is not running, we have to count it.
|
||||
return true
|
||||
default:
|
||||
// Pod is not in terminal phase.
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// scaleUpNewReplicaSetForRecreate scales up new replica set when deployment strategy is "Recreate".
|
||||
func (dc *DeploymentController) scaleUpNewReplicaSetForRecreate(newRS *apps.ReplicaSet, deployment *apps.Deployment) (bool, error) {
|
||||
scaled, _, err := dc.scaleReplicaSetAndRecordEvent(newRS, *(deployment.Spec.Replicas), deployment)
|
||||
return scaled, err
|
||||
}
|
246
vendor/k8s.io/kubernetes/pkg/controller/deployment/recreate_test.go
generated
vendored
246
vendor/k8s.io/kubernetes/pkg/controller/deployment/recreate_test.go
generated
vendored
@ -1,246 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package deployment
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
apps "k8s.io/api/apps/v1"
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"k8s.io/kubernetes/pkg/controller"
|
||||
)
|
||||
|
||||
func TestScaleDownOldReplicaSets(t *testing.T) {
|
||||
tests := []struct {
|
||||
oldRSSizes []int
|
||||
d *apps.Deployment
|
||||
}{
|
||||
{
|
||||
oldRSSizes: []int{3},
|
||||
d: newDeployment("foo", 3, nil, nil, nil, map[string]string{"foo": "bar"}),
|
||||
},
|
||||
}
|
||||
|
||||
for i := range tests {
|
||||
t.Logf("running scenario %d", i)
|
||||
test := tests[i]
|
||||
|
||||
var oldRSs []*apps.ReplicaSet
|
||||
var expected []runtime.Object
|
||||
|
||||
for n, size := range test.oldRSSizes {
|
||||
rs := newReplicaSet(test.d, fmt.Sprintf("%s-%d", test.d.Name, n), size)
|
||||
oldRSs = append(oldRSs, rs)
|
||||
|
||||
rsCopy := rs.DeepCopy()
|
||||
|
||||
zero := int32(0)
|
||||
rsCopy.Spec.Replicas = &zero
|
||||
expected = append(expected, rsCopy)
|
||||
|
||||
if *(oldRSs[n].Spec.Replicas) == *(expected[n].(*apps.ReplicaSet).Spec.Replicas) {
|
||||
t.Errorf("broken test - original and expected RS have the same size")
|
||||
}
|
||||
}
|
||||
|
||||
kc := fake.NewSimpleClientset(expected...)
|
||||
informers := informers.NewSharedInformerFactory(kc, controller.NoResyncPeriodFunc())
|
||||
c, err := NewDeploymentController(informers.Apps().V1().Deployments(), informers.Apps().V1().ReplicaSets(), informers.Core().V1().Pods(), kc)
|
||||
if err != nil {
|
||||
t.Fatalf("error creating Deployment controller: %v", err)
|
||||
}
|
||||
c.eventRecorder = &record.FakeRecorder{}
|
||||
|
||||
c.scaleDownOldReplicaSetsForRecreate(oldRSs, test.d)
|
||||
for j := range oldRSs {
|
||||
rs := oldRSs[j]
|
||||
|
||||
if *rs.Spec.Replicas != 0 {
|
||||
t.Errorf("rs %q has non-zero replicas", rs.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOldPodsRunning(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
newRS *apps.ReplicaSet
|
||||
oldRSs []*apps.ReplicaSet
|
||||
podMap map[types.UID]*v1.PodList
|
||||
|
||||
hasOldPodsRunning bool
|
||||
}{
|
||||
{
|
||||
name: "no old RSs",
|
||||
hasOldPodsRunning: false,
|
||||
},
|
||||
{
|
||||
name: "old RSs with running pods",
|
||||
oldRSs: []*apps.ReplicaSet{rsWithUID("some-uid"), rsWithUID("other-uid")},
|
||||
podMap: podMapWithUIDs([]string{"some-uid", "other-uid"}),
|
||||
hasOldPodsRunning: true,
|
||||
},
|
||||
{
|
||||
name: "old RSs without pods but with non-zero status replicas",
|
||||
oldRSs: []*apps.ReplicaSet{newRSWithStatus("rs-1", 0, 1, nil)},
|
||||
hasOldPodsRunning: true,
|
||||
},
|
||||
{
|
||||
name: "old RSs without pods or non-zero status replicas",
|
||||
oldRSs: []*apps.ReplicaSet{newRSWithStatus("rs-1", 0, 0, nil)},
|
||||
hasOldPodsRunning: false,
|
||||
},
|
||||
{
|
||||
name: "old RSs with zero status replicas but pods in terminal state are present",
|
||||
oldRSs: []*apps.ReplicaSet{newRSWithStatus("rs-1", 0, 0, nil)},
|
||||
podMap: map[types.UID]*v1.PodList{
|
||||
"uid-1": {
|
||||
Items: []v1.Pod{
|
||||
{
|
||||
Status: v1.PodStatus{
|
||||
Phase: v1.PodFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
Status: v1.PodStatus{
|
||||
Phase: v1.PodSucceeded,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
hasOldPodsRunning: false,
|
||||
},
|
||||
{
|
||||
name: "old RSs with zero status replicas but pod in unknown phase present",
|
||||
oldRSs: []*apps.ReplicaSet{newRSWithStatus("rs-1", 0, 0, nil)},
|
||||
podMap: map[types.UID]*v1.PodList{
|
||||
"uid-1": {
|
||||
Items: []v1.Pod{
|
||||
{
|
||||
Status: v1.PodStatus{
|
||||
Phase: v1.PodUnknown,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
hasOldPodsRunning: true,
|
||||
},
|
||||
{
|
||||
name: "old RSs with zero status replicas with pending pod present",
|
||||
oldRSs: []*apps.ReplicaSet{newRSWithStatus("rs-1", 0, 0, nil)},
|
||||
podMap: map[types.UID]*v1.PodList{
|
||||
"uid-1": {
|
||||
Items: []v1.Pod{
|
||||
{
|
||||
Status: v1.PodStatus{
|
||||
Phase: v1.PodPending,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
hasOldPodsRunning: true,
|
||||
},
|
||||
{
|
||||
name: "old RSs with zero status replicas with running pod present",
|
||||
oldRSs: []*apps.ReplicaSet{newRSWithStatus("rs-1", 0, 0, nil)},
|
||||
podMap: map[types.UID]*v1.PodList{
|
||||
"uid-1": {
|
||||
Items: []v1.Pod{
|
||||
{
|
||||
Status: v1.PodStatus{
|
||||
Phase: v1.PodRunning,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
hasOldPodsRunning: true,
|
||||
},
|
||||
{
|
||||
name: "old RSs with zero status replicas but pods in terminal state and pending are present",
|
||||
oldRSs: []*apps.ReplicaSet{newRSWithStatus("rs-1", 0, 0, nil)},
|
||||
podMap: map[types.UID]*v1.PodList{
|
||||
"uid-1": {
|
||||
Items: []v1.Pod{
|
||||
{
|
||||
Status: v1.PodStatus{
|
||||
Phase: v1.PodFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
Status: v1.PodStatus{
|
||||
Phase: v1.PodSucceeded,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"uid-2": {
|
||||
Items: []v1.Pod{},
|
||||
},
|
||||
"uid-3": {
|
||||
Items: []v1.Pod{
|
||||
{
|
||||
Status: v1.PodStatus{
|
||||
Phase: v1.PodPending,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
hasOldPodsRunning: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if expected, got := test.hasOldPodsRunning, oldPodsRunning(test.newRS, test.oldRSs, test.podMap); expected != got {
|
||||
t.Errorf("%s: expected %t, got %t", test.name, expected, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func rsWithUID(uid string) *apps.ReplicaSet {
|
||||
d := newDeployment("foo", 1, nil, nil, nil, map[string]string{"foo": "bar"})
|
||||
rs := newReplicaSet(d, fmt.Sprintf("foo-%s", uid), 0)
|
||||
rs.UID = types.UID(uid)
|
||||
return rs
|
||||
}
|
||||
|
||||
func podMapWithUIDs(uids []string) map[types.UID]*v1.PodList {
|
||||
podMap := make(map[types.UID]*v1.PodList)
|
||||
for _, uid := range uids {
|
||||
podMap[types.UID(uid)] = &v1.PodList{
|
||||
Items: []v1.Pod{
|
||||
{ /* supposedly a pod */ },
|
||||
{ /* supposedly another pod pod */ },
|
||||
},
|
||||
}
|
||||
}
|
||||
return podMap
|
||||
}
|
148
vendor/k8s.io/kubernetes/pkg/controller/deployment/rollback.go
generated
vendored
148
vendor/k8s.io/kubernetes/pkg/controller/deployment/rollback.go
generated
vendored
@ -1,148 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package deployment
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
apps "k8s.io/api/apps/v1"
|
||||
"k8s.io/api/core/v1"
|
||||
extensions "k8s.io/api/extensions/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util"
|
||||
)
|
||||
|
||||
// rollback the deployment to the specified revision. In any case cleanup the rollback spec.
|
||||
func (dc *DeploymentController) rollback(d *apps.Deployment, rsList []*apps.ReplicaSet, podMap map[types.UID]*v1.PodList) error {
|
||||
newRS, allOldRSs, err := dc.getAllReplicaSetsAndSyncRevision(d, rsList, podMap, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
allRSs := append(allOldRSs, newRS)
|
||||
rollbackTo := getRollbackTo(d)
|
||||
// If rollback revision is 0, rollback to the last revision
|
||||
if rollbackTo.Revision == 0 {
|
||||
if rollbackTo.Revision = deploymentutil.LastRevision(allRSs); rollbackTo.Revision == 0 {
|
||||
// If we still can't find the last revision, gives up rollback
|
||||
dc.emitRollbackWarningEvent(d, deploymentutil.RollbackRevisionNotFound, "Unable to find last revision.")
|
||||
// Gives up rollback
|
||||
return dc.updateDeploymentAndClearRollbackTo(d)
|
||||
}
|
||||
}
|
||||
for _, rs := range allRSs {
|
||||
v, err := deploymentutil.Revision(rs)
|
||||
if err != nil {
|
||||
glog.V(4).Infof("Unable to extract revision from deployment's replica set %q: %v", rs.Name, err)
|
||||
continue
|
||||
}
|
||||
if v == rollbackTo.Revision {
|
||||
glog.V(4).Infof("Found replica set %q with desired revision %d", rs.Name, v)
|
||||
// rollback by copying podTemplate.Spec from the replica set
|
||||
// revision number will be incremented during the next getAllReplicaSetsAndSyncRevision call
|
||||
// no-op if the spec matches current deployment's podTemplate.Spec
|
||||
performedRollback, err := dc.rollbackToTemplate(d, rs)
|
||||
if performedRollback && err == nil {
|
||||
dc.emitRollbackNormalEvent(d, fmt.Sprintf("Rolled back deployment %q to revision %d", d.Name, rollbackTo.Revision))
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
dc.emitRollbackWarningEvent(d, deploymentutil.RollbackRevisionNotFound, "Unable to find the revision to rollback to.")
|
||||
// Gives up rollback
|
||||
return dc.updateDeploymentAndClearRollbackTo(d)
|
||||
}
|
||||
|
||||
// rollbackToTemplate compares the templates of the provided deployment and replica set and
|
||||
// updates the deployment with the replica set template in case they are different. It also
|
||||
// cleans up the rollback spec so subsequent requeues of the deployment won't end up in here.
|
||||
func (dc *DeploymentController) rollbackToTemplate(d *apps.Deployment, rs *apps.ReplicaSet) (bool, error) {
|
||||
performedRollback := false
|
||||
if !deploymentutil.EqualIgnoreHash(&d.Spec.Template, &rs.Spec.Template) {
|
||||
glog.V(4).Infof("Rolling back deployment %q to template spec %+v", d.Name, rs.Spec.Template.Spec)
|
||||
deploymentutil.SetFromReplicaSetTemplate(d, rs.Spec.Template)
|
||||
// set RS (the old RS we'll rolling back to) annotations back to the deployment;
|
||||
// otherwise, the deployment's current annotations (should be the same as current new RS) will be copied to the RS after the rollback.
|
||||
//
|
||||
// For example,
|
||||
// A Deployment has old RS1 with annotation {change-cause:create}, and new RS2 {change-cause:edit}.
|
||||
// Note that both annotations are copied from Deployment, and the Deployment should be annotated {change-cause:edit} as well.
|
||||
// Now, rollback Deployment to RS1, we should update Deployment's pod-template and also copy annotation from RS1.
|
||||
// Deployment is now annotated {change-cause:create}, and we have new RS1 {change-cause:create}, old RS2 {change-cause:edit}.
|
||||
//
|
||||
// If we don't copy the annotations back from RS to deployment on rollback, the Deployment will stay as {change-cause:edit},
|
||||
// and new RS1 becomes {change-cause:edit} (copied from deployment after rollback), old RS2 {change-cause:edit}, which is not correct.
|
||||
deploymentutil.SetDeploymentAnnotationsTo(d, rs)
|
||||
performedRollback = true
|
||||
} else {
|
||||
glog.V(4).Infof("Rolling back to a revision that contains the same template as current deployment %q, skipping rollback...", d.Name)
|
||||
eventMsg := fmt.Sprintf("The rollback revision contains the same template as current deployment %q", d.Name)
|
||||
dc.emitRollbackWarningEvent(d, deploymentutil.RollbackTemplateUnchanged, eventMsg)
|
||||
}
|
||||
|
||||
return performedRollback, dc.updateDeploymentAndClearRollbackTo(d)
|
||||
}
|
||||
|
||||
func (dc *DeploymentController) emitRollbackWarningEvent(d *apps.Deployment, reason, message string) {
|
||||
dc.eventRecorder.Eventf(d, v1.EventTypeWarning, reason, message)
|
||||
}
|
||||
|
||||
func (dc *DeploymentController) emitRollbackNormalEvent(d *apps.Deployment, message string) {
|
||||
dc.eventRecorder.Eventf(d, v1.EventTypeNormal, deploymentutil.RollbackDone, message)
|
||||
}
|
||||
|
||||
// updateDeploymentAndClearRollbackTo sets .spec.rollbackTo to nil and update the input deployment
|
||||
// It is assumed that the caller will have updated the deployment template appropriately (in case
|
||||
// we want to rollback).
|
||||
func (dc *DeploymentController) updateDeploymentAndClearRollbackTo(d *apps.Deployment) error {
|
||||
glog.V(4).Infof("Cleans up rollbackTo of deployment %q", d.Name)
|
||||
setRollbackTo(d, nil)
|
||||
_, err := dc.client.AppsV1().Deployments(d.Namespace).Update(d)
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: Remove this when extensions/v1beta1 and apps/v1beta1 Deployment are dropped.
|
||||
func getRollbackTo(d *apps.Deployment) *extensions.RollbackConfig {
|
||||
// Extract the annotation used for round-tripping the deprecated RollbackTo field.
|
||||
revision := d.Annotations[apps.DeprecatedRollbackTo]
|
||||
if revision == "" {
|
||||
return nil
|
||||
}
|
||||
revision64, err := strconv.ParseInt(revision, 10, 64)
|
||||
if err != nil {
|
||||
// If it's invalid, ignore it.
|
||||
return nil
|
||||
}
|
||||
return &extensions.RollbackConfig{
|
||||
Revision: revision64,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Remove this when extensions/v1beta1 and apps/v1beta1 Deployment are dropped.
|
||||
func setRollbackTo(d *apps.Deployment, rollbackTo *extensions.RollbackConfig) {
|
||||
if rollbackTo == nil {
|
||||
delete(d.Annotations, apps.DeprecatedRollbackTo)
|
||||
return
|
||||
}
|
||||
if d.Annotations == nil {
|
||||
d.Annotations = make(map[string]string)
|
||||
}
|
||||
d.Annotations[apps.DeprecatedRollbackTo] = strconv.FormatInt(rollbackTo.Revision, 10)
|
||||
}
|
235
vendor/k8s.io/kubernetes/pkg/controller/deployment/rolling.go
generated
vendored
235
vendor/k8s.io/kubernetes/pkg/controller/deployment/rolling.go
generated
vendored
@ -1,235 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package deployment
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/golang/glog"
|
||||
apps "k8s.io/api/apps/v1"
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/util/integer"
|
||||
"k8s.io/kubernetes/pkg/controller"
|
||||
deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util"
|
||||
)
|
||||
|
||||
// rolloutRolling implements the logic for rolling a new replica set.
|
||||
func (dc *DeploymentController) rolloutRolling(d *apps.Deployment, rsList []*apps.ReplicaSet, podMap map[types.UID]*v1.PodList) error {
|
||||
newRS, oldRSs, err := dc.getAllReplicaSetsAndSyncRevision(d, rsList, podMap, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
allRSs := append(oldRSs, newRS)
|
||||
|
||||
// Scale up, if we can.
|
||||
scaledUp, err := dc.reconcileNewReplicaSet(allRSs, newRS, d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if scaledUp {
|
||||
// Update DeploymentStatus
|
||||
return dc.syncRolloutStatus(allRSs, newRS, d)
|
||||
}
|
||||
|
||||
// Scale down, if we can.
|
||||
scaledDown, err := dc.reconcileOldReplicaSets(allRSs, controller.FilterActiveReplicaSets(oldRSs), newRS, d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if scaledDown {
|
||||
// Update DeploymentStatus
|
||||
return dc.syncRolloutStatus(allRSs, newRS, d)
|
||||
}
|
||||
|
||||
if deploymentutil.DeploymentComplete(d, &d.Status) {
|
||||
if err := dc.cleanupDeployment(oldRSs, d); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Sync deployment status
|
||||
return dc.syncRolloutStatus(allRSs, newRS, d)
|
||||
}
|
||||
|
||||
func (dc *DeploymentController) reconcileNewReplicaSet(allRSs []*apps.ReplicaSet, newRS *apps.ReplicaSet, deployment *apps.Deployment) (bool, error) {
|
||||
if *(newRS.Spec.Replicas) == *(deployment.Spec.Replicas) {
|
||||
// Scaling not required.
|
||||
return false, nil
|
||||
}
|
||||
if *(newRS.Spec.Replicas) > *(deployment.Spec.Replicas) {
|
||||
// Scale down.
|
||||
scaled, _, err := dc.scaleReplicaSetAndRecordEvent(newRS, *(deployment.Spec.Replicas), deployment)
|
||||
return scaled, err
|
||||
}
|
||||
newReplicasCount, err := deploymentutil.NewRSNewReplicas(deployment, allRSs, newRS)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
scaled, _, err := dc.scaleReplicaSetAndRecordEvent(newRS, newReplicasCount, deployment)
|
||||
return scaled, err
|
||||
}
|
||||
|
||||
func (dc *DeploymentController) reconcileOldReplicaSets(allRSs []*apps.ReplicaSet, oldRSs []*apps.ReplicaSet, newRS *apps.ReplicaSet, deployment *apps.Deployment) (bool, error) {
|
||||
oldPodsCount := deploymentutil.GetReplicaCountForReplicaSets(oldRSs)
|
||||
if oldPodsCount == 0 {
|
||||
// Can't scale down further
|
||||
return false, nil
|
||||
}
|
||||
|
||||
allPodsCount := deploymentutil.GetReplicaCountForReplicaSets(allRSs)
|
||||
glog.V(4).Infof("New replica set %s/%s has %d available pods.", newRS.Namespace, newRS.Name, newRS.Status.AvailableReplicas)
|
||||
maxUnavailable := deploymentutil.MaxUnavailable(*deployment)
|
||||
|
||||
// Check if we can scale down. We can scale down in the following 2 cases:
|
||||
// * Some old replica sets have unhealthy replicas, we could safely scale down those unhealthy replicas since that won't further
|
||||
// increase unavailability.
|
||||
// * New replica set has scaled up and it's replicas becomes ready, then we can scale down old replica sets in a further step.
|
||||
//
|
||||
// maxScaledDown := allPodsCount - minAvailable - newReplicaSetPodsUnavailable
|
||||
// take into account not only maxUnavailable and any surge pods that have been created, but also unavailable pods from
|
||||
// the newRS, so that the unavailable pods from the newRS would not make us scale down old replica sets in a further
|
||||
// step(that will increase unavailability).
|
||||
//
|
||||
// Concrete example:
|
||||
//
|
||||
// * 10 replicas
|
||||
// * 2 maxUnavailable (absolute number, not percent)
|
||||
// * 3 maxSurge (absolute number, not percent)
|
||||
//
|
||||
// case 1:
|
||||
// * Deployment is updated, newRS is created with 3 replicas, oldRS is scaled down to 8, and newRS is scaled up to 5.
|
||||
// * The new replica set pods crashloop and never become available.
|
||||
// * allPodsCount is 13. minAvailable is 8. newRSPodsUnavailable is 5.
|
||||
// * A node fails and causes one of the oldRS pods to become unavailable. However, 13 - 8 - 5 = 0, so the oldRS won't be scaled down.
|
||||
// * The user notices the crashloop and does kubectl rollout undo to rollback.
|
||||
// * newRSPodsUnavailable is 1, since we rolled back to the good replica set, so maxScaledDown = 13 - 8 - 1 = 4. 4 of the crashlooping pods will be scaled down.
|
||||
// * The total number of pods will then be 9 and the newRS can be scaled up to 10.
|
||||
//
|
||||
// case 2:
|
||||
// Same example, but pushing a new pod template instead of rolling back (aka "roll over"):
|
||||
// * The new replica set created must start with 0 replicas because allPodsCount is already at 13.
|
||||
// * However, newRSPodsUnavailable would also be 0, so the 2 old replica sets could be scaled down by 5 (13 - 8 - 0), which would then
|
||||
// allow the new replica set to be scaled up by 5.
|
||||
minAvailable := *(deployment.Spec.Replicas) - maxUnavailable
|
||||
newRSUnavailablePodCount := *(newRS.Spec.Replicas) - newRS.Status.AvailableReplicas
|
||||
maxScaledDown := allPodsCount - minAvailable - newRSUnavailablePodCount
|
||||
if maxScaledDown <= 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Clean up unhealthy replicas first, otherwise unhealthy replicas will block deployment
|
||||
// and cause timeout. See https://github.com/kubernetes/kubernetes/issues/16737
|
||||
oldRSs, cleanupCount, err := dc.cleanupUnhealthyReplicas(oldRSs, deployment, maxScaledDown)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
glog.V(4).Infof("Cleaned up unhealthy replicas from old RSes by %d", cleanupCount)
|
||||
|
||||
// Scale down old replica sets, need check maxUnavailable to ensure we can scale down
|
||||
allRSs = append(oldRSs, newRS)
|
||||
scaledDownCount, err := dc.scaleDownOldReplicaSetsForRollingUpdate(allRSs, oldRSs, deployment)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
glog.V(4).Infof("Scaled down old RSes of deployment %s by %d", deployment.Name, scaledDownCount)
|
||||
|
||||
totalScaledDown := cleanupCount + scaledDownCount
|
||||
return totalScaledDown > 0, nil
|
||||
}
|
||||
|
||||
// cleanupUnhealthyReplicas will scale down old replica sets with unhealthy replicas, so that all unhealthy replicas will be deleted.
|
||||
func (dc *DeploymentController) cleanupUnhealthyReplicas(oldRSs []*apps.ReplicaSet, deployment *apps.Deployment, maxCleanupCount int32) ([]*apps.ReplicaSet, int32, error) {
|
||||
sort.Sort(controller.ReplicaSetsByCreationTimestamp(oldRSs))
|
||||
// Safely scale down all old replica sets with unhealthy replicas. Replica set will sort the pods in the order
|
||||
// such that not-ready < ready, unscheduled < scheduled, and pending < running. This ensures that unhealthy replicas will
|
||||
// been deleted first and won't increase unavailability.
|
||||
totalScaledDown := int32(0)
|
||||
for i, targetRS := range oldRSs {
|
||||
if totalScaledDown >= maxCleanupCount {
|
||||
break
|
||||
}
|
||||
if *(targetRS.Spec.Replicas) == 0 {
|
||||
// cannot scale down this replica set.
|
||||
continue
|
||||
}
|
||||
glog.V(4).Infof("Found %d available pods in old RS %s/%s", targetRS.Status.AvailableReplicas, targetRS.Namespace, targetRS.Name)
|
||||
if *(targetRS.Spec.Replicas) == targetRS.Status.AvailableReplicas {
|
||||
// no unhealthy replicas found, no scaling required.
|
||||
continue
|
||||
}
|
||||
|
||||
scaledDownCount := int32(integer.IntMin(int(maxCleanupCount-totalScaledDown), int(*(targetRS.Spec.Replicas)-targetRS.Status.AvailableReplicas)))
|
||||
newReplicasCount := *(targetRS.Spec.Replicas) - scaledDownCount
|
||||
if newReplicasCount > *(targetRS.Spec.Replicas) {
|
||||
return nil, 0, fmt.Errorf("when cleaning up unhealthy replicas, got invalid request to scale down %s/%s %d -> %d", targetRS.Namespace, targetRS.Name, *(targetRS.Spec.Replicas), newReplicasCount)
|
||||
}
|
||||
_, updatedOldRS, err := dc.scaleReplicaSetAndRecordEvent(targetRS, newReplicasCount, deployment)
|
||||
if err != nil {
|
||||
return nil, totalScaledDown, err
|
||||
}
|
||||
totalScaledDown += scaledDownCount
|
||||
oldRSs[i] = updatedOldRS
|
||||
}
|
||||
return oldRSs, totalScaledDown, nil
|
||||
}
|
||||
|
||||
// scaleDownOldReplicaSetsForRollingUpdate scales down old replica sets when deployment strategy is "RollingUpdate".
|
||||
// Need check maxUnavailable to ensure availability
|
||||
func (dc *DeploymentController) scaleDownOldReplicaSetsForRollingUpdate(allRSs []*apps.ReplicaSet, oldRSs []*apps.ReplicaSet, deployment *apps.Deployment) (int32, error) {
|
||||
maxUnavailable := deploymentutil.MaxUnavailable(*deployment)
|
||||
|
||||
// Check if we can scale down.
|
||||
minAvailable := *(deployment.Spec.Replicas) - maxUnavailable
|
||||
// Find the number of available pods.
|
||||
availablePodCount := deploymentutil.GetAvailableReplicaCountForReplicaSets(allRSs)
|
||||
if availablePodCount <= minAvailable {
|
||||
// Cannot scale down.
|
||||
return 0, nil
|
||||
}
|
||||
glog.V(4).Infof("Found %d available pods in deployment %s, scaling down old RSes", availablePodCount, deployment.Name)
|
||||
|
||||
sort.Sort(controller.ReplicaSetsByCreationTimestamp(oldRSs))
|
||||
|
||||
totalScaledDown := int32(0)
|
||||
totalScaleDownCount := availablePodCount - minAvailable
|
||||
for _, targetRS := range oldRSs {
|
||||
if totalScaledDown >= totalScaleDownCount {
|
||||
// No further scaling required.
|
||||
break
|
||||
}
|
||||
if *(targetRS.Spec.Replicas) == 0 {
|
||||
// cannot scale down this ReplicaSet.
|
||||
continue
|
||||
}
|
||||
// Scale down.
|
||||
scaleDownCount := int32(integer.IntMin(int(*(targetRS.Spec.Replicas)), int(totalScaleDownCount-totalScaledDown)))
|
||||
newReplicasCount := *(targetRS.Spec.Replicas) - scaleDownCount
|
||||
if newReplicasCount > *(targetRS.Spec.Replicas) {
|
||||
return 0, fmt.Errorf("when scaling down old RS, got invalid request to scale down %s/%s %d -> %d", targetRS.Namespace, targetRS.Name, *(targetRS.Spec.Replicas), newReplicasCount)
|
||||
}
|
||||
_, _, err := dc.scaleReplicaSetAndRecordEvent(targetRS, newReplicasCount, deployment)
|
||||
if err != nil {
|
||||
return totalScaledDown, err
|
||||
}
|
||||
|
||||
totalScaledDown += scaleDownCount
|
||||
}
|
||||
|
||||
return totalScaledDown, nil
|
||||
}
|
379
vendor/k8s.io/kubernetes/pkg/controller/deployment/rolling_test.go
generated
vendored
379
vendor/k8s.io/kubernetes/pkg/controller/deployment/rolling_test.go
generated
vendored
@ -1,379 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package deployment
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
apps "k8s.io/api/apps/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
core "k8s.io/client-go/testing"
|
||||
"k8s.io/client-go/tools/record"
|
||||
)
|
||||
|
||||
func TestDeploymentController_reconcileNewReplicaSet(t *testing.T) {
|
||||
tests := []struct {
|
||||
deploymentReplicas int
|
||||
maxSurge intstr.IntOrString
|
||||
oldReplicas int
|
||||
newReplicas int
|
||||
scaleExpected bool
|
||||
expectedNewReplicas int
|
||||
}{
|
||||
{
|
||||
// Should not scale up.
|
||||
deploymentReplicas: 10,
|
||||
maxSurge: intstr.FromInt(0),
|
||||
oldReplicas: 10,
|
||||
newReplicas: 0,
|
||||
scaleExpected: false,
|
||||
},
|
||||
{
|
||||
deploymentReplicas: 10,
|
||||
maxSurge: intstr.FromInt(2),
|
||||
oldReplicas: 10,
|
||||
newReplicas: 0,
|
||||
scaleExpected: true,
|
||||
expectedNewReplicas: 2,
|
||||
},
|
||||
{
|
||||
deploymentReplicas: 10,
|
||||
maxSurge: intstr.FromInt(2),
|
||||
oldReplicas: 5,
|
||||
newReplicas: 0,
|
||||
scaleExpected: true,
|
||||
expectedNewReplicas: 7,
|
||||
},
|
||||
{
|
||||
deploymentReplicas: 10,
|
||||
maxSurge: intstr.FromInt(2),
|
||||
oldReplicas: 10,
|
||||
newReplicas: 2,
|
||||
scaleExpected: false,
|
||||
},
|
||||
{
|
||||
// Should scale down.
|
||||
deploymentReplicas: 10,
|
||||
maxSurge: intstr.FromInt(2),
|
||||
oldReplicas: 2,
|
||||
newReplicas: 11,
|
||||
scaleExpected: true,
|
||||
expectedNewReplicas: 10,
|
||||
},
|
||||
}
|
||||
|
||||
for i := range tests {
|
||||
test := tests[i]
|
||||
t.Logf("executing scenario %d", i)
|
||||
newRS := rs("foo-v2", test.newReplicas, nil, noTimestamp)
|
||||
oldRS := rs("foo-v2", test.oldReplicas, nil, noTimestamp)
|
||||
allRSs := []*apps.ReplicaSet{newRS, oldRS}
|
||||
maxUnavailable := intstr.FromInt(0)
|
||||
deployment := newDeployment("foo", test.deploymentReplicas, nil, &test.maxSurge, &maxUnavailable, map[string]string{"foo": "bar"})
|
||||
fake := fake.Clientset{}
|
||||
controller := &DeploymentController{
|
||||
client: &fake,
|
||||
eventRecorder: &record.FakeRecorder{},
|
||||
}
|
||||
scaled, err := controller.reconcileNewReplicaSet(allRSs, newRS, deployment)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
continue
|
||||
}
|
||||
if !test.scaleExpected {
|
||||
if scaled || len(fake.Actions()) > 0 {
|
||||
t.Errorf("unexpected scaling: %v", fake.Actions())
|
||||
}
|
||||
continue
|
||||
}
|
||||
if test.scaleExpected && !scaled {
|
||||
t.Errorf("expected scaling to occur")
|
||||
continue
|
||||
}
|
||||
if len(fake.Actions()) != 1 {
|
||||
t.Errorf("expected 1 action during scale, got: %v", fake.Actions())
|
||||
continue
|
||||
}
|
||||
updated := fake.Actions()[0].(core.UpdateAction).GetObject().(*apps.ReplicaSet)
|
||||
if e, a := test.expectedNewReplicas, int(*(updated.Spec.Replicas)); e != a {
|
||||
t.Errorf("expected update to %d replicas, got %d", e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeploymentController_reconcileOldReplicaSets(t *testing.T) {
|
||||
tests := []struct {
|
||||
deploymentReplicas int
|
||||
maxUnavailable intstr.IntOrString
|
||||
oldReplicas int
|
||||
newReplicas int
|
||||
readyPodsFromOldRS int
|
||||
readyPodsFromNewRS int
|
||||
scaleExpected bool
|
||||
expectedOldReplicas int
|
||||
}{
|
||||
{
|
||||
deploymentReplicas: 10,
|
||||
maxUnavailable: intstr.FromInt(0),
|
||||
oldReplicas: 10,
|
||||
newReplicas: 0,
|
||||
readyPodsFromOldRS: 10,
|
||||
readyPodsFromNewRS: 0,
|
||||
scaleExpected: true,
|
||||
expectedOldReplicas: 9,
|
||||
},
|
||||
{
|
||||
deploymentReplicas: 10,
|
||||
maxUnavailable: intstr.FromInt(2),
|
||||
oldReplicas: 10,
|
||||
newReplicas: 0,
|
||||
readyPodsFromOldRS: 10,
|
||||
readyPodsFromNewRS: 0,
|
||||
scaleExpected: true,
|
||||
expectedOldReplicas: 8,
|
||||
},
|
||||
{ // expect unhealthy replicas from old replica sets been cleaned up
|
||||
deploymentReplicas: 10,
|
||||
maxUnavailable: intstr.FromInt(2),
|
||||
oldReplicas: 10,
|
||||
newReplicas: 0,
|
||||
readyPodsFromOldRS: 8,
|
||||
readyPodsFromNewRS: 0,
|
||||
scaleExpected: true,
|
||||
expectedOldReplicas: 8,
|
||||
},
|
||||
{ // expect 1 unhealthy replica from old replica sets been cleaned up, and 1 ready pod been scaled down
|
||||
deploymentReplicas: 10,
|
||||
maxUnavailable: intstr.FromInt(2),
|
||||
oldReplicas: 10,
|
||||
newReplicas: 0,
|
||||
readyPodsFromOldRS: 9,
|
||||
readyPodsFromNewRS: 0,
|
||||
scaleExpected: true,
|
||||
expectedOldReplicas: 8,
|
||||
},
|
||||
{ // the unavailable pods from the newRS would not make us scale down old RSs in a further step
|
||||
deploymentReplicas: 10,
|
||||
maxUnavailable: intstr.FromInt(2),
|
||||
oldReplicas: 8,
|
||||
newReplicas: 2,
|
||||
readyPodsFromOldRS: 8,
|
||||
readyPodsFromNewRS: 0,
|
||||
scaleExpected: false,
|
||||
},
|
||||
}
|
||||
for i := range tests {
|
||||
test := tests[i]
|
||||
t.Logf("executing scenario %d", i)
|
||||
|
||||
newSelector := map[string]string{"foo": "new"}
|
||||
oldSelector := map[string]string{"foo": "old"}
|
||||
newRS := rs("foo-new", test.newReplicas, newSelector, noTimestamp)
|
||||
newRS.Status.AvailableReplicas = int32(test.readyPodsFromNewRS)
|
||||
oldRS := rs("foo-old", test.oldReplicas, oldSelector, noTimestamp)
|
||||
oldRS.Status.AvailableReplicas = int32(test.readyPodsFromOldRS)
|
||||
oldRSs := []*apps.ReplicaSet{oldRS}
|
||||
allRSs := []*apps.ReplicaSet{oldRS, newRS}
|
||||
maxSurge := intstr.FromInt(0)
|
||||
deployment := newDeployment("foo", test.deploymentReplicas, nil, &maxSurge, &test.maxUnavailable, newSelector)
|
||||
fakeClientset := fake.Clientset{}
|
||||
controller := &DeploymentController{
|
||||
client: &fakeClientset,
|
||||
eventRecorder: &record.FakeRecorder{},
|
||||
}
|
||||
|
||||
scaled, err := controller.reconcileOldReplicaSets(allRSs, oldRSs, newRS, deployment)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
continue
|
||||
}
|
||||
if !test.scaleExpected && scaled {
|
||||
t.Errorf("unexpected scaling: %v", fakeClientset.Actions())
|
||||
}
|
||||
if test.scaleExpected && !scaled {
|
||||
t.Errorf("expected scaling to occur")
|
||||
continue
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeploymentController_cleanupUnhealthyReplicas(t *testing.T) {
|
||||
tests := []struct {
|
||||
oldReplicas int
|
||||
readyPods int
|
||||
unHealthyPods int
|
||||
maxCleanupCount int
|
||||
cleanupCountExpected int
|
||||
}{
|
||||
{
|
||||
oldReplicas: 10,
|
||||
readyPods: 8,
|
||||
unHealthyPods: 2,
|
||||
maxCleanupCount: 1,
|
||||
cleanupCountExpected: 1,
|
||||
},
|
||||
{
|
||||
oldReplicas: 10,
|
||||
readyPods: 8,
|
||||
unHealthyPods: 2,
|
||||
maxCleanupCount: 3,
|
||||
cleanupCountExpected: 2,
|
||||
},
|
||||
{
|
||||
oldReplicas: 10,
|
||||
readyPods: 8,
|
||||
unHealthyPods: 2,
|
||||
maxCleanupCount: 0,
|
||||
cleanupCountExpected: 0,
|
||||
},
|
||||
{
|
||||
oldReplicas: 10,
|
||||
readyPods: 10,
|
||||
unHealthyPods: 0,
|
||||
maxCleanupCount: 3,
|
||||
cleanupCountExpected: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
t.Logf("executing scenario %d", i)
|
||||
oldRS := rs("foo-v2", test.oldReplicas, nil, noTimestamp)
|
||||
oldRS.Status.AvailableReplicas = int32(test.readyPods)
|
||||
oldRSs := []*apps.ReplicaSet{oldRS}
|
||||
maxSurge := intstr.FromInt(2)
|
||||
maxUnavailable := intstr.FromInt(2)
|
||||
deployment := newDeployment("foo", 10, nil, &maxSurge, &maxUnavailable, nil)
|
||||
fakeClientset := fake.Clientset{}
|
||||
|
||||
controller := &DeploymentController{
|
||||
client: &fakeClientset,
|
||||
eventRecorder: &record.FakeRecorder{},
|
||||
}
|
||||
_, cleanupCount, err := controller.cleanupUnhealthyReplicas(oldRSs, deployment, int32(test.maxCleanupCount))
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
continue
|
||||
}
|
||||
if int(cleanupCount) != test.cleanupCountExpected {
|
||||
t.Errorf("expected %v unhealthy replicas been cleaned up, got %v", test.cleanupCountExpected, cleanupCount)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeploymentController_scaleDownOldReplicaSetsForRollingUpdate(t *testing.T) {
|
||||
tests := []struct {
|
||||
deploymentReplicas int
|
||||
maxUnavailable intstr.IntOrString
|
||||
readyPods int
|
||||
oldReplicas int
|
||||
scaleExpected bool
|
||||
expectedOldReplicas int
|
||||
}{
|
||||
{
|
||||
deploymentReplicas: 10,
|
||||
maxUnavailable: intstr.FromInt(0),
|
||||
readyPods: 10,
|
||||
oldReplicas: 10,
|
||||
scaleExpected: true,
|
||||
expectedOldReplicas: 9,
|
||||
},
|
||||
{
|
||||
deploymentReplicas: 10,
|
||||
maxUnavailable: intstr.FromInt(2),
|
||||
readyPods: 10,
|
||||
oldReplicas: 10,
|
||||
scaleExpected: true,
|
||||
expectedOldReplicas: 8,
|
||||
},
|
||||
{
|
||||
deploymentReplicas: 10,
|
||||
maxUnavailable: intstr.FromInt(2),
|
||||
readyPods: 8,
|
||||
oldReplicas: 10,
|
||||
scaleExpected: false,
|
||||
},
|
||||
{
|
||||
deploymentReplicas: 10,
|
||||
maxUnavailable: intstr.FromInt(2),
|
||||
readyPods: 10,
|
||||
oldReplicas: 0,
|
||||
scaleExpected: false,
|
||||
},
|
||||
{
|
||||
deploymentReplicas: 10,
|
||||
maxUnavailable: intstr.FromInt(2),
|
||||
readyPods: 1,
|
||||
oldReplicas: 10,
|
||||
scaleExpected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for i := range tests {
|
||||
test := tests[i]
|
||||
t.Logf("executing scenario %d", i)
|
||||
oldRS := rs("foo-v2", test.oldReplicas, nil, noTimestamp)
|
||||
oldRS.Status.AvailableReplicas = int32(test.readyPods)
|
||||
allRSs := []*apps.ReplicaSet{oldRS}
|
||||
oldRSs := []*apps.ReplicaSet{oldRS}
|
||||
maxSurge := intstr.FromInt(0)
|
||||
deployment := newDeployment("foo", test.deploymentReplicas, nil, &maxSurge, &test.maxUnavailable, map[string]string{"foo": "bar"})
|
||||
fakeClientset := fake.Clientset{}
|
||||
controller := &DeploymentController{
|
||||
client: &fakeClientset,
|
||||
eventRecorder: &record.FakeRecorder{},
|
||||
}
|
||||
scaled, err := controller.scaleDownOldReplicaSetsForRollingUpdate(allRSs, oldRSs, deployment)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
continue
|
||||
}
|
||||
if !test.scaleExpected {
|
||||
if scaled != 0 {
|
||||
t.Errorf("unexpected scaling: %v", fakeClientset.Actions())
|
||||
}
|
||||
continue
|
||||
}
|
||||
if test.scaleExpected && scaled == 0 {
|
||||
t.Errorf("expected scaling to occur; actions: %v", fakeClientset.Actions())
|
||||
continue
|
||||
}
|
||||
// There are both list and update actions logged, so extract the update
|
||||
// action for verification.
|
||||
var updateAction core.UpdateAction
|
||||
for _, action := range fakeClientset.Actions() {
|
||||
switch a := action.(type) {
|
||||
case core.UpdateAction:
|
||||
if updateAction != nil {
|
||||
t.Errorf("expected only 1 update action; had %v and found %v", updateAction, a)
|
||||
} else {
|
||||
updateAction = a
|
||||
}
|
||||
}
|
||||
}
|
||||
if updateAction == nil {
|
||||
t.Errorf("expected an update action")
|
||||
continue
|
||||
}
|
||||
updated := updateAction.GetObject().(*apps.ReplicaSet)
|
||||
if e, a := test.expectedOldReplicas, int(*(updated.Spec.Replicas)); e != a {
|
||||
t.Errorf("expected update to %d replicas, got %d", e, a)
|
||||
}
|
||||
}
|
||||
}
|
539
vendor/k8s.io/kubernetes/pkg/controller/deployment/sync.go
generated
vendored
539
vendor/k8s.io/kubernetes/pkg/controller/deployment/sync.go
generated
vendored
@ -1,539 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package deployment
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/golang/glog"
|
||||
apps "k8s.io/api/apps/v1"
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/rand"
|
||||
"k8s.io/kubernetes/pkg/controller"
|
||||
deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util"
|
||||
labelsutil "k8s.io/kubernetes/pkg/util/labels"
|
||||
)
|
||||
|
||||
// syncStatusOnly only updates Deployments Status and doesn't take any mutating actions.
|
||||
func (dc *DeploymentController) syncStatusOnly(d *apps.Deployment, rsList []*apps.ReplicaSet, podMap map[types.UID]*v1.PodList) error {
|
||||
newRS, oldRSs, err := dc.getAllReplicaSetsAndSyncRevision(d, rsList, podMap, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
allRSs := append(oldRSs, newRS)
|
||||
return dc.syncDeploymentStatus(allRSs, newRS, d)
|
||||
}
|
||||
|
||||
// sync is responsible for reconciling deployments on scaling events or when they
|
||||
// are paused.
|
||||
func (dc *DeploymentController) sync(d *apps.Deployment, rsList []*apps.ReplicaSet, podMap map[types.UID]*v1.PodList) error {
|
||||
newRS, oldRSs, err := dc.getAllReplicaSetsAndSyncRevision(d, rsList, podMap, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := dc.scale(d, newRS, oldRSs); err != nil {
|
||||
// If we get an error while trying to scale, the deployment will be requeued
|
||||
// so we can abort this resync
|
||||
return err
|
||||
}
|
||||
|
||||
// Clean up the deployment when it's paused and no rollback is in flight.
|
||||
if d.Spec.Paused && getRollbackTo(d) == nil {
|
||||
if err := dc.cleanupDeployment(oldRSs, d); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
allRSs := append(oldRSs, newRS)
|
||||
return dc.syncDeploymentStatus(allRSs, newRS, d)
|
||||
}
|
||||
|
||||
// checkPausedConditions checks if the given deployment is paused or not and adds an appropriate condition.
|
||||
// These conditions are needed so that we won't accidentally report lack of progress for resumed deployments
|
||||
// that were paused for longer than progressDeadlineSeconds.
|
||||
func (dc *DeploymentController) checkPausedConditions(d *apps.Deployment) error {
|
||||
if d.Spec.ProgressDeadlineSeconds == nil {
|
||||
return nil
|
||||
}
|
||||
cond := deploymentutil.GetDeploymentCondition(d.Status, apps.DeploymentProgressing)
|
||||
if cond != nil && cond.Reason == deploymentutil.TimedOutReason {
|
||||
// If we have reported lack of progress, do not overwrite it with a paused condition.
|
||||
return nil
|
||||
}
|
||||
pausedCondExists := cond != nil && cond.Reason == deploymentutil.PausedDeployReason
|
||||
|
||||
needsUpdate := false
|
||||
if d.Spec.Paused && !pausedCondExists {
|
||||
condition := deploymentutil.NewDeploymentCondition(apps.DeploymentProgressing, v1.ConditionUnknown, deploymentutil.PausedDeployReason, "Deployment is paused")
|
||||
deploymentutil.SetDeploymentCondition(&d.Status, *condition)
|
||||
needsUpdate = true
|
||||
} else if !d.Spec.Paused && pausedCondExists {
|
||||
condition := deploymentutil.NewDeploymentCondition(apps.DeploymentProgressing, v1.ConditionUnknown, deploymentutil.ResumedDeployReason, "Deployment is resumed")
|
||||
deploymentutil.SetDeploymentCondition(&d.Status, *condition)
|
||||
needsUpdate = true
|
||||
}
|
||||
|
||||
if !needsUpdate {
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
d, err = dc.client.AppsV1().Deployments(d.Namespace).UpdateStatus(d)
|
||||
return err
|
||||
}
|
||||
|
||||
// getAllReplicaSetsAndSyncRevision returns all the replica sets for the provided deployment (new and all old), with new RS's and deployment's revision updated.
|
||||
//
|
||||
// rsList should come from getReplicaSetsForDeployment(d).
|
||||
// podMap should come from getPodMapForDeployment(d, rsList).
|
||||
//
|
||||
// 1. Get all old RSes this deployment targets, and calculate the max revision number among them (maxOldV).
|
||||
// 2. Get new RS this deployment targets (whose pod template matches deployment's), and update new RS's revision number to (maxOldV + 1),
|
||||
// only if its revision number is smaller than (maxOldV + 1). If this step failed, we'll update it in the next deployment sync loop.
|
||||
// 3. Copy new RS's revision number to deployment (update deployment's revision). If this step failed, we'll update it in the next deployment sync loop.
|
||||
//
|
||||
// Note that currently the deployment controller is using caches to avoid querying the server for reads.
|
||||
// This may lead to stale reads of replica sets, thus incorrect deployment status.
|
||||
func (dc *DeploymentController) getAllReplicaSetsAndSyncRevision(d *apps.Deployment, rsList []*apps.ReplicaSet, podMap map[types.UID]*v1.PodList, createIfNotExisted bool) (*apps.ReplicaSet, []*apps.ReplicaSet, error) {
|
||||
_, allOldRSs := deploymentutil.FindOldReplicaSets(d, rsList)
|
||||
|
||||
// Get new replica set with the updated revision number
|
||||
newRS, err := dc.getNewReplicaSet(d, rsList, allOldRSs, createIfNotExisted)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return newRS, allOldRSs, nil
|
||||
}
|
||||
|
||||
// Returns a replica set that matches the intent of the given deployment. Returns nil if the new replica set doesn't exist yet.
|
||||
// 1. Get existing new RS (the RS that the given deployment targets, whose pod template is the same as deployment's).
|
||||
// 2. If there's existing new RS, update its revision number if it's smaller than (maxOldRevision + 1), where maxOldRevision is the max revision number among all old RSes.
|
||||
// 3. If there's no existing new RS and createIfNotExisted is true, create one with appropriate revision number (maxOldRevision + 1) and replicas.
|
||||
// Note that the pod-template-hash will be added to adopted RSes and pods.
|
||||
func (dc *DeploymentController) getNewReplicaSet(d *apps.Deployment, rsList, oldRSs []*apps.ReplicaSet, createIfNotExisted bool) (*apps.ReplicaSet, error) {
|
||||
existingNewRS := deploymentutil.FindNewReplicaSet(d, rsList)
|
||||
|
||||
// Calculate the max revision number among all old RSes
|
||||
maxOldRevision := deploymentutil.MaxRevision(oldRSs)
|
||||
// Calculate revision number for this new replica set
|
||||
newRevision := strconv.FormatInt(maxOldRevision+1, 10)
|
||||
|
||||
// Latest replica set exists. We need to sync its annotations (includes copying all but
|
||||
// annotationsToSkip from the parent deployment, and update revision, desiredReplicas,
|
||||
// and maxReplicas) and also update the revision annotation in the deployment with the
|
||||
// latest revision.
|
||||
if existingNewRS != nil {
|
||||
rsCopy := existingNewRS.DeepCopy()
|
||||
|
||||
// Set existing new replica set's annotation
|
||||
annotationsUpdated := deploymentutil.SetNewReplicaSetAnnotations(d, rsCopy, newRevision, true)
|
||||
minReadySecondsNeedsUpdate := rsCopy.Spec.MinReadySeconds != d.Spec.MinReadySeconds
|
||||
if annotationsUpdated || minReadySecondsNeedsUpdate {
|
||||
rsCopy.Spec.MinReadySeconds = d.Spec.MinReadySeconds
|
||||
return dc.client.AppsV1().ReplicaSets(rsCopy.ObjectMeta.Namespace).Update(rsCopy)
|
||||
}
|
||||
|
||||
// Should use the revision in existingNewRS's annotation, since it set by before
|
||||
needsUpdate := deploymentutil.SetDeploymentRevision(d, rsCopy.Annotations[deploymentutil.RevisionAnnotation])
|
||||
// If no other Progressing condition has been recorded and we need to estimate the progress
|
||||
// of this deployment then it is likely that old users started caring about progress. In that
|
||||
// case we need to take into account the first time we noticed their new replica set.
|
||||
cond := deploymentutil.GetDeploymentCondition(d.Status, apps.DeploymentProgressing)
|
||||
if d.Spec.ProgressDeadlineSeconds != nil && cond == nil {
|
||||
msg := fmt.Sprintf("Found new replica set %q", rsCopy.Name)
|
||||
condition := deploymentutil.NewDeploymentCondition(apps.DeploymentProgressing, v1.ConditionTrue, deploymentutil.FoundNewRSReason, msg)
|
||||
deploymentutil.SetDeploymentCondition(&d.Status, *condition)
|
||||
needsUpdate = true
|
||||
}
|
||||
|
||||
if needsUpdate {
|
||||
var err error
|
||||
if d, err = dc.client.AppsV1().Deployments(d.Namespace).UpdateStatus(d); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return rsCopy, nil
|
||||
}
|
||||
|
||||
if !createIfNotExisted {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// new ReplicaSet does not exist, create one.
|
||||
newRSTemplate := *d.Spec.Template.DeepCopy()
|
||||
podTemplateSpecHash := fmt.Sprintf("%d", controller.ComputeHash(&newRSTemplate, d.Status.CollisionCount))
|
||||
newRSTemplate.Labels = labelsutil.CloneAndAddLabel(d.Spec.Template.Labels, apps.DefaultDeploymentUniqueLabelKey, podTemplateSpecHash)
|
||||
// Add podTemplateHash label to selector.
|
||||
newRSSelector := labelsutil.CloneSelectorAndAddLabel(d.Spec.Selector, apps.DefaultDeploymentUniqueLabelKey, podTemplateSpecHash)
|
||||
|
||||
// Create new ReplicaSet
|
||||
newRS := apps.ReplicaSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
// Make the name deterministic, to ensure idempotence
|
||||
Name: d.Name + "-" + rand.SafeEncodeString(podTemplateSpecHash),
|
||||
Namespace: d.Namespace,
|
||||
OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(d, controllerKind)},
|
||||
Labels: newRSTemplate.Labels,
|
||||
},
|
||||
Spec: apps.ReplicaSetSpec{
|
||||
Replicas: new(int32),
|
||||
MinReadySeconds: d.Spec.MinReadySeconds,
|
||||
Selector: newRSSelector,
|
||||
Template: newRSTemplate,
|
||||
},
|
||||
}
|
||||
allRSs := append(oldRSs, &newRS)
|
||||
newReplicasCount, err := deploymentutil.NewRSNewReplicas(d, allRSs, &newRS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
*(newRS.Spec.Replicas) = newReplicasCount
|
||||
// Set new replica set's annotation
|
||||
deploymentutil.SetNewReplicaSetAnnotations(d, &newRS, newRevision, false)
|
||||
// Create the new ReplicaSet. If it already exists, then we need to check for possible
|
||||
// hash collisions. If there is any other error, we need to report it in the status of
|
||||
// the Deployment.
|
||||
alreadyExists := false
|
||||
createdRS, err := dc.client.AppsV1().ReplicaSets(d.Namespace).Create(&newRS)
|
||||
switch {
|
||||
// We may end up hitting this due to a slow cache or a fast resync of the Deployment.
|
||||
case errors.IsAlreadyExists(err):
|
||||
alreadyExists = true
|
||||
|
||||
// Fetch a copy of the ReplicaSet.
|
||||
rs, rsErr := dc.rsLister.ReplicaSets(newRS.Namespace).Get(newRS.Name)
|
||||
if rsErr != nil {
|
||||
return nil, rsErr
|
||||
}
|
||||
|
||||
// If the Deployment owns the ReplicaSet and the ReplicaSet's PodTemplateSpec is semantically
|
||||
// deep equal to the PodTemplateSpec of the Deployment, it's the Deployment's new ReplicaSet.
|
||||
// Otherwise, this is a hash collision and we need to increment the collisionCount field in
|
||||
// the status of the Deployment and requeue to try the creation in the next sync.
|
||||
controllerRef := metav1.GetControllerOf(rs)
|
||||
if controllerRef != nil && controllerRef.UID == d.UID && deploymentutil.EqualIgnoreHash(&d.Spec.Template, &rs.Spec.Template) {
|
||||
createdRS = rs
|
||||
err = nil
|
||||
break
|
||||
}
|
||||
|
||||
// Matching ReplicaSet is not equal - increment the collisionCount in the DeploymentStatus
|
||||
// and requeue the Deployment.
|
||||
if d.Status.CollisionCount == nil {
|
||||
d.Status.CollisionCount = new(int32)
|
||||
}
|
||||
preCollisionCount := *d.Status.CollisionCount
|
||||
*d.Status.CollisionCount++
|
||||
// Update the collisionCount for the Deployment and let it requeue by returning the original
|
||||
// error.
|
||||
_, dErr := dc.client.AppsV1().Deployments(d.Namespace).UpdateStatus(d)
|
||||
if dErr == nil {
|
||||
glog.V(2).Infof("Found a hash collision for deployment %q - bumping collisionCount (%d->%d) to resolve it", d.Name, preCollisionCount, *d.Status.CollisionCount)
|
||||
}
|
||||
return nil, err
|
||||
case err != nil:
|
||||
msg := fmt.Sprintf("Failed to create new replica set %q: %v", newRS.Name, err)
|
||||
if d.Spec.ProgressDeadlineSeconds != nil {
|
||||
cond := deploymentutil.NewDeploymentCondition(apps.DeploymentProgressing, v1.ConditionFalse, deploymentutil.FailedRSCreateReason, msg)
|
||||
deploymentutil.SetDeploymentCondition(&d.Status, *cond)
|
||||
// We don't really care about this error at this point, since we have a bigger issue to report.
|
||||
// TODO: Identify which errors are permanent and switch DeploymentIsFailed to take into account
|
||||
// these reasons as well. Related issue: https://github.com/kubernetes/kubernetes/issues/18568
|
||||
_, _ = dc.client.AppsV1().Deployments(d.Namespace).UpdateStatus(d)
|
||||
}
|
||||
dc.eventRecorder.Eventf(d, v1.EventTypeWarning, deploymentutil.FailedRSCreateReason, msg)
|
||||
return nil, err
|
||||
}
|
||||
if !alreadyExists && newReplicasCount > 0 {
|
||||
dc.eventRecorder.Eventf(d, v1.EventTypeNormal, "ScalingReplicaSet", "Scaled up replica set %s to %d", createdRS.Name, newReplicasCount)
|
||||
}
|
||||
|
||||
needsUpdate := deploymentutil.SetDeploymentRevision(d, newRevision)
|
||||
if !alreadyExists && d.Spec.ProgressDeadlineSeconds != nil {
|
||||
msg := fmt.Sprintf("Created new replica set %q", createdRS.Name)
|
||||
condition := deploymentutil.NewDeploymentCondition(apps.DeploymentProgressing, v1.ConditionTrue, deploymentutil.NewReplicaSetReason, msg)
|
||||
deploymentutil.SetDeploymentCondition(&d.Status, *condition)
|
||||
needsUpdate = true
|
||||
}
|
||||
if needsUpdate {
|
||||
_, err = dc.client.AppsV1().Deployments(d.Namespace).UpdateStatus(d)
|
||||
}
|
||||
return createdRS, err
|
||||
}
|
||||
|
||||
// scale scales proportionally in order to mitigate risk. Otherwise, scaling up can increase the size
|
||||
// of the new replica set and scaling down can decrease the sizes of the old ones, both of which would
|
||||
// have the effect of hastening the rollout progress, which could produce a higher proportion of unavailable
|
||||
// replicas in the event of a problem with the rolled out template. Should run only on scaling events or
|
||||
// when a deployment is paused and not during the normal rollout process.
|
||||
func (dc *DeploymentController) scale(deployment *apps.Deployment, newRS *apps.ReplicaSet, oldRSs []*apps.ReplicaSet) error {
|
||||
// If there is only one active replica set then we should scale that up to the full count of the
|
||||
// deployment. If there is no active replica set, then we should scale up the newest replica set.
|
||||
if activeOrLatest := deploymentutil.FindActiveOrLatest(newRS, oldRSs); activeOrLatest != nil {
|
||||
if *(activeOrLatest.Spec.Replicas) == *(deployment.Spec.Replicas) {
|
||||
return nil
|
||||
}
|
||||
_, _, err := dc.scaleReplicaSetAndRecordEvent(activeOrLatest, *(deployment.Spec.Replicas), deployment)
|
||||
return err
|
||||
}
|
||||
|
||||
// If the new replica set is saturated, old replica sets should be fully scaled down.
|
||||
// This case handles replica set adoption during a saturated new replica set.
|
||||
if deploymentutil.IsSaturated(deployment, newRS) {
|
||||
for _, old := range controller.FilterActiveReplicaSets(oldRSs) {
|
||||
if _, _, err := dc.scaleReplicaSetAndRecordEvent(old, 0, deployment); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// There are old replica sets with pods and the new replica set is not saturated.
|
||||
// We need to proportionally scale all replica sets (new and old) in case of a
|
||||
// rolling deployment.
|
||||
if deploymentutil.IsRollingUpdate(deployment) {
|
||||
allRSs := controller.FilterActiveReplicaSets(append(oldRSs, newRS))
|
||||
allRSsReplicas := deploymentutil.GetReplicaCountForReplicaSets(allRSs)
|
||||
|
||||
allowedSize := int32(0)
|
||||
if *(deployment.Spec.Replicas) > 0 {
|
||||
allowedSize = *(deployment.Spec.Replicas) + deploymentutil.MaxSurge(*deployment)
|
||||
}
|
||||
|
||||
// Number of additional replicas that can be either added or removed from the total
|
||||
// replicas count. These replicas should be distributed proportionally to the active
|
||||
// replica sets.
|
||||
deploymentReplicasToAdd := allowedSize - allRSsReplicas
|
||||
|
||||
// The additional replicas should be distributed proportionally amongst the active
|
||||
// replica sets from the larger to the smaller in size replica set. Scaling direction
|
||||
// drives what happens in case we are trying to scale replica sets of the same size.
|
||||
// In such a case when scaling up, we should scale up newer replica sets first, and
|
||||
// when scaling down, we should scale down older replica sets first.
|
||||
var scalingOperation string
|
||||
switch {
|
||||
case deploymentReplicasToAdd > 0:
|
||||
sort.Sort(controller.ReplicaSetsBySizeNewer(allRSs))
|
||||
scalingOperation = "up"
|
||||
|
||||
case deploymentReplicasToAdd < 0:
|
||||
sort.Sort(controller.ReplicaSetsBySizeOlder(allRSs))
|
||||
scalingOperation = "down"
|
||||
}
|
||||
|
||||
// Iterate over all active replica sets and estimate proportions for each of them.
|
||||
// The absolute value of deploymentReplicasAdded should never exceed the absolute
|
||||
// value of deploymentReplicasToAdd.
|
||||
deploymentReplicasAdded := int32(0)
|
||||
nameToSize := make(map[string]int32)
|
||||
for i := range allRSs {
|
||||
rs := allRSs[i]
|
||||
|
||||
// Estimate proportions if we have replicas to add, otherwise simply populate
|
||||
// nameToSize with the current sizes for each replica set.
|
||||
if deploymentReplicasToAdd != 0 {
|
||||
proportion := deploymentutil.GetProportion(rs, *deployment, deploymentReplicasToAdd, deploymentReplicasAdded)
|
||||
|
||||
nameToSize[rs.Name] = *(rs.Spec.Replicas) + proportion
|
||||
deploymentReplicasAdded += proportion
|
||||
} else {
|
||||
nameToSize[rs.Name] = *(rs.Spec.Replicas)
|
||||
}
|
||||
}
|
||||
|
||||
// Update all replica sets
|
||||
for i := range allRSs {
|
||||
rs := allRSs[i]
|
||||
|
||||
// Add/remove any leftovers to the largest replica set.
|
||||
if i == 0 && deploymentReplicasToAdd != 0 {
|
||||
leftover := deploymentReplicasToAdd - deploymentReplicasAdded
|
||||
nameToSize[rs.Name] = nameToSize[rs.Name] + leftover
|
||||
if nameToSize[rs.Name] < 0 {
|
||||
nameToSize[rs.Name] = 0
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Use transactions when we have them.
|
||||
if _, _, err := dc.scaleReplicaSet(rs, nameToSize[rs.Name], deployment, scalingOperation); err != nil {
|
||||
// Return as soon as we fail, the deployment is requeued
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dc *DeploymentController) scaleReplicaSetAndRecordEvent(rs *apps.ReplicaSet, newScale int32, deployment *apps.Deployment) (bool, *apps.ReplicaSet, error) {
|
||||
// No need to scale
|
||||
if *(rs.Spec.Replicas) == newScale {
|
||||
return false, rs, nil
|
||||
}
|
||||
var scalingOperation string
|
||||
if *(rs.Spec.Replicas) < newScale {
|
||||
scalingOperation = "up"
|
||||
} else {
|
||||
scalingOperation = "down"
|
||||
}
|
||||
scaled, newRS, err := dc.scaleReplicaSet(rs, newScale, deployment, scalingOperation)
|
||||
return scaled, newRS, err
|
||||
}
|
||||
|
||||
func (dc *DeploymentController) scaleReplicaSet(rs *apps.ReplicaSet, newScale int32, deployment *apps.Deployment, scalingOperation string) (bool, *apps.ReplicaSet, error) {
|
||||
|
||||
sizeNeedsUpdate := *(rs.Spec.Replicas) != newScale
|
||||
|
||||
annotationsNeedUpdate := deploymentutil.ReplicasAnnotationsNeedUpdate(rs, *(deployment.Spec.Replicas), *(deployment.Spec.Replicas)+deploymentutil.MaxSurge(*deployment))
|
||||
|
||||
scaled := false
|
||||
var err error
|
||||
if sizeNeedsUpdate || annotationsNeedUpdate {
|
||||
rsCopy := rs.DeepCopy()
|
||||
*(rsCopy.Spec.Replicas) = newScale
|
||||
deploymentutil.SetReplicasAnnotations(rsCopy, *(deployment.Spec.Replicas), *(deployment.Spec.Replicas)+deploymentutil.MaxSurge(*deployment))
|
||||
rs, err = dc.client.AppsV1().ReplicaSets(rsCopy.Namespace).Update(rsCopy)
|
||||
if err == nil && sizeNeedsUpdate {
|
||||
scaled = true
|
||||
dc.eventRecorder.Eventf(deployment, v1.EventTypeNormal, "ScalingReplicaSet", "Scaled %s replica set %s to %d", scalingOperation, rs.Name, newScale)
|
||||
}
|
||||
}
|
||||
return scaled, rs, err
|
||||
}
|
||||
|
||||
// cleanupDeployment is responsible for cleaning up a deployment ie. retains all but the latest N old replica sets
|
||||
// where N=d.Spec.RevisionHistoryLimit. Old replica sets are older versions of the podtemplate of a deployment kept
|
||||
// around by default 1) for historical reasons and 2) for the ability to rollback a deployment.
|
||||
func (dc *DeploymentController) cleanupDeployment(oldRSs []*apps.ReplicaSet, deployment *apps.Deployment) error {
|
||||
if deployment.Spec.RevisionHistoryLimit == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Avoid deleting replica set with deletion timestamp set
|
||||
aliveFilter := func(rs *apps.ReplicaSet) bool {
|
||||
return rs != nil && rs.ObjectMeta.DeletionTimestamp == nil
|
||||
}
|
||||
cleanableRSes := controller.FilterReplicaSets(oldRSs, aliveFilter)
|
||||
|
||||
diff := int32(len(cleanableRSes)) - *deployment.Spec.RevisionHistoryLimit
|
||||
if diff <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
sort.Sort(controller.ReplicaSetsByCreationTimestamp(cleanableRSes))
|
||||
glog.V(4).Infof("Looking to cleanup old replica sets for deployment %q", deployment.Name)
|
||||
|
||||
for i := int32(0); i < diff; i++ {
|
||||
rs := cleanableRSes[i]
|
||||
// Avoid delete replica set with non-zero replica counts
|
||||
if rs.Status.Replicas != 0 || *(rs.Spec.Replicas) != 0 || rs.Generation > rs.Status.ObservedGeneration || rs.DeletionTimestamp != nil {
|
||||
continue
|
||||
}
|
||||
glog.V(4).Infof("Trying to cleanup replica set %q for deployment %q", rs.Name, deployment.Name)
|
||||
if err := dc.client.AppsV1().ReplicaSets(rs.Namespace).Delete(rs.Name, nil); err != nil && !errors.IsNotFound(err) {
|
||||
// Return error instead of aggregating and continuing DELETEs on the theory
|
||||
// that we may be overloading the api server.
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// syncDeploymentStatus checks if the status is up-to-date and sync it if necessary
|
||||
func (dc *DeploymentController) syncDeploymentStatus(allRSs []*apps.ReplicaSet, newRS *apps.ReplicaSet, d *apps.Deployment) error {
|
||||
newStatus := calculateStatus(allRSs, newRS, d)
|
||||
|
||||
if reflect.DeepEqual(d.Status, newStatus) {
|
||||
return nil
|
||||
}
|
||||
|
||||
newDeployment := d
|
||||
newDeployment.Status = newStatus
|
||||
_, err := dc.client.AppsV1().Deployments(newDeployment.Namespace).UpdateStatus(newDeployment)
|
||||
return err
|
||||
}
|
||||
|
||||
// calculateStatus calculates the latest status for the provided deployment by looking into the provided replica sets.
|
||||
func calculateStatus(allRSs []*apps.ReplicaSet, newRS *apps.ReplicaSet, deployment *apps.Deployment) apps.DeploymentStatus {
|
||||
availableReplicas := deploymentutil.GetAvailableReplicaCountForReplicaSets(allRSs)
|
||||
totalReplicas := deploymentutil.GetReplicaCountForReplicaSets(allRSs)
|
||||
unavailableReplicas := totalReplicas - availableReplicas
|
||||
// If unavailableReplicas is negative, then that means the Deployment has more available replicas running than
|
||||
// desired, e.g. whenever it scales down. In such a case we should simply default unavailableReplicas to zero.
|
||||
if unavailableReplicas < 0 {
|
||||
unavailableReplicas = 0
|
||||
}
|
||||
|
||||
status := apps.DeploymentStatus{
|
||||
// TODO: Ensure that if we start retrying status updates, we won't pick up a new Generation value.
|
||||
ObservedGeneration: deployment.Generation,
|
||||
Replicas: deploymentutil.GetActualReplicaCountForReplicaSets(allRSs),
|
||||
UpdatedReplicas: deploymentutil.GetActualReplicaCountForReplicaSets([]*apps.ReplicaSet{newRS}),
|
||||
ReadyReplicas: deploymentutil.GetReadyReplicaCountForReplicaSets(allRSs),
|
||||
AvailableReplicas: availableReplicas,
|
||||
UnavailableReplicas: unavailableReplicas,
|
||||
CollisionCount: deployment.Status.CollisionCount,
|
||||
}
|
||||
|
||||
// Copy conditions one by one so we won't mutate the original object.
|
||||
conditions := deployment.Status.Conditions
|
||||
for i := range conditions {
|
||||
status.Conditions = append(status.Conditions, conditions[i])
|
||||
}
|
||||
|
||||
if availableReplicas >= *(deployment.Spec.Replicas)-deploymentutil.MaxUnavailable(*deployment) {
|
||||
minAvailability := deploymentutil.NewDeploymentCondition(apps.DeploymentAvailable, v1.ConditionTrue, deploymentutil.MinimumReplicasAvailable, "Deployment has minimum availability.")
|
||||
deploymentutil.SetDeploymentCondition(&status, *minAvailability)
|
||||
} else {
|
||||
noMinAvailability := deploymentutil.NewDeploymentCondition(apps.DeploymentAvailable, v1.ConditionFalse, deploymentutil.MinimumReplicasUnavailable, "Deployment does not have minimum availability.")
|
||||
deploymentutil.SetDeploymentCondition(&status, *noMinAvailability)
|
||||
}
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
// isScalingEvent checks whether the provided deployment has been updated with a scaling event
|
||||
// by looking at the desired-replicas annotation in the active replica sets of the deployment.
|
||||
//
|
||||
// rsList should come from getReplicaSetsForDeployment(d).
|
||||
// podMap should come from getPodMapForDeployment(d, rsList).
|
||||
func (dc *DeploymentController) isScalingEvent(d *apps.Deployment, rsList []*apps.ReplicaSet, podMap map[types.UID]*v1.PodList) (bool, error) {
|
||||
newRS, oldRSs, err := dc.getAllReplicaSetsAndSyncRevision(d, rsList, podMap, false)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
allRSs := append(oldRSs, newRS)
|
||||
for _, rs := range controller.FilterActiveReplicaSets(allRSs) {
|
||||
desired, ok := deploymentutil.GetDesiredReplicasAnnotation(rs)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if desired != *(d.Spec.Replicas) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
435
vendor/k8s.io/kubernetes/pkg/controller/deployment/sync_test.go
generated
vendored
435
vendor/k8s.io/kubernetes/pkg/controller/deployment/sync_test.go
generated
vendored
@ -1,435 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package deployment
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
apps "k8s.io/api/apps/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
testclient "k8s.io/client-go/testing"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"k8s.io/kubernetes/pkg/controller"
|
||||
deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util"
|
||||
)
|
||||
|
||||
func intOrStrP(val int) *intstr.IntOrString {
|
||||
intOrStr := intstr.FromInt(val)
|
||||
return &intOrStr
|
||||
}
|
||||
|
||||
func TestScale(t *testing.T) {
|
||||
newTimestamp := metav1.Date(2016, 5, 20, 2, 0, 0, 0, time.UTC)
|
||||
oldTimestamp := metav1.Date(2016, 5, 20, 1, 0, 0, 0, time.UTC)
|
||||
olderTimestamp := metav1.Date(2016, 5, 20, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
var updatedTemplate = func(replicas int) *apps.Deployment {
|
||||
d := newDeployment("foo", replicas, nil, nil, nil, map[string]string{"foo": "bar"})
|
||||
d.Spec.Template.Labels["another"] = "label"
|
||||
return d
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
deployment *apps.Deployment
|
||||
oldDeployment *apps.Deployment
|
||||
|
||||
newRS *apps.ReplicaSet
|
||||
oldRSs []*apps.ReplicaSet
|
||||
|
||||
expectedNew *apps.ReplicaSet
|
||||
expectedOld []*apps.ReplicaSet
|
||||
wasntUpdated map[string]bool
|
||||
|
||||
desiredReplicasAnnotations map[string]int32
|
||||
}{
|
||||
{
|
||||
name: "normal scaling event: 10 -> 12",
|
||||
deployment: newDeployment("foo", 12, nil, nil, nil, nil),
|
||||
oldDeployment: newDeployment("foo", 10, nil, nil, nil, nil),
|
||||
|
||||
newRS: rs("foo-v1", 10, nil, newTimestamp),
|
||||
oldRSs: []*apps.ReplicaSet{},
|
||||
|
||||
expectedNew: rs("foo-v1", 12, nil, newTimestamp),
|
||||
expectedOld: []*apps.ReplicaSet{},
|
||||
},
|
||||
{
|
||||
name: "normal scaling event: 10 -> 5",
|
||||
deployment: newDeployment("foo", 5, nil, nil, nil, nil),
|
||||
oldDeployment: newDeployment("foo", 10, nil, nil, nil, nil),
|
||||
|
||||
newRS: rs("foo-v1", 10, nil, newTimestamp),
|
||||
oldRSs: []*apps.ReplicaSet{},
|
||||
|
||||
expectedNew: rs("foo-v1", 5, nil, newTimestamp),
|
||||
expectedOld: []*apps.ReplicaSet{},
|
||||
},
|
||||
{
|
||||
name: "proportional scaling: 5 -> 10",
|
||||
deployment: newDeployment("foo", 10, nil, nil, nil, nil),
|
||||
oldDeployment: newDeployment("foo", 5, nil, nil, nil, nil),
|
||||
|
||||
newRS: rs("foo-v2", 2, nil, newTimestamp),
|
||||
oldRSs: []*apps.ReplicaSet{rs("foo-v1", 3, nil, oldTimestamp)},
|
||||
|
||||
expectedNew: rs("foo-v2", 4, nil, newTimestamp),
|
||||
expectedOld: []*apps.ReplicaSet{rs("foo-v1", 6, nil, oldTimestamp)},
|
||||
},
|
||||
{
|
||||
name: "proportional scaling: 5 -> 3",
|
||||
deployment: newDeployment("foo", 3, nil, nil, nil, nil),
|
||||
oldDeployment: newDeployment("foo", 5, nil, nil, nil, nil),
|
||||
|
||||
newRS: rs("foo-v2", 2, nil, newTimestamp),
|
||||
oldRSs: []*apps.ReplicaSet{rs("foo-v1", 3, nil, oldTimestamp)},
|
||||
|
||||
expectedNew: rs("foo-v2", 1, nil, newTimestamp),
|
||||
expectedOld: []*apps.ReplicaSet{rs("foo-v1", 2, nil, oldTimestamp)},
|
||||
},
|
||||
{
|
||||
name: "proportional scaling: 9 -> 4",
|
||||
deployment: newDeployment("foo", 4, nil, nil, nil, nil),
|
||||
oldDeployment: newDeployment("foo", 9, nil, nil, nil, nil),
|
||||
|
||||
newRS: rs("foo-v2", 8, nil, newTimestamp),
|
||||
oldRSs: []*apps.ReplicaSet{rs("foo-v1", 1, nil, oldTimestamp)},
|
||||
|
||||
expectedNew: rs("foo-v2", 4, nil, newTimestamp),
|
||||
expectedOld: []*apps.ReplicaSet{rs("foo-v1", 0, nil, oldTimestamp)},
|
||||
},
|
||||
{
|
||||
name: "proportional scaling: 7 -> 10",
|
||||
deployment: newDeployment("foo", 10, nil, nil, nil, nil),
|
||||
oldDeployment: newDeployment("foo", 7, nil, nil, nil, nil),
|
||||
|
||||
newRS: rs("foo-v3", 2, nil, newTimestamp),
|
||||
oldRSs: []*apps.ReplicaSet{rs("foo-v2", 3, nil, oldTimestamp), rs("foo-v1", 2, nil, olderTimestamp)},
|
||||
|
||||
expectedNew: rs("foo-v3", 3, nil, newTimestamp),
|
||||
expectedOld: []*apps.ReplicaSet{rs("foo-v2", 4, nil, oldTimestamp), rs("foo-v1", 3, nil, olderTimestamp)},
|
||||
},
|
||||
{
|
||||
name: "proportional scaling: 13 -> 8",
|
||||
deployment: newDeployment("foo", 8, nil, nil, nil, nil),
|
||||
oldDeployment: newDeployment("foo", 13, nil, nil, nil, nil),
|
||||
|
||||
newRS: rs("foo-v3", 2, nil, newTimestamp),
|
||||
oldRSs: []*apps.ReplicaSet{rs("foo-v2", 8, nil, oldTimestamp), rs("foo-v1", 3, nil, olderTimestamp)},
|
||||
|
||||
expectedNew: rs("foo-v3", 1, nil, newTimestamp),
|
||||
expectedOld: []*apps.ReplicaSet{rs("foo-v2", 5, nil, oldTimestamp), rs("foo-v1", 2, nil, olderTimestamp)},
|
||||
},
|
||||
// Scales up the new replica set.
|
||||
{
|
||||
name: "leftover distribution: 3 -> 4",
|
||||
deployment: newDeployment("foo", 4, nil, nil, nil, nil),
|
||||
oldDeployment: newDeployment("foo", 3, nil, nil, nil, nil),
|
||||
|
||||
newRS: rs("foo-v3", 1, nil, newTimestamp),
|
||||
oldRSs: []*apps.ReplicaSet{rs("foo-v2", 1, nil, oldTimestamp), rs("foo-v1", 1, nil, olderTimestamp)},
|
||||
|
||||
expectedNew: rs("foo-v3", 2, nil, newTimestamp),
|
||||
expectedOld: []*apps.ReplicaSet{rs("foo-v2", 1, nil, oldTimestamp), rs("foo-v1", 1, nil, olderTimestamp)},
|
||||
},
|
||||
// Scales down the older replica set.
|
||||
{
|
||||
name: "leftover distribution: 3 -> 2",
|
||||
deployment: newDeployment("foo", 2, nil, nil, nil, nil),
|
||||
oldDeployment: newDeployment("foo", 3, nil, nil, nil, nil),
|
||||
|
||||
newRS: rs("foo-v3", 1, nil, newTimestamp),
|
||||
oldRSs: []*apps.ReplicaSet{rs("foo-v2", 1, nil, oldTimestamp), rs("foo-v1", 1, nil, olderTimestamp)},
|
||||
|
||||
expectedNew: rs("foo-v3", 1, nil, newTimestamp),
|
||||
expectedOld: []*apps.ReplicaSet{rs("foo-v2", 1, nil, oldTimestamp), rs("foo-v1", 0, nil, olderTimestamp)},
|
||||
},
|
||||
// Scales up the latest replica set first.
|
||||
{
|
||||
name: "proportional scaling (no new rs): 4 -> 5",
|
||||
deployment: newDeployment("foo", 5, nil, nil, nil, nil),
|
||||
oldDeployment: newDeployment("foo", 4, nil, nil, nil, nil),
|
||||
|
||||
newRS: nil,
|
||||
oldRSs: []*apps.ReplicaSet{rs("foo-v2", 2, nil, oldTimestamp), rs("foo-v1", 2, nil, olderTimestamp)},
|
||||
|
||||
expectedNew: nil,
|
||||
expectedOld: []*apps.ReplicaSet{rs("foo-v2", 3, nil, oldTimestamp), rs("foo-v1", 2, nil, olderTimestamp)},
|
||||
},
|
||||
// Scales down to zero
|
||||
{
|
||||
name: "proportional scaling: 6 -> 0",
|
||||
deployment: newDeployment("foo", 0, nil, nil, nil, nil),
|
||||
oldDeployment: newDeployment("foo", 6, nil, nil, nil, nil),
|
||||
|
||||
newRS: rs("foo-v3", 3, nil, newTimestamp),
|
||||
oldRSs: []*apps.ReplicaSet{rs("foo-v2", 2, nil, oldTimestamp), rs("foo-v1", 1, nil, olderTimestamp)},
|
||||
|
||||
expectedNew: rs("foo-v3", 0, nil, newTimestamp),
|
||||
expectedOld: []*apps.ReplicaSet{rs("foo-v2", 0, nil, oldTimestamp), rs("foo-v1", 0, nil, olderTimestamp)},
|
||||
},
|
||||
// Scales up from zero
|
||||
{
|
||||
name: "proportional scaling: 0 -> 6",
|
||||
deployment: newDeployment("foo", 6, nil, nil, nil, nil),
|
||||
oldDeployment: newDeployment("foo", 6, nil, nil, nil, nil),
|
||||
|
||||
newRS: rs("foo-v3", 0, nil, newTimestamp),
|
||||
oldRSs: []*apps.ReplicaSet{rs("foo-v2", 0, nil, oldTimestamp), rs("foo-v1", 0, nil, olderTimestamp)},
|
||||
|
||||
expectedNew: rs("foo-v3", 6, nil, newTimestamp),
|
||||
expectedOld: []*apps.ReplicaSet{rs("foo-v2", 0, nil, oldTimestamp), rs("foo-v1", 0, nil, olderTimestamp)},
|
||||
wasntUpdated: map[string]bool{"foo-v2": true, "foo-v1": true},
|
||||
},
|
||||
// Scenario: deployment.spec.replicas == 3 ( foo-v1.spec.replicas == foo-v2.spec.replicas == foo-v3.spec.replicas == 1 )
|
||||
// Deployment is scaled to 5. foo-v3.spec.replicas and foo-v2.spec.replicas should increment by 1 but foo-v2 fails to
|
||||
// update.
|
||||
{
|
||||
name: "failed rs update",
|
||||
deployment: newDeployment("foo", 5, nil, nil, nil, nil),
|
||||
oldDeployment: newDeployment("foo", 5, nil, nil, nil, nil),
|
||||
|
||||
newRS: rs("foo-v3", 2, nil, newTimestamp),
|
||||
oldRSs: []*apps.ReplicaSet{rs("foo-v2", 1, nil, oldTimestamp), rs("foo-v1", 1, nil, olderTimestamp)},
|
||||
|
||||
expectedNew: rs("foo-v3", 2, nil, newTimestamp),
|
||||
expectedOld: []*apps.ReplicaSet{rs("foo-v2", 2, nil, oldTimestamp), rs("foo-v1", 1, nil, olderTimestamp)},
|
||||
wasntUpdated: map[string]bool{"foo-v3": true, "foo-v1": true},
|
||||
|
||||
desiredReplicasAnnotations: map[string]int32{"foo-v2": int32(3)},
|
||||
},
|
||||
{
|
||||
name: "deployment with surge pods",
|
||||
deployment: newDeployment("foo", 20, nil, intOrStrP(2), nil, nil),
|
||||
oldDeployment: newDeployment("foo", 10, nil, intOrStrP(2), nil, nil),
|
||||
|
||||
newRS: rs("foo-v2", 6, nil, newTimestamp),
|
||||
oldRSs: []*apps.ReplicaSet{rs("foo-v1", 6, nil, oldTimestamp)},
|
||||
|
||||
expectedNew: rs("foo-v2", 11, nil, newTimestamp),
|
||||
expectedOld: []*apps.ReplicaSet{rs("foo-v1", 11, nil, oldTimestamp)},
|
||||
},
|
||||
{
|
||||
name: "change both surge and size",
|
||||
deployment: newDeployment("foo", 50, nil, intOrStrP(6), nil, nil),
|
||||
oldDeployment: newDeployment("foo", 10, nil, intOrStrP(3), nil, nil),
|
||||
|
||||
newRS: rs("foo-v2", 5, nil, newTimestamp),
|
||||
oldRSs: []*apps.ReplicaSet{rs("foo-v1", 8, nil, oldTimestamp)},
|
||||
|
||||
expectedNew: rs("foo-v2", 22, nil, newTimestamp),
|
||||
expectedOld: []*apps.ReplicaSet{rs("foo-v1", 34, nil, oldTimestamp)},
|
||||
},
|
||||
{
|
||||
name: "change both size and template",
|
||||
deployment: updatedTemplate(14),
|
||||
oldDeployment: newDeployment("foo", 10, nil, nil, nil, map[string]string{"foo": "bar"}),
|
||||
|
||||
newRS: nil,
|
||||
oldRSs: []*apps.ReplicaSet{rs("foo-v2", 7, nil, newTimestamp), rs("foo-v1", 3, nil, oldTimestamp)},
|
||||
|
||||
expectedNew: nil,
|
||||
expectedOld: []*apps.ReplicaSet{rs("foo-v2", 10, nil, newTimestamp), rs("foo-v1", 4, nil, oldTimestamp)},
|
||||
},
|
||||
{
|
||||
name: "saturated but broken new replica set does not affect old pods",
|
||||
deployment: newDeployment("foo", 2, nil, intOrStrP(1), intOrStrP(1), nil),
|
||||
oldDeployment: newDeployment("foo", 2, nil, intOrStrP(1), intOrStrP(1), nil),
|
||||
|
||||
newRS: func() *apps.ReplicaSet {
|
||||
rs := rs("foo-v2", 2, nil, newTimestamp)
|
||||
rs.Status.AvailableReplicas = 0
|
||||
return rs
|
||||
}(),
|
||||
oldRSs: []*apps.ReplicaSet{rs("foo-v1", 1, nil, oldTimestamp)},
|
||||
|
||||
expectedNew: rs("foo-v2", 2, nil, newTimestamp),
|
||||
expectedOld: []*apps.ReplicaSet{rs("foo-v1", 1, nil, oldTimestamp)},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
_ = olderTimestamp
|
||||
t.Log(test.name)
|
||||
fake := fake.Clientset{}
|
||||
dc := &DeploymentController{
|
||||
client: &fake,
|
||||
eventRecorder: &record.FakeRecorder{},
|
||||
}
|
||||
|
||||
if test.newRS != nil {
|
||||
desiredReplicas := *(test.oldDeployment.Spec.Replicas)
|
||||
if desired, ok := test.desiredReplicasAnnotations[test.newRS.Name]; ok {
|
||||
desiredReplicas = desired
|
||||
}
|
||||
deploymentutil.SetReplicasAnnotations(test.newRS, desiredReplicas, desiredReplicas+deploymentutil.MaxSurge(*test.oldDeployment))
|
||||
}
|
||||
for i := range test.oldRSs {
|
||||
rs := test.oldRSs[i]
|
||||
if rs == nil {
|
||||
continue
|
||||
}
|
||||
desiredReplicas := *(test.oldDeployment.Spec.Replicas)
|
||||
if desired, ok := test.desiredReplicasAnnotations[rs.Name]; ok {
|
||||
desiredReplicas = desired
|
||||
}
|
||||
deploymentutil.SetReplicasAnnotations(rs, desiredReplicas, desiredReplicas+deploymentutil.MaxSurge(*test.oldDeployment))
|
||||
}
|
||||
|
||||
if err := dc.scale(test.deployment, test.newRS, test.oldRSs); err != nil {
|
||||
t.Errorf("%s: unexpected error: %v", test.name, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Construct the nameToSize map that will hold all the sizes we got our of tests
|
||||
// Skip updating the map if the replica set wasn't updated since there will be
|
||||
// no update action for it.
|
||||
nameToSize := make(map[string]int32)
|
||||
if test.newRS != nil {
|
||||
nameToSize[test.newRS.Name] = *(test.newRS.Spec.Replicas)
|
||||
}
|
||||
for i := range test.oldRSs {
|
||||
rs := test.oldRSs[i]
|
||||
nameToSize[rs.Name] = *(rs.Spec.Replicas)
|
||||
}
|
||||
// Get all the UPDATE actions and update nameToSize with all the updated sizes.
|
||||
for _, action := range fake.Actions() {
|
||||
rs := action.(testclient.UpdateAction).GetObject().(*apps.ReplicaSet)
|
||||
if !test.wasntUpdated[rs.Name] {
|
||||
nameToSize[rs.Name] = *(rs.Spec.Replicas)
|
||||
}
|
||||
}
|
||||
|
||||
if test.expectedNew != nil && test.newRS != nil && *(test.expectedNew.Spec.Replicas) != nameToSize[test.newRS.Name] {
|
||||
t.Errorf("%s: expected new replicas: %d, got: %d", test.name, *(test.expectedNew.Spec.Replicas), nameToSize[test.newRS.Name])
|
||||
return
|
||||
}
|
||||
if len(test.expectedOld) != len(test.oldRSs) {
|
||||
t.Errorf("%s: expected %d old replica sets, got %d", test.name, len(test.expectedOld), len(test.oldRSs))
|
||||
return
|
||||
}
|
||||
for n := range test.oldRSs {
|
||||
rs := test.oldRSs[n]
|
||||
expected := test.expectedOld[n]
|
||||
if *(expected.Spec.Replicas) != nameToSize[rs.Name] {
|
||||
t.Errorf("%s: expected old (%s) replicas: %d, got: %d", test.name, rs.Name, *(expected.Spec.Replicas), nameToSize[rs.Name])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeploymentController_cleanupDeployment(t *testing.T) {
|
||||
selector := map[string]string{"foo": "bar"}
|
||||
alreadyDeleted := newRSWithStatus("foo-1", 0, 0, selector)
|
||||
now := metav1.Now()
|
||||
alreadyDeleted.DeletionTimestamp = &now
|
||||
|
||||
tests := []struct {
|
||||
oldRSs []*apps.ReplicaSet
|
||||
revisionHistoryLimit int32
|
||||
expectedDeletions int
|
||||
}{
|
||||
{
|
||||
oldRSs: []*apps.ReplicaSet{
|
||||
newRSWithStatus("foo-1", 0, 0, selector),
|
||||
newRSWithStatus("foo-2", 0, 0, selector),
|
||||
newRSWithStatus("foo-3", 0, 0, selector),
|
||||
},
|
||||
revisionHistoryLimit: 1,
|
||||
expectedDeletions: 2,
|
||||
},
|
||||
{
|
||||
// Only delete the replica set with Spec.Replicas = Status.Replicas = 0.
|
||||
oldRSs: []*apps.ReplicaSet{
|
||||
newRSWithStatus("foo-1", 0, 0, selector),
|
||||
newRSWithStatus("foo-2", 0, 1, selector),
|
||||
newRSWithStatus("foo-3", 1, 0, selector),
|
||||
newRSWithStatus("foo-4", 1, 1, selector),
|
||||
},
|
||||
revisionHistoryLimit: 0,
|
||||
expectedDeletions: 1,
|
||||
},
|
||||
|
||||
{
|
||||
oldRSs: []*apps.ReplicaSet{
|
||||
newRSWithStatus("foo-1", 0, 0, selector),
|
||||
newRSWithStatus("foo-2", 0, 0, selector),
|
||||
},
|
||||
revisionHistoryLimit: 0,
|
||||
expectedDeletions: 2,
|
||||
},
|
||||
{
|
||||
oldRSs: []*apps.ReplicaSet{
|
||||
newRSWithStatus("foo-1", 1, 1, selector),
|
||||
newRSWithStatus("foo-2", 1, 1, selector),
|
||||
},
|
||||
revisionHistoryLimit: 0,
|
||||
expectedDeletions: 0,
|
||||
},
|
||||
{
|
||||
oldRSs: []*apps.ReplicaSet{
|
||||
alreadyDeleted,
|
||||
},
|
||||
revisionHistoryLimit: 0,
|
||||
expectedDeletions: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for i := range tests {
|
||||
test := tests[i]
|
||||
t.Logf("scenario %d", i)
|
||||
|
||||
fake := &fake.Clientset{}
|
||||
informers := informers.NewSharedInformerFactory(fake, controller.NoResyncPeriodFunc())
|
||||
controller, err := NewDeploymentController(informers.Apps().V1().Deployments(), informers.Apps().V1().ReplicaSets(), informers.Core().V1().Pods(), fake)
|
||||
if err != nil {
|
||||
t.Fatalf("error creating Deployment controller: %v", err)
|
||||
}
|
||||
|
||||
controller.eventRecorder = &record.FakeRecorder{}
|
||||
controller.dListerSynced = alwaysReady
|
||||
controller.rsListerSynced = alwaysReady
|
||||
controller.podListerSynced = alwaysReady
|
||||
for _, rs := range test.oldRSs {
|
||||
informers.Apps().V1().ReplicaSets().Informer().GetIndexer().Add(rs)
|
||||
}
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
defer close(stopCh)
|
||||
informers.Start(stopCh)
|
||||
|
||||
d := newDeployment("foo", 1, &test.revisionHistoryLimit, nil, nil, map[string]string{"foo": "bar"})
|
||||
controller.cleanupDeployment(test.oldRSs, d)
|
||||
|
||||
gotDeletions := 0
|
||||
for _, action := range fake.Actions() {
|
||||
if "delete" == action.GetVerb() {
|
||||
gotDeletions++
|
||||
}
|
||||
}
|
||||
if gotDeletions != test.expectedDeletions {
|
||||
t.Errorf("expect %v old replica sets been deleted, but got %v", test.expectedDeletions, gotDeletions)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
75
vendor/k8s.io/kubernetes/pkg/controller/deployment/util/BUILD
generated
vendored
75
vendor/k8s.io/kubernetes/pkg/controller/deployment/util/BUILD
generated
vendored
@ -1,75 +0,0 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"deployment_util.go",
|
||||
"pod_util.go",
|
||||
"replicaset_util.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/pkg/controller/deployment/util",
|
||||
deps = [
|
||||
"//pkg/apis/extensions:go_default_library",
|
||||
"//pkg/controller:go_default_library",
|
||||
"//pkg/util/labels:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/k8s.io/api/apps/v1:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes/typed/apps/v1:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/client-go/listers/apps/v1:go_default_library",
|
||||
"//vendor/k8s.io/client-go/listers/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/client-go/util/integer:go_default_library",
|
||||
"//vendor/k8s.io/client-go/util/retry:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"deployment_util_test.go",
|
||||
"hash_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//pkg/controller:go_default_library",
|
||||
"//pkg/util/hash:go_default_library",
|
||||
"//vendor/k8s.io/api/apps/v1:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/storage/names:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes/fake:go_default_library",
|
||||
"//vendor/k8s.io/client-go/testing:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
920
vendor/k8s.io/kubernetes/pkg/controller/deployment/util/deployment_util.go
generated
vendored
920
vendor/k8s.io/kubernetes/pkg/controller/deployment/util/deployment_util.go
generated
vendored
@ -1,920 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
apps "k8s.io/api/apps/v1"
|
||||
"k8s.io/api/core/v1"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
intstrutil "k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
appsclient "k8s.io/client-go/kubernetes/typed/apps/v1"
|
||||
"k8s.io/client-go/util/integer"
|
||||
internalextensions "k8s.io/kubernetes/pkg/apis/extensions"
|
||||
"k8s.io/kubernetes/pkg/controller"
|
||||
labelsutil "k8s.io/kubernetes/pkg/util/labels"
|
||||
)
|
||||
|
||||
const (
|
||||
// RevisionAnnotation is the revision annotation of a deployment's replica sets which records its rollout sequence
|
||||
RevisionAnnotation = "deployment.kubernetes.io/revision"
|
||||
// RevisionHistoryAnnotation maintains the history of all old revisions that a replica set has served for a deployment.
|
||||
RevisionHistoryAnnotation = "deployment.kubernetes.io/revision-history"
|
||||
// DesiredReplicasAnnotation is the desired replicas for a deployment recorded as an annotation
|
||||
// in its replica sets. Helps in separating scaling events from the rollout process and for
|
||||
// determining if the new replica set for a deployment is really saturated.
|
||||
DesiredReplicasAnnotation = "deployment.kubernetes.io/desired-replicas"
|
||||
// MaxReplicasAnnotation is the maximum replicas a deployment can have at a given point, which
|
||||
// is deployment.spec.replicas + maxSurge. Used by the underlying replica sets to estimate their
|
||||
// proportions in case the deployment has surge replicas.
|
||||
MaxReplicasAnnotation = "deployment.kubernetes.io/max-replicas"
|
||||
|
||||
// RollbackRevisionNotFound is not found rollback event reason
|
||||
RollbackRevisionNotFound = "DeploymentRollbackRevisionNotFound"
|
||||
// RollbackTemplateUnchanged is the template unchanged rollback event reason
|
||||
RollbackTemplateUnchanged = "DeploymentRollbackTemplateUnchanged"
|
||||
// RollbackDone is the done rollback event reason
|
||||
RollbackDone = "DeploymentRollback"
|
||||
// Reasons for deployment conditions
|
||||
//
|
||||
// Progressing:
|
||||
//
|
||||
// ReplicaSetUpdatedReason is added in a deployment when one of its replica sets is updated as part
|
||||
// of the rollout process.
|
||||
ReplicaSetUpdatedReason = "ReplicaSetUpdated"
|
||||
// FailedRSCreateReason is added in a deployment when it cannot create a new replica set.
|
||||
FailedRSCreateReason = "ReplicaSetCreateError"
|
||||
// NewReplicaSetReason is added in a deployment when it creates a new replica set.
|
||||
NewReplicaSetReason = "NewReplicaSetCreated"
|
||||
// FoundNewRSReason is added in a deployment when it adopts an existing replica set.
|
||||
FoundNewRSReason = "FoundNewReplicaSet"
|
||||
// NewRSAvailableReason is added in a deployment when its newest replica set 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.
|
||||
NewRSAvailableReason = "NewReplicaSetAvailable"
|
||||
// TimedOutReason is added in a deployment when its newest replica set fails to show any progress
|
||||
// within the given deadline (progressDeadlineSeconds).
|
||||
TimedOutReason = "ProgressDeadlineExceeded"
|
||||
// PausedDeployReason is added in a deployment when it is paused. Lack of progress shouldn't be
|
||||
// estimated once a deployment is paused.
|
||||
PausedDeployReason = "DeploymentPaused"
|
||||
// ResumedDeployReason is added in a deployment when it is resumed. Useful for not failing accidentally
|
||||
// deployments that paused amidst a rollout and are bounded by a deadline.
|
||||
ResumedDeployReason = "DeploymentResumed"
|
||||
//
|
||||
// Available:
|
||||
//
|
||||
// MinimumReplicasAvailable is added in a deployment when it has its minimum replicas required available.
|
||||
MinimumReplicasAvailable = "MinimumReplicasAvailable"
|
||||
// MinimumReplicasUnavailable is added in a deployment when it doesn't have the minimum required replicas
|
||||
// available.
|
||||
MinimumReplicasUnavailable = "MinimumReplicasUnavailable"
|
||||
)
|
||||
|
||||
// NewDeploymentCondition creates a new deployment condition.
|
||||
func NewDeploymentCondition(condType apps.DeploymentConditionType, status v1.ConditionStatus, reason, message string) *apps.DeploymentCondition {
|
||||
return &apps.DeploymentCondition{
|
||||
Type: condType,
|
||||
Status: status,
|
||||
LastUpdateTime: metav1.Now(),
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: reason,
|
||||
Message: message,
|
||||
}
|
||||
}
|
||||
|
||||
// GetDeploymentCondition returns the condition with the provided type.
|
||||
func GetDeploymentCondition(status apps.DeploymentStatus, condType apps.DeploymentConditionType) *apps.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 *apps.DeploymentStatus, condition apps.DeploymentCondition) {
|
||||
currentCond := GetDeploymentCondition(*status, condition.Type)
|
||||
if currentCond != nil && currentCond.Status == condition.Status && currentCond.Reason == condition.Reason {
|
||||
return
|
||||
}
|
||||
// Do not update lastTransitionTime if the status of the condition doesn't change.
|
||||
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 *apps.DeploymentStatus, condType apps.DeploymentConditionType) {
|
||||
status.Conditions = filterOutCondition(status.Conditions, condType)
|
||||
}
|
||||
|
||||
// filterOutCondition returns a new slice of deployment conditions without conditions with the provided type.
|
||||
func filterOutCondition(conditions []apps.DeploymentCondition, condType apps.DeploymentConditionType) []apps.DeploymentCondition {
|
||||
var newConditions []apps.DeploymentCondition
|
||||
for _, c := range conditions {
|
||||
if c.Type == condType {
|
||||
continue
|
||||
}
|
||||
newConditions = append(newConditions, c)
|
||||
}
|
||||
return newConditions
|
||||
}
|
||||
|
||||
// ReplicaSetToDeploymentCondition converts a replica set condition into a deployment condition.
|
||||
// Useful for promoting replica set failure conditions into deployments.
|
||||
func ReplicaSetToDeploymentCondition(cond apps.ReplicaSetCondition) apps.DeploymentCondition {
|
||||
return apps.DeploymentCondition{
|
||||
Type: apps.DeploymentConditionType(cond.Type),
|
||||
Status: cond.Status,
|
||||
LastTransitionTime: cond.LastTransitionTime,
|
||||
LastUpdateTime: cond.LastTransitionTime,
|
||||
Reason: cond.Reason,
|
||||
Message: cond.Message,
|
||||
}
|
||||
}
|
||||
|
||||
// SetDeploymentRevision updates the revision for a deployment.
|
||||
func SetDeploymentRevision(deployment *apps.Deployment, revision string) bool {
|
||||
updated := false
|
||||
|
||||
if deployment.Annotations == nil {
|
||||
deployment.Annotations = make(map[string]string)
|
||||
}
|
||||
if deployment.Annotations[RevisionAnnotation] != revision {
|
||||
deployment.Annotations[RevisionAnnotation] = revision
|
||||
updated = true
|
||||
}
|
||||
|
||||
return updated
|
||||
}
|
||||
|
||||
// MaxRevision finds the highest revision in the replica sets
|
||||
func MaxRevision(allRSs []*apps.ReplicaSet) int64 {
|
||||
max := int64(0)
|
||||
for _, rs := range allRSs {
|
||||
if v, err := Revision(rs); err != nil {
|
||||
// Skip the replica sets when it failed to parse their revision information
|
||||
glog.V(4).Infof("Error: %v. Couldn't parse revision for replica set %#v, deployment controller will skip it when reconciling revisions.", err, rs)
|
||||
} else if v > max {
|
||||
max = v
|
||||
}
|
||||
}
|
||||
return max
|
||||
}
|
||||
|
||||
// LastRevision finds the second max revision number in all replica sets (the last revision)
|
||||
func LastRevision(allRSs []*apps.ReplicaSet) int64 {
|
||||
max, secMax := int64(0), int64(0)
|
||||
for _, rs := range allRSs {
|
||||
if v, err := Revision(rs); err != nil {
|
||||
// Skip the replica sets when it failed to parse their revision information
|
||||
glog.V(4).Infof("Error: %v. Couldn't parse revision for replica set %#v, deployment controller will skip it when reconciling revisions.", err, rs)
|
||||
} else if v >= max {
|
||||
secMax = max
|
||||
max = v
|
||||
} else if v > secMax {
|
||||
secMax = v
|
||||
}
|
||||
}
|
||||
return secMax
|
||||
}
|
||||
|
||||
// Revision returns the revision number of the input object.
|
||||
func Revision(obj runtime.Object) (int64, error) {
|
||||
acc, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
v, ok := acc.GetAnnotations()[RevisionAnnotation]
|
||||
if !ok {
|
||||
return 0, nil
|
||||
}
|
||||
return strconv.ParseInt(v, 10, 64)
|
||||
}
|
||||
|
||||
// SetNewReplicaSetAnnotations sets new replica set's annotations appropriately by updating its revision and
|
||||
// copying required deployment annotations to it; it returns true if replica set's annotation is changed.
|
||||
func SetNewReplicaSetAnnotations(deployment *apps.Deployment, newRS *apps.ReplicaSet, newRevision string, exists bool) bool {
|
||||
// First, copy deployment's annotations (except for apply and revision annotations)
|
||||
annotationChanged := copyDeploymentAnnotationsToReplicaSet(deployment, newRS)
|
||||
// Then, update replica set's revision annotation
|
||||
if newRS.Annotations == nil {
|
||||
newRS.Annotations = make(map[string]string)
|
||||
}
|
||||
oldRevision, ok := newRS.Annotations[RevisionAnnotation]
|
||||
// The newRS's revision should be the greatest among all RSes. Usually, its revision number is newRevision (the max revision number
|
||||
// of all old RSes + 1). However, it's possible that some of the old RSes are deleted after the newRS revision being updated, and
|
||||
// newRevision becomes smaller than newRS's revision. We should only update newRS revision when it's smaller than newRevision.
|
||||
|
||||
oldRevisionInt, err := strconv.ParseInt(oldRevision, 10, 64)
|
||||
if err != nil {
|
||||
if oldRevision != "" {
|
||||
glog.Warningf("Updating replica set revision OldRevision not int %s", err)
|
||||
return false
|
||||
}
|
||||
//If the RS annotation is empty then initialise it to 0
|
||||
oldRevisionInt = 0
|
||||
}
|
||||
newRevisionInt, err := strconv.ParseInt(newRevision, 10, 64)
|
||||
if err != nil {
|
||||
glog.Warningf("Updating replica set revision NewRevision not int %s", err)
|
||||
return false
|
||||
}
|
||||
if oldRevisionInt < newRevisionInt {
|
||||
newRS.Annotations[RevisionAnnotation] = newRevision
|
||||
annotationChanged = true
|
||||
glog.V(4).Infof("Updating replica set %q revision to %s", newRS.Name, newRevision)
|
||||
}
|
||||
// If a revision annotation already existed and this replica set was updated with a new revision
|
||||
// then that means we are rolling back to this replica set. We need to preserve the old revisions
|
||||
// for historical information.
|
||||
if ok && annotationChanged {
|
||||
revisionHistoryAnnotation := newRS.Annotations[RevisionHistoryAnnotation]
|
||||
oldRevisions := strings.Split(revisionHistoryAnnotation, ",")
|
||||
if len(oldRevisions[0]) == 0 {
|
||||
newRS.Annotations[RevisionHistoryAnnotation] = oldRevision
|
||||
} else {
|
||||
oldRevisions = append(oldRevisions, oldRevision)
|
||||
newRS.Annotations[RevisionHistoryAnnotation] = strings.Join(oldRevisions, ",")
|
||||
}
|
||||
}
|
||||
// If the new replica set is about to be created, we need to add replica annotations to it.
|
||||
if !exists && SetReplicasAnnotations(newRS, *(deployment.Spec.Replicas), *(deployment.Spec.Replicas)+MaxSurge(*deployment)) {
|
||||
annotationChanged = true
|
||||
}
|
||||
return annotationChanged
|
||||
}
|
||||
|
||||
var annotationsToSkip = map[string]bool{
|
||||
v1.LastAppliedConfigAnnotation: true,
|
||||
RevisionAnnotation: true,
|
||||
RevisionHistoryAnnotation: true,
|
||||
DesiredReplicasAnnotation: true,
|
||||
MaxReplicasAnnotation: true,
|
||||
apps.DeprecatedRollbackTo: true,
|
||||
}
|
||||
|
||||
// skipCopyAnnotation returns true if we should skip copying the annotation with the given annotation key
|
||||
// TODO: How to decide which annotations should / should not be copied?
|
||||
// See https://github.com/kubernetes/kubernetes/pull/20035#issuecomment-179558615
|
||||
func skipCopyAnnotation(key string) bool {
|
||||
return annotationsToSkip[key]
|
||||
}
|
||||
|
||||
// copyDeploymentAnnotationsToReplicaSet copies deployment's annotations to replica set's annotations,
|
||||
// and returns true if replica set's annotation is changed.
|
||||
// Note that apply and revision annotations are not copied.
|
||||
func copyDeploymentAnnotationsToReplicaSet(deployment *apps.Deployment, rs *apps.ReplicaSet) bool {
|
||||
rsAnnotationsChanged := false
|
||||
if rs.Annotations == nil {
|
||||
rs.Annotations = make(map[string]string)
|
||||
}
|
||||
for k, v := range deployment.Annotations {
|
||||
// newRS revision is updated automatically in getNewReplicaSet, and the deployment's revision number is then updated
|
||||
// by copying its newRS revision number. We should not copy deployment's revision to its newRS, since the update of
|
||||
// deployment revision number may fail (revision becomes stale) and the revision number in newRS is more reliable.
|
||||
if skipCopyAnnotation(k) || rs.Annotations[k] == v {
|
||||
continue
|
||||
}
|
||||
rs.Annotations[k] = v
|
||||
rsAnnotationsChanged = true
|
||||
}
|
||||
return rsAnnotationsChanged
|
||||
}
|
||||
|
||||
// SetDeploymentAnnotationsTo sets deployment's annotations as given RS's annotations.
|
||||
// This action should be done if and only if the deployment is rolling back to this rs.
|
||||
// Note that apply and revision annotations are not changed.
|
||||
func SetDeploymentAnnotationsTo(deployment *apps.Deployment, rollbackToRS *apps.ReplicaSet) {
|
||||
deployment.Annotations = getSkippedAnnotations(deployment.Annotations)
|
||||
for k, v := range rollbackToRS.Annotations {
|
||||
if !skipCopyAnnotation(k) {
|
||||
deployment.Annotations[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getSkippedAnnotations(annotations map[string]string) map[string]string {
|
||||
skippedAnnotations := make(map[string]string)
|
||||
for k, v := range annotations {
|
||||
if skipCopyAnnotation(k) {
|
||||
skippedAnnotations[k] = v
|
||||
}
|
||||
}
|
||||
return skippedAnnotations
|
||||
}
|
||||
|
||||
// FindActiveOrLatest returns the only active or the latest replica set in case there is at most one active
|
||||
// replica set. If there are more active replica sets, then we should proportionally scale them.
|
||||
func FindActiveOrLatest(newRS *apps.ReplicaSet, oldRSs []*apps.ReplicaSet) *apps.ReplicaSet {
|
||||
if newRS == nil && len(oldRSs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
sort.Sort(sort.Reverse(controller.ReplicaSetsByCreationTimestamp(oldRSs)))
|
||||
allRSs := controller.FilterActiveReplicaSets(append(oldRSs, newRS))
|
||||
|
||||
switch len(allRSs) {
|
||||
case 0:
|
||||
// If there is no active replica set then we should return the newest.
|
||||
if newRS != nil {
|
||||
return newRS
|
||||
}
|
||||
return oldRSs[0]
|
||||
case 1:
|
||||
return allRSs[0]
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// GetDesiredReplicasAnnotation returns the number of desired replicas
|
||||
func GetDesiredReplicasAnnotation(rs *apps.ReplicaSet) (int32, bool) {
|
||||
return getIntFromAnnotation(rs, DesiredReplicasAnnotation)
|
||||
}
|
||||
|
||||
func getMaxReplicasAnnotation(rs *apps.ReplicaSet) (int32, bool) {
|
||||
return getIntFromAnnotation(rs, MaxReplicasAnnotation)
|
||||
}
|
||||
|
||||
func getIntFromAnnotation(rs *apps.ReplicaSet, annotationKey string) (int32, bool) {
|
||||
annotationValue, ok := rs.Annotations[annotationKey]
|
||||
if !ok {
|
||||
return int32(0), false
|
||||
}
|
||||
intValue, err := strconv.Atoi(annotationValue)
|
||||
if err != nil {
|
||||
glog.V(2).Infof("Cannot convert the value %q with annotation key %q for the replica set %q", annotationValue, annotationKey, rs.Name)
|
||||
return int32(0), false
|
||||
}
|
||||
return int32(intValue), true
|
||||
}
|
||||
|
||||
// SetReplicasAnnotations sets the desiredReplicas and maxReplicas into the annotations
|
||||
func SetReplicasAnnotations(rs *apps.ReplicaSet, desiredReplicas, maxReplicas int32) bool {
|
||||
updated := false
|
||||
if rs.Annotations == nil {
|
||||
rs.Annotations = make(map[string]string)
|
||||
}
|
||||
desiredString := fmt.Sprintf("%d", desiredReplicas)
|
||||
if hasString := rs.Annotations[DesiredReplicasAnnotation]; hasString != desiredString {
|
||||
rs.Annotations[DesiredReplicasAnnotation] = desiredString
|
||||
updated = true
|
||||
}
|
||||
maxString := fmt.Sprintf("%d", maxReplicas)
|
||||
if hasString := rs.Annotations[MaxReplicasAnnotation]; hasString != maxString {
|
||||
rs.Annotations[MaxReplicasAnnotation] = maxString
|
||||
updated = true
|
||||
}
|
||||
return updated
|
||||
}
|
||||
|
||||
// AnnotationsNeedUpdate return true if ReplicasAnnotations need to be updated
|
||||
func ReplicasAnnotationsNeedUpdate(rs *apps.ReplicaSet, desiredReplicas, maxReplicas int32) bool {
|
||||
if rs.Annotations == nil {
|
||||
return true
|
||||
}
|
||||
desiredString := fmt.Sprintf("%d", desiredReplicas)
|
||||
if hasString := rs.Annotations[DesiredReplicasAnnotation]; hasString != desiredString {
|
||||
return true
|
||||
}
|
||||
maxString := fmt.Sprintf("%d", maxReplicas)
|
||||
if hasString := rs.Annotations[MaxReplicasAnnotation]; hasString != maxString {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MaxUnavailable returns the maximum unavailable pods a rolling deployment can take.
|
||||
func MaxUnavailable(deployment apps.Deployment) int32 {
|
||||
if !IsRollingUpdate(&deployment) || *(deployment.Spec.Replicas) == 0 {
|
||||
return int32(0)
|
||||
}
|
||||
// Error caught by validation
|
||||
_, maxUnavailable, _ := ResolveFenceposts(deployment.Spec.Strategy.RollingUpdate.MaxSurge, deployment.Spec.Strategy.RollingUpdate.MaxUnavailable, *(deployment.Spec.Replicas))
|
||||
if maxUnavailable > *deployment.Spec.Replicas {
|
||||
return *deployment.Spec.Replicas
|
||||
}
|
||||
return maxUnavailable
|
||||
}
|
||||
|
||||
// MinAvailable returns the minimum available pods of a given deployment
|
||||
func MinAvailable(deployment *apps.Deployment) int32 {
|
||||
if !IsRollingUpdate(deployment) {
|
||||
return int32(0)
|
||||
}
|
||||
return *(deployment.Spec.Replicas) - MaxUnavailable(*deployment)
|
||||
}
|
||||
|
||||
// MaxSurge returns the maximum surge pods a rolling deployment can take.
|
||||
func MaxSurge(deployment apps.Deployment) int32 {
|
||||
if !IsRollingUpdate(&deployment) {
|
||||
return int32(0)
|
||||
}
|
||||
// Error caught by validation
|
||||
maxSurge, _, _ := ResolveFenceposts(deployment.Spec.Strategy.RollingUpdate.MaxSurge, deployment.Spec.Strategy.RollingUpdate.MaxUnavailable, *(deployment.Spec.Replicas))
|
||||
return maxSurge
|
||||
}
|
||||
|
||||
// GetProportion will estimate the proportion for the provided replica set using 1. the current size
|
||||
// of the parent deployment, 2. the replica count that needs be added on the replica sets of the
|
||||
// deployment, and 3. the total replicas added in the replica sets of the deployment so far.
|
||||
func GetProportion(rs *apps.ReplicaSet, d apps.Deployment, deploymentReplicasToAdd, deploymentReplicasAdded int32) int32 {
|
||||
if rs == nil || *(rs.Spec.Replicas) == 0 || deploymentReplicasToAdd == 0 || deploymentReplicasToAdd == deploymentReplicasAdded {
|
||||
return int32(0)
|
||||
}
|
||||
|
||||
rsFraction := getReplicaSetFraction(*rs, d)
|
||||
allowed := deploymentReplicasToAdd - deploymentReplicasAdded
|
||||
|
||||
if deploymentReplicasToAdd > 0 {
|
||||
// Use the minimum between the replica set fraction and the maximum allowed replicas
|
||||
// when scaling up. This way we ensure we will not scale up more than the allowed
|
||||
// replicas we can add.
|
||||
return integer.Int32Min(rsFraction, allowed)
|
||||
}
|
||||
// Use the maximum between the replica set fraction and the maximum allowed replicas
|
||||
// when scaling down. This way we ensure we will not scale down more than the allowed
|
||||
// replicas we can remove.
|
||||
return integer.Int32Max(rsFraction, allowed)
|
||||
}
|
||||
|
||||
// getReplicaSetFraction estimates the fraction of replicas a replica set can have in
|
||||
// 1. a scaling event during a rollout or 2. when scaling a paused deployment.
|
||||
func getReplicaSetFraction(rs apps.ReplicaSet, d apps.Deployment) int32 {
|
||||
// If we are scaling down to zero then the fraction of this replica set is its whole size (negative)
|
||||
if *(d.Spec.Replicas) == int32(0) {
|
||||
return -*(rs.Spec.Replicas)
|
||||
}
|
||||
|
||||
deploymentReplicas := *(d.Spec.Replicas) + MaxSurge(d)
|
||||
annotatedReplicas, ok := getMaxReplicasAnnotation(&rs)
|
||||
if !ok {
|
||||
// If we cannot find the annotation then fallback to the current deployment size. Note that this
|
||||
// will not be an accurate proportion estimation in case other replica sets have different values
|
||||
// which means that the deployment was scaled at some point but we at least will stay in limits
|
||||
// due to the min-max comparisons in getProportion.
|
||||
annotatedReplicas = d.Status.Replicas
|
||||
}
|
||||
|
||||
// We should never proportionally scale up from zero which means rs.spec.replicas and annotatedReplicas
|
||||
// will never be zero here.
|
||||
newRSsize := (float64(*(rs.Spec.Replicas) * deploymentReplicas)) / float64(annotatedReplicas)
|
||||
return integer.RoundToInt32(newRSsize) - *(rs.Spec.Replicas)
|
||||
}
|
||||
|
||||
// GetAllReplicaSets returns the old and new replica sets targeted by the given Deployment. It gets PodList and ReplicaSetList from client interface.
|
||||
// Note that the first set of old replica sets doesn't include the ones with no pods, and the second set of old replica sets include all old replica sets.
|
||||
// The third returned value is the new replica set, and it may be nil if it doesn't exist yet.
|
||||
func GetAllReplicaSets(deployment *apps.Deployment, c appsclient.AppsV1Interface) ([]*apps.ReplicaSet, []*apps.ReplicaSet, *apps.ReplicaSet, error) {
|
||||
rsList, err := ListReplicaSets(deployment, RsListFromClient(c))
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
oldRSes, allOldRSes := FindOldReplicaSets(deployment, rsList)
|
||||
newRS := FindNewReplicaSet(deployment, rsList)
|
||||
return oldRSes, allOldRSes, newRS, nil
|
||||
}
|
||||
|
||||
// GetOldReplicaSets returns the old replica sets targeted by the given Deployment; get PodList and ReplicaSetList from client interface.
|
||||
// Note that the first set of old replica sets doesn't include the ones with no pods, and the second set of old replica sets include all old replica sets.
|
||||
func GetOldReplicaSets(deployment *apps.Deployment, c appsclient.AppsV1Interface) ([]*apps.ReplicaSet, []*apps.ReplicaSet, error) {
|
||||
rsList, err := ListReplicaSets(deployment, RsListFromClient(c))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
oldRSes, allOldRSes := FindOldReplicaSets(deployment, rsList)
|
||||
return oldRSes, allOldRSes, nil
|
||||
}
|
||||
|
||||
// GetNewReplicaSet returns a replica set that matches the intent of the given deployment; get ReplicaSetList from client interface.
|
||||
// Returns nil if the new replica set doesn't exist yet.
|
||||
func GetNewReplicaSet(deployment *apps.Deployment, c appsclient.AppsV1Interface) (*apps.ReplicaSet, error) {
|
||||
rsList, err := ListReplicaSets(deployment, RsListFromClient(c))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return FindNewReplicaSet(deployment, rsList), nil
|
||||
}
|
||||
|
||||
// RsListFromClient returns an rsListFunc that wraps the given client.
|
||||
func RsListFromClient(c appsclient.AppsV1Interface) RsListFunc {
|
||||
return func(namespace string, options metav1.ListOptions) ([]*apps.ReplicaSet, error) {
|
||||
rsList, err := c.ReplicaSets(namespace).List(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var ret []*apps.ReplicaSet
|
||||
for i := range rsList.Items {
|
||||
ret = append(ret, &rsList.Items[i])
|
||||
}
|
||||
return ret, err
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: switch this to full namespacers
|
||||
type RsListFunc func(string, metav1.ListOptions) ([]*apps.ReplicaSet, error)
|
||||
type podListFunc func(string, metav1.ListOptions) (*v1.PodList, error)
|
||||
|
||||
// ListReplicaSets returns a slice of RSes the given deployment targets.
|
||||
// Note that this does NOT attempt to reconcile ControllerRef (adopt/orphan),
|
||||
// because only the controller itself should do that.
|
||||
// However, it does filter out anything whose ControllerRef doesn't match.
|
||||
func ListReplicaSets(deployment *apps.Deployment, getRSList RsListFunc) ([]*apps.ReplicaSet, error) {
|
||||
// TODO: Right now we list replica sets by their labels. We should list them by selector, i.e. the replica set's selector
|
||||
// should be a superset of the deployment's selector, see https://github.com/kubernetes/kubernetes/issues/19830.
|
||||
namespace := deployment.Namespace
|
||||
selector, err := metav1.LabelSelectorAsSelector(deployment.Spec.Selector)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
options := metav1.ListOptions{LabelSelector: selector.String()}
|
||||
all, err := getRSList(namespace, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Only include those whose ControllerRef matches the Deployment.
|
||||
owned := make([]*apps.ReplicaSet, 0, len(all))
|
||||
for _, rs := range all {
|
||||
if metav1.IsControlledBy(rs, deployment) {
|
||||
owned = append(owned, rs)
|
||||
}
|
||||
}
|
||||
return owned, nil
|
||||
}
|
||||
|
||||
// ListReplicaSetsInternal is ListReplicaSets for internalextensions.
|
||||
// TODO: Remove the duplicate when call sites are updated to ListReplicaSets.
|
||||
func ListReplicaSetsInternal(deployment *internalextensions.Deployment, getRSList func(string, metav1.ListOptions) ([]*internalextensions.ReplicaSet, error)) ([]*internalextensions.ReplicaSet, error) {
|
||||
namespace := deployment.Namespace
|
||||
selector, err := metav1.LabelSelectorAsSelector(deployment.Spec.Selector)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
options := metav1.ListOptions{LabelSelector: selector.String()}
|
||||
all, err := getRSList(namespace, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Only include those whose ControllerRef matches the Deployment.
|
||||
filtered := make([]*internalextensions.ReplicaSet, 0, len(all))
|
||||
for _, rs := range all {
|
||||
if metav1.IsControlledBy(rs, deployment) {
|
||||
filtered = append(filtered, rs)
|
||||
}
|
||||
}
|
||||
return filtered, nil
|
||||
}
|
||||
|
||||
// ListPods returns a list of pods the given deployment targets.
|
||||
// This needs a list of ReplicaSets for the Deployment,
|
||||
// which can be found with ListReplicaSets().
|
||||
// Note that this does NOT attempt to reconcile ControllerRef (adopt/orphan),
|
||||
// because only the controller itself should do that.
|
||||
// However, it does filter out anything whose ControllerRef doesn't match.
|
||||
func ListPods(deployment *apps.Deployment, rsList []*apps.ReplicaSet, getPodList podListFunc) (*v1.PodList, error) {
|
||||
namespace := deployment.Namespace
|
||||
selector, err := metav1.LabelSelectorAsSelector(deployment.Spec.Selector)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
options := metav1.ListOptions{LabelSelector: selector.String()}
|
||||
all, err := getPodList(namespace, options)
|
||||
if err != nil {
|
||||
return all, err
|
||||
}
|
||||
// Only include those whose ControllerRef points to a ReplicaSet that is in
|
||||
// turn owned by this Deployment.
|
||||
rsMap := make(map[types.UID]bool, len(rsList))
|
||||
for _, rs := range rsList {
|
||||
rsMap[rs.UID] = true
|
||||
}
|
||||
owned := &v1.PodList{Items: make([]v1.Pod, 0, len(all.Items))}
|
||||
for i := range all.Items {
|
||||
pod := &all.Items[i]
|
||||
controllerRef := metav1.GetControllerOf(pod)
|
||||
if controllerRef != nil && rsMap[controllerRef.UID] {
|
||||
owned.Items = append(owned.Items, *pod)
|
||||
}
|
||||
}
|
||||
return owned, nil
|
||||
}
|
||||
|
||||
// EqualIgnoreHash returns true if two given podTemplateSpec are equal, ignoring the diff in value of Labels[pod-template-hash]
|
||||
// We ignore pod-template-hash because:
|
||||
// 1. The hash result would be different upon podTemplateSpec API changes
|
||||
// (e.g. the addition of a new field will cause the hash code to change)
|
||||
// 2. The deployment template won't have hash labels
|
||||
func EqualIgnoreHash(template1, template2 *v1.PodTemplateSpec) bool {
|
||||
t1Copy := template1.DeepCopy()
|
||||
t2Copy := template2.DeepCopy()
|
||||
// Remove hash labels from template.Labels before comparing
|
||||
delete(t1Copy.Labels, apps.DefaultDeploymentUniqueLabelKey)
|
||||
delete(t2Copy.Labels, apps.DefaultDeploymentUniqueLabelKey)
|
||||
return apiequality.Semantic.DeepEqual(t1Copy, t2Copy)
|
||||
}
|
||||
|
||||
// FindNewReplicaSet returns the new RS this given deployment targets (the one with the same pod template).
|
||||
func FindNewReplicaSet(deployment *apps.Deployment, rsList []*apps.ReplicaSet) *apps.ReplicaSet {
|
||||
sort.Sort(controller.ReplicaSetsByCreationTimestamp(rsList))
|
||||
for i := range rsList {
|
||||
if EqualIgnoreHash(&rsList[i].Spec.Template, &deployment.Spec.Template) {
|
||||
// In rare cases, such as after cluster upgrades, Deployment may end up with
|
||||
// having more than one new ReplicaSets that have the same template as its template,
|
||||
// see https://github.com/kubernetes/kubernetes/issues/40415
|
||||
// We deterministically choose the oldest new ReplicaSet.
|
||||
return rsList[i]
|
||||
}
|
||||
}
|
||||
// new ReplicaSet does not exist.
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindOldReplicaSets returns the old replica sets targeted by the given Deployment, with the given slice of RSes.
|
||||
// Note that the first set of old replica sets doesn't include the ones with no pods, and the second set of old replica sets include all old replica sets.
|
||||
func FindOldReplicaSets(deployment *apps.Deployment, rsList []*apps.ReplicaSet) ([]*apps.ReplicaSet, []*apps.ReplicaSet) {
|
||||
var requiredRSs []*apps.ReplicaSet
|
||||
var allRSs []*apps.ReplicaSet
|
||||
newRS := FindNewReplicaSet(deployment, rsList)
|
||||
for _, rs := range rsList {
|
||||
// Filter out new replica set
|
||||
if newRS != nil && rs.UID == newRS.UID {
|
||||
continue
|
||||
}
|
||||
allRSs = append(allRSs, rs)
|
||||
if *(rs.Spec.Replicas) != 0 {
|
||||
requiredRSs = append(requiredRSs, rs)
|
||||
}
|
||||
}
|
||||
return requiredRSs, allRSs
|
||||
}
|
||||
|
||||
// SetFromReplicaSetTemplate sets the desired PodTemplateSpec from a replica set template to the given deployment.
|
||||
func SetFromReplicaSetTemplate(deployment *apps.Deployment, template v1.PodTemplateSpec) *apps.Deployment {
|
||||
deployment.Spec.Template.ObjectMeta = template.ObjectMeta
|
||||
deployment.Spec.Template.Spec = template.Spec
|
||||
deployment.Spec.Template.ObjectMeta.Labels = labelsutil.CloneAndRemoveLabel(
|
||||
deployment.Spec.Template.ObjectMeta.Labels,
|
||||
apps.DefaultDeploymentUniqueLabelKey)
|
||||
return deployment
|
||||
}
|
||||
|
||||
// GetReplicaCountForReplicaSets returns the sum of Replicas of the given replica sets.
|
||||
func GetReplicaCountForReplicaSets(replicaSets []*apps.ReplicaSet) int32 {
|
||||
totalReplicas := int32(0)
|
||||
for _, rs := range replicaSets {
|
||||
if rs != nil {
|
||||
totalReplicas += *(rs.Spec.Replicas)
|
||||
}
|
||||
}
|
||||
return totalReplicas
|
||||
}
|
||||
|
||||
// GetActualReplicaCountForReplicaSets returns the sum of actual replicas of the given replica sets.
|
||||
func GetActualReplicaCountForReplicaSets(replicaSets []*apps.ReplicaSet) int32 {
|
||||
totalActualReplicas := int32(0)
|
||||
for _, rs := range replicaSets {
|
||||
if rs != nil {
|
||||
totalActualReplicas += rs.Status.Replicas
|
||||
}
|
||||
}
|
||||
return totalActualReplicas
|
||||
}
|
||||
|
||||
// GetReadyReplicaCountForReplicaSets returns the number of ready pods corresponding to the given replica sets.
|
||||
func GetReadyReplicaCountForReplicaSets(replicaSets []*apps.ReplicaSet) int32 {
|
||||
totalReadyReplicas := int32(0)
|
||||
for _, rs := range replicaSets {
|
||||
if rs != nil {
|
||||
totalReadyReplicas += rs.Status.ReadyReplicas
|
||||
}
|
||||
}
|
||||
return totalReadyReplicas
|
||||
}
|
||||
|
||||
// GetAvailableReplicaCountForReplicaSets returns the number of available pods corresponding to the given replica sets.
|
||||
func GetAvailableReplicaCountForReplicaSets(replicaSets []*apps.ReplicaSet) int32 {
|
||||
totalAvailableReplicas := int32(0)
|
||||
for _, rs := range replicaSets {
|
||||
if rs != nil {
|
||||
totalAvailableReplicas += rs.Status.AvailableReplicas
|
||||
}
|
||||
}
|
||||
return totalAvailableReplicas
|
||||
}
|
||||
|
||||
// IsRollingUpdate returns true if the strategy type is a rolling update.
|
||||
func IsRollingUpdate(deployment *apps.Deployment) bool {
|
||||
return deployment.Spec.Strategy.Type == apps.RollingUpdateDeploymentStrategyType
|
||||
}
|
||||
|
||||
// DeploymentComplete considers a deployment to be complete once all of its desired replicas
|
||||
// are updated and available, and no old pods are running.
|
||||
func DeploymentComplete(deployment *apps.Deployment, newStatus *apps.DeploymentStatus) bool {
|
||||
return newStatus.UpdatedReplicas == *(deployment.Spec.Replicas) &&
|
||||
newStatus.Replicas == *(deployment.Spec.Replicas) &&
|
||||
newStatus.AvailableReplicas == *(deployment.Spec.Replicas) &&
|
||||
newStatus.ObservedGeneration >= deployment.Generation
|
||||
}
|
||||
|
||||
// DeploymentProgressing reports progress for a deployment. Progress is estimated by comparing the
|
||||
// current with the new status of the deployment that the controller is observing. More specifically,
|
||||
// when new pods are scaled up or become ready or available, or old pods are scaled down, then we
|
||||
// consider the deployment is progressing.
|
||||
func DeploymentProgressing(deployment *apps.Deployment, newStatus *apps.DeploymentStatus) bool {
|
||||
oldStatus := deployment.Status
|
||||
|
||||
// Old replicas that need to be scaled down
|
||||
oldStatusOldReplicas := oldStatus.Replicas - oldStatus.UpdatedReplicas
|
||||
newStatusOldReplicas := newStatus.Replicas - newStatus.UpdatedReplicas
|
||||
|
||||
return (newStatus.UpdatedReplicas > oldStatus.UpdatedReplicas) ||
|
||||
(newStatusOldReplicas < oldStatusOldReplicas) ||
|
||||
newStatus.ReadyReplicas > deployment.Status.ReadyReplicas ||
|
||||
newStatus.AvailableReplicas > deployment.Status.AvailableReplicas
|
||||
}
|
||||
|
||||
// used for unit testing
|
||||
var nowFn = func() time.Time { return time.Now() }
|
||||
|
||||
// DeploymentTimedOut considers a deployment to have timed out once its condition that reports progress
|
||||
// is older than progressDeadlineSeconds or a Progressing condition with a TimedOutReason reason already
|
||||
// exists.
|
||||
func DeploymentTimedOut(deployment *apps.Deployment, newStatus *apps.DeploymentStatus) bool {
|
||||
if deployment.Spec.ProgressDeadlineSeconds == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Look for the Progressing condition. If it doesn't exist, we have no base to estimate progress.
|
||||
// If it's already set with a TimedOutReason reason, we have already timed out, no need to check
|
||||
// again.
|
||||
condition := GetDeploymentCondition(*newStatus, apps.DeploymentProgressing)
|
||||
if condition == nil {
|
||||
return false
|
||||
}
|
||||
// If the previous condition has been a successful rollout then we shouldn't try to
|
||||
// estimate any progress. Scenario:
|
||||
//
|
||||
// * progressDeadlineSeconds is smaller than the difference between now and the time
|
||||
// the last rollout finished in the past.
|
||||
// * the creation of a new ReplicaSet triggers a resync of the Deployment prior to the
|
||||
// cached copy of the Deployment getting updated with the status.condition that indicates
|
||||
// the creation of the new ReplicaSet.
|
||||
//
|
||||
// The Deployment will be resynced and eventually its Progressing condition will catch
|
||||
// up with the state of the world.
|
||||
if condition.Reason == NewRSAvailableReason {
|
||||
return false
|
||||
}
|
||||
if condition.Reason == TimedOutReason {
|
||||
return true
|
||||
}
|
||||
|
||||
// Look at the difference in seconds between now and the last time we reported any
|
||||
// progress or tried to create a replica set, or resumed a paused deployment and
|
||||
// compare against progressDeadlineSeconds.
|
||||
from := condition.LastUpdateTime
|
||||
now := nowFn()
|
||||
delta := time.Duration(*deployment.Spec.ProgressDeadlineSeconds) * time.Second
|
||||
timedOut := from.Add(delta).Before(now)
|
||||
|
||||
glog.V(4).Infof("Deployment %q timed out (%t) [last progress check: %v - now: %v]", deployment.Name, timedOut, from, now)
|
||||
return timedOut
|
||||
}
|
||||
|
||||
// NewRSNewReplicas calculates the number of replicas a deployment's new RS should have.
|
||||
// When one of the followings is true, we're rolling out the deployment; otherwise, we're scaling it.
|
||||
// 1) The new RS is saturated: newRS's replicas == deployment's replicas
|
||||
// 2) Max number of pods allowed is reached: deployment's replicas + maxSurge == all RSs' replicas
|
||||
func NewRSNewReplicas(deployment *apps.Deployment, allRSs []*apps.ReplicaSet, newRS *apps.ReplicaSet) (int32, error) {
|
||||
switch deployment.Spec.Strategy.Type {
|
||||
case apps.RollingUpdateDeploymentStrategyType:
|
||||
// Check if we can scale up.
|
||||
maxSurge, err := intstrutil.GetValueFromIntOrPercent(deployment.Spec.Strategy.RollingUpdate.MaxSurge, int(*(deployment.Spec.Replicas)), true)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// Find the total number of pods
|
||||
currentPodCount := GetReplicaCountForReplicaSets(allRSs)
|
||||
maxTotalPods := *(deployment.Spec.Replicas) + int32(maxSurge)
|
||||
if currentPodCount >= maxTotalPods {
|
||||
// Cannot scale up.
|
||||
return *(newRS.Spec.Replicas), nil
|
||||
}
|
||||
// Scale up.
|
||||
scaleUpCount := maxTotalPods - currentPodCount
|
||||
// Do not exceed the number of desired replicas.
|
||||
scaleUpCount = int32(integer.IntMin(int(scaleUpCount), int(*(deployment.Spec.Replicas)-*(newRS.Spec.Replicas))))
|
||||
return *(newRS.Spec.Replicas) + scaleUpCount, nil
|
||||
case apps.RecreateDeploymentStrategyType:
|
||||
return *(deployment.Spec.Replicas), nil
|
||||
default:
|
||||
return 0, fmt.Errorf("deployment type %v isn't supported", deployment.Spec.Strategy.Type)
|
||||
}
|
||||
}
|
||||
|
||||
// IsSaturated checks if the new replica set is saturated by comparing its size with its deployment size.
|
||||
// Both the deployment and the replica set have to believe this replica set can own all of the desired
|
||||
// replicas in the deployment and the annotation helps in achieving that. All pods of the ReplicaSet
|
||||
// need to be available.
|
||||
func IsSaturated(deployment *apps.Deployment, rs *apps.ReplicaSet) bool {
|
||||
if rs == nil {
|
||||
return false
|
||||
}
|
||||
desiredString := rs.Annotations[DesiredReplicasAnnotation]
|
||||
desired, err := strconv.Atoi(desiredString)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return *(rs.Spec.Replicas) == *(deployment.Spec.Replicas) &&
|
||||
int32(desired) == *(deployment.Spec.Replicas) &&
|
||||
rs.Status.AvailableReplicas == *(deployment.Spec.Replicas)
|
||||
}
|
||||
|
||||
// WaitForObservedDeployment polls for deployment to be updated so that deployment.Status.ObservedGeneration >= desiredGeneration.
|
||||
// Returns error if polling timesout.
|
||||
func WaitForObservedDeployment(getDeploymentFunc func() (*apps.Deployment, error), desiredGeneration int64, interval, timeout time.Duration) error {
|
||||
// TODO: This should take clientset.Interface when all code is updated to use clientset. Keeping it this way allows the function to be used by callers who have client.Interface.
|
||||
return wait.PollImmediate(interval, timeout, func() (bool, error) {
|
||||
deployment, err := getDeploymentFunc()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return deployment.Status.ObservedGeneration >= desiredGeneration, nil
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: remove the duplicate
|
||||
// WaitForObservedInternalDeployment polls for deployment to be updated so that deployment.Status.ObservedGeneration >= desiredGeneration.
|
||||
// Returns error if polling timesout.
|
||||
func WaitForObservedDeploymentInternal(getDeploymentFunc func() (*internalextensions.Deployment, error), desiredGeneration int64, interval, timeout time.Duration) error {
|
||||
return wait.Poll(interval, timeout, func() (bool, error) {
|
||||
deployment, err := getDeploymentFunc()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return deployment.Status.ObservedGeneration >= desiredGeneration, nil
|
||||
})
|
||||
}
|
||||
|
||||
// ResolveFenceposts resolves both maxSurge and maxUnavailable. This needs to happen in one
|
||||
// step. For example:
|
||||
//
|
||||
// 2 desired, max unavailable 1%, surge 0% - should scale old(-1), then new(+1), then old(-1), then new(+1)
|
||||
// 1 desired, max unavailable 1%, surge 0% - should scale old(-1), then new(+1)
|
||||
// 2 desired, max unavailable 25%, surge 1% - should scale new(+1), then old(-1), then new(+1), then old(-1)
|
||||
// 1 desired, max unavailable 25%, surge 1% - should scale new(+1), then old(-1)
|
||||
// 2 desired, max unavailable 0%, surge 1% - should scale new(+1), then old(-1), then new(+1), then old(-1)
|
||||
// 1 desired, max unavailable 0%, surge 1% - should scale new(+1), then old(-1)
|
||||
func ResolveFenceposts(maxSurge, maxUnavailable *intstrutil.IntOrString, desired int32) (int32, int32, error) {
|
||||
surge, err := intstrutil.GetValueFromIntOrPercent(maxSurge, int(desired), true)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
unavailable, err := intstrutil.GetValueFromIntOrPercent(maxUnavailable, int(desired), false)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
if surge == 0 && unavailable == 0 {
|
||||
// Validation should never allow the user to explicitly use zero values for both maxSurge
|
||||
// maxUnavailable. Due to rounding down maxUnavailable though, it may resolve to zero.
|
||||
// If both fenceposts resolve to zero, then we should set maxUnavailable to 1 on the
|
||||
// theory that surge might not work due to quota.
|
||||
unavailable = 1
|
||||
}
|
||||
|
||||
return int32(surge), int32(unavailable), nil
|
||||
}
|
1353
vendor/k8s.io/kubernetes/pkg/controller/deployment/util/deployment_util_test.go
generated
vendored
1353
vendor/k8s.io/kubernetes/pkg/controller/deployment/util/deployment_util_test.go
generated
vendored
File diff suppressed because it is too large
Load Diff
145
vendor/k8s.io/kubernetes/pkg/controller/deployment/util/hash_test.go
generated
vendored
145
vendor/k8s.io/kubernetes/pkg/controller/deployment/util/hash_test.go
generated
vendored
@ -1,145 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"hash/adler32"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/kubernetes/pkg/controller"
|
||||
hashutil "k8s.io/kubernetes/pkg/util/hash"
|
||||
)
|
||||
|
||||
var podSpec string = `
|
||||
{
|
||||
"metadata": {
|
||||
"creationTimestamp": null,
|
||||
"labels": {
|
||||
"app": "cats"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"containers": [
|
||||
{
|
||||
"name": "cats",
|
||||
"image": "registry/test/cats:v0.@@VERSION@@.0",
|
||||
"ports": [
|
||||
{
|
||||
"name": "http",
|
||||
"containerPort": 9077,
|
||||
"protocol": "TCP"
|
||||
}
|
||||
],
|
||||
"env": [
|
||||
{
|
||||
"name": "DEPLOYMENT_ENVIRONMENT",
|
||||
"value": "cats-stubbed-functional"
|
||||
},
|
||||
{
|
||||
"name": "APP_NAME",
|
||||
"value": "cats"
|
||||
}
|
||||
],
|
||||
"resources": {
|
||||
"limits": {
|
||||
"cpu": "1",
|
||||
"memory": "1Gi"
|
||||
},
|
||||
"requests": {
|
||||
"cpu": "1",
|
||||
"memory": "1Gi"
|
||||
}
|
||||
},
|
||||
"livenessProbe": {
|
||||
"httpGet": {
|
||||
"path": "/private/status",
|
||||
"port": 9077,
|
||||
"scheme": "HTTP"
|
||||
},
|
||||
"initialDelaySeconds": 30,
|
||||
"timeoutSeconds": 1,
|
||||
"periodSeconds": 10,
|
||||
"successThreshold": 1,
|
||||
"failureThreshold": 3
|
||||
},
|
||||
"readinessProbe": {
|
||||
"httpGet": {
|
||||
"path": "/private/status",
|
||||
"port": 9077,
|
||||
"scheme": "HTTP"
|
||||
},
|
||||
"initialDelaySeconds": 1,
|
||||
"timeoutSeconds": 1,
|
||||
"periodSeconds": 10,
|
||||
"successThreshold": 1,
|
||||
"failureThreshold": 3
|
||||
},
|
||||
"terminationMessagePath": "/dev/termination-log",
|
||||
"imagePullPolicy": "IfNotPresent"
|
||||
}
|
||||
],
|
||||
"restartPolicy": "Always",
|
||||
"terminationGracePeriodSeconds": 30,
|
||||
"dnsPolicy": "ClusterFirst",
|
||||
"securityContext": {}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
func TestPodTemplateSpecHash(t *testing.T) {
|
||||
seenHashes := make(map[uint32]int)
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
specJson := strings.Replace(podSpec, "@@VERSION@@", strconv.Itoa(i), 1)
|
||||
spec := v1.PodTemplateSpec{}
|
||||
json.Unmarshal([]byte(specJson), &spec)
|
||||
hash := controller.ComputeHash(&spec, nil)
|
||||
if v, ok := seenHashes[hash]; ok {
|
||||
t.Errorf("Hash collision, old: %d new: %d", v, i)
|
||||
break
|
||||
}
|
||||
seenHashes[hash] = i
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAdler(b *testing.B) {
|
||||
spec := v1.PodTemplateSpec{}
|
||||
json.Unmarshal([]byte(podSpec), &spec)
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
getPodTemplateSpecOldHash(spec)
|
||||
}
|
||||
}
|
||||
|
||||
func getPodTemplateSpecOldHash(template v1.PodTemplateSpec) uint32 {
|
||||
podTemplateSpecHasher := adler32.New()
|
||||
hashutil.DeepHashObject(podTemplateSpecHasher, template)
|
||||
return podTemplateSpecHasher.Sum32()
|
||||
}
|
||||
|
||||
func BenchmarkFnv(b *testing.B) {
|
||||
spec := v1.PodTemplateSpec{}
|
||||
json.Unmarshal([]byte(podSpec), &spec)
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
controller.ComputeHash(&spec, nil)
|
||||
}
|
||||
}
|
60
vendor/k8s.io/kubernetes/pkg/controller/deployment/util/pod_util.go
generated
vendored
60
vendor/k8s.io/kubernetes/pkg/controller/deployment/util/pod_util.go
generated
vendored
@ -1,60 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
errorsutil "k8s.io/apimachinery/pkg/util/errors"
|
||||
v1core "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
corelisters "k8s.io/client-go/listers/core/v1"
|
||||
"k8s.io/client-go/util/retry"
|
||||
)
|
||||
|
||||
// TODO: use client library instead when it starts to support update retries
|
||||
// see https://github.com/kubernetes/kubernetes/issues/21479
|
||||
type updatePodFunc func(pod *v1.Pod) error
|
||||
|
||||
// UpdatePodWithRetries updates a pod with given applyUpdate function.
|
||||
func UpdatePodWithRetries(podClient v1core.PodInterface, podLister corelisters.PodLister, namespace, name string, applyUpdate updatePodFunc) (*v1.Pod, error) {
|
||||
var pod *v1.Pod
|
||||
|
||||
retryErr := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
||||
var err error
|
||||
pod, err = podLister.Pods(namespace).Get(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pod = pod.DeepCopy()
|
||||
// Apply the update, then attempt to push it to the apiserver.
|
||||
if applyErr := applyUpdate(pod); applyErr != nil {
|
||||
return applyErr
|
||||
}
|
||||
pod, err = podClient.Update(pod)
|
||||
return err
|
||||
})
|
||||
|
||||
// Ignore the precondition violated error, this pod is already updated
|
||||
// with the desired label.
|
||||
if retryErr == errorsutil.ErrPreconditionViolated {
|
||||
glog.V(4).Infof("Pod %s/%s precondition doesn't hold, skip updating it.", namespace, name)
|
||||
retryErr = nil
|
||||
}
|
||||
|
||||
return pod, retryErr
|
||||
}
|
60
vendor/k8s.io/kubernetes/pkg/controller/deployment/util/replicaset_util.go
generated
vendored
60
vendor/k8s.io/kubernetes/pkg/controller/deployment/util/replicaset_util.go
generated
vendored
@ -1,60 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"github.com/golang/glog"
|
||||
|
||||
apps "k8s.io/api/apps/v1"
|
||||
errorsutil "k8s.io/apimachinery/pkg/util/errors"
|
||||
appsclient "k8s.io/client-go/kubernetes/typed/apps/v1"
|
||||
appslisters "k8s.io/client-go/listers/apps/v1"
|
||||
"k8s.io/client-go/util/retry"
|
||||
)
|
||||
|
||||
// TODO: use client library instead when it starts to support update retries
|
||||
// see https://github.com/kubernetes/kubernetes/issues/21479
|
||||
type updateRSFunc func(rs *apps.ReplicaSet) error
|
||||
|
||||
// UpdateRSWithRetries updates a RS with given applyUpdate function. Note that RS not found error is ignored.
|
||||
// The returned bool value can be used to tell if the RS is actually updated.
|
||||
func UpdateRSWithRetries(rsClient appsclient.ReplicaSetInterface, rsLister appslisters.ReplicaSetLister, namespace, name string, applyUpdate updateRSFunc) (*apps.ReplicaSet, error) {
|
||||
var rs *apps.ReplicaSet
|
||||
|
||||
retryErr := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
||||
var err error
|
||||
rs, err = rsLister.ReplicaSets(namespace).Get(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rs = rs.DeepCopy()
|
||||
// Apply the update, then attempt to push it to the apiserver.
|
||||
if applyErr := applyUpdate(rs); applyErr != nil {
|
||||
return applyErr
|
||||
}
|
||||
rs, err = rsClient.Update(rs)
|
||||
return err
|
||||
})
|
||||
|
||||
// Ignore the precondition violated error, but the RS isn't updated.
|
||||
if retryErr == errorsutil.ErrPreconditionViolated {
|
||||
glog.V(4).Infof("Replica set %s/%s precondition doesn't hold, skip updating it.", namespace, name)
|
||||
retryErr = nil
|
||||
}
|
||||
|
||||
return rs, retryErr
|
||||
}
|
Reference in New Issue
Block a user