Commit 1602e2a3 authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #45587 from foxish/pdb-maxunavailab

Automatic merge from submit-queue (batch tested with PRs 45587, 46286) PDB Max Unavailable Field Completes https://github.com/kubernetes/features/issues/285 ```release-note Adds a MaxUnavailable field to PodDisruptionBudget ``` Individual commits are self-contained; Last commit can be ignored because it is autogenerated code. cc @kubernetes/sig-apps-api-reviews @kubernetes/sig-apps-pr-reviews
parents 8e07e61a 48d76edc
......@@ -50491,6 +50491,10 @@
"io.k8s.kubernetes.pkg.apis.policy.v1beta1.PodDisruptionBudgetSpec": {
"description": "PodDisruptionBudgetSpec is a description of a PodDisruptionBudget.",
"properties": {
"maxUnavailable": {
"description": "An eviction is allowed if at most \"maxUnavailable\" pods selected by \"selector\" are unavailable after the eviction, i.e. even in absence of the evicted pod. For example, one can prevent all voluntary evictions by specifying 0. This is a mutually exclusive setting with \"minAvailable\".",
"$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString"
},
"minAvailable": {
"description": "An eviction is allowed if at least \"minAvailable\" pods selected by \"selector\" will still be available after the eviction, i.e. even in the absence of the evicted pod. So for example you can prevent all voluntary evictions by specifying \"100%\".",
"$ref": "#/definitions/io.k8s.apimachinery.pkg.util.intstr.IntOrString"
......@@ -1373,6 +1373,10 @@
"selector": {
"$ref": "v1.LabelSelector",
"description": "Label query over pods whose evictions are managed by the disruption budget."
},
"maxUnavailable": {
"type": "string",
"description": "An eviction is allowed if at most \"maxUnavailable\" pods selected by \"selector\" are unavailable after the eviction, i.e. even in absence of the evicted pod. For example, one can prevent all voluntary evictions by specifying 0. This is a mutually exclusive setting with \"minAvailable\"."
}
}
},
......
......@@ -1517,6 +1517,13 @@ Examples:<br>
<td class="tableblock halign-left valign-top"><p class="tableblock"><a href="#_v1_labelselector">v1.LabelSelector</a></p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">maxUnavailable</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">An eviction is allowed if at most "maxUnavailable" pods selected by "selector" are unavailable after the eviction, i.e. even in absence of the evicted pod. For example, one can prevent all voluntary evictions by specifying 0. This is a mutually exclusive setting with "minAvailable".</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
......@@ -1532,7 +1539,7 @@ Examples:<br>
</div>
<div id="footer">
<div id="footer-text">
Last updated 2017-05-17 22:25:27 UTC
Last updated 2017-05-23 14:28:21 UTC
</div>
</div>
</body>
......
......@@ -431,16 +431,29 @@ run_pod_tests() {
# Post-condition: configmap exists and has expected values
kube::test::get_object_assert 'configmap/test-configmap --namespace=test-kubectl-describe-pod' "{{$id_field}}" 'test-configmap'
### Create a pod disruption budget
### Create a pod disruption budget with minAvailable
# Command
kubectl create pdb test-pdb --selector=app=rails --min-available=2 --namespace=test-kubectl-describe-pod
kubectl create pdb test-pdb-1 --selector=app=rails --min-available=2 --namespace=test-kubectl-describe-pod
# Post-condition: pdb exists and has expected values
kube::test::get_object_assert 'pdb/test-pdb --namespace=test-kubectl-describe-pod' "{{$pdb_min_available}}" '2'
kube::test::get_object_assert 'pdb/test-pdb-1 --namespace=test-kubectl-describe-pod' "{{$pdb_min_available}}" '2'
# Command
kubectl create pdb test-pdb-2 --selector=app=rails --min-available=50% --namespace=test-kubectl-describe-pod
# Post-condition: pdb exists and has expected values
kube::test::get_object_assert 'pdb/test-pdb-2 --namespace=test-kubectl-describe-pod' "{{$pdb_min_available}}" '50%'
### Create a pod disruption budget with maxUnavailable
# Command
kubectl create pdb test-pdb-3 --selector=app=rails --max-unavailable=2 --namespace=test-kubectl-describe-pod
# Post-condition: pdb exists and has expected values
kube::test::get_object_assert 'pdb/test-pdb-3 --namespace=test-kubectl-describe-pod' "{{$pdb_max_unavailable}}" '2'
# Command
kubectl create pdb test-pdb-4 --selector=app=rails --max-unavailable=50% --namespace=test-kubectl-describe-pod
# Post-condition: pdb exists and has expected values
kube::test::get_object_assert 'pdb/test-pdb-4 --namespace=test-kubectl-describe-pod' "{{$pdb_max_unavailable}}" '50%'
### Fail creating a pod disruption budget if both maxUnavailable and minAvailable specified
! kubectl create pdb test-pdb --selector=app=rails --min-available=2 --max-unavailable=3 --namespace=test-kubectl-describe-pod
# Create a pod that consumes secret, configmap, and downward API keys as envs
kube::test::get_object_assert 'pods --namespace=test-kubectl-describe-pod' "{{range.items}}{{$id_field}}:{{end}}" ''
kubectl create -f hack/testdata/pod-with-api-env.yaml --namespace=test-kubectl-describe-pod
......@@ -453,7 +466,7 @@ run_pod_tests() {
kubectl delete pod env-test-pod --namespace=test-kubectl-describe-pod
kubectl delete secret test-secret --namespace=test-kubectl-describe-pod
kubectl delete configmap test-configmap --namespace=test-kubectl-describe-pod
kubectl delete pdb/test-pdb pdb/test-pdb-2 --namespace=test-kubectl-describe-pod
kubectl delete pdb/test-pdb-1 pdb/test-pdb-2 pdb/test-pdb-3 pdb/test-pdb-4 --namespace=test-kubectl-describe-pod
kubectl delete namespace test-kubectl-describe-pod
### Create two PODs
......@@ -2990,6 +3003,7 @@ runTests() {
deployment_second_image_field="(index .spec.template.spec.containers 1).image"
change_cause_annotation='.*kubernetes.io/change-cause.*'
pdb_min_available=".spec.minAvailable"
pdb_max_unavailable=".spec.maxUnavailable"
template_generation_field=".spec.templateGeneration"
# Make sure "default" namespace exists.
......
......@@ -457,6 +457,7 @@ max-outgoing-burst
max-outgoing-qps
max-pods
max-requests-inflight
max-unavailable
mesos-authentication-principal
mesos-authentication-provider
mesos-authentication-secret-file
......
......@@ -27,7 +27,7 @@ import "k8s.io/apimachinery/pkg/runtime/schema/generated.proto";
import "k8s.io/apimachinery/pkg/util/intstr/generated.proto";
import "k8s.io/apiserver/pkg/apis/example/v1/generated.proto";
import "k8s.io/kubernetes/pkg/api/v1/generated.proto";
import "k8s.io/kubernetes/pkg/apis/extensions/v1beta1/generated.proto";
import "k8s.io/kubernetes/pkg/apis/policy/v1beta1/generated.proto";
// Package-wide variables from generator "generated".
option go_package = "v1beta1";
......
......@@ -28,6 +28,7 @@ import "k8s.io/apimachinery/pkg/runtime/schema/generated.proto";
import "k8s.io/apimachinery/pkg/util/intstr/generated.proto";
import "k8s.io/apiserver/pkg/apis/example/v1/generated.proto";
import "k8s.io/kubernetes/pkg/api/v1/generated.proto";
import "k8s.io/kubernetes/pkg/apis/policy/v1beta1/generated.proto";
// Package-wide variables from generator "generated".
option go_package = "v1beta1";
......
......@@ -28,12 +28,19 @@ type PodDisruptionBudgetSpec struct {
// absence of the evicted pod. So for example you can prevent all voluntary
// evictions by specifying "100%".
// +optional
MinAvailable intstr.IntOrString
MinAvailable *intstr.IntOrString
// Label query over pods whose evictions are managed by the disruption
// budget.
// +optional
Selector *metav1.LabelSelector
// An eviction is allowed if at most "maxUnavailable" pods selected by
// "selector" are unavailable after the eviction, i.e. even in absence of
// the evicted pod. For example, one can prevent all voluntary evictions
// by specifying 0. This is a mutually exclusive setting with "minAvailable".
// +optional
MaxUnavailable *intstr.IntOrString
}
// PodDisruptionBudgetStatus represents information about the status of a
......
......@@ -71,6 +71,12 @@ message PodDisruptionBudgetSpec {
// Label query over pods whose evictions are managed by the disruption
// budget.
optional k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector selector = 2;
// An eviction is allowed if at most "maxUnavailable" pods selected by
// "selector" are unavailable after the eviction, i.e. even in absence of
// the evicted pod. For example, one can prevent all voluntary evictions
// by specifying 0. This is a mutually exclusive setting with "minAvailable".
optional k8s.io.apimachinery.pkg.util.intstr.IntOrString maxUnavailable = 3;
}
// PodDisruptionBudgetStatus represents information about the status of a
......
......@@ -85,14 +85,15 @@ func (x *PodDisruptionBudgetSpec) CodecEncodeSelf(e *codec1978.Encoder) {
} else {
yysep2 := !z.EncBinary()
yy2arr2 := z.EncBasicHandle().StructToArray
var yyq2 [2]bool
var yyq2 [3]bool
_, _, _ = yysep2, yyq2, yy2arr2
const yyr2 bool = false
yyq2[0] = true
yyq2[0] = x.MinAvailable != nil
yyq2[1] = x.Selector != nil
yyq2[2] = x.MaxUnavailable != nil
var yynn2 int
if yyr2 || yy2arr2 {
r.EncodeArrayStart(2)
r.EncodeArrayStart(3)
} else {
yynn2 = 0
for _, b := range yyq2 {
......@@ -106,15 +107,18 @@ func (x *PodDisruptionBudgetSpec) CodecEncodeSelf(e *codec1978.Encoder) {
if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayElem1234)
if yyq2[0] {
yy4 := &x.MinAvailable
yym5 := z.EncBinary()
_ = yym5
if false {
} else if z.HasExtensions() && z.EncExt(yy4) {
} else if !yym5 && z.IsJSONHandle() {
z.EncJSONMarshal(yy4)
if x.MinAvailable == nil {
r.EncodeNil()
} else {
z.EncFallback(yy4)
yym4 := z.EncBinary()
_ = yym4
if false {
} else if z.HasExtensions() && z.EncExt(x.MinAvailable) {
} else if !yym4 && z.IsJSONHandle() {
z.EncJSONMarshal(x.MinAvailable)
} else {
z.EncFallback(x.MinAvailable)
}
}
} else {
r.EncodeNil()
......@@ -124,15 +128,18 @@ func (x *PodDisruptionBudgetSpec) CodecEncodeSelf(e *codec1978.Encoder) {
z.EncSendContainerState(codecSelfer_containerMapKey1234)
r.EncodeString(codecSelferC_UTF81234, string("minAvailable"))
z.EncSendContainerState(codecSelfer_containerMapValue1234)
yy6 := &x.MinAvailable
yym7 := z.EncBinary()
_ = yym7
if false {
} else if z.HasExtensions() && z.EncExt(yy6) {
} else if !yym7 && z.IsJSONHandle() {
z.EncJSONMarshal(yy6)
if x.MinAvailable == nil {
r.EncodeNil()
} else {
z.EncFallback(yy6)
yym5 := z.EncBinary()
_ = yym5
if false {
} else if z.HasExtensions() && z.EncExt(x.MinAvailable) {
} else if !yym5 && z.IsJSONHandle() {
z.EncJSONMarshal(x.MinAvailable)
} else {
z.EncFallback(x.MinAvailable)
}
}
}
}
......@@ -142,8 +149,8 @@ func (x *PodDisruptionBudgetSpec) CodecEncodeSelf(e *codec1978.Encoder) {
if x.Selector == nil {
r.EncodeNil()
} else {
yym9 := z.EncBinary()
_ = yym9
yym7 := z.EncBinary()
_ = yym7
if false {
} else if z.HasExtensions() && z.EncExt(x.Selector) {
} else {
......@@ -161,8 +168,8 @@ func (x *PodDisruptionBudgetSpec) CodecEncodeSelf(e *codec1978.Encoder) {
if x.Selector == nil {
r.EncodeNil()
} else {
yym10 := z.EncBinary()
_ = yym10
yym8 := z.EncBinary()
_ = yym8
if false {
} else if z.HasExtensions() && z.EncExt(x.Selector) {
} else {
......@@ -172,6 +179,45 @@ func (x *PodDisruptionBudgetSpec) CodecEncodeSelf(e *codec1978.Encoder) {
}
}
if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayElem1234)
if yyq2[2] {
if x.MaxUnavailable == nil {
r.EncodeNil()
} else {
yym10 := z.EncBinary()
_ = yym10
if false {
} else if z.HasExtensions() && z.EncExt(x.MaxUnavailable) {
} else if !yym10 && z.IsJSONHandle() {
z.EncJSONMarshal(x.MaxUnavailable)
} else {
z.EncFallback(x.MaxUnavailable)
}
}
} else {
r.EncodeNil()
}
} else {
if yyq2[2] {
z.EncSendContainerState(codecSelfer_containerMapKey1234)
r.EncodeString(codecSelferC_UTF81234, string("maxUnavailable"))
z.EncSendContainerState(codecSelfer_containerMapValue1234)
if x.MaxUnavailable == nil {
r.EncodeNil()
} else {
yym11 := z.EncBinary()
_ = yym11
if false {
} else if z.HasExtensions() && z.EncExt(x.MaxUnavailable) {
} else if !yym11 && z.IsJSONHandle() {
z.EncJSONMarshal(x.MaxUnavailable)
} else {
z.EncFallback(x.MaxUnavailable)
}
}
}
}
if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayEnd1234)
} else {
z.EncSendContainerState(codecSelfer_containerMapEnd1234)
......@@ -234,17 +280,21 @@ func (x *PodDisruptionBudgetSpec) codecDecodeSelfFromMap(l int, d *codec1978.Dec
switch yys3 {
case "minAvailable":
if r.TryDecodeAsNil() {
x.MinAvailable = pkg1_intstr.IntOrString{}
if x.MinAvailable != nil {
x.MinAvailable = nil
}
} else {
yyv4 := &x.MinAvailable
if x.MinAvailable == nil {
x.MinAvailable = new(pkg1_intstr.IntOrString)
}
yym5 := z.DecBinary()
_ = yym5
if false {
} else if z.HasExtensions() && z.DecExt(yyv4) {
} else if z.HasExtensions() && z.DecExt(x.MinAvailable) {
} else if !yym5 && z.IsJSONHandle() {
z.DecJSONUnmarshal(yyv4)
z.DecJSONUnmarshal(x.MinAvailable)
} else {
z.DecFallback(yyv4, false)
z.DecFallback(x.MinAvailable, false)
}
}
case "selector":
......@@ -264,6 +314,25 @@ func (x *PodDisruptionBudgetSpec) codecDecodeSelfFromMap(l int, d *codec1978.Dec
z.DecFallback(x.Selector, false)
}
}
case "maxUnavailable":
if r.TryDecodeAsNil() {
if x.MaxUnavailable != nil {
x.MaxUnavailable = nil
}
} else {
if x.MaxUnavailable == nil {
x.MaxUnavailable = new(pkg1_intstr.IntOrString)
}
yym9 := z.DecBinary()
_ = yym9
if false {
} else if z.HasExtensions() && z.DecExt(x.MaxUnavailable) {
} else if !yym9 && z.IsJSONHandle() {
z.DecJSONUnmarshal(x.MaxUnavailable)
} else {
z.DecFallback(x.MaxUnavailable, false)
}
}
default:
z.DecStructFieldNotFound(-1, yys3)
} // end switch yys3
......@@ -275,41 +344,45 @@ func (x *PodDisruptionBudgetSpec) codecDecodeSelfFromArray(l int, d *codec1978.D
var h codecSelfer1234
z, r := codec1978.GenHelperDecoder(d)
_, _, _ = h, z, r
var yyj8 int
var yyb8 bool
var yyhl8 bool = l >= 0
yyj8++
if yyhl8 {
yyb8 = yyj8 > l
var yyj10 int
var yyb10 bool
var yyhl10 bool = l >= 0
yyj10++
if yyhl10 {
yyb10 = yyj10 > l
} else {
yyb8 = r.CheckBreak()
yyb10 = r.CheckBreak()
}
if yyb8 {
if yyb10 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
z.DecSendContainerState(codecSelfer_containerArrayElem1234)
if r.TryDecodeAsNil() {
x.MinAvailable = pkg1_intstr.IntOrString{}
if x.MinAvailable != nil {
x.MinAvailable = nil
}
} else {
yyv9 := &x.MinAvailable
yym10 := z.DecBinary()
_ = yym10
if x.MinAvailable == nil {
x.MinAvailable = new(pkg1_intstr.IntOrString)
}
yym12 := z.DecBinary()
_ = yym12
if false {
} else if z.HasExtensions() && z.DecExt(yyv9) {
} else if !yym10 && z.IsJSONHandle() {
z.DecJSONUnmarshal(yyv9)
} else if z.HasExtensions() && z.DecExt(x.MinAvailable) {
} else if !yym12 && z.IsJSONHandle() {
z.DecJSONUnmarshal(x.MinAvailable)
} else {
z.DecFallback(yyv9, false)
z.DecFallback(x.MinAvailable, false)
}
}
yyj8++
if yyhl8 {
yyb8 = yyj8 > l
yyj10++
if yyhl10 {
yyb10 = yyj10 > l
} else {
yyb8 = r.CheckBreak()
yyb10 = r.CheckBreak()
}
if yyb8 {
if yyb10 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
......@@ -322,26 +395,55 @@ func (x *PodDisruptionBudgetSpec) codecDecodeSelfFromArray(l int, d *codec1978.D
if x.Selector == nil {
x.Selector = new(pkg2_v1.LabelSelector)
}
yym12 := z.DecBinary()
_ = yym12
yym14 := z.DecBinary()
_ = yym14
if false {
} else if z.HasExtensions() && z.DecExt(x.Selector) {
} else {
z.DecFallback(x.Selector, false)
}
}
yyj10++
if yyhl10 {
yyb10 = yyj10 > l
} else {
yyb10 = r.CheckBreak()
}
if yyb10 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
z.DecSendContainerState(codecSelfer_containerArrayElem1234)
if r.TryDecodeAsNil() {
if x.MaxUnavailable != nil {
x.MaxUnavailable = nil
}
} else {
if x.MaxUnavailable == nil {
x.MaxUnavailable = new(pkg1_intstr.IntOrString)
}
yym16 := z.DecBinary()
_ = yym16
if false {
} else if z.HasExtensions() && z.DecExt(x.MaxUnavailable) {
} else if !yym16 && z.IsJSONHandle() {
z.DecJSONUnmarshal(x.MaxUnavailable)
} else {
z.DecFallback(x.MaxUnavailable, false)
}
}
for {
yyj8++
if yyhl8 {
yyb8 = yyj8 > l
yyj10++
if yyhl10 {
yyb10 = yyj10 > l
} else {
yyb8 = r.CheckBreak()
yyb10 = r.CheckBreak()
}
if yyb8 {
if yyb10 {
break
}
z.DecSendContainerState(codecSelfer_containerArrayElem1234)
z.DecStructFieldNotFound(yyj8-1, "")
z.DecStructFieldNotFound(yyj10-1, "")
}
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
}
......@@ -2122,7 +2224,7 @@ func (x codecSelfer1234) decSlicePodDisruptionBudget(v *[]PodDisruptionBudget, d
yyrg1 := len(yyv1) > 0
yyv21 := yyv1
yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 336)
yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 320)
if yyrt1 {
if yyrl1 <= cap(yyv1) {
yyv1 = yyv1[:yyrl1]
......
......@@ -27,11 +27,17 @@ type PodDisruptionBudgetSpec struct {
// "selector" will still be available after the eviction, i.e. even in the
// absence of the evicted pod. So for example you can prevent all voluntary
// evictions by specifying "100%".
MinAvailable intstr.IntOrString `json:"minAvailable,omitempty" protobuf:"bytes,1,opt,name=minAvailable"`
MinAvailable *intstr.IntOrString `json:"minAvailable,omitempty" protobuf:"bytes,1,opt,name=minAvailable"`
// Label query over pods whose evictions are managed by the disruption
// budget.
Selector *metav1.LabelSelector `json:"selector,omitempty" protobuf:"bytes,2,opt,name=selector"`
// An eviction is allowed if at most "maxUnavailable" pods selected by
// "selector" are unavailable after the eviction, i.e. even in absence of
// the evicted pod. For example, one can prevent all voluntary evictions
// by specifying 0. This is a mutually exclusive setting with "minAvailable".
MaxUnavailable *intstr.IntOrString `json:"maxUnavailable,omitempty" protobuf:"bytes,3,opt,name=maxUnavailable"`
}
// PodDisruptionBudgetStatus represents information about the status of a
......
......@@ -56,9 +56,10 @@ func (PodDisruptionBudgetList) SwaggerDoc() map[string]string {
}
var map_PodDisruptionBudgetSpec = map[string]string{
"": "PodDisruptionBudgetSpec is a description of a PodDisruptionBudget.",
"minAvailable": "An eviction is allowed if at least \"minAvailable\" pods selected by \"selector\" will still be available after the eviction, i.e. even in the absence of the evicted pod. So for example you can prevent all voluntary evictions by specifying \"100%\".",
"selector": "Label query over pods whose evictions are managed by the disruption budget.",
"": "PodDisruptionBudgetSpec is a description of a PodDisruptionBudget.",
"minAvailable": "An eviction is allowed if at least \"minAvailable\" pods selected by \"selector\" will still be available after the eviction, i.e. even in the absence of the evicted pod. So for example you can prevent all voluntary evictions by specifying \"100%\".",
"selector": "Label query over pods whose evictions are managed by the disruption budget.",
"maxUnavailable": "An eviction is allowed if at most \"maxUnavailable\" pods selected by \"selector\" are unavailable after the eviction, i.e. even in absence of the evicted pod. For example, one can prevent all voluntary evictions by specifying 0. This is a mutually exclusive setting with \"minAvailable\".",
}
func (PodDisruptionBudgetSpec) SwaggerDoc() map[string]string {
......
......@@ -24,6 +24,7 @@ import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
conversion "k8s.io/apimachinery/pkg/conversion"
runtime "k8s.io/apimachinery/pkg/runtime"
intstr "k8s.io/apimachinery/pkg/util/intstr"
policy "k8s.io/kubernetes/pkg/apis/policy"
unsafe "unsafe"
)
......@@ -130,8 +131,9 @@ func Convert_policy_PodDisruptionBudgetList_To_v1beta1_PodDisruptionBudgetList(i
}
func autoConvert_v1beta1_PodDisruptionBudgetSpec_To_policy_PodDisruptionBudgetSpec(in *PodDisruptionBudgetSpec, out *policy.PodDisruptionBudgetSpec, s conversion.Scope) error {
out.MinAvailable = in.MinAvailable
out.MinAvailable = (*intstr.IntOrString)(unsafe.Pointer(in.MinAvailable))
out.Selector = (*v1.LabelSelector)(unsafe.Pointer(in.Selector))
out.MaxUnavailable = (*intstr.IntOrString)(unsafe.Pointer(in.MaxUnavailable))
return nil
}
......@@ -141,8 +143,9 @@ func Convert_v1beta1_PodDisruptionBudgetSpec_To_policy_PodDisruptionBudgetSpec(i
}
func autoConvert_policy_PodDisruptionBudgetSpec_To_v1beta1_PodDisruptionBudgetSpec(in *policy.PodDisruptionBudgetSpec, out *PodDisruptionBudgetSpec, s conversion.Scope) error {
out.MinAvailable = in.MinAvailable
out.MinAvailable = (*intstr.IntOrString)(unsafe.Pointer(in.MinAvailable))
out.Selector = (*v1.LabelSelector)(unsafe.Pointer(in.Selector))
out.MaxUnavailable = (*intstr.IntOrString)(unsafe.Pointer(in.MaxUnavailable))
return nil
}
......
......@@ -24,6 +24,7 @@ import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
conversion "k8s.io/apimachinery/pkg/conversion"
runtime "k8s.io/apimachinery/pkg/runtime"
intstr "k8s.io/apimachinery/pkg/util/intstr"
reflect "reflect"
)
......@@ -112,6 +113,11 @@ func DeepCopy_v1beta1_PodDisruptionBudgetSpec(in interface{}, out interface{}, c
in := in.(*PodDisruptionBudgetSpec)
out := out.(*PodDisruptionBudgetSpec)
*out = *in
if in.MinAvailable != nil {
in, out := &in.MinAvailable, &out.MinAvailable
*out = new(intstr.IntOrString)
**out = **in
}
if in.Selector != nil {
in, out := &in.Selector, &out.Selector
if newVal, err := c.DeepCopy(*in); err != nil {
......@@ -120,6 +126,11 @@ func DeepCopy_v1beta1_PodDisruptionBudgetSpec(in interface{}, out interface{}, c
*out = newVal.(*v1.LabelSelector)
}
}
if in.MaxUnavailable != nil {
in, out := &in.MaxUnavailable, &out.MaxUnavailable
*out = new(intstr.IntOrString)
**out = **in
}
return nil
}
}
......
......@@ -50,8 +50,20 @@ func ValidatePodDisruptionBudgetUpdate(pdb, oldPdb *policy.PodDisruptionBudget)
func ValidatePodDisruptionBudgetSpec(spec policy.PodDisruptionBudgetSpec, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
allErrs = append(allErrs, extensionsvalidation.ValidatePositiveIntOrPercent(spec.MinAvailable, fldPath.Child("minAvailable"))...)
allErrs = append(allErrs, extensionsvalidation.IsNotMoreThan100Percent(spec.MinAvailable, fldPath.Child("minAvailable"))...)
if spec.MinAvailable != nil && spec.MaxUnavailable != nil {
allErrs = append(allErrs, field.Invalid(fldPath, spec, "minAvailable and maxUnavailable cannot be both set"))
}
if spec.MinAvailable != nil {
allErrs = append(allErrs, extensionsvalidation.ValidatePositiveIntOrPercent(*spec.MinAvailable, fldPath.Child("minAvailable"))...)
allErrs = append(allErrs, extensionsvalidation.IsNotMoreThan100Percent(*spec.MinAvailable, fldPath.Child("minAvailable"))...)
}
if spec.MaxUnavailable != nil {
allErrs = append(allErrs, extensionsvalidation.ValidatePositiveIntOrPercent(*spec.MaxUnavailable, fldPath.Child("maxUnavailable"))...)
allErrs = append(allErrs, extensionsvalidation.IsNotMoreThan100Percent(*spec.MaxUnavailable, fldPath.Child("maxUnavailable"))...)
}
allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...)
return allErrs
......
......@@ -25,6 +25,20 @@ import (
)
func TestValidatePodDisruptionBudgetSpec(t *testing.T) {
minAvailable := intstr.FromString("0%")
maxUnavailable := intstr.FromString("10%")
spec := policy.PodDisruptionBudgetSpec{
MinAvailable: &minAvailable,
MaxUnavailable: &maxUnavailable,
}
errs := ValidatePodDisruptionBudgetSpec(spec, field.NewPath("foo"))
if len(errs) == 0 {
t.Errorf("unexpected success for %v", spec)
}
}
func TestValidateMinAvailablePodDisruptionBudgetSpec(t *testing.T) {
successCases := []intstr.IntOrString{
intstr.FromString("0%"),
intstr.FromString("1%"),
......@@ -35,7 +49,7 @@ func TestValidatePodDisruptionBudgetSpec(t *testing.T) {
}
for _, c := range successCases {
spec := policy.PodDisruptionBudgetSpec{
MinAvailable: c,
MinAvailable: &c,
}
errs := ValidatePodDisruptionBudgetSpec(spec, field.NewPath("foo"))
if len(errs) != 0 {
......@@ -52,7 +66,7 @@ func TestValidatePodDisruptionBudgetSpec(t *testing.T) {
}
for _, c := range failureCases {
spec := policy.PodDisruptionBudgetSpec{
MinAvailable: c,
MinAvailable: &c,
}
errs := ValidatePodDisruptionBudgetSpec(spec, field.NewPath("foo"))
if len(errs) == 0 {
......@@ -61,6 +75,20 @@ func TestValidatePodDisruptionBudgetSpec(t *testing.T) {
}
}
func TestValidateMinAvailablePodAndMaxUnavailableDisruptionBudgetSpec(t *testing.T) {
c1 := intstr.FromString("10%")
c2 := intstr.FromInt(1)
spec := policy.PodDisruptionBudgetSpec{
MinAvailable: &c1,
MaxUnavailable: &c2,
}
errs := ValidatePodDisruptionBudgetSpec(spec, field.NewPath("foo"))
if len(errs) == 0 {
t.Errorf("unexpected success for %v", spec)
}
}
func TestValidatePodDisruptionBudgetStatus(t *testing.T) {
successCases := []policy.PodDisruptionBudgetStatus{
{PodDisruptionsAllowed: 10},
......
......@@ -24,6 +24,7 @@ import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
conversion "k8s.io/apimachinery/pkg/conversion"
runtime "k8s.io/apimachinery/pkg/runtime"
intstr "k8s.io/apimachinery/pkg/util/intstr"
reflect "reflect"
)
......@@ -112,6 +113,11 @@ func DeepCopy_policy_PodDisruptionBudgetSpec(in interface{}, out interface{}, c
in := in.(*PodDisruptionBudgetSpec)
out := out.(*PodDisruptionBudgetSpec)
*out = *in
if in.MinAvailable != nil {
in, out := &in.MinAvailable, &out.MinAvailable
*out = new(intstr.IntOrString)
**out = **in
}
if in.Selector != nil {
in, out := &in.Selector, &out.Selector
if newVal, err := c.DeepCopy(*in); err != nil {
......@@ -120,6 +126,11 @@ func DeepCopy_policy_PodDisruptionBudgetSpec(in interface{}, out interface{}, c
*out = newVal.(*v1.LabelSelector)
}
}
if in.MaxUnavailable != nil {
in, out := &in.MaxUnavailable, &out.MaxUnavailable
*out = new(intstr.IntOrString)
**out = **in
}
return nil
}
}
......
......@@ -56,6 +56,7 @@ go_test(
"//pkg/apis/policy/v1beta1:go_default_library",
"//pkg/client/informers/informers_generated/externalversions:go_default_library",
"//pkg/controller:go_default_library",
"//vendor/github.com/Azure/go-autorest/autorest/to:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library",
......
......@@ -516,64 +516,88 @@ func (dc *DisruptionController) getExpectedPodCount(pdb *policy.PodDisruptionBud
// permitted controller configurations (specifically, considering it an error
// if a pod covered by a PDB has 0 controllers or > 1 controller) should be
// handled the same way for integer and percentage minAvailable
if pdb.Spec.MinAvailable.Type == intstr.Int {
desiredHealthy = pdb.Spec.MinAvailable.IntVal
expectedCount = int32(len(pods))
} else if pdb.Spec.MinAvailable.Type == intstr.String {
// When the user specifies a fraction of pods that must be available, we
// use as the fraction's denominator
// SUM_{all c in C} scale(c)
// where C is the union of C_p1, C_p2, ..., C_pN
// and each C_pi is the set of controllers controlling the pod pi
// k8s only defines what will happens when 0 or 1 controllers control a
// given pod. We explicitly exclude the 0 controllers case here, and we
// report an error if we find a pod with more than 1 controller. Thus in
// practice each C_pi is a set of exactly 1 controller.
// A mapping from controllers to their scale.
controllerScale := map[types.UID]int32{}
// 1. Find the controller(s) for each pod. If any pod has 0 controllers,
// that's an error. If any pod has more than 1 controller, that's also an
// error.
for _, pod := range pods {
controllerCount := 0
for _, finder := range dc.finders() {
var controllers []controllerAndScale
controllers, err = finder(pod)
if err != nil {
return
}
for _, controller := range controllers {
controllerScale[controller.UID] = controller.scale
controllerCount++
}
}
if controllerCount == 0 {
err = fmt.Errorf("asked for percentage, but found no controllers for pod %q", pod.Name)
dc.recorder.Event(pdb, v1.EventTypeWarning, "NoControllers", err.Error())
if pdb.Spec.MaxUnavailable != nil {
expectedCount, err = dc.getExpectedScale(pdb, pods)
if err != nil {
return
}
var maxUnavailable int
maxUnavailable, err = intstr.GetValueFromIntOrPercent(pdb.Spec.MaxUnavailable, int(expectedCount), true)
if err != nil {
return
}
desiredHealthy = expectedCount - int32(maxUnavailable)
if desiredHealthy < 0 {
desiredHealthy = 0
}
} else if pdb.Spec.MinAvailable != nil {
if pdb.Spec.MinAvailable.Type == intstr.Int {
desiredHealthy = pdb.Spec.MinAvailable.IntVal
expectedCount = int32(len(pods))
} else if pdb.Spec.MinAvailable.Type == intstr.String {
expectedCount, err = dc.getExpectedScale(pdb, pods)
if err != nil {
return
} else if controllerCount > 1 {
err = fmt.Errorf("pod %q has %v>1 controllers", pod.Name, controllerCount)
dc.recorder.Event(pdb, v1.EventTypeWarning, "TooManyControllers", err.Error())
}
var minAvailable int
minAvailable, err = intstr.GetValueFromIntOrPercent(pdb.Spec.MinAvailable, int(expectedCount), true)
if err != nil {
return
}
desiredHealthy = int32(minAvailable)
}
}
return
}
// 2. Add up all the controllers.
expectedCount = 0
for _, count := range controllerScale {
expectedCount += count
}
func (dc *DisruptionController) getExpectedScale(pdb *policy.PodDisruptionBudget, pods []*v1.Pod) (expectedCount int32, err error) {
// When the user specifies a fraction of pods that must be available, we
// use as the fraction's denominator
// SUM_{all c in C} scale(c)
// where C is the union of C_p1, C_p2, ..., C_pN
// and each C_pi is the set of controllers controlling the pod pi
// 3. Do the math.
var dh int
dh, err = intstr.GetValueFromIntOrPercent(&pdb.Spec.MinAvailable, int(expectedCount), true)
if err != nil {
// k8s only defines what will happens when 0 or 1 controllers control a
// given pod. We explicitly exclude the 0 controllers case here, and we
// report an error if we find a pod with more than 1 controller. Thus in
// practice each C_pi is a set of exactly 1 controller.
// A mapping from controllers to their scale.
controllerScale := map[types.UID]int32{}
// 1. Find the controller(s) for each pod. If any pod has 0 controllers,
// that's an error. If any pod has more than 1 controller, that's also an
// error.
for _, pod := range pods {
controllerCount := 0
for _, finder := range dc.finders() {
var controllers []controllerAndScale
controllers, err = finder(pod)
if err != nil {
return
}
for _, controller := range controllers {
controllerScale[controller.UID] = controller.scale
controllerCount++
}
}
if controllerCount == 0 {
err = fmt.Errorf("found no controllers for pod %q", pod.Name)
dc.recorder.Event(pdb, v1.EventTypeWarning, "NoControllers", err.Error())
return
} else if controllerCount > 1 {
err = fmt.Errorf("pod %q has %v>1 controllers", pod.Name, controllerCount)
dc.recorder.Event(pdb, v1.EventTypeWarning, "TooManyControllers", err.Error())
return
}
desiredHealthy = int32(dh)
}
// 2. Add up all the controllers.
expectedCount = 0
for _, count := range controllerScale {
expectedCount += count
}
return
......
......@@ -35,6 +35,8 @@ import (
policy "k8s.io/kubernetes/pkg/apis/policy/v1beta1"
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/externalversions"
"k8s.io/kubernetes/pkg/controller"
"github.com/Azure/go-autorest/autorest/to"
)
type pdbStates map[string]policy.PodDisruptionBudget
......@@ -141,7 +143,7 @@ func newSelFooBar() *metav1.LabelSelector {
return newSel(map[string]string{"foo": "bar"})
}
func newPodDisruptionBudget(t *testing.T, minAvailable intstr.IntOrString) (*policy.PodDisruptionBudget, string) {
func newMinAvailablePodDisruptionBudget(t *testing.T, minAvailable intstr.IntOrString) (*policy.PodDisruptionBudget, string) {
pdb := &policy.PodDisruptionBudget{
TypeMeta: metav1.TypeMeta{APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()},
......@@ -152,7 +154,7 @@ func newPodDisruptionBudget(t *testing.T, minAvailable intstr.IntOrString) (*pol
ResourceVersion: "18",
},
Spec: policy.PodDisruptionBudgetSpec{
MinAvailable: minAvailable,
MinAvailable: &minAvailable,
Selector: newSelFooBar(),
},
}
......@@ -165,6 +167,29 @@ func newPodDisruptionBudget(t *testing.T, minAvailable intstr.IntOrString) (*pol
return pdb, pdbName
}
func newMaxUnavailablePodDisruptionBudget(t *testing.T, maxUnavailable intstr.IntOrString) (*policy.PodDisruptionBudget, string) {
pdb := &policy.PodDisruptionBudget{
TypeMeta: metav1.TypeMeta{APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
UID: uuid.NewUUID(),
Name: "foobar",
Namespace: metav1.NamespaceDefault,
ResourceVersion: "18",
},
Spec: policy.PodDisruptionBudgetSpec{
MaxUnavailable: &maxUnavailable,
Selector: newSelFooBar(),
},
}
pdbName, err := controller.KeyFunc(pdb)
if err != nil {
t.Fatalf("Unexpected error naming pdb %q: %v", pdb.Name, err)
}
return pdb, pdbName
}
func newPod(t *testing.T, name string) (*v1.Pod, string) {
pod := &v1.Pod{
TypeMeta: metav1.TypeMeta{APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()},
......@@ -304,7 +329,7 @@ func add(t *testing.T, store cache.Store, obj interface{}) {
func TestNoSelector(t *testing.T) {
dc, ps := newFakeDisruptionController()
pdb, pdbName := newPodDisruptionBudget(t, intstr.FromInt(3))
pdb, pdbName := newMinAvailablePodDisruptionBudget(t, intstr.FromInt(3))
pdb.Spec.Selector = &metav1.LabelSelector{}
pod, _ := newPod(t, "yo-yo-yo")
......@@ -322,7 +347,7 @@ func TestNoSelector(t *testing.T) {
func TestUnavailable(t *testing.T) {
dc, ps := newFakeDisruptionController()
pdb, pdbName := newPodDisruptionBudget(t, intstr.FromInt(3))
pdb, pdbName := newMinAvailablePodDisruptionBudget(t, intstr.FromInt(3))
add(t, dc.pdbStore, pdb)
dc.sync(pdbName)
......@@ -346,12 +371,54 @@ func TestUnavailable(t *testing.T) {
ps.VerifyPdbStatus(t, pdbName, 0, 3, 3, 4, map[string]metav1.Time{})
}
// Verify that an integer MaxUnavailable won't
// allow a disruption for pods with no controller.
func TestIntegerMaxUnavailable(t *testing.T) {
dc, ps := newFakeDisruptionController()
pdb, pdbName := newMaxUnavailablePodDisruptionBudget(t, intstr.FromInt(1))
add(t, dc.pdbStore, pdb)
dc.sync(pdbName)
// This verifies that when a PDB has 0 pods, disruptions are not allowed.
ps.VerifyDisruptionAllowed(t, pdbName, 0)
pod, _ := newPod(t, "naked")
add(t, dc.podStore, pod)
dc.sync(pdbName)
ps.VerifyDisruptionAllowed(t, pdbName, 0)
}
// Verify that an integer MaxUnavailable will recompute allowed disruptions when the scale of
// the selected pod's controller is modified.
func TestIntegerMaxUnavailableWithScaling(t *testing.T) {
dc, ps := newFakeDisruptionController()
pdb, pdbName := newMaxUnavailablePodDisruptionBudget(t, intstr.FromInt(2))
add(t, dc.pdbStore, pdb)
rs, _ := newReplicaSet(t, 7)
add(t, dc.rsStore, rs)
pod, _ := newPod(t, "pod")
add(t, dc.podStore, pod)
dc.sync(pdbName)
ps.VerifyPdbStatus(t, pdbName, 0, 1, 5, 7, map[string]metav1.Time{})
// Update scale of ReplicaSet and check PDB
rs.Spec.Replicas = to.Int32Ptr(5)
update(t, dc.rsStore, rs)
dc.sync(pdbName)
ps.VerifyPdbStatus(t, pdbName, 0, 1, 3, 5, map[string]metav1.Time{})
}
// Create a pod with no controller, and verify that a PDB with a percentage
// specified won't allow a disruption.
func TestNakedPod(t *testing.T) {
dc, ps := newFakeDisruptionController()
pdb, pdbName := newPodDisruptionBudget(t, intstr.FromString("28%"))
pdb, pdbName := newMinAvailablePodDisruptionBudget(t, intstr.FromString("28%"))
add(t, dc.pdbStore, pdb)
dc.sync(pdbName)
// This verifies that when a PDB has 0 pods, disruptions are not allowed.
......@@ -368,7 +435,7 @@ func TestNakedPod(t *testing.T) {
func TestReplicaSet(t *testing.T) {
dc, ps := newFakeDisruptionController()
pdb, pdbName := newPodDisruptionBudget(t, intstr.FromString("20%"))
pdb, pdbName := newMinAvailablePodDisruptionBudget(t, intstr.FromString("20%"))
add(t, dc.pdbStore, pdb)
rs, _ := newReplicaSet(t, 10)
......@@ -387,7 +454,7 @@ func TestMultipleControllers(t *testing.T) {
dc, ps := newFakeDisruptionController()
pdb, pdbName := newPodDisruptionBudget(t, intstr.FromString("1%"))
pdb, pdbName := newMinAvailablePodDisruptionBudget(t, intstr.FromString("1%"))
add(t, dc.pdbStore, pdb)
for i := 0; i < podCount; i++ {
......@@ -429,7 +496,7 @@ func TestReplicationController(t *testing.T) {
dc, ps := newFakeDisruptionController()
// 34% should round up to 2
pdb, pdbName := newPodDisruptionBudget(t, intstr.FromString("34%"))
pdb, pdbName := newMinAvailablePodDisruptionBudget(t, intstr.FromString("34%"))
add(t, dc.pdbStore, pdb)
rc, _ := newReplicationController(t, 3)
rc.Spec.Selector = labels
......@@ -470,7 +537,7 @@ func TestStatefulSetController(t *testing.T) {
dc, ps := newFakeDisruptionController()
// 34% should round up to 2
pdb, pdbName := newPodDisruptionBudget(t, intstr.FromString("34%"))
pdb, pdbName := newMinAvailablePodDisruptionBudget(t, intstr.FromString("34%"))
add(t, dc.pdbStore, pdb)
ss, _ := newStatefulSet(t, 3)
add(t, dc.ssStore, ss)
......@@ -518,7 +585,7 @@ func TestTwoControllers(t *testing.T) {
const minimumOne int32 = 4 // integer minimum with one controller
const minimumTwo int32 = 7 // integer minimum with two controllers
pdb, pdbName := newPodDisruptionBudget(t, intstr.FromString("28%"))
pdb, pdbName := newMinAvailablePodDisruptionBudget(t, intstr.FromString("28%"))
add(t, dc.pdbStore, pdb)
rc, _ := newReplicationController(t, collectionSize)
rc.Spec.Selector = rcLabels
......@@ -605,7 +672,7 @@ func TestTwoControllers(t *testing.T) {
// Test pdb doesn't exist
func TestPDBNotExist(t *testing.T) {
dc, _ := newFakeDisruptionController()
pdb, _ := newPodDisruptionBudget(t, intstr.FromString("67%"))
pdb, _ := newMinAvailablePodDisruptionBudget(t, intstr.FromString("67%"))
add(t, dc.pdbStore, pdb)
if err := dc.sync("notExist"); err != nil {
t.Errorf("Unexpected error: %v, expect nil", err)
......@@ -615,7 +682,7 @@ func TestPDBNotExist(t *testing.T) {
func TestUpdateDisruptedPods(t *testing.T) {
dc, ps := newFakeDisruptionController()
dc.recheckQueue = workqueue.NewNamedDelayingQueue("pdb-queue")
pdb, pdbName := newPodDisruptionBudget(t, intstr.FromInt(1))
pdb, pdbName := newMinAvailablePodDisruptionBudget(t, intstr.FromInt(1))
currentTime := time.Now()
pdb.Status.DisruptedPods = map[string]metav1.Time{
"p1": {Time: currentTime}, // Should be removed, pod deletion started.
......
......@@ -59,8 +59,10 @@ func NewCmdCreatePodDisruptionBudget(f cmdutil.Factory, cmdOut io.Writer) *cobra
cmdutil.AddApplyAnnotationFlags(cmd)
cmdutil.AddValidateFlags(cmd)
cmdutil.AddPrinterFlags(cmd)
cmdutil.AddGeneratorFlags(cmd, cmdutil.PodDisruptionBudgetV1GeneratorName)
cmd.Flags().String("min-available", "1", i18n.T("The minimum number or percentage of available pods this budget requires."))
cmdutil.AddGeneratorFlags(cmd, cmdutil.PodDisruptionBudgetV2GeneratorName)
cmd.Flags().String("min-available", "", i18n.T("The minimum number or percentage of available pods this budget requires."))
cmd.Flags().String("max-unavailable", "", i18n.T("The maximum number or percentage of unavailable pods this budget requires."))
cmd.Flags().String("selector", "", i18n.T("A label selector to use for this budget. Only equality-based selector requirements are supported."))
return cmd
}
......@@ -79,6 +81,13 @@ func CreatePodDisruptionBudget(f cmdutil.Factory, cmdOut io.Writer, cmd *cobra.C
MinAvailable: cmdutil.GetFlagString(cmd, "min-available"),
Selector: cmdutil.GetFlagString(cmd, "selector"),
}
case cmdutil.PodDisruptionBudgetV2GeneratorName:
generator = &kubectl.PodDisruptionBudgetV2Generator{
Name: name,
MinAvailable: cmdutil.GetFlagString(cmd, "min-available"),
MaxUnavailable: cmdutil.GetFlagString(cmd, "max-unavailable"),
Selector: cmdutil.GetFlagString(cmd, "selector"),
}
default:
return cmdutil.UsageError(cmd, fmt.Sprintf("Generator: %s not supported.", generatorName))
}
......
......@@ -455,6 +455,9 @@ func (f *ring0Factory) DefaultNamespace() (string, bool, error) {
}
const (
// TODO(sig-cli): Enforce consistent naming for generators here.
// See discussion in https://github.com/kubernetes/kubernetes/issues/46237
// before you add any more.
RunV1GeneratorName = "run/v1"
RunPodV1GeneratorName = "run-pod/v1"
ServiceV1GeneratorName = "service/v1"
......@@ -482,6 +485,7 @@ const (
RoleBindingV1GeneratorName = "rolebinding.rbac.authorization.k8s.io/v1alpha1"
ClusterV1Beta1GeneratorName = "cluster/v1beta1"
PodDisruptionBudgetV1GeneratorName = "poddisruptionbudget/v1beta1"
PodDisruptionBudgetV2GeneratorName = "poddisruptionbudget/v1beta1/v2"
)
// DefaultGenerators returns the set of default generators for use in Factory instances
......
......@@ -18,6 +18,7 @@ package kubectl
import (
"fmt"
"os"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
......@@ -38,7 +39,7 @@ var _ StructuredGenerator = &PodDisruptionBudgetV1Generator{}
func (PodDisruptionBudgetV1Generator) ParamNames() []GeneratorParam {
return []GeneratorParam{
{"name", true},
{"mim-available", true},
{"min-available", true},
{"selector", true},
}
}
......@@ -52,11 +53,11 @@ func (s PodDisruptionBudgetV1Generator) Generate(params map[string]interface{})
if !isString {
return nil, fmt.Errorf("expected string, saw %v for 'name'", name)
}
minAvailable, isString := params["mim-available"].(string)
minAvailable, isString := params["min-available"].(string)
if !isString {
return nil, fmt.Errorf("expected string, found %v", minAvailable)
}
selector, isString := params["selecor"].(string)
selector, isString := params["selector"].(string)
if !isString {
return nil, fmt.Errorf("expected string, found %v", selector)
}
......@@ -66,6 +67,11 @@ func (s PodDisruptionBudgetV1Generator) Generate(params map[string]interface{})
// StructuredGenerate outputs a pod disruption budget object using the configured fields.
func (s *PodDisruptionBudgetV1Generator) StructuredGenerate() (runtime.Object, error) {
if len(s.MinAvailable) == 0 {
// defaulting behavior seen in Kubernetes 1.6 and below.
s.MinAvailable = "1"
}
if err := s.validate(); err != nil {
return nil, err
}
......@@ -75,12 +81,13 @@ func (s *PodDisruptionBudgetV1Generator) StructuredGenerate() (runtime.Object, e
return nil, err
}
minAvailable := intstr.Parse(s.MinAvailable)
return &policy.PodDisruptionBudget{
ObjectMeta: metav1.ObjectMeta{
Name: s.Name,
},
Spec: policy.PodDisruptionBudgetSpec{
MinAvailable: intstr.Parse(s.MinAvailable),
MinAvailable: &minAvailable,
Selector: selector,
},
}, nil
......@@ -95,7 +102,119 @@ func (s *PodDisruptionBudgetV1Generator) validate() error {
return fmt.Errorf("a selector must be specified")
}
if len(s.MinAvailable) == 0 {
return fmt.Errorf("the minimim number of available pods required must be specified")
return fmt.Errorf("the minimum number of available pods required must be specified")
}
return nil
}
// PodDisruptionBudgetV2Generator supports stable generation of a pod disruption budget.
type PodDisruptionBudgetV2Generator struct {
Name string
MinAvailable string
MaxUnavailable string
Selector string
}
// Ensure it supports the generator pattern that uses parameters specified during construction.
var _ StructuredGenerator = &PodDisruptionBudgetV2Generator{}
func (PodDisruptionBudgetV2Generator) ParamNames() []GeneratorParam {
return []GeneratorParam{
{"name", true},
{"min-available", false},
{"max-unavailable", false},
{"selector", false},
}
}
func (s PodDisruptionBudgetV2Generator) Generate(params map[string]interface{}) (runtime.Object, error) {
err := ValidateParams(s.ParamNames(), params)
if err != nil {
return nil, err
}
name, isString := params["name"].(string)
if !isString {
return nil, fmt.Errorf("expected string, saw %v for 'name'", name)
}
minAvailable, isString := params["min-available"].(string)
if !isString {
return nil, fmt.Errorf("expected string, found %v", minAvailable)
}
maxUnavailable, isString := params["max-available"].(string)
if !isString {
return nil, fmt.Errorf("expected string, found %v", maxUnavailable)
}
selector, isString := params["selector"].(string)
if !isString {
return nil, fmt.Errorf("expected string, found %v", selector)
}
delegate := &PodDisruptionBudgetV2Generator{Name: name, MinAvailable: minAvailable, MaxUnavailable: maxUnavailable, Selector: selector}
return delegate.StructuredGenerate()
}
// StructuredGenerate outputs a pod disruption budget object using the configured fields.
func (s *PodDisruptionBudgetV2Generator) StructuredGenerate() (runtime.Object, error) {
if err := s.validate(); err != nil {
return nil, err
}
selector, err := metav1.ParseToLabelSelector(s.Selector)
if err != nil {
return nil, err
}
if len(s.MaxUnavailable) == 0 && len(s.MinAvailable) == 0 {
s.MinAvailable = "1"
// This behavior is intended for backward compatibility.
// TODO: remove in Kubernetes 1.8
fmt.Fprintln(os.Stderr, "Deprecated behavior in kubectl create pdb: Defaulting min-available to 1. "+
"Kubernetes 1.8 will remove this default, and one of min-available/max-available must be specified. ")
}
if len(s.MaxUnavailable) > 0 {
maxUnavailable := intstr.Parse(s.MaxUnavailable)
return &policy.PodDisruptionBudget{
ObjectMeta: metav1.ObjectMeta{
Name: s.Name,
},
Spec: policy.PodDisruptionBudgetSpec{
MaxUnavailable: &maxUnavailable,
Selector: selector,
},
}, nil
}
if len(s.MinAvailable) > 0 {
minAvailable := intstr.Parse(s.MinAvailable)
return &policy.PodDisruptionBudget{
ObjectMeta: metav1.ObjectMeta{
Name: s.Name,
},
Spec: policy.PodDisruptionBudgetSpec{
MinAvailable: &minAvailable,
Selector: selector,
},
}, nil
}
return nil, err
}
// validate validates required fields are set to support structured generation.
func (s *PodDisruptionBudgetV2Generator) validate() error {
if len(s.Name) == 0 {
return fmt.Errorf("name must be specified")
}
if len(s.Selector) == 0 {
return fmt.Errorf("a selector must be specified")
}
if len(s.MaxUnavailable) > 0 && len(s.MinAvailable) > 0 {
return fmt.Errorf("exactly one of min-available and max-available must be specified")
}
return nil
}
......@@ -2881,7 +2881,13 @@ func describePodDisruptionBudget(pdb *policy.PodDisruptionBudget, events *api.Ev
return tabbedString(func(out io.Writer) error {
w := NewPrefixWriter(out)
w.Write(LEVEL_0, "Name:\t%s\n", pdb.Name)
w.Write(LEVEL_0, "Min available:\t%s\n", pdb.Spec.MinAvailable.String())
if pdb.Spec.MinAvailable != nil {
w.Write(LEVEL_0, "Min available:\t%s\n", pdb.Spec.MinAvailable.String())
} else if pdb.Spec.MaxUnavailable != nil {
w.Write(LEVEL_0, "Max unavailable:\t%s\n", pdb.Spec.MaxUnavailable.String())
}
if pdb.Spec.Selector != nil {
w.Write(LEVEL_0, "Selector:\t%s\n", metav1.FormatLabelSelector(pdb.Spec.Selector))
} else {
......
......@@ -774,6 +774,7 @@ func TestDescribeStorageClass(t *testing.T) {
}
func TestDescribePodDisruptionBudget(t *testing.T) {
minAvailable := intstr.FromInt(22)
f := fake.NewSimpleClientset(&policy.PodDisruptionBudget{
ObjectMeta: metav1.ObjectMeta{
Namespace: "ns1",
......@@ -781,7 +782,7 @@ func TestDescribePodDisruptionBudget(t *testing.T) {
CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
},
Spec: policy.PodDisruptionBudgetSpec{
MinAvailable: intstr.FromInt(22),
MinAvailable: &minAvailable,
},
Status: policy.PodDisruptionBudgetStatus{
PodDisruptionsAllowed: 5,
......
......@@ -55,7 +55,7 @@ var (
podColumns = []string{"NAME", "READY", "STATUS", "RESTARTS", "AGE"}
podWideColumns = []string{"IP", "NODE"}
podTemplateColumns = []string{"TEMPLATE", "CONTAINER(S)", "IMAGE(S)", "PODLABELS"}
podDisruptionBudgetColumns = []string{"NAME", "MIN-AVAILABLE", "ALLOWED-DISRUPTIONS", "AGE"}
podDisruptionBudgetColumns = []string{"NAME", "MIN-AVAILABLE", "MAX-UNAVAILABLE", "ALLOWED-DISRUPTIONS", "AGE"}
replicationControllerColumns = []string{"NAME", "DESIRED", "CURRENT", "READY", "AGE"}
replicationControllerWideColumns = []string{"CONTAINER(S)", "IMAGE(S)", "SELECTOR"}
replicaSetColumns = []string{"NAME", "DESIRED", "CURRENT", "READY", "AGE"}
......@@ -397,7 +397,7 @@ func printPodTemplateList(podList *api.PodTemplateList, w io.Writer, options pri
}
func printPodDisruptionBudget(pdb *policy.PodDisruptionBudget, w io.Writer, options printers.PrintOptions) error {
// name, minavailable, selector
// name, minavailable, maxUnavailable, selector
name := printers.FormatResourceName(options.Kind, pdb.Name, options.WithKind)
namespace := pdb.Namespace
......@@ -406,9 +406,25 @@ func printPodDisruptionBudget(pdb *policy.PodDisruptionBudget, w io.Writer, opti
return err
}
}
if _, err := fmt.Fprintf(w, "%s\t%s\t%d\t%s\n",
var minAvailable string
var maxUnavailable string
if pdb.Spec.MinAvailable != nil {
minAvailable = pdb.Spec.MinAvailable.String()
} else {
minAvailable = "N/A"
}
if pdb.Spec.MaxUnavailable != nil {
maxUnavailable = pdb.Spec.MaxUnavailable.String()
} else {
maxUnavailable = "N/A"
}
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%d\t%s\n",
name,
pdb.Spec.MinAvailable.String(),
minAvailable,
maxUnavailable,
pdb.Status.PodDisruptionsAllowed,
translateTimestamp(pdb.CreationTimestamp),
); err != nil {
......
......@@ -2186,6 +2186,7 @@ func TestPrintService(t *testing.T) {
}
func TestPrintPodDisruptionBudget(t *testing.T) {
minAvailable := intstr.FromInt(22)
tests := []struct {
pdb policy.PodDisruptionBudget
expect string
......@@ -2198,13 +2199,13 @@ func TestPrintPodDisruptionBudget(t *testing.T) {
CreationTimestamp: metav1.Time{Time: time.Now().Add(1.9e9)},
},
Spec: policy.PodDisruptionBudgetSpec{
MinAvailable: intstr.FromInt(22),
MinAvailable: &minAvailable,
},
Status: policy.PodDisruptionBudgetStatus{
PodDisruptionsAllowed: 5,
},
},
"pdb1\t22\t5\t0s\n",
"pdb1\t22\tN/A\t5\t0s\n",
}}
buf := bytes.NewBuffer([]byte{})
......
......@@ -51,6 +51,7 @@ func createPodDisruptionBudget(storage *REST, pdb policy.PodDisruptionBudget, t
}
func validNewPodDisruptionBudget() *policy.PodDisruptionBudget {
minAvailable := intstr.FromInt(7)
return &policy.PodDisruptionBudget{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
......@@ -59,7 +60,7 @@ func validNewPodDisruptionBudget() *policy.PodDisruptionBudget {
},
Spec: policy.PodDisruptionBudgetSpec{
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}},
MinAvailable: intstr.FromInt(7),
MinAvailable: &minAvailable,
},
Status: policy.PodDisruptionBudgetStatus{},
}
......@@ -98,10 +99,11 @@ func TestStatusUpdate(t *testing.T) {
}
obtainedPdb := obj.(*policy.PodDisruptionBudget)
minAvailable := intstr.FromInt(8)
update := policy.PodDisruptionBudget{
ObjectMeta: obtainedPdb.ObjectMeta,
Spec: policy.PodDisruptionBudgetSpec{
MinAvailable: intstr.FromInt(8),
MinAvailable: &minAvailable,
},
Status: policy.PodDisruptionBudgetStatus{
ExpectedPods: 8,
......
......@@ -35,10 +35,11 @@ func TestPodDisruptionBudgetStrategy(t *testing.T) {
}
validSelector := map[string]string{"a": "b"}
minAvailable := intstr.FromInt(3)
pdb := &policy.PodDisruptionBudget{
ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
Spec: policy.PodDisruptionBudgetSpec{
MinAvailable: intstr.FromInt(3),
MinAvailable: &minAvailable,
Selector: &metav1.LabelSelector{MatchLabels: validSelector},
},
}
......@@ -77,7 +78,16 @@ func TestPodDisruptionBudgetStrategy(t *testing.T) {
newPdb.Spec.Selector = pdb.Spec.Selector
// Changing MinAvailable? Also no.
newPdb.Spec.MinAvailable = intstr.FromString("28%")
newMinAvailable := intstr.FromString("28%")
newPdb.Spec.MinAvailable = &newMinAvailable
Strategy.PrepareForUpdate(ctx, newPdb, pdb)
errs = Strategy.ValidateUpdate(ctx, newPdb, pdb)
if len(errs) == 0 {
t.Errorf("Expected a validation error since updates are disallowed on poddisruptionbudgets.")
}
maxUnavailable := intstr.FromString("28%")
newPdb.Spec.MaxUnavailable = &maxUnavailable
Strategy.PrepareForUpdate(ctx, newPdb, pdb)
errs = Strategy.ValidateUpdate(ctx, newPdb, pdb)
if len(errs) == 0 {
......@@ -93,12 +103,16 @@ func TestPodDisruptionBudgetStatusStrategy(t *testing.T) {
if StatusStrategy.AllowCreateOnUpdate() {
t.Errorf("PodDisruptionBudgetStatus should not allow create on update")
}
oldMinAvailable := intstr.FromInt(3)
newMinAvailable := intstr.FromInt(2)
validSelector := map[string]string{"a": "b"}
oldPdb := &policy.PodDisruptionBudget{
ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault, ResourceVersion: "10"},
Spec: policy.PodDisruptionBudgetSpec{
Selector: &metav1.LabelSelector{MatchLabels: validSelector},
MinAvailable: intstr.FromInt(3),
MinAvailable: &oldMinAvailable,
},
Status: policy.PodDisruptionBudgetStatus{
PodDisruptionsAllowed: 1,
......@@ -111,7 +125,7 @@ func TestPodDisruptionBudgetStatusStrategy(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault, ResourceVersion: "9"},
Spec: policy.PodDisruptionBudgetSpec{
Selector: &metav1.LabelSelector{MatchLabels: validSelector},
MinAvailable: intstr.FromInt(2),
MinAvailable: &newMinAvailable,
},
Status: policy.PodDisruptionBudgetStatus{
PodDisruptionsAllowed: 0,
......
......@@ -27,7 +27,7 @@ import "k8s.io/apimachinery/pkg/runtime/schema/generated.proto";
import "k8s.io/apimachinery/pkg/util/intstr/generated.proto";
import "k8s.io/apiserver/pkg/apis/example/v1/generated.proto";
import "k8s.io/kubernetes/pkg/api/v1/generated.proto";
import "k8s.io/kubernetes/pkg/apis/extensions/v1beta1/generated.proto";
import "k8s.io/kubernetes/pkg/apis/policy/v1beta1/generated.proto";
// Package-wide variables from generator "generated".
option go_package = "v1beta1";
......
......@@ -28,6 +28,7 @@ import "k8s.io/apimachinery/pkg/runtime/schema/generated.proto";
import "k8s.io/apimachinery/pkg/util/intstr/generated.proto";
import "k8s.io/apiserver/pkg/apis/example/v1/generated.proto";
import "k8s.io/kubernetes/pkg/api/v1/generated.proto";
import "k8s.io/kubernetes/pkg/apis/policy/v1beta1/generated.proto";
// Package-wide variables from generator "generated".
option go_package = "v1beta1";
......
......@@ -28,12 +28,19 @@ type PodDisruptionBudgetSpec struct {
// absence of the evicted pod. So for example you can prevent all voluntary
// evictions by specifying "100%".
// +optional
MinAvailable intstr.IntOrString
MinAvailable *intstr.IntOrString
// Label query over pods whose evictions are managed by the disruption
// budget.
// +optional
Selector *metav1.LabelSelector
// An eviction is allowed if at most "maxUnavailable" pods selected by
// "selector" are unavailable after the eviction, i.e. even in absence of
// the evicted pod. For example, one can prevent all voluntary evictions
// by specifying 0. This is a mutually exclusive setting with "minAvailable".
// +optional
MaxUnavailable *intstr.IntOrString
}
// PodDisruptionBudgetStatus represents information about the status of a
......
......@@ -71,6 +71,12 @@ message PodDisruptionBudgetSpec {
// Label query over pods whose evictions are managed by the disruption
// budget.
optional k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector selector = 2;
// An eviction is allowed if at most "maxUnavailable" pods selected by
// "selector" are unavailable after the eviction, i.e. even in absence of
// the evicted pod. For example, one can prevent all voluntary evictions
// by specifying 0. This is a mutually exclusive setting with "minAvailable".
optional k8s.io.apimachinery.pkg.util.intstr.IntOrString maxUnavailable = 3;
}
// PodDisruptionBudgetStatus represents information about the status of a
......
......@@ -85,14 +85,15 @@ func (x *PodDisruptionBudgetSpec) CodecEncodeSelf(e *codec1978.Encoder) {
} else {
yysep2 := !z.EncBinary()
yy2arr2 := z.EncBasicHandle().StructToArray
var yyq2 [2]bool
var yyq2 [3]bool
_, _, _ = yysep2, yyq2, yy2arr2
const yyr2 bool = false
yyq2[0] = true
yyq2[0] = x.MinAvailable != nil
yyq2[1] = x.Selector != nil
yyq2[2] = x.MaxUnavailable != nil
var yynn2 int
if yyr2 || yy2arr2 {
r.EncodeArrayStart(2)
r.EncodeArrayStart(3)
} else {
yynn2 = 0
for _, b := range yyq2 {
......@@ -106,15 +107,18 @@ func (x *PodDisruptionBudgetSpec) CodecEncodeSelf(e *codec1978.Encoder) {
if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayElem1234)
if yyq2[0] {
yy4 := &x.MinAvailable
yym5 := z.EncBinary()
_ = yym5
if false {
} else if z.HasExtensions() && z.EncExt(yy4) {
} else if !yym5 && z.IsJSONHandle() {
z.EncJSONMarshal(yy4)
if x.MinAvailable == nil {
r.EncodeNil()
} else {
z.EncFallback(yy4)
yym4 := z.EncBinary()
_ = yym4
if false {
} else if z.HasExtensions() && z.EncExt(x.MinAvailable) {
} else if !yym4 && z.IsJSONHandle() {
z.EncJSONMarshal(x.MinAvailable)
} else {
z.EncFallback(x.MinAvailable)
}
}
} else {
r.EncodeNil()
......@@ -124,15 +128,18 @@ func (x *PodDisruptionBudgetSpec) CodecEncodeSelf(e *codec1978.Encoder) {
z.EncSendContainerState(codecSelfer_containerMapKey1234)
r.EncodeString(codecSelferC_UTF81234, string("minAvailable"))
z.EncSendContainerState(codecSelfer_containerMapValue1234)
yy6 := &x.MinAvailable
yym7 := z.EncBinary()
_ = yym7
if false {
} else if z.HasExtensions() && z.EncExt(yy6) {
} else if !yym7 && z.IsJSONHandle() {
z.EncJSONMarshal(yy6)
if x.MinAvailable == nil {
r.EncodeNil()
} else {
z.EncFallback(yy6)
yym5 := z.EncBinary()
_ = yym5
if false {
} else if z.HasExtensions() && z.EncExt(x.MinAvailable) {
} else if !yym5 && z.IsJSONHandle() {
z.EncJSONMarshal(x.MinAvailable)
} else {
z.EncFallback(x.MinAvailable)
}
}
}
}
......@@ -142,8 +149,8 @@ func (x *PodDisruptionBudgetSpec) CodecEncodeSelf(e *codec1978.Encoder) {
if x.Selector == nil {
r.EncodeNil()
} else {
yym9 := z.EncBinary()
_ = yym9
yym7 := z.EncBinary()
_ = yym7
if false {
} else if z.HasExtensions() && z.EncExt(x.Selector) {
} else {
......@@ -161,8 +168,8 @@ func (x *PodDisruptionBudgetSpec) CodecEncodeSelf(e *codec1978.Encoder) {
if x.Selector == nil {
r.EncodeNil()
} else {
yym10 := z.EncBinary()
_ = yym10
yym8 := z.EncBinary()
_ = yym8
if false {
} else if z.HasExtensions() && z.EncExt(x.Selector) {
} else {
......@@ -172,6 +179,45 @@ func (x *PodDisruptionBudgetSpec) CodecEncodeSelf(e *codec1978.Encoder) {
}
}
if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayElem1234)
if yyq2[2] {
if x.MaxUnavailable == nil {
r.EncodeNil()
} else {
yym10 := z.EncBinary()
_ = yym10
if false {
} else if z.HasExtensions() && z.EncExt(x.MaxUnavailable) {
} else if !yym10 && z.IsJSONHandle() {
z.EncJSONMarshal(x.MaxUnavailable)
} else {
z.EncFallback(x.MaxUnavailable)
}
}
} else {
r.EncodeNil()
}
} else {
if yyq2[2] {
z.EncSendContainerState(codecSelfer_containerMapKey1234)
r.EncodeString(codecSelferC_UTF81234, string("maxUnavailable"))
z.EncSendContainerState(codecSelfer_containerMapValue1234)
if x.MaxUnavailable == nil {
r.EncodeNil()
} else {
yym11 := z.EncBinary()
_ = yym11
if false {
} else if z.HasExtensions() && z.EncExt(x.MaxUnavailable) {
} else if !yym11 && z.IsJSONHandle() {
z.EncJSONMarshal(x.MaxUnavailable)
} else {
z.EncFallback(x.MaxUnavailable)
}
}
}
}
if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayEnd1234)
} else {
z.EncSendContainerState(codecSelfer_containerMapEnd1234)
......@@ -234,17 +280,21 @@ func (x *PodDisruptionBudgetSpec) codecDecodeSelfFromMap(l int, d *codec1978.Dec
switch yys3 {
case "minAvailable":
if r.TryDecodeAsNil() {
x.MinAvailable = pkg1_intstr.IntOrString{}
if x.MinAvailable != nil {
x.MinAvailable = nil
}
} else {
yyv4 := &x.MinAvailable
if x.MinAvailable == nil {
x.MinAvailable = new(pkg1_intstr.IntOrString)
}
yym5 := z.DecBinary()
_ = yym5
if false {
} else if z.HasExtensions() && z.DecExt(yyv4) {
} else if z.HasExtensions() && z.DecExt(x.MinAvailable) {
} else if !yym5 && z.IsJSONHandle() {
z.DecJSONUnmarshal(yyv4)
z.DecJSONUnmarshal(x.MinAvailable)
} else {
z.DecFallback(yyv4, false)
z.DecFallback(x.MinAvailable, false)
}
}
case "selector":
......@@ -264,6 +314,25 @@ func (x *PodDisruptionBudgetSpec) codecDecodeSelfFromMap(l int, d *codec1978.Dec
z.DecFallback(x.Selector, false)
}
}
case "maxUnavailable":
if r.TryDecodeAsNil() {
if x.MaxUnavailable != nil {
x.MaxUnavailable = nil
}
} else {
if x.MaxUnavailable == nil {
x.MaxUnavailable = new(pkg1_intstr.IntOrString)
}
yym9 := z.DecBinary()
_ = yym9
if false {
} else if z.HasExtensions() && z.DecExt(x.MaxUnavailable) {
} else if !yym9 && z.IsJSONHandle() {
z.DecJSONUnmarshal(x.MaxUnavailable)
} else {
z.DecFallback(x.MaxUnavailable, false)
}
}
default:
z.DecStructFieldNotFound(-1, yys3)
} // end switch yys3
......@@ -275,41 +344,45 @@ func (x *PodDisruptionBudgetSpec) codecDecodeSelfFromArray(l int, d *codec1978.D
var h codecSelfer1234
z, r := codec1978.GenHelperDecoder(d)
_, _, _ = h, z, r
var yyj8 int
var yyb8 bool
var yyhl8 bool = l >= 0
yyj8++
if yyhl8 {
yyb8 = yyj8 > l
var yyj10 int
var yyb10 bool
var yyhl10 bool = l >= 0
yyj10++
if yyhl10 {
yyb10 = yyj10 > l
} else {
yyb8 = r.CheckBreak()
yyb10 = r.CheckBreak()
}
if yyb8 {
if yyb10 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
z.DecSendContainerState(codecSelfer_containerArrayElem1234)
if r.TryDecodeAsNil() {
x.MinAvailable = pkg1_intstr.IntOrString{}
if x.MinAvailable != nil {
x.MinAvailable = nil
}
} else {
yyv9 := &x.MinAvailable
yym10 := z.DecBinary()
_ = yym10
if x.MinAvailable == nil {
x.MinAvailable = new(pkg1_intstr.IntOrString)
}
yym12 := z.DecBinary()
_ = yym12
if false {
} else if z.HasExtensions() && z.DecExt(yyv9) {
} else if !yym10 && z.IsJSONHandle() {
z.DecJSONUnmarshal(yyv9)
} else if z.HasExtensions() && z.DecExt(x.MinAvailable) {
} else if !yym12 && z.IsJSONHandle() {
z.DecJSONUnmarshal(x.MinAvailable)
} else {
z.DecFallback(yyv9, false)
z.DecFallback(x.MinAvailable, false)
}
}
yyj8++
if yyhl8 {
yyb8 = yyj8 > l
yyj10++
if yyhl10 {
yyb10 = yyj10 > l
} else {
yyb8 = r.CheckBreak()
yyb10 = r.CheckBreak()
}
if yyb8 {
if yyb10 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
......@@ -322,26 +395,55 @@ func (x *PodDisruptionBudgetSpec) codecDecodeSelfFromArray(l int, d *codec1978.D
if x.Selector == nil {
x.Selector = new(pkg2_v1.LabelSelector)
}
yym12 := z.DecBinary()
_ = yym12
yym14 := z.DecBinary()
_ = yym14
if false {
} else if z.HasExtensions() && z.DecExt(x.Selector) {
} else {
z.DecFallback(x.Selector, false)
}
}
yyj10++
if yyhl10 {
yyb10 = yyj10 > l
} else {
yyb10 = r.CheckBreak()
}
if yyb10 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
z.DecSendContainerState(codecSelfer_containerArrayElem1234)
if r.TryDecodeAsNil() {
if x.MaxUnavailable != nil {
x.MaxUnavailable = nil
}
} else {
if x.MaxUnavailable == nil {
x.MaxUnavailable = new(pkg1_intstr.IntOrString)
}
yym16 := z.DecBinary()
_ = yym16
if false {
} else if z.HasExtensions() && z.DecExt(x.MaxUnavailable) {
} else if !yym16 && z.IsJSONHandle() {
z.DecJSONUnmarshal(x.MaxUnavailable)
} else {
z.DecFallback(x.MaxUnavailable, false)
}
}
for {
yyj8++
if yyhl8 {
yyb8 = yyj8 > l
yyj10++
if yyhl10 {
yyb10 = yyj10 > l
} else {
yyb8 = r.CheckBreak()
yyb10 = r.CheckBreak()
}
if yyb8 {
if yyb10 {
break
}
z.DecSendContainerState(codecSelfer_containerArrayElem1234)
z.DecStructFieldNotFound(yyj8-1, "")
z.DecStructFieldNotFound(yyj10-1, "")
}
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
}
......@@ -2122,7 +2224,7 @@ func (x codecSelfer1234) decSlicePodDisruptionBudget(v *[]PodDisruptionBudget, d
yyrg1 := len(yyv1) > 0
yyv21 := yyv1
yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 336)
yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 320)
if yyrt1 {
if yyrl1 <= cap(yyv1) {
yyv1 = yyv1[:yyrl1]
......
......@@ -27,11 +27,17 @@ type PodDisruptionBudgetSpec struct {
// "selector" will still be available after the eviction, i.e. even in the
// absence of the evicted pod. So for example you can prevent all voluntary
// evictions by specifying "100%".
MinAvailable intstr.IntOrString `json:"minAvailable,omitempty" protobuf:"bytes,1,opt,name=minAvailable"`
MinAvailable *intstr.IntOrString `json:"minAvailable,omitempty" protobuf:"bytes,1,opt,name=minAvailable"`
// Label query over pods whose evictions are managed by the disruption
// budget.
Selector *metav1.LabelSelector `json:"selector,omitempty" protobuf:"bytes,2,opt,name=selector"`
// An eviction is allowed if at most "maxUnavailable" pods selected by
// "selector" are unavailable after the eviction, i.e. even in absence of
// the evicted pod. For example, one can prevent all voluntary evictions
// by specifying 0. This is a mutually exclusive setting with "minAvailable".
MaxUnavailable *intstr.IntOrString `json:"maxUnavailable,omitempty" protobuf:"bytes,3,opt,name=maxUnavailable"`
}
// PodDisruptionBudgetStatus represents information about the status of a
......
......@@ -56,9 +56,10 @@ func (PodDisruptionBudgetList) SwaggerDoc() map[string]string {
}
var map_PodDisruptionBudgetSpec = map[string]string{
"": "PodDisruptionBudgetSpec is a description of a PodDisruptionBudget.",
"minAvailable": "An eviction is allowed if at least \"minAvailable\" pods selected by \"selector\" will still be available after the eviction, i.e. even in the absence of the evicted pod. So for example you can prevent all voluntary evictions by specifying \"100%\".",
"selector": "Label query over pods whose evictions are managed by the disruption budget.",
"": "PodDisruptionBudgetSpec is a description of a PodDisruptionBudget.",
"minAvailable": "An eviction is allowed if at least \"minAvailable\" pods selected by \"selector\" will still be available after the eviction, i.e. even in the absence of the evicted pod. So for example you can prevent all voluntary evictions by specifying \"100%\".",
"selector": "Label query over pods whose evictions are managed by the disruption budget.",
"maxUnavailable": "An eviction is allowed if at most \"maxUnavailable\" pods selected by \"selector\" are unavailable after the eviction, i.e. even in absence of the evicted pod. For example, one can prevent all voluntary evictions by specifying 0. This is a mutually exclusive setting with \"minAvailable\".",
}
func (PodDisruptionBudgetSpec) SwaggerDoc() map[string]string {
......
......@@ -24,6 +24,7 @@ import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
conversion "k8s.io/apimachinery/pkg/conversion"
runtime "k8s.io/apimachinery/pkg/runtime"
intstr "k8s.io/apimachinery/pkg/util/intstr"
policy "k8s.io/client-go/pkg/apis/policy"
unsafe "unsafe"
)
......@@ -130,8 +131,9 @@ func Convert_policy_PodDisruptionBudgetList_To_v1beta1_PodDisruptionBudgetList(i
}
func autoConvert_v1beta1_PodDisruptionBudgetSpec_To_policy_PodDisruptionBudgetSpec(in *PodDisruptionBudgetSpec, out *policy.PodDisruptionBudgetSpec, s conversion.Scope) error {
out.MinAvailable = in.MinAvailable
out.MinAvailable = (*intstr.IntOrString)(unsafe.Pointer(in.MinAvailable))
out.Selector = (*v1.LabelSelector)(unsafe.Pointer(in.Selector))
out.MaxUnavailable = (*intstr.IntOrString)(unsafe.Pointer(in.MaxUnavailable))
return nil
}
......@@ -141,8 +143,9 @@ func Convert_v1beta1_PodDisruptionBudgetSpec_To_policy_PodDisruptionBudgetSpec(i
}
func autoConvert_policy_PodDisruptionBudgetSpec_To_v1beta1_PodDisruptionBudgetSpec(in *policy.PodDisruptionBudgetSpec, out *PodDisruptionBudgetSpec, s conversion.Scope) error {
out.MinAvailable = in.MinAvailable
out.MinAvailable = (*intstr.IntOrString)(unsafe.Pointer(in.MinAvailable))
out.Selector = (*v1.LabelSelector)(unsafe.Pointer(in.Selector))
out.MaxUnavailable = (*intstr.IntOrString)(unsafe.Pointer(in.MaxUnavailable))
return nil
}
......
......@@ -24,6 +24,7 @@ import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
conversion "k8s.io/apimachinery/pkg/conversion"
runtime "k8s.io/apimachinery/pkg/runtime"
intstr "k8s.io/apimachinery/pkg/util/intstr"
reflect "reflect"
)
......@@ -112,6 +113,11 @@ func DeepCopy_v1beta1_PodDisruptionBudgetSpec(in interface{}, out interface{}, c
in := in.(*PodDisruptionBudgetSpec)
out := out.(*PodDisruptionBudgetSpec)
*out = *in
if in.MinAvailable != nil {
in, out := &in.MinAvailable, &out.MinAvailable
*out = new(intstr.IntOrString)
**out = **in
}
if in.Selector != nil {
in, out := &in.Selector, &out.Selector
if newVal, err := c.DeepCopy(*in); err != nil {
......@@ -120,6 +126,11 @@ func DeepCopy_v1beta1_PodDisruptionBudgetSpec(in interface{}, out interface{}, c
*out = newVal.(*v1.LabelSelector)
}
}
if in.MaxUnavailable != nil {
in, out := &in.MaxUnavailable, &out.MaxUnavailable
*out = new(intstr.IntOrString)
**out = **in
}
return nil
}
}
......
......@@ -24,6 +24,7 @@ import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
conversion "k8s.io/apimachinery/pkg/conversion"
runtime "k8s.io/apimachinery/pkg/runtime"
intstr "k8s.io/apimachinery/pkg/util/intstr"
reflect "reflect"
)
......@@ -112,6 +113,11 @@ func DeepCopy_policy_PodDisruptionBudgetSpec(in interface{}, out interface{}, c
in := in.(*PodDisruptionBudgetSpec)
out := out.(*PodDisruptionBudgetSpec)
*out = *in
if in.MinAvailable != nil {
in, out := &in.MinAvailable, &out.MinAvailable
*out = new(intstr.IntOrString)
**out = **in
}
if in.Selector != nil {
in, out := &in.Selector, &out.Selector
if newVal, err := c.DeepCopy(*in); err != nil {
......@@ -120,6 +126,11 @@ func DeepCopy_policy_PodDisruptionBudgetSpec(in interface{}, out interface{}, c
*out = newVal.(*v1.LabelSelector)
}
}
if in.MaxUnavailable != nil {
in, out := &in.MaxUnavailable, &out.MaxUnavailable
*out = new(intstr.IntOrString)
**out = **in
}
return nil
}
}
......
......@@ -478,6 +478,7 @@ func runDrainTest(f *framework.Framework, migSizes map[string]int, podsPerNode,
defer framework.DeleteRCAndPods(f.ClientSet, f.InternalClientset, f.Namespace.Name, "reschedulable-pods")
By("Create a PodDisruptionBudget")
minAvailable := intstr.FromInt(numPods - pdbSize)
pdb := &policy.PodDisruptionBudget{
ObjectMeta: metav1.ObjectMeta{
Name: "test_pdb",
......@@ -485,7 +486,7 @@ func runDrainTest(f *framework.Framework, migSizes map[string]int, podsPerNode,
},
Spec: policy.PodDisruptionBudgetSpec{
Selector: &metav1.LabelSelector{MatchLabels: labelMap},
MinAvailable: intstr.FromInt(numPods - pdbSize),
MinAvailable: &minAvailable,
},
}
_, err = f.StagingClient.Policy().PodDisruptionBudgets(namespace).Create(pdb)
......
......@@ -52,11 +52,11 @@ var _ = framework.KubeDescribe("DisruptionController", func() {
})
It("should create a PodDisruptionBudget", func() {
createPodDisruptionBudgetOrDie(cs, ns, intstr.FromString("1%"))
createPDBMinAvailableOrDie(cs, ns, intstr.FromString("1%"))
})
It("should update PodDisruptionBudget status", func() {
createPodDisruptionBudgetOrDie(cs, ns, intstr.FromInt(2))
createPDBMinAvailableOrDie(cs, ns, intstr.FromInt(2))
createPodsOrDie(cs, ns, 3)
waitForPodsOrDie(cs, ns, 3)
......@@ -71,12 +71,12 @@ var _ = framework.KubeDescribe("DisruptionController", func() {
return pdb.Status.PodDisruptionsAllowed > 0, nil
})
Expect(err).NotTo(HaveOccurred())
})
evictionCases := []struct {
description string
minAvailable intstr.IntOrString
maxUnavailable intstr.IntOrString
podCount int
replicaSetSize int32
shouldDeny bool
......@@ -84,29 +84,52 @@ var _ = framework.KubeDescribe("DisruptionController", func() {
skipForBigClusters bool
}{
{
description: "no PDB",
minAvailable: intstr.FromString(""),
podCount: 1,
shouldDeny: false,
description: "no PDB",
minAvailable: intstr.FromString(""),
maxUnavailable: intstr.FromString(""),
podCount: 1,
shouldDeny: false,
}, {
description: "too few pods, absolute",
minAvailable: intstr.FromInt(2),
podCount: 2,
shouldDeny: true,
description: "too few pods, absolute",
minAvailable: intstr.FromInt(2),
maxUnavailable: intstr.FromString(""),
podCount: 2,
shouldDeny: true,
}, {
description: "enough pods, absolute",
minAvailable: intstr.FromInt(2),
podCount: 3,
shouldDeny: false,
description: "enough pods, absolute",
minAvailable: intstr.FromInt(2),
maxUnavailable: intstr.FromString(""),
podCount: 3,
shouldDeny: false,
}, {
description: "enough pods, replicaSet, percentage",
minAvailable: intstr.FromString("90%"),
maxUnavailable: intstr.FromString(""),
replicaSetSize: 10,
exclusive: false,
shouldDeny: false,
}, {
description: "too few pods, replicaSet, percentage",
minAvailable: intstr.FromString("90%"),
maxUnavailable: intstr.FromString(""),
replicaSetSize: 10,
exclusive: true,
shouldDeny: true,
// This tests assumes that there is less than replicaSetSize nodes in the cluster.
skipForBigClusters: true,
},
{
description: "maxUnavailable allow single eviction, percentage",
minAvailable: intstr.FromString(""),
maxUnavailable: intstr.FromString("10%"),
replicaSetSize: 10,
exclusive: false,
shouldDeny: false,
},
{
description: "maxUnavailable deny evictions, integer",
minAvailable: intstr.FromString(""),
maxUnavailable: intstr.FromInt(1),
replicaSetSize: 10,
exclusive: true,
shouldDeny: true,
......@@ -130,7 +153,11 @@ var _ = framework.KubeDescribe("DisruptionController", func() {
}
if c.minAvailable.String() != "" {
createPodDisruptionBudgetOrDie(cs, ns, c.minAvailable)
createPDBMinAvailableOrDie(cs, ns, c.minAvailable)
}
if c.maxUnavailable.String() != "" {
createPDBMaxUnavailableOrDie(cs, ns, c.maxUnavailable)
}
// Locate a running pod.
......@@ -186,10 +213,9 @@ var _ = framework.KubeDescribe("DisruptionController", func() {
}
})
}
})
func createPodDisruptionBudgetOrDie(cs *kubernetes.Clientset, ns string, minAvailable intstr.IntOrString) {
func createPDBMinAvailableOrDie(cs *kubernetes.Clientset, ns string, minAvailable intstr.IntOrString) {
pdb := policy.PodDisruptionBudget{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
......@@ -197,7 +223,22 @@ func createPodDisruptionBudgetOrDie(cs *kubernetes.Clientset, ns string, minAvai
},
Spec: policy.PodDisruptionBudgetSpec{
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
MinAvailable: minAvailable,
MinAvailable: &minAvailable,
},
}
_, err := cs.Policy().PodDisruptionBudgets(ns).Create(&pdb)
Expect(err).NotTo(HaveOccurred())
}
func createPDBMaxUnavailableOrDie(cs *kubernetes.Clientset, ns string, maxUnavailable intstr.IntOrString) {
pdb := policy.PodDisruptionBudget{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: ns,
},
Spec: policy.PodDisruptionBudgetSpec{
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
MaxUnavailable: &maxUnavailable,
},
}
_, err := cs.Policy().PodDisruptionBudgets(ns).Create(&pdb)
......
......@@ -200,7 +200,7 @@ func newPDB() *v1beta1.PodDisruptionBudget {
Name: "test-pdb",
},
Spec: v1beta1.PodDisruptionBudgetSpec{
MinAvailable: intstr.IntOrString{
MinAvailable: &intstr.IntOrString{
Type: intstr.Int,
IntVal: 0,
},
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment