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