Support maxSurge for CloneSet (#256)

Signed-off-by: Siyu Wang <FillZpp.pub@gmail.com>
This commit is contained in:
Siyu Wang 2020-05-09 00:35:43 +08:00 committed by GitHub
parent 06e7faee29
commit 75dfe17c99
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 523 additions and 55 deletions

View File

@ -373,8 +373,12 @@
"description": "InPlaceUpdateStrategy contains strategies for in-place update.",
"$ref": "#/definitions/kruise.apps.v1alpha1.InPlaceUpdateStrategy"
},
"maxSurge": {
"description": "The maximum number of pods that can be scheduled above the desired replicas during the update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). Absolute number is calculated from percentage by rounding up. Defaults to nil.",
"$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString"
},
"maxUnavailable": {
"description": "The maximum number of pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). Absolute number is calculated from percentage by rounding down. Defaults to 20%.",
"description": "The maximum number of pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). Absolute number is calculated from percentage by rounding up. Defaults to 20%.",
"$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString"
},
"partition": {

View File

@ -151,6 +151,30 @@ spec:
maxUnavailable: 2
```
## Graceful in-place update
When a Pod being in-place update, controller will firstly update Pod status to make it become not-ready using readinessGate,
and then update images in Pod spec to trigger Kubelet recreate the container on Node.
However, sometimes Kubelet recreate containers so fast that other controllers such as endpoints-controller in kcm
have not noticed the Pod has turned to not-ready. This may lead to requests damaged.
So we bring **graceful period** into in-place update. Advanced StatefulSet has supported `gracePeriodSeconds`, which is a period
duration between controller update pod status and update pod images.
So that endpoints-controller could have enough time to remove this Pod from endpoints.
```yaml
apiVersion: apps.kruise.io/v1alpha1
kind: StatefulSet
spec:
# ...
updateStrategy:
type: RollingUpdate
rollingUpdate:
inPlaceUpdateStrategy:
gracePeriodSeconds: 10
```
## `Priority` Unordered Rolling Update Strategy
This controller adds a `unorderedUpdate` field in `spec.updateStrategy.rollingUpdate`, which contains strategies for non-ordered update.

View File

@ -218,6 +218,59 @@ MaxUnavailable is the maximum number of Pods that can be unavailable during the
Value can be an absolute number (e.g., 5) or a percentage of desired number of Pods (e.g., 10%).
Default value is 20%.
```yaml
apiVersion: apps.kruise.io/v1alpha1
kind: CloneSet
spec:
# ...
updateStrategy:
maxUnavailable: 20%
```
### MaxSurge
MaxSurge is the maximum number of pods that can be scheduled above the desired replicas during the update.
Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%).
Defaults to 0.
If maxSurge is set somewhere, cloneset-controller will create `maxSurge` number of Pods above the `replicas`,
when it finds multiple active revisions of Pods which means the CloneSet is in the update stage.
What's more, maxSurge is forbidden to use with `InPlaceOnly` policy.
When maxSurge is used with `InPlaceIfPossible`, controller will create additional Pods with latest revision first,
and then update the rest Pods with old revisions,
```yaml
apiVersion: apps.kruise.io/v1alpha1
kind: CloneSet
spec:
# ...
updateStrategy:
maxSurge: 3
```
### Graceful in-place update
When a Pod being in-place update, controller will firstly update Pod status to make it become not-ready using readinessGate,
and then update images in Pod spec to trigger Kubelet recreate the container on Node.
However, sometimes Kubelet recreate containers so fast that other controllers such as endpoints-controller in kcm
have not noticed the Pod has turned to not-ready. This may lead to requests damaged.
So we bring **graceful period** into in-place update. CloneSet has supported `gracePeriodSeconds`, which is a period
duration between controller update pod status and update pod images.
So that endpoints-controller could have enough time to remove this Pod from endpoints.
```yaml
apiVersion: apps.kruise.io/v1alpha1
kind: CloneSet
spec:
# ...
updateStrategy:
inPlaceUpdateStrategy:
gracePeriodSeconds: 10
```
### Update sequence
When controller chooses Pods to update, it has default sort logic based on Pod phase and conditions:

View File

@ -89,9 +89,14 @@ type CloneSetUpdateStrategy struct {
Partition *int32 `json:"partition,omitempty"`
// The maximum number of pods that can be unavailable during the update.
// Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%).
// Absolute number is calculated from percentage by rounding down.
// Absolute number is calculated from percentage by rounding up.
// Defaults to 20%.
MaxUnavailable *intstr.IntOrString `json:"maxUnavailable,omitempty"`
// The maximum number of pods that can be scheduled above the desired replicas during the update.
// Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%).
// Absolute number is calculated from percentage by rounding up.
// Defaults to 0.
MaxSurge *intstr.IntOrString `json:"maxSurge,omitempty"`
// Paused indicates that the CloneSet is paused.
// Default value is false
Paused bool `json:"paused,omitempty"`

View File

@ -219,4 +219,8 @@ func SetDefaults_CloneSet(obj *CloneSet) {
maxUnavailable := intstr.FromString(DefaultCloneSetMaxUnavailable)
obj.Spec.UpdateStrategy.MaxUnavailable = &maxUnavailable
}
if obj.Spec.UpdateStrategy.MaxSurge == nil {
maxSurge := intstr.FromInt(0)
obj.Spec.UpdateStrategy.MaxSurge = &maxSurge
}
}

View File

@ -706,7 +706,13 @@ func schema_pkg_apis_apps_v1alpha1_CloneSetUpdateStrategy(ref common.ReferenceCa
},
"maxUnavailable": {
SchemaProps: spec.SchemaProps{
Description: "The maximum number of pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). Absolute number is calculated from percentage by rounding down. Defaults to 20%.",
Description: "The maximum number of pods that can be unavailable during the update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). Absolute number is calculated from percentage by rounding up. Defaults to 20%.",
Ref: ref("k8s.io/apimachinery/pkg/util/intstr.IntOrString"),
},
},
"maxSurge": {
SchemaProps: spec.SchemaProps{
Description: "The maximum number of pods that can be scheduled above the desired replicas during the update. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). Absolute number is calculated from percentage by rounding up. Defaults to nil.",
Ref: ref("k8s.io/apimachinery/pkg/util/intstr.IntOrString"),
},
},

View File

@ -378,6 +378,11 @@ func (in *CloneSetUpdateStrategy) DeepCopyInto(out *CloneSetUpdateStrategy) {
*out = new(intstr.IntOrString)
**out = **in
}
if in.MaxSurge != nil {
in, out := &in.MaxSurge, &out.MaxSurge
*out = new(intstr.IntOrString)
**out = **in
}
if in.PriorityStrategy != nil {
in, out := &in.PriorityStrategy, &out.PriorityStrategy
*out = new(UpdatePriorityStrategy)

View File

@ -3,7 +3,6 @@ package scale
import (
"context"
"fmt"
"sort"
"sync"
appsv1alpha1 "github.com/openkruise/kruise/pkg/apis/apps/v1alpha1"
@ -16,7 +15,6 @@ import (
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/tools/record"
"k8s.io/klog"
kubecontroller "k8s.io/kubernetes/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/client"
)
@ -69,20 +67,22 @@ func (r *realControl) Manage(
return true, r.deletePods(updateCS, podsToDelete, pvcs)
}
updatedPods, notUpdatedPods := clonesetutils.SplitPodsByRevision(pods, updateRevision)
var err error
diff := len(pods) - int(*updateCS.Spec.Replicas)
diff, currentRevDiff := calculateDiffs(updateCS, updateRevision == currentRevision, len(pods), len(notUpdatedPods))
if diff < 0 {
// total number of this creation
expectedCreations := diff * -1
// lack number of current version
expectedCurrentCreations := 0
if updateCS.Spec.UpdateStrategy.Partition != nil {
notUpdatedNum := len(pods) - len(clonesetutils.GetUpdatedPods(pods, updateRevision))
expectedCurrentCreations = int(*updateCS.Spec.UpdateStrategy.Partition) - notUpdatedNum
if currentRevDiff < 0 {
expectedCurrentCreations = currentRevDiff * -1
}
klog.V(3).Infof("CloneSet %s begin to scale out %d pods", controllerKey, expectedCreations)
klog.V(3).Infof("CloneSet %s begin to scale out %d pods including %d (current rev)",
controllerKey, expectedCreations, expectedCurrentCreations)
// available instance-id come from free pvc
availableIDs := getOrGenAvailableIDs(expectedCreations, pods, pvcs)
@ -97,11 +97,12 @@ func (r *realControl) Manage(
return true, err
} else if diff > 0 {
klog.V(3).Infof("CloneSet %s begin to scale in %d pods", controllerKey, diff)
klog.V(3).Infof("CloneSet %s begin to scale in %d pods including %d (current rev)",
controllerKey, diff, currentRevDiff)
podsToDelete := choosePodsToDelete(diff, currentRevDiff, notUpdatedPods, updatedPods)
podsToDelete := r.choosePodsToDelete(pods, diff)
err = r.deletePods(updateCS, podsToDelete, pvcs)
return true, err
}
@ -205,15 +206,3 @@ func (r *realControl) deletePods(cs *appsv1alpha1.CloneSet, podsToDelete []*v1.P
return nil
}
func (r *realControl) choosePodsToDelete(pods []*v1.Pod, diff int) []*v1.Pod {
// No need to sort pods if we are about to delete all of them.
// diff will always be <= len(filteredPods), so not need to handle > case.
if diff < len(pods) {
// Sort the pods in the order such that not-ready < ready, unscheduled
// < scheduled, and pending < running. This ensures that we delete pods
// in the earlier stages whenever possible.
sort.Sort(kubecontroller.ActivePods(pods))
}
return pods[:diff]
}

View File

@ -1,10 +1,15 @@
package scale
import (
"sort"
appsv1alpha1 "github.com/openkruise/kruise/pkg/apis/apps/v1alpha1"
v1 "k8s.io/api/core/v1"
intstrutil "k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/rand"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/klog"
kubecontroller "k8s.io/kubernetes/pkg/controller"
)
func getPodsToDelete(cs *appsv1alpha1.CloneSet, pods []*v1.Pod) []*v1.Pod {
@ -60,3 +65,49 @@ func getOrGenInstanceID(existingIDs, availableIDs sets.String) string {
}
return id
}
func calculateDiffs(cs *appsv1alpha1.CloneSet, revConsistent bool, totalPods int, notUpdatedPods int) (totalDiff int, currentRevDiff int) {
var maxSurge int
if !revConsistent {
if cs.Spec.UpdateStrategy.MaxSurge != nil {
maxSurge, _ = intstrutil.GetValueFromIntOrPercent(cs.Spec.UpdateStrategy.MaxSurge, int(*cs.Spec.Replicas), true)
}
if cs.Spec.UpdateStrategy.Partition != nil {
currentRevDiff = notUpdatedPods - int(*cs.Spec.UpdateStrategy.Partition)
}
}
totalDiff = totalPods - int(*cs.Spec.Replicas) - maxSurge
if totalDiff != 0 && maxSurge > 0 {
klog.V(3).Infof("CloneSet scale diff(%d),currentRevDiff(%d) with maxSurge %d", totalDiff, currentRevDiff, maxSurge)
}
return
}
func choosePodsToDelete(totalDiff int, currentRevDiff int, notUpdatedPods, updatedPods []*v1.Pod) []*v1.Pod {
choose := func(pods []*v1.Pod, diff int) []*v1.Pod {
// No need to sort pods if we are about to delete all of them.
// diff will always be <= len(filteredPods), so not need to handle > case.
if diff < len(pods) {
// Sort the pods in the order such that not-ready < ready, unscheduled
// < scheduled, and pending < running. This ensures that we delete pods
// in the earlier stages whenever possible.
sort.Sort(kubecontroller.ActivePods(pods))
}
return pods[:diff]
}
var podsToDelete []*v1.Pod
if currentRevDiff >= totalDiff {
podsToDelete = choose(notUpdatedPods, totalDiff)
} else if currentRevDiff > 0 {
podsToDelete = choose(notUpdatedPods, currentRevDiff)
podsToDelete = append(podsToDelete, choose(updatedPods, totalDiff-currentRevDiff)...)
} else {
podsToDelete = choose(updatedPods, totalDiff)
}
return podsToDelete
}

View File

@ -3,6 +3,9 @@ package scale
import (
"testing"
"k8s.io/apimachinery/pkg/util/intstr"
utilpointer "k8s.io/utils/pointer"
appsv1alpha1 "github.com/openkruise/kruise/pkg/apis/apps/v1alpha1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -49,3 +52,267 @@ func TestGetOrGenAvailableIDs(t *testing.T) {
t.Fatalf("expected got random id, but actually %v", id)
}
}
func TestCalculateDiffs(t *testing.T) {
intOrStr0 := intstr.FromInt(0)
intOrStr1 := intstr.FromInt(1)
cases := []struct {
name string
cs *appsv1alpha1.CloneSet
revConsistent bool
totalPods int
notUpdatedPods int
expectedTotalDiff int
expectedCurrentRevDiff int
}{
{
name: "normal 1",
cs: &appsv1alpha1.CloneSet{
Spec: appsv1alpha1.CloneSetSpec{
Replicas: utilpointer.Int32Ptr(10),
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
MaxSurge: &intOrStr0,
Partition: utilpointer.Int32Ptr(0),
},
},
},
revConsistent: true,
totalPods: 10,
notUpdatedPods: 0,
expectedTotalDiff: 0,
expectedCurrentRevDiff: 0,
},
{
name: "normal 2",
cs: &appsv1alpha1.CloneSet{
Spec: appsv1alpha1.CloneSetSpec{
Replicas: utilpointer.Int32Ptr(10),
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
MaxSurge: &intOrStr1,
Partition: utilpointer.Int32Ptr(0),
},
},
},
revConsistent: true,
totalPods: 10,
notUpdatedPods: 0,
expectedTotalDiff: 0,
expectedCurrentRevDiff: 0,
},
{
name: "scale out with revConsistent",
cs: &appsv1alpha1.CloneSet{
Spec: appsv1alpha1.CloneSetSpec{
Replicas: utilpointer.Int32Ptr(10),
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
MaxSurge: &intOrStr1,
Partition: utilpointer.Int32Ptr(0),
},
},
},
revConsistent: true,
totalPods: 8,
notUpdatedPods: 0,
expectedTotalDiff: -2,
expectedCurrentRevDiff: 0,
},
{
name: "scale out without revConsistent 1",
cs: &appsv1alpha1.CloneSet{
Spec: appsv1alpha1.CloneSetSpec{
Replicas: utilpointer.Int32Ptr(10),
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
MaxSurge: &intOrStr0,
Partition: utilpointer.Int32Ptr(0),
},
},
},
revConsistent: false,
totalPods: 8,
notUpdatedPods: 0,
expectedTotalDiff: -2,
expectedCurrentRevDiff: 0,
},
{
name: "scale out without revConsistent 2",
cs: &appsv1alpha1.CloneSet{
Spec: appsv1alpha1.CloneSetSpec{
Replicas: utilpointer.Int32Ptr(10),
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
MaxSurge: &intOrStr1,
Partition: utilpointer.Int32Ptr(0),
},
},
},
revConsistent: false,
totalPods: 8,
notUpdatedPods: 0,
expectedTotalDiff: -3,
expectedCurrentRevDiff: 0,
},
{
name: "scale out without revConsistent 3",
cs: &appsv1alpha1.CloneSet{
Spec: appsv1alpha1.CloneSetSpec{
Replicas: utilpointer.Int32Ptr(10),
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
MaxSurge: &intOrStr1,
Partition: utilpointer.Int32Ptr(0),
},
},
},
revConsistent: false,
totalPods: 10,
notUpdatedPods: 0,
expectedTotalDiff: -1,
expectedCurrentRevDiff: 0,
},
{
name: "scale out without revConsistent 4",
cs: &appsv1alpha1.CloneSet{
Spec: appsv1alpha1.CloneSetSpec{
Replicas: utilpointer.Int32Ptr(10),
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
MaxSurge: &intOrStr1,
Partition: utilpointer.Int32Ptr(0),
},
},
},
revConsistent: false,
totalPods: 11,
notUpdatedPods: 0,
expectedTotalDiff: 0,
expectedCurrentRevDiff: 0,
},
{
name: "scale out without revConsistent 5",
cs: &appsv1alpha1.CloneSet{
Spec: appsv1alpha1.CloneSetSpec{
Replicas: utilpointer.Int32Ptr(10),
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
MaxSurge: &intOrStr1,
Partition: utilpointer.Int32Ptr(8),
},
},
},
revConsistent: false,
totalPods: 8,
notUpdatedPods: 6,
expectedTotalDiff: -3,
expectedCurrentRevDiff: -2,
},
{
name: "scale in with revConsistent 1",
cs: &appsv1alpha1.CloneSet{
Spec: appsv1alpha1.CloneSetSpec{
Replicas: utilpointer.Int32Ptr(10),
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
MaxSurge: &intOrStr0,
Partition: utilpointer.Int32Ptr(0),
},
},
},
revConsistent: true,
totalPods: 11,
notUpdatedPods: 0,
expectedTotalDiff: 1,
expectedCurrentRevDiff: 0,
},
{
name: "scale in with revConsistent 2",
cs: &appsv1alpha1.CloneSet{
Spec: appsv1alpha1.CloneSetSpec{
Replicas: utilpointer.Int32Ptr(10),
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
MaxSurge: &intOrStr1,
Partition: utilpointer.Int32Ptr(0),
},
},
},
revConsistent: true,
totalPods: 11,
notUpdatedPods: 0,
expectedTotalDiff: 1,
expectedCurrentRevDiff: 0,
},
{
name: "scale in without revConsistent 1",
cs: &appsv1alpha1.CloneSet{
Spec: appsv1alpha1.CloneSetSpec{
Replicas: utilpointer.Int32Ptr(10),
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
MaxSurge: &intOrStr0,
Partition: utilpointer.Int32Ptr(0),
},
},
},
revConsistent: false,
totalPods: 11,
notUpdatedPods: 0,
expectedTotalDiff: 1,
expectedCurrentRevDiff: 0,
},
{
name: "scale in without revConsistent 2",
cs: &appsv1alpha1.CloneSet{
Spec: appsv1alpha1.CloneSetSpec{
Replicas: utilpointer.Int32Ptr(10),
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
MaxSurge: &intOrStr1,
Partition: utilpointer.Int32Ptr(0),
},
},
},
revConsistent: false,
totalPods: 11,
notUpdatedPods: 0,
expectedTotalDiff: 0,
expectedCurrentRevDiff: 0,
},
{
name: "scale in without revConsistent 2",
cs: &appsv1alpha1.CloneSet{
Spec: appsv1alpha1.CloneSetSpec{
Replicas: utilpointer.Int32Ptr(10),
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
MaxSurge: &intOrStr1,
Partition: utilpointer.Int32Ptr(0),
},
},
},
revConsistent: false,
totalPods: 12,
notUpdatedPods: 0,
expectedTotalDiff: 1,
expectedCurrentRevDiff: 0,
},
{
name: "scale in without revConsistent 3",
cs: &appsv1alpha1.CloneSet{
Spec: appsv1alpha1.CloneSetSpec{
Replicas: utilpointer.Int32Ptr(10),
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
MaxSurge: &intOrStr1,
Partition: utilpointer.Int32Ptr(6),
},
},
},
revConsistent: false,
totalPods: 13,
notUpdatedPods: 7,
expectedTotalDiff: 2,
expectedCurrentRevDiff: 1,
},
}
for _, tc := range cases {
t.Run(tc.name, func(*testing.T) {
totalDiff, currentRevDiff := calculateDiffs(tc.cs, tc.revConsistent, tc.totalPods, tc.notUpdatedPods)
if totalDiff != tc.expectedTotalDiff || currentRevDiff != tc.expectedCurrentRevDiff {
t.Fatalf("failed calculateDiffs, expected totalDiff %d, got %d, expected currentRevDiff %d, got %d",
tc.expectedTotalDiff, totalDiff, tc.expectedCurrentRevDiff, currentRevDiff)
}
})
}
}

View File

@ -94,15 +94,16 @@ func IsRunningAndAvailable(pod *v1.Pod, minReadySeconds int32) bool {
return pod.Status.Phase == v1.PodRunning && podutil.IsPodAvailable(pod, minReadySeconds, metav1.Now())
}
// GetUpdatedPods returns Pods already reach latest revision
func GetUpdatedPods(pods []*v1.Pod, updateRevision string) []*v1.Pod {
var updated []*v1.Pod
// SplitPodsByRevision returns Pods matched and unmatched the given revision
func SplitPodsByRevision(pods []*v1.Pod, rev string) (matched, unmatched []*v1.Pod) {
for _, p := range pods {
if GetPodRevision(p) == updateRevision {
updated = append(updated, p)
if GetPodRevision(p) == rev {
matched = append(matched, p)
} else {
unmatched = append(unmatched, p)
}
}
return updated
return
}
// UpdateStorage insert volumes generated by cs.Spec.VolumeClaimTemplates into Pod.

View File

@ -60,7 +60,7 @@ func (h *CloneSetCreateUpdateHandler) validateCloneSetSpec(spec *appsv1alpha1.Cl
}
allErrs = append(allErrs, h.validateScaleStrategy(&spec.ScaleStrategy, metadata, fldPath.Child("scaleStrategy"))...)
allErrs = append(allErrs, h.validateUpdateStrategy(&spec.UpdateStrategy, fldPath.Child("updateStrategy"))...)
allErrs = append(allErrs, h.validateUpdateStrategy(&spec.UpdateStrategy, int(*spec.Replicas), fldPath.Child("updateStrategy"))...)
return allErrs
}
@ -87,8 +87,9 @@ func (h *CloneSetCreateUpdateHandler) validateScaleStrategy(strategy *appsv1alph
return allErrs
}
func (h *CloneSetCreateUpdateHandler) validateUpdateStrategy(strategy *appsv1alpha1.CloneSetUpdateStrategy, fldPath *field.Path) field.ErrorList {
func (h *CloneSetCreateUpdateHandler) validateUpdateStrategy(strategy *appsv1alpha1.CloneSetUpdateStrategy, replicas int, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
var err error
switch strategy.Type {
case appsv1alpha1.RecreateCloneSetUpdateStrategyType,
@ -111,12 +112,31 @@ func (h *CloneSetCreateUpdateHandler) validateUpdateStrategy(strategy *appsv1alp
allErrs = append(allErrs, field.Required(fldPath.Child("scatterStrategy"), err.Error()))
}
if maxUnavailable, err := intstrutil.GetValueFromIntOrPercent(intstrutil.ValueOrDefault(strategy.MaxUnavailable, intstrutil.FromString(appsv1alpha1.DefaultCloneSetMaxUnavailable)), 1, true); err != nil {
var maxUnavailable int
if strategy.MaxUnavailable != nil {
maxUnavailable, err = intstrutil.GetValueFromIntOrPercent(strategy.MaxUnavailable, replicas, true)
if err != nil {
allErrs = append(allErrs, field.Invalid(fldPath.Child("maxUnavailable"), strategy.MaxUnavailable.String(),
fmt.Sprintf("failed getValueFromIntOrPercent for maxUnavailable: %v", err)))
}
}
var maxSurge int
if strategy.MaxSurge != nil {
if strategy.Type == appsv1alpha1.InPlaceOnlyCloneSetUpdateStrategyType {
allErrs = append(allErrs, field.Invalid(fldPath.Child("maxSurge"), strategy.MaxSurge.String(),
fmt.Sprintf("can not use maxSurge with strategy type InPlaceOnly")))
}
maxSurge, err = intstrutil.GetValueFromIntOrPercent(strategy.MaxSurge, replicas, true)
if err != nil {
allErrs = append(allErrs, field.Invalid(fldPath.Child("maxSurge"), strategy.MaxSurge.String(),
fmt.Sprintf("failed getValueFromIntOrPercent for maxSurge: %v", err)))
}
}
if replicas > 0 && maxUnavailable < 1 && maxSurge < 1 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("maxUnavailable"), strategy.MaxUnavailable,
fmt.Sprintf("failed getValueFromIntOrPercent for maxUnavailable: %v", err)))
} else if maxUnavailable < 1 {
allErrs = append(allErrs, field.Invalid(fldPath.Child("maxUnavailable"), strategy.MaxUnavailable,
"getValueFromIntOrPercent for maxUnavailable should not be less than 1"))
"maxUnavailable and maxSurge should not both be less than 1"))
}
return allErrs

View File

@ -75,8 +75,8 @@ func TestValidate(t *testing.T) {
var val1 int32 = 1
var val2 int32 = 2
var minus1 int32 = -1
maxUnavailable0 := intstr.FromInt(0)
maxUnavailable1 := intstr.FromInt(1)
intOrStr0 := intstr.FromInt(0)
intOrStr1 := intstr.FromInt(1)
maxUnavailable120Percent := intstr.FromString("120%")
uid := uuid.NewUUID()
@ -97,13 +97,39 @@ func TestValidate(t *testing.T) {
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
Type: appsv1alpha1.InPlaceIfPossibleCloneSetUpdateStrategyType,
Partition: &val2,
MaxUnavailable: &maxUnavailable1,
MaxUnavailable: &intOrStr1,
},
ScaleStrategy: appsv1alpha1.CloneSetScaleStrategy{
PodsToDelete: []string{"p0"},
},
},
},
{
spec: &appsv1alpha1.CloneSetSpec{
Replicas: &val1,
Selector: &metav1.LabelSelector{MatchLabels: validLabels},
Template: validPodTemplate.Template,
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
Type: appsv1alpha1.InPlaceIfPossibleCloneSetUpdateStrategyType,
Partition: &val2,
MaxUnavailable: &intOrStr1,
MaxSurge: &intOrStr1,
},
},
},
{
spec: &appsv1alpha1.CloneSetSpec{
Replicas: &val1,
Selector: &metav1.LabelSelector{MatchLabels: validLabels},
Template: validPodTemplate.Template,
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
Type: appsv1alpha1.InPlaceIfPossibleCloneSetUpdateStrategyType,
Partition: &val2,
MaxUnavailable: &intOrStr0,
MaxSurge: &intOrStr1,
},
},
},
{
spec: &appsv1alpha1.CloneSetSpec{
Replicas: &val1,
@ -141,7 +167,7 @@ func TestValidate(t *testing.T) {
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
Type: appsv1alpha1.InPlaceOnlyCloneSetUpdateStrategyType,
Partition: &val2,
MaxUnavailable: &maxUnavailable1,
MaxUnavailable: &intOrStr1,
},
},
oldSpec: &appsv1alpha1.CloneSetSpec{
@ -151,7 +177,7 @@ func TestValidate(t *testing.T) {
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
Type: appsv1alpha1.InPlaceOnlyCloneSetUpdateStrategyType,
Partition: &val2,
MaxUnavailable: &maxUnavailable1,
MaxUnavailable: &intOrStr1,
},
},
},
@ -189,7 +215,7 @@ func TestValidate(t *testing.T) {
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
Type: appsv1alpha1.InPlaceIfPossibleCloneSetUpdateStrategyType,
Partition: &val2,
MaxUnavailable: &maxUnavailable1,
MaxUnavailable: &intOrStr1,
},
},
},
@ -201,7 +227,7 @@ func TestValidate(t *testing.T) {
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
Type: appsv1alpha1.InPlaceIfPossibleCloneSetUpdateStrategyType,
Partition: &val2,
MaxUnavailable: &maxUnavailable1,
MaxUnavailable: &intOrStr1,
},
},
},
@ -213,7 +239,7 @@ func TestValidate(t *testing.T) {
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
Type: appsv1alpha1.InPlaceIfPossibleCloneSetUpdateStrategyType,
Partition: &val2,
MaxUnavailable: &maxUnavailable1,
MaxUnavailable: &intOrStr1,
},
},
},
@ -225,7 +251,7 @@ func TestValidate(t *testing.T) {
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
Type: "",
Partition: &val2,
MaxUnavailable: &maxUnavailable1,
MaxUnavailable: &intOrStr1,
},
},
},
@ -237,7 +263,7 @@ func TestValidate(t *testing.T) {
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
Type: appsv1alpha1.InPlaceIfPossibleCloneSetUpdateStrategyType,
Partition: &minus1,
MaxUnavailable: &maxUnavailable1,
MaxUnavailable: &intOrStr1,
},
},
},
@ -249,7 +275,20 @@ func TestValidate(t *testing.T) {
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
Type: appsv1alpha1.InPlaceIfPossibleCloneSetUpdateStrategyType,
Partition: &val2,
MaxUnavailable: &maxUnavailable0,
MaxUnavailable: &intOrStr0,
},
},
},
"invalid-maxSurge": {
spec: &appsv1alpha1.CloneSetSpec{
Replicas: &val1,
Selector: &metav1.LabelSelector{MatchLabels: validLabels},
Template: validPodTemplate.Template,
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
Type: appsv1alpha1.InPlaceIfPossibleCloneSetUpdateStrategyType,
Partition: &val2,
MaxUnavailable: &intOrStr0,
MaxSurge: &intOrStr0,
},
},
},
@ -261,7 +300,7 @@ func TestValidate(t *testing.T) {
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
Type: appsv1alpha1.InPlaceIfPossibleCloneSetUpdateStrategyType,
Partition: &val2,
MaxUnavailable: &maxUnavailable1,
MaxUnavailable: &intOrStr1,
},
ScaleStrategy: appsv1alpha1.CloneSetScaleStrategy{
PodsToDelete: []string{"p0", "p0"},
@ -276,7 +315,7 @@ func TestValidate(t *testing.T) {
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
Type: appsv1alpha1.InPlaceIfPossibleCloneSetUpdateStrategyType,
Partition: &val2,
MaxUnavailable: &maxUnavailable1,
MaxUnavailable: &intOrStr1,
},
ScaleStrategy: appsv1alpha1.CloneSetScaleStrategy{
PodsToDelete: []string{"p0", "p1"},
@ -292,7 +331,7 @@ func TestValidate(t *testing.T) {
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
Type: appsv1alpha1.InPlaceIfPossibleCloneSetUpdateStrategyType,
Partition: &val2,
MaxUnavailable: &maxUnavailable1,
MaxUnavailable: &intOrStr1,
},
},
oldSpec: &appsv1alpha1.CloneSetSpec{
@ -303,7 +342,7 @@ func TestValidate(t *testing.T) {
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
Type: appsv1alpha1.InPlaceIfPossibleCloneSetUpdateStrategyType,
Partition: &val2,
MaxUnavailable: &maxUnavailable1,
MaxUnavailable: &intOrStr1,
},
},
},
@ -315,7 +354,7 @@ func TestValidate(t *testing.T) {
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
Type: appsv1alpha1.InPlaceOnlyCloneSetUpdateStrategyType,
Partition: &val2,
MaxUnavailable: &maxUnavailable1,
MaxUnavailable: &intOrStr1,
},
},
oldSpec: &appsv1alpha1.CloneSetSpec{
@ -325,7 +364,7 @@ func TestValidate(t *testing.T) {
UpdateStrategy: appsv1alpha1.CloneSetUpdateStrategy{
Type: appsv1alpha1.InPlaceOnlyCloneSetUpdateStrategyType,
Partition: &val2,
MaxUnavailable: &maxUnavailable1,
MaxUnavailable: &intOrStr1,
},
},
},