Merge pull request #113375 from atiratree/PodHealthyPolicy-api

api: add unhealthyPodEvictionPolicy for PDBs
This commit is contained in:
Kubernetes Prow Robot 2022-11-11 04:02:10 -08:00 committed by GitHub
commit cc704f9778
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 1286 additions and 325 deletions

View File

@ -12305,6 +12305,10 @@
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector",
"description": "Label query over pods whose evictions are managed by the disruption budget. A null selector will match no pods, while an empty ({}) selector will select all pods within the namespace.",
"x-kubernetes-patch-strategy": "replace"
},
"unhealthyPodEvictionPolicy": {
"description": "UnhealthyPodEvictionPolicy defines the criteria for when unhealthy pods should be considered for eviction. Current implementation considers healthy pods, as pods that have status.conditions item with type=\"Ready\",status=\"True\".\n\nValid policies are IfHealthyBudget and AlwaysAllow. If no policy is specified, the default behavior will be used, which corresponds to the IfHealthyBudget policy.\n\nIfHealthyBudget policy means that running pods (status.phase=\"Running\"), but not yet healthy can be evicted only if the guarded application is not disrupted (status.currentHealthy is at least equal to status.desiredHealthy). Healthy pods will be subject to the PDB for eviction.\n\nAlwaysAllow policy means that all running pods (status.phase=\"Running\"), but not yet healthy are considered disrupted and can be evicted regardless of whether the criteria in a PDB is met. This means perspective running pods of a disrupted application might not get a chance to become healthy. Healthy pods will be subject to the PDB for eviction.\n\nAdditional policies may be added in the future. Clients making eviction decisions should disallow eviction of unhealthy pods if they encounter an unrecognized policy in this field.\n\nThis field is alpha-level. The eviction API uses this field when the feature gate PDBUnhealthyPodEvictionPolicy is enabled (disabled by default).",
"type": "string"
}
},
"type": "object"

View File

@ -121,6 +121,10 @@
],
"description": "Label query over pods whose evictions are managed by the disruption budget. A null selector will match no pods, while an empty ({}) selector will select all pods within the namespace.",
"x-kubernetes-patch-strategy": "replace"
},
"unhealthyPodEvictionPolicy": {
"description": "UnhealthyPodEvictionPolicy defines the criteria for when unhealthy pods should be considered for eviction. Current implementation considers healthy pods, as pods that have status.conditions item with type=\"Ready\",status=\"True\".\n\nValid policies are IfHealthyBudget and AlwaysAllow. If no policy is specified, the default behavior will be used, which corresponds to the IfHealthyBudget policy.\n\nIfHealthyBudget policy means that running pods (status.phase=\"Running\"), but not yet healthy can be evicted only if the guarded application is not disrupted (status.currentHealthy is at least equal to status.desiredHealthy). Healthy pods will be subject to the PDB for eviction.\n\nAlwaysAllow policy means that all running pods (status.phase=\"Running\"), but not yet healthy are considered disrupted and can be evicted regardless of whether the criteria in a PDB is met. This means perspective running pods of a disrupted application might not get a chance to become healthy. Healthy pods will be subject to the PDB for eviction.\n\nAdditional policies may be added in the future. Clients making eviction decisions should disallow eviction of unhealthy pods if they encounter an unrecognized policy in this field.\n\nThis field is alpha-level. The eviction API uses this field when the feature gate PDBUnhealthyPodEvictionPolicy is enabled (disabled by default).",
"type": "string"
}
},
"type": "object"

View File

@ -42,8 +42,56 @@ type PodDisruptionBudgetSpec struct {
// by specifying 0. This is a mutually exclusive setting with "minAvailable".
// +optional
MaxUnavailable *intstr.IntOrString
// UnhealthyPodEvictionPolicy defines the criteria for when unhealthy pods
// should be considered for eviction. Current implementation considers healthy pods,
// as pods that have status.conditions item with type="Ready",status="True".
//
// Valid policies are IfHealthyBudget and AlwaysAllow.
// If no policy is specified, the default behavior will be used,
// which corresponds to the IfHealthyBudget policy.
//
// IfHealthyBudget policy means that running pods (status.phase="Running"),
// but not yet healthy can be evicted only if the guarded application is not
// disrupted (status.currentHealthy is at least equal to status.desiredHealthy).
// Healthy pods will be subject to the PDB for eviction.
//
// AlwaysAllow policy means that all running pods (status.phase="Running"),
// but not yet healthy are considered disrupted and can be evicted regardless
// of whether the criteria in a PDB is met. This means perspective running
// pods of a disrupted application might not get a chance to become healthy.
// Healthy pods will be subject to the PDB for eviction.
//
// Additional policies may be added in the future.
// Clients making eviction decisions should disallow eviction of unhealthy pods
// if they encounter an unrecognized policy in this field.
//
// This field is alpha-level. The eviction API uses this field when
// the feature gate PDBUnhealthyPodEvictionPolicy is enabled (disabled by default).
// +optional
UnhealthyPodEvictionPolicy *UnhealthyPodEvictionPolicyType
}
// UnhealthyPodEvictionPolicyType defines the criteria for when unhealthy pods
// should be considered for eviction.
// +enum
type UnhealthyPodEvictionPolicyType string
const (
// IfHealthyBudget policy means that running pods (status.phase="Running"),
// but not yet healthy can be evicted only if the guarded application is not
// disrupted (status.currentHealthy is at least equal to status.desiredHealthy).
// Healthy pods will be subject to the PDB for eviction.
IfHealthyBudget UnhealthyPodEvictionPolicyType = "IfHealthyBudget"
// AlwaysAllow policy means that all running pods (status.phase="Running"),
// but not yet healthy are considered disrupted and can be evicted regardless
// of whether the criteria in a PDB is met. This means perspective running
// pods of a disrupted application might not get a chance to become healthy.
// Healthy pods will be subject to the PDB for eviction.
AlwaysAllow UnhealthyPodEvictionPolicyType = "AlwaysAllow"
)
// PodDisruptionBudgetStatus represents information about the status of a
// PodDisruptionBudget. Status may trail the actual state of a system.
type PodDisruptionBudgetStatus struct {

View File

@ -187,6 +187,7 @@ func autoConvert_v1_PodDisruptionBudgetSpec_To_policy_PodDisruptionBudgetSpec(in
out.MinAvailable = (*intstr.IntOrString)(unsafe.Pointer(in.MinAvailable))
out.Selector = (*metav1.LabelSelector)(unsafe.Pointer(in.Selector))
out.MaxUnavailable = (*intstr.IntOrString)(unsafe.Pointer(in.MaxUnavailable))
out.UnhealthyPodEvictionPolicy = (*policy.UnhealthyPodEvictionPolicyType)(unsafe.Pointer(in.UnhealthyPodEvictionPolicy))
return nil
}
@ -199,6 +200,7 @@ func autoConvert_policy_PodDisruptionBudgetSpec_To_v1_PodDisruptionBudgetSpec(in
out.MinAvailable = (*intstr.IntOrString)(unsafe.Pointer(in.MinAvailable))
out.Selector = (*metav1.LabelSelector)(unsafe.Pointer(in.Selector))
out.MaxUnavailable = (*intstr.IntOrString)(unsafe.Pointer(in.MaxUnavailable))
out.UnhealthyPodEvictionPolicy = (*v1.UnhealthyPodEvictionPolicyType)(unsafe.Pointer(in.UnhealthyPodEvictionPolicy))
return nil
}

View File

@ -452,6 +452,7 @@ func autoConvert_v1beta1_PodDisruptionBudgetSpec_To_policy_PodDisruptionBudgetSp
out.MinAvailable = (*intstr.IntOrString)(unsafe.Pointer(in.MinAvailable))
out.Selector = (*v1.LabelSelector)(unsafe.Pointer(in.Selector))
out.MaxUnavailable = (*intstr.IntOrString)(unsafe.Pointer(in.MaxUnavailable))
out.UnhealthyPodEvictionPolicy = (*policy.UnhealthyPodEvictionPolicyType)(unsafe.Pointer(in.UnhealthyPodEvictionPolicy))
return nil
}
@ -464,6 +465,7 @@ func autoConvert_policy_PodDisruptionBudgetSpec_To_v1beta1_PodDisruptionBudgetSp
out.MinAvailable = (*intstr.IntOrString)(unsafe.Pointer(in.MinAvailable))
out.Selector = (*v1.LabelSelector)(unsafe.Pointer(in.Selector))
out.MaxUnavailable = (*intstr.IntOrString)(unsafe.Pointer(in.MaxUnavailable))
out.UnhealthyPodEvictionPolicy = (*v1beta1.UnhealthyPodEvictionPolicyType)(unsafe.Pointer(in.UnhealthyPodEvictionPolicy))
return nil
}

View File

@ -44,6 +44,11 @@ const (
seccompAllowedProfilesAnnotationKey = "seccomp.security.alpha.kubernetes.io/allowedProfileNames"
)
var supportedUnhealthyPodEvictionPolicies = sets.NewString(
string(policy.IfHealthyBudget),
string(policy.AlwaysAllow),
)
type PodDisruptionBudgetValidationOptions struct {
AllowInvalidLabelValueInSelector bool
}
@ -78,6 +83,10 @@ func ValidatePodDisruptionBudgetSpec(spec policy.PodDisruptionBudgetSpec, opts P
allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, labelSelectorValidationOptions, fldPath.Child("selector"))...)
if spec.UnhealthyPodEvictionPolicy != nil && !supportedUnhealthyPodEvictionPolicies.Has(string(*spec.UnhealthyPodEvictionPolicy)) {
allErrs = append(allErrs, field.NotSupported(fldPath.Child("unhealthyPodEvictionPolicy"), *spec.UnhealthyPodEvictionPolicy, supportedUnhealthyPodEvictionPolicies.List()))
}
return allErrs
}

View File

@ -98,6 +98,63 @@ func TestValidateMinAvailablePodAndMaxUnavailableDisruptionBudgetSpec(t *testing
}
}
func TestValidateUnhealthyPodEvictionPolicyDisruptionBudgetSpec(t *testing.T) {
c1 := intstr.FromString("10%")
alwaysAllowPolicy := policy.AlwaysAllow
invalidPolicy := policy.UnhealthyPodEvictionPolicyType("Invalid")
testCases := []struct {
name string
pdbSpec policy.PodDisruptionBudgetSpec
expectErr bool
}{
{
name: "valid nil UnhealthyPodEvictionPolicy",
pdbSpec: policy.PodDisruptionBudgetSpec{
MinAvailable: &c1,
UnhealthyPodEvictionPolicy: nil,
},
expectErr: false,
},
{
name: "valid UnhealthyPodEvictionPolicy",
pdbSpec: policy.PodDisruptionBudgetSpec{
MinAvailable: &c1,
UnhealthyPodEvictionPolicy: &alwaysAllowPolicy,
},
expectErr: false,
},
{
name: "empty UnhealthyPodEvictionPolicy",
pdbSpec: policy.PodDisruptionBudgetSpec{
MinAvailable: &c1,
UnhealthyPodEvictionPolicy: new(policy.UnhealthyPodEvictionPolicyType),
},
expectErr: true,
},
{
name: "invalid UnhealthyPodEvictionPolicy",
pdbSpec: policy.PodDisruptionBudgetSpec{
MinAvailable: &c1,
UnhealthyPodEvictionPolicy: &invalidPolicy,
},
expectErr: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
errs := ValidatePodDisruptionBudgetSpec(tc.pdbSpec, PodDisruptionBudgetValidationOptions{true}, field.NewPath("foo"))
if len(errs) == 0 && tc.expectErr {
t.Errorf("unexpected success for %v", tc.pdbSpec)
}
if len(errs) != 0 && !tc.expectErr {
t.Errorf("unexpected failure for %v", tc.pdbSpec)
}
})
}
}
func TestValidatePodDisruptionBudgetStatus(t *testing.T) {
const expectNoErrors = false
const expectErrors = true

View File

@ -239,6 +239,11 @@ func (in *PodDisruptionBudgetSpec) DeepCopyInto(out *PodDisruptionBudgetSpec) {
*out = new(intstr.IntOrString)
**out = **in
}
if in.UnhealthyPodEvictionPolicy != nil {
in, out := &in.UnhealthyPodEvictionPolicy, &out.UnhealthyPodEvictionPolicy
*out = new(UnhealthyPodEvictionPolicyType)
**out = **in
}
return
}

View File

@ -633,6 +633,13 @@ const (
// Permits kubelet to run with swap enabled
NodeSwap featuregate.Feature = "NodeSwap"
// owner: @mortent, @atiratree, @ravig
// kep: http://kep.k8s.io/3018
// alpha: v1.26
//
// Enables PDBUnhealthyPodEvictionPolicy for PodDisruptionBudgets
PDBUnhealthyPodEvictionPolicy featuregate.Feature = "PDBUnhealthyPodEvictionPolicy"
// owner: @haircommander
// kep: https://kep.k8s.io/2364
// alpha: v1.23
@ -1062,6 +1069,8 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
NodeSwap: {Default: false, PreRelease: featuregate.Alpha},
PDBUnhealthyPodEvictionPolicy: {Default: false, PreRelease: featuregate.Alpha},
PodAndContainerStatsFromCRI: {Default: false, PreRelease: featuregate.Alpha},
PodDeletionCost: {Default: true, PreRelease: featuregate.Beta},

View File

@ -38300,6 +38300,13 @@ func schema_k8sio_api_policy_v1_PodDisruptionBudgetSpec(ref common.ReferenceCall
Ref: ref("k8s.io/apimachinery/pkg/util/intstr.IntOrString"),
},
},
"unhealthyPodEvictionPolicy": {
SchemaProps: spec.SchemaProps{
Description: "UnhealthyPodEvictionPolicy defines the criteria for when unhealthy pods should be considered for eviction. Current implementation considers healthy pods, as pods that have status.conditions item with type=\"Ready\",status=\"True\".\n\nValid policies are IfHealthyBudget and AlwaysAllow. If no policy is specified, the default behavior will be used, which corresponds to the IfHealthyBudget policy.\n\nIfHealthyBudget policy means that running pods (status.phase=\"Running\"), but not yet healthy can be evicted only if the guarded application is not disrupted (status.currentHealthy is at least equal to status.desiredHealthy). Healthy pods will be subject to the PDB for eviction.\n\nAlwaysAllow policy means that all running pods (status.phase=\"Running\"), but not yet healthy are considered disrupted and can be evicted regardless of whether the criteria in a PDB is met. This means perspective running pods of a disrupted application might not get a chance to become healthy. Healthy pods will be subject to the PDB for eviction.\n\nAdditional policies may be added in the future. Clients making eviction decisions should disallow eviction of unhealthy pods if they encounter an unrecognized policy in this field.\n\nThis field is alpha-level. The eviction API uses this field when the feature gate PDBUnhealthyPodEvictionPolicy is enabled (disabled by default).",
Type: []string{"string"},
Format: "",
},
},
},
},
},
@ -38737,6 +38744,13 @@ func schema_k8sio_api_policy_v1beta1_PodDisruptionBudgetSpec(ref common.Referenc
Ref: ref("k8s.io/apimachinery/pkg/util/intstr.IntOrString"),
},
},
"unhealthyPodEvictionPolicy": {
SchemaProps: spec.SchemaProps{
Description: "UnhealthyPodEvictionPolicy defines the criteria for when unhealthy pods should be considered for eviction. Current implementation considers healthy pods, as pods that have status.conditions item with type=\"Ready\",status=\"True\".\n\nValid policies are IfHealthyBudget and AlwaysAllow. If no policy is specified, the default behavior will be used, which corresponds to the IfHealthyBudget policy.\n\nIfHealthyBudget policy means that running pods (status.phase=\"Running\"), but not yet healthy can be evicted only if the guarded application is not disrupted (status.currentHealthy is at least equal to status.desiredHealthy). Healthy pods will be subject to the PDB for eviction.\n\nAlwaysAllow policy means that all running pods (status.phase=\"Running\"), but not yet healthy are considered disrupted and can be evicted regardless of whether the criteria in a PDB is met. This means perspective running pods of a disrupted application might not get a chance to become healthy. Healthy pods will be subject to the PDB for eviction.\n\nAdditional policies may be added in the future. Clients making eviction decisions should disallow eviction of unhealthy pods if they encounter an unrecognized policy in this field.\n\nThis field is alpha-level. The eviction API uses this field when the feature gate PDBUnhealthyPodEvictionPolicy is enabled (disabled by default).",
Type: []string{"string"},
Format: "",
},
},
},
},
},

View File

@ -226,11 +226,25 @@ func (r *EvictionREST) Create(ctx context.Context, name string, obj runtime.Obje
pdb := &pdbs[0]
pdbName = pdb.Name
// If the pod is not ready, it doesn't count towards healthy and we should not decrement
if !podutil.IsPodReady(pod) && pdb.Status.CurrentHealthy >= pdb.Status.DesiredHealthy && pdb.Status.DesiredHealthy > 0 {
// IsPodReady is the current implementation of IsHealthy
// If the pod is healthy, it should be guarded by the PDB.
if !podutil.IsPodReady(pod) {
if feature.DefaultFeatureGate.Enabled(features.PDBUnhealthyPodEvictionPolicy) {
if pdb.Spec.UnhealthyPodEvictionPolicy != nil && *pdb.Spec.UnhealthyPodEvictionPolicy == policyv1.AlwaysAllow {
// Delete the unhealthy pod, it doesn't count towards currentHealthy and desiredHealthy and we should not decrement disruptionsAllowed.
updateDeletionOptions = true
return nil
}
}
// default nil and IfHealthyBudget policy
if pdb.Status.CurrentHealthy >= pdb.Status.DesiredHealthy && pdb.Status.DesiredHealthy > 0 {
// Delete the unhealthy pod, it doesn't count towards currentHealthy and desiredHealthy and we should not decrement disruptionsAllowed.
// Application guarded by the PDB is not disrupted at the moment and deleting unhealthy (unready) pod will not disrupt it.
updateDeletionOptions = true
return nil
}
// confirm no disruptions allowed in checkAndDecrement
}
refresh := false
err = retry.RetryOnConflict(EvictionsRetry, func() error {
@ -264,12 +278,12 @@ func (r *EvictionREST) Create(ctx context.Context, name string, obj runtime.Obje
}
// At this point there was either no PDB or we succeeded in decrementing or
// the pod was unready and we have enough healthy replicas
// the pod was unhealthy (unready) and we have enough healthy replicas
deleteOptions := originalDeleteOptions
// Set deleteOptions.Preconditions.ResourceVersion to ensure
// the pod hasn't been considered ready since we calculated
// the pod hasn't been considered healthy (ready) since we calculated
if updateDeletionOptions {
// Take a copy so we can compare to client-provied Options later.
deleteOptions = deleteOptions.DeepCopy()
@ -351,7 +365,7 @@ func shouldEnforceResourceVersion(pod *api.Pod) bool {
return false
}
// Return true for all other pods to ensure we don't race against a pod becoming
// ready and violating PDBs.
// healthy (ready) and violating PDBs.
return true
}

View File

@ -19,6 +19,9 @@ package storage
import (
"context"
"errors"
"fmt"
"reflect"
"strings"
"testing"
policyv1 "k8s.io/api/policy/v1"
@ -31,21 +34,25 @@ import (
"k8s.io/apimachinery/pkg/watch"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/kubernetes/fake"
featuregatetesting "k8s.io/component-base/featuregate/testing"
podapi "k8s.io/kubernetes/pkg/api/pod"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/policy"
"k8s.io/kubernetes/pkg/features"
)
func TestEviction(t *testing.T) {
testcases := []struct {
name string
pdbs []runtime.Object
policies []*policyv1.UnhealthyPodEvictionPolicyType
eviction *policy.Eviction
badNameInURL bool
expectError bool
expectError string
expectDeleted bool
podPhase api.PodPhase
podName string
@ -58,9 +65,10 @@ func TestEviction(t *testing.T) {
Status: policyv1.PodDisruptionBudgetStatus{DisruptionsAllowed: 0},
}},
eviction: &policy.Eviction{ObjectMeta: metav1.ObjectMeta{Name: "t1", Namespace: "default"}, DeleteOptions: metav1.NewDeleteOptions(0)},
expectError: true,
expectError: "Cannot evict pod as it would violate the pod's disruption budget.: TooManyRequests: The disruption budget foo needs 0 healthy pods and has 0 currently",
podPhase: api.PodRunning,
podName: "t1",
policies: []*policyv1.UnhealthyPodEvictionPolicyType{nil, unhealthyPolicyPtr(policyv1.IfHealthyBudget)}, // AlwaysAllow would terminate the pod since Running pods are not guarded by this policy
},
{
name: "matching pdbs with no disruptions allowed, pod pending",
@ -70,7 +78,7 @@ func TestEviction(t *testing.T) {
Status: policyv1.PodDisruptionBudgetStatus{DisruptionsAllowed: 0},
}},
eviction: &policy.Eviction{ObjectMeta: metav1.ObjectMeta{Name: "t2", Namespace: "default"}, DeleteOptions: metav1.NewDeleteOptions(0)},
expectError: false,
expectError: "",
podPhase: api.PodPending,
expectDeleted: true,
podName: "t2",
@ -83,7 +91,7 @@ func TestEviction(t *testing.T) {
Status: policyv1.PodDisruptionBudgetStatus{DisruptionsAllowed: 0},
}},
eviction: &policy.Eviction{ObjectMeta: metav1.ObjectMeta{Name: "t3", Namespace: "default"}, DeleteOptions: metav1.NewDeleteOptions(0)},
expectError: false,
expectError: "",
podPhase: api.PodSucceeded,
expectDeleted: true,
podName: "t3",
@ -96,7 +104,7 @@ func TestEviction(t *testing.T) {
Status: policyv1.PodDisruptionBudgetStatus{DisruptionsAllowed: 0},
}},
eviction: &policy.Eviction{ObjectMeta: metav1.ObjectMeta{Name: "t4", Namespace: "default"}, DeleteOptions: metav1.NewDeleteOptions(0)},
expectError: false,
expectError: "",
podPhase: api.PodFailed,
expectDeleted: true,
podName: "t4",
@ -132,13 +140,29 @@ func TestEviction(t *testing.T) {
}},
badNameInURL: true,
eviction: &policy.Eviction{ObjectMeta: metav1.ObjectMeta{Name: "t7", Namespace: "default"}, DeleteOptions: metav1.NewDeleteOptions(0)},
expectError: true,
expectError: "name in URL does not match name in Eviction object: BadRequest",
podName: "t7",
},
}
for _, unhealthyPodEvictionPolicy := range []*policyv1.UnhealthyPodEvictionPolicyType{nil, unhealthyPolicyPtr(policyv1.IfHealthyBudget), unhealthyPolicyPtr(policyv1.AlwaysAllow)} {
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.policies) > 0 && !hasUnhealthyPolicy(tc.policies, unhealthyPodEvictionPolicy) {
// unhealthyPodEvictionPolicy is not covered by this test
continue
}
t.Run(fmt.Sprintf("%v with %v policy", tc.name, unhealthyPolicyStr(unhealthyPodEvictionPolicy)), func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PDBUnhealthyPodEvictionPolicy, true)()
// same test runs multiple times, make copy of objects to have unique ones
evictionCopy := tc.eviction.DeepCopy()
var pdbsCopy []runtime.Object
for _, pdb := range tc.pdbs {
pdbCopy := pdb.DeepCopyObject()
pdbCopy.(*policyv1.PodDisruptionBudget).Spec.UnhealthyPodEvictionPolicy = unhealthyPodEvictionPolicy
pdbsCopy = append(pdbsCopy, pdbCopy)
}
testContext := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
storage, _, statusStorage, server := newStorage(t)
defer server.Terminate(t)
@ -160,7 +184,7 @@ func TestEviction(t *testing.T) {
}
}
client := fake.NewSimpleClientset(tc.pdbs...)
client := fake.NewSimpleClientset(pdbsCopy...)
evictionRest := newEvictionStorage(storage.Store, client.PolicyV1())
name := pod.Name
@ -168,10 +192,10 @@ func TestEviction(t *testing.T) {
name += "bad-name"
}
_, err := evictionRest.Create(testContext, name, tc.eviction, nil, &metav1.CreateOptions{})
//_, err = evictionRest.Create(testContext, name, tc.eviction, nil, &metav1.CreateOptions{})
if (err != nil) != tc.expectError {
t.Errorf("expected error=%v, got %v; name %v", tc.expectError, err, pod.Name)
_, err := evictionRest.Create(testContext, name, evictionCopy, nil, &metav1.CreateOptions{})
gotErr := errToString(err)
if gotErr != tc.expectError {
t.Errorf("error mismatch: expected %v, got %v; name %v", tc.expectError, gotErr, pod.Name)
return
}
if tc.badNameInURL {
@ -183,7 +207,7 @@ func TestEviction(t *testing.T) {
t.Errorf("got unexpected error: %v", err)
}
}
if tc.expectError {
if tc.expectError != "" {
return
}
@ -209,14 +233,16 @@ func TestEviction(t *testing.T) {
})
}
}
}
func TestEvictionIngorePDB(t *testing.T) {
func TestEvictionIgnorePDB(t *testing.T) {
testcases := []struct {
name string
pdbs []runtime.Object
policies []*policyv1.UnhealthyPodEvictionPolicyType
eviction *policy.Eviction
expectError bool
expectError string
podPhase api.PodPhase
podName string
expectedDeleteCount int
@ -231,7 +257,7 @@ func TestEvictionIngorePDB(t *testing.T) {
Status: policyv1.PodDisruptionBudgetStatus{DisruptionsAllowed: 0},
}},
eviction: &policy.Eviction{ObjectMeta: metav1.ObjectMeta{Name: "t1", Namespace: "default"}, DeleteOptions: metav1.NewDeleteOptions(0)},
expectError: false,
expectError: "",
podPhase: api.PodPending,
podName: "t1",
expectedDeleteCount: 3,
@ -247,10 +273,30 @@ func TestEvictionIngorePDB(t *testing.T) {
Status: policyv1.PodDisruptionBudgetStatus{DisruptionsAllowed: 0},
}},
eviction: &policy.Eviction{ObjectMeta: metav1.ObjectMeta{Name: "t2", Namespace: "default"}, DeleteOptions: metav1.NewDeleteOptions(0)},
expectError: true,
expectError: "Cannot evict pod as it would violate the pod's disruption budget.: TooManyRequests: The disruption budget foo needs 0 healthy pods and has 0 currently",
podPhase: api.PodPending,
podName: "t2",
expectedDeleteCount: 1,
policies: []*policyv1.UnhealthyPodEvictionPolicyType{nil, unhealthyPolicyPtr(policyv1.IfHealthyBudget)}, // AlwaysAllow does not continueToPDBs, but straight to deletion
},
{
name: "pdbs No disruptions allowed, pod pending, first delete conflict, pod becomes running, skip PDB check, conflict",
pdbs: []runtime.Object{&policyv1.PodDisruptionBudget{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"},
Spec: policyv1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
Status: policyv1.PodDisruptionBudgetStatus{DisruptionsAllowed: 0},
}},
eviction: &policy.Eviction{ObjectMeta: metav1.ObjectMeta{Name: "t2", Namespace: "default"}, DeleteOptions: metav1.NewDeleteOptions(0)},
expectError: "Cannot evict pod as it would violate the pod's disruption budget.: TooManyRequests: The disruption budget foo is still being processed by the server.",
podPhase: api.PodPending,
podName: "t2",
podTerminating: false,
expectedDeleteCount: 2,
prc: &api.PodCondition{
Type: api.PodReady,
Status: api.ConditionFalse,
},
policies: []*policyv1.UnhealthyPodEvictionPolicyType{unhealthyPolicyPtr(policyv1.AlwaysAllow)}, // nil, IfHealthyBudget continueToPDBs
},
{
name: "pdbs disruptions allowed, pod pending, first delete conflict, pod becomes running, continueToPDBs",
@ -260,10 +306,11 @@ func TestEvictionIngorePDB(t *testing.T) {
Status: policyv1.PodDisruptionBudgetStatus{DisruptionsAllowed: 1},
}},
eviction: &policy.Eviction{ObjectMeta: metav1.ObjectMeta{Name: "t3", Namespace: "default"}, DeleteOptions: metav1.NewDeleteOptions(0)},
expectError: false,
expectError: "",
podPhase: api.PodPending,
podName: "t3",
expectedDeleteCount: 2,
policies: []*policyv1.UnhealthyPodEvictionPolicyType{nil, unhealthyPolicyPtr(policyv1.IfHealthyBudget)}, // AlwaysAllow does not continueToPDBs, but straight to deletion if pod not healthy (ready)
},
{
name: "pod pending, always conflict on delete",
@ -273,7 +320,7 @@ func TestEvictionIngorePDB(t *testing.T) {
Status: policyv1.PodDisruptionBudgetStatus{DisruptionsAllowed: 0},
}},
eviction: &policy.Eviction{ObjectMeta: metav1.ObjectMeta{Name: "t4", Namespace: "default"}, DeleteOptions: metav1.NewDeleteOptions(0)},
expectError: true,
expectError: `Operation cannot be fulfilled on tests "2": message: Conflict`,
podPhase: api.PodPending,
podName: "t4",
expectedDeleteCount: EvictionsRetry.Steps,
@ -286,7 +333,7 @@ func TestEvictionIngorePDB(t *testing.T) {
Status: policyv1.PodDisruptionBudgetStatus{DisruptionsAllowed: 0},
}},
eviction: &policy.Eviction{ObjectMeta: metav1.ObjectMeta{Name: "t5", Namespace: "default"}, DeleteOptions: metav1.NewRVDeletionPrecondition("userProvided")},
expectError: true,
expectError: `Operation cannot be fulfilled on tests "2": message: Conflict`,
podPhase: api.PodPending,
podName: "t5",
expectedDeleteCount: 1,
@ -299,7 +346,7 @@ func TestEvictionIngorePDB(t *testing.T) {
Status: policyv1.PodDisruptionBudgetStatus{DisruptionsAllowed: 0},
}},
eviction: &policy.Eviction{ObjectMeta: metav1.ObjectMeta{Name: "t6", Namespace: "default"}, DeleteOptions: metav1.NewDeleteOptions(300)},
expectError: false,
expectError: "",
podName: "t6",
expectedDeleteCount: 1,
podTerminating: true,
@ -310,14 +357,14 @@ func TestEvictionIngorePDB(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"},
Spec: policyv1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
Status: policyv1.PodDisruptionBudgetStatus{
// This simulates 3 pods desired, our pod healthy, unhealthy pod is not ours.
// This simulates 3 pods expected, our pod healthy, unhealthy pod is not ours.
DisruptionsAllowed: 0,
CurrentHealthy: 2,
DesiredHealthy: 2,
},
}},
eviction: &policy.Eviction{ObjectMeta: metav1.ObjectMeta{Name: "t7", Namespace: "default"}, DeleteOptions: metav1.NewDeleteOptions(0)},
expectError: true,
expectError: "Cannot evict pod as it would violate the pod's disruption budget.: TooManyRequests: The disruption budget foo needs 2 healthy pods and has 2 currently",
podName: "t7",
expectedDeleteCount: 0,
podTerminating: false,
@ -327,20 +374,42 @@ func TestEvictionIngorePDB(t *testing.T) {
Status: api.ConditionTrue,
},
},
{
name: "matching pdbs with disruptions allowed, pod running, pod healthy, healthy pod ours, deletes pod by honoring the PDB",
pdbs: []runtime.Object{&policyv1.PodDisruptionBudget{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"},
Spec: policyv1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
Status: policyv1.PodDisruptionBudgetStatus{
DisruptionsAllowed: 1,
CurrentHealthy: 3,
DesiredHealthy: 3,
},
}},
eviction: &policy.Eviction{ObjectMeta: metav1.ObjectMeta{Name: "t8", Namespace: "default"}, DeleteOptions: metav1.NewDeleteOptions(0)},
expectError: "",
podName: "t8",
expectedDeleteCount: 1,
podTerminating: false,
podPhase: api.PodRunning,
prc: &api.PodCondition{
Type: api.PodReady,
Status: api.ConditionTrue,
},
},
{
name: "matching pdbs with no disruptions allowed, pod running, pod unhealthy, unhealthy pod ours",
pdbs: []runtime.Object{&policyv1.PodDisruptionBudget{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"},
Spec: policyv1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
Status: policyv1.PodDisruptionBudgetStatus{
// This simulates 3 pods desired, our pod unhealthy
// This simulates 3 pods expected, our pod unhealthy
DisruptionsAllowed: 0,
CurrentHealthy: 2,
DesiredHealthy: 2,
},
}},
eviction: &policy.Eviction{ObjectMeta: metav1.ObjectMeta{Name: "t8", Namespace: "default"}, DeleteOptions: metav1.NewDeleteOptions(0)},
expectError: false,
expectError: "",
podName: "t8",
expectedDeleteCount: 1,
podTerminating: false,
@ -350,6 +419,25 @@ func TestEvictionIngorePDB(t *testing.T) {
Status: api.ConditionFalse,
},
},
{
name: "matching pdbs with no disruptions allowed, pod running, pod unhealthy, unhealthy pod ours, skips the PDB check and deletes",
pdbs: []runtime.Object{&policyv1.PodDisruptionBudget{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"},
Spec: policyv1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
Status: policyv1.PodDisruptionBudgetStatus{DisruptionsAllowed: 0},
}},
eviction: &policy.Eviction{ObjectMeta: metav1.ObjectMeta{Name: "t8", Namespace: "default"}, DeleteOptions: metav1.NewDeleteOptions(0)},
expectError: "",
podName: "t8",
expectedDeleteCount: 1,
podTerminating: false,
podPhase: api.PodRunning,
prc: &api.PodCondition{
Type: api.PodReady,
Status: api.ConditionFalse,
},
policies: []*policyv1.UnhealthyPodEvictionPolicyType{unhealthyPolicyPtr(policyv1.AlwaysAllow)}, // nil, IfHealthyBudget would not skip the PDB check
},
{
// This case should return the 529 retry error.
name: "matching pdbs with no disruptions allowed, pod running, pod unhealthy, unhealthy pod ours, resource version conflict",
@ -357,14 +445,14 @@ func TestEvictionIngorePDB(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"},
Spec: policyv1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
Status: policyv1.PodDisruptionBudgetStatus{
// This simulates 3 pods desired, our pod unhealthy
// This simulates 3 pods expected, our pod unhealthy
DisruptionsAllowed: 0,
CurrentHealthy: 2,
DesiredHealthy: 2,
},
}},
eviction: &policy.Eviction{ObjectMeta: metav1.ObjectMeta{Name: "t9", Namespace: "default"}, DeleteOptions: metav1.NewDeleteOptions(0)},
expectError: true,
expectError: "Cannot evict pod as it would violate the pod's disruption budget.: TooManyRequests: The disruption budget foo is still being processed by the server.",
podName: "t9",
expectedDeleteCount: 1,
podTerminating: false,
@ -381,14 +469,14 @@ func TestEvictionIngorePDB(t *testing.T) {
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"},
Spec: policyv1.PodDisruptionBudgetSpec{Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}},
Status: policyv1.PodDisruptionBudgetStatus{
// This simulates 3 pods desired, our pod unhealthy
// This simulates 3 pods expected, our pod unhealthy
DisruptionsAllowed: 0,
CurrentHealthy: 2,
DesiredHealthy: 2,
},
}},
eviction: &policy.Eviction{ObjectMeta: metav1.ObjectMeta{Name: "t10", Namespace: "default"}, DeleteOptions: metav1.NewDeleteOptions(0)},
expectError: true,
expectError: "test designed to error: BadRequest",
podName: "t10",
expectedDeleteCount: 1,
podTerminating: false,
@ -400,8 +488,25 @@ func TestEvictionIngorePDB(t *testing.T) {
},
}
for _, unhealthyPodEvictionPolicy := range []*policyv1.UnhealthyPodEvictionPolicyType{unhealthyPolicyPtr(policyv1.AlwaysAllow), nil, unhealthyPolicyPtr(policyv1.IfHealthyBudget)} {
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
if len(tc.policies) > 0 && !hasUnhealthyPolicy(tc.policies, unhealthyPodEvictionPolicy) {
// unhealthyPodEvictionPolicy is not covered by this test
continue
}
t.Run(fmt.Sprintf("%v with %v policy", tc.name, unhealthyPolicyStr(unhealthyPodEvictionPolicy)), func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PDBUnhealthyPodEvictionPolicy, true)()
// same test runs 3 times, make copy of objects to have unique ones
evictionCopy := tc.eviction.DeepCopy()
prcCopy := tc.prc.DeepCopy()
var pdbsCopy []runtime.Object
for _, pdb := range tc.pdbs {
pdbCopy := pdb.DeepCopyObject()
pdbCopy.(*policyv1.PodDisruptionBudget).Spec.UnhealthyPodEvictionPolicy = unhealthyPodEvictionPolicy
pdbsCopy = append(pdbsCopy, pdbCopy)
}
testContext := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
ms := &mockStore{
deleteCount: 0,
@ -422,30 +527,31 @@ func TestEvictionIngorePDB(t *testing.T) {
// Setup pod condition
if tc.prc != nil {
if !podapi.UpdatePodCondition(&pod.Status, tc.prc) {
if !podapi.UpdatePodCondition(&pod.Status, prcCopy) {
t.Fatalf("Unable to update pod ready condition")
}
}
client := fake.NewSimpleClientset(tc.pdbs...)
client := fake.NewSimpleClientset(pdbsCopy...)
evictionRest := newEvictionStorage(ms, client.PolicyV1())
name := pod.Name
ms.pod = pod
_, err := evictionRest.Create(testContext, name, tc.eviction, nil, &metav1.CreateOptions{})
if (err != nil) != tc.expectError {
t.Errorf("expected error=%v, got %v; name %v", tc.expectError, err, pod.Name)
_, err := evictionRest.Create(testContext, name, evictionCopy, nil, &metav1.CreateOptions{})
gotErr := errToString(err)
if gotErr != tc.expectError {
t.Errorf("error mismatch: expected %v, got %v; name %v", tc.expectError, gotErr, pod.Name)
return
}
if tc.expectedDeleteCount != ms.deleteCount {
t.Errorf("expected delete count=%v, got %v; name %v", tc.expectedDeleteCount, ms.deleteCount, pod.Name)
}
})
}
}
}
func TestEvictionDryRun(t *testing.T) {
testcases := []struct {
@ -692,3 +798,41 @@ func (ms *mockStore) ConvertToTable(ctx context.Context, object runtime.Object,
func (ms *mockStore) Destroy() {
}
func unhealthyPolicyPtr(unhealthyPodEvictionPolicy policyv1.UnhealthyPodEvictionPolicyType) *policyv1.UnhealthyPodEvictionPolicyType {
return &unhealthyPodEvictionPolicy
}
func unhealthyPolicyStr(unhealthyPodEvictionPolicy *policyv1.UnhealthyPodEvictionPolicyType) string {
if unhealthyPodEvictionPolicy == nil {
return "nil"
}
return string(*unhealthyPodEvictionPolicy)
}
func hasUnhealthyPolicy(unhealthyPolicies []*policyv1.UnhealthyPodEvictionPolicyType, unhealthyPolicy *policyv1.UnhealthyPodEvictionPolicyType) bool {
for _, p := range unhealthyPolicies {
if reflect.DeepEqual(unhealthyPolicy, p) {
return true
}
}
return false
}
func errToString(err error) string {
result := ""
if err != nil {
result = err.Error()
if statusErr, ok := err.(*apierrors.StatusError); ok {
result += fmt.Sprintf(": %v", statusErr.ErrStatus.Reason)
if statusErr.ErrStatus.Details != nil {
for _, cause := range statusErr.ErrStatus.Details.Causes {
if !strings.HasSuffix(err.Error(), cause.Message) {
result += fmt.Sprintf(": %v", cause.Message)
}
}
}
}
}
return result
}

View File

@ -26,9 +26,11 @@ import (
"k8s.io/apimachinery/pkg/util/validation/field"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/storage/names"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/apis/policy"
"k8s.io/kubernetes/pkg/apis/policy/validation"
"k8s.io/kubernetes/pkg/features"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
@ -68,6 +70,8 @@ func (podDisruptionBudgetStrategy) PrepareForCreate(ctx context.Context, obj run
podDisruptionBudget.Status = policy.PodDisruptionBudgetStatus{}
podDisruptionBudget.Generation = 1
dropDisabledFields(&podDisruptionBudget.Spec, nil)
}
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
@ -83,6 +87,8 @@ func (podDisruptionBudgetStrategy) PrepareForUpdate(ctx context.Context, obj, ol
if !apiequality.Semantic.DeepEqual(oldPodDisruptionBudget.Spec, newPodDisruptionBudget.Spec) {
newPodDisruptionBudget.Generation = oldPodDisruptionBudget.Generation + 1
}
dropDisabledFields(&newPodDisruptionBudget.Spec, &oldPodDisruptionBudget.Spec)
}
// Validate validates a new PodDisruptionBudget.
@ -182,3 +188,23 @@ func hasInvalidLabelValueInLabelSelector(pdb *policy.PodDisruptionBudget) bool {
}
return false
}
// dropDisabledFields removes disabled fields from the pod disruption budget spec.
// This should be called from PrepareForCreate/PrepareForUpdate for all resources containing a pod disruption budget spec.
func dropDisabledFields(pdbSpec, oldPDBSpec *policy.PodDisruptionBudgetSpec) {
if !utilfeature.DefaultFeatureGate.Enabled(features.PDBUnhealthyPodEvictionPolicy) {
if !unhealthyPodEvictionPolicyInUse(oldPDBSpec) {
pdbSpec.UnhealthyPodEvictionPolicy = nil
}
}
}
func unhealthyPodEvictionPolicyInUse(oldPDBSpec *policy.PodDisruptionBudgetSpec) bool {
if oldPDBSpec == nil {
return false
}
if oldPDBSpec.UnhealthyPodEvictionPolicy != nil {
return true
}
return false
}

View File

@ -17,15 +17,176 @@ limitations under the License.
package poddisruptionbudget
import (
"reflect"
"testing"
"github.com/google/go-cmp/cmp"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/kubernetes/pkg/apis/policy"
"k8s.io/kubernetes/pkg/features"
)
type unhealthyPodEvictionPolicyStrategyTestCase struct {
name string
enableUnhealthyPodEvictionPolicy bool
disablePDBUnhealthyPodEvictionPolicyFeatureGateAfterCreate bool
unhealthyPodEvictionPolicy *policy.UnhealthyPodEvictionPolicyType
expectedUnhealthyPodEvictionPolicy *policy.UnhealthyPodEvictionPolicyType
expectedValidationErr bool
updateUnhealthyPodEvictionPolicy *policy.UnhealthyPodEvictionPolicyType
expectedUpdateUnhealthyPodEvictionPolicy *policy.UnhealthyPodEvictionPolicyType
expectedValidationUpdateErr bool
}
func TestPodDisruptionBudgetStrategy(t *testing.T) {
tests := map[string]bool{
"PodDisruptionBudget strategy with PDBUnhealthyPodEvictionPolicy feature gate disabled": false,
"PodDisruptionBudget strategy with PDBUnhealthyPodEvictionPolicy feature gate enabled": true,
}
for name, enableUnhealthyPodEvictionPolicy := range tests {
t.Run(name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PDBUnhealthyPodEvictionPolicy, enableUnhealthyPodEvictionPolicy)()
testPodDisruptionBudgetStrategy(t)
})
}
healthyPolicyTests := []unhealthyPodEvictionPolicyStrategyTestCase{
{
name: "PodDisruptionBudget strategy with FeatureGate disabled should remove unhealthyPodEvictionPolicy",
enableUnhealthyPodEvictionPolicy: false,
unhealthyPodEvictionPolicy: unhealthyPolicyPtr(policy.IfHealthyBudget),
updateUnhealthyPodEvictionPolicy: unhealthyPolicyPtr(policy.IfHealthyBudget),
},
{
name: "PodDisruptionBudget strategy with FeatureGate disabled should remove invalid unhealthyPodEvictionPolicy",
enableUnhealthyPodEvictionPolicy: false,
unhealthyPodEvictionPolicy: unhealthyPolicyPtr("Invalid"),
updateUnhealthyPodEvictionPolicy: unhealthyPolicyPtr("Invalid"),
},
{
name: "PodDisruptionBudget strategy with FeatureGate enabled",
enableUnhealthyPodEvictionPolicy: true,
},
{
name: "PodDisruptionBudget strategy with FeatureGate enabled should respect unhealthyPodEvictionPolicy",
enableUnhealthyPodEvictionPolicy: true,
unhealthyPodEvictionPolicy: unhealthyPolicyPtr(policy.AlwaysAllow),
expectedUnhealthyPodEvictionPolicy: unhealthyPolicyPtr(policy.AlwaysAllow),
updateUnhealthyPodEvictionPolicy: unhealthyPolicyPtr(policy.IfHealthyBudget),
expectedUpdateUnhealthyPodEvictionPolicy: unhealthyPolicyPtr(policy.IfHealthyBudget),
},
{
name: "PodDisruptionBudget strategy with FeatureGate enabled should fail invalid unhealthyPodEvictionPolicy",
enableUnhealthyPodEvictionPolicy: true,
unhealthyPodEvictionPolicy: unhealthyPolicyPtr("Invalid"),
expectedValidationErr: true,
},
{
name: "PodDisruptionBudget strategy with FeatureGate enabled should fail invalid unhealthyPodEvictionPolicy when updated",
enableUnhealthyPodEvictionPolicy: true,
updateUnhealthyPodEvictionPolicy: unhealthyPolicyPtr("Invalid"),
expectedValidationUpdateErr: true,
},
{
name: "PodDisruptionBudget strategy with unhealthyPodEvictionPolicy should be updated when feature gate is disabled",
enableUnhealthyPodEvictionPolicy: true,
disablePDBUnhealthyPodEvictionPolicyFeatureGateAfterCreate: true,
unhealthyPodEvictionPolicy: unhealthyPolicyPtr(policy.AlwaysAllow),
expectedUnhealthyPodEvictionPolicy: unhealthyPolicyPtr(policy.AlwaysAllow),
updateUnhealthyPodEvictionPolicy: unhealthyPolicyPtr(policy.IfHealthyBudget),
expectedUpdateUnhealthyPodEvictionPolicy: unhealthyPolicyPtr(policy.IfHealthyBudget),
},
{
name: "PodDisruptionBudget strategy with unhealthyPodEvictionPolicy should not be updated to invalid when feature gate is disabled",
enableUnhealthyPodEvictionPolicy: true,
disablePDBUnhealthyPodEvictionPolicyFeatureGateAfterCreate: true,
unhealthyPodEvictionPolicy: unhealthyPolicyPtr(policy.AlwaysAllow),
expectedUnhealthyPodEvictionPolicy: unhealthyPolicyPtr(policy.AlwaysAllow),
updateUnhealthyPodEvictionPolicy: unhealthyPolicyPtr("Invalid"),
expectedValidationUpdateErr: true,
expectedUpdateUnhealthyPodEvictionPolicy: unhealthyPolicyPtr(policy.AlwaysAllow),
},
}
for _, tc := range healthyPolicyTests {
t.Run(tc.name, func(t *testing.T) {
testPodDisruptionBudgetStrategyWithUnhealthyPodEvictionPolicy(t, tc)
})
}
}
func testPodDisruptionBudgetStrategyWithUnhealthyPodEvictionPolicy(t *testing.T, tc unhealthyPodEvictionPolicyStrategyTestCase) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PDBUnhealthyPodEvictionPolicy, tc.enableUnhealthyPodEvictionPolicy)()
ctx := genericapirequest.NewDefaultContext()
if !Strategy.NamespaceScoped() {
t.Errorf("PodDisruptionBudget must be namespace scoped")
}
if Strategy.AllowCreateOnUpdate() {
t.Errorf("PodDisruptionBudget should not allow create on update")
}
validSelector := map[string]string{"a": "b"}
minAvailable := intstr.FromInt(3)
pdb := &policy.PodDisruptionBudget{
ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault},
Spec: policy.PodDisruptionBudgetSpec{
MinAvailable: &minAvailable,
Selector: &metav1.LabelSelector{MatchLabels: validSelector},
UnhealthyPodEvictionPolicy: tc.unhealthyPodEvictionPolicy,
},
}
Strategy.PrepareForCreate(ctx, pdb)
errs := Strategy.Validate(ctx, pdb)
if len(errs) != 0 {
if !tc.expectedValidationErr {
t.Errorf("Unexpected error validating %v", errs)
}
return // no point going further when we have invalid PDB
}
if len(errs) == 0 && tc.expectedValidationErr {
t.Errorf("Expected error validating")
}
if !reflect.DeepEqual(pdb.Spec.UnhealthyPodEvictionPolicy, tc.expectedUnhealthyPodEvictionPolicy) {
t.Errorf("Unexpected UnhealthyPodEvictionPolicy set: expected %v, got %v", tc.expectedUnhealthyPodEvictionPolicy, pdb.Spec.UnhealthyPodEvictionPolicy)
}
if tc.disablePDBUnhealthyPodEvictionPolicyFeatureGateAfterCreate {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PDBUnhealthyPodEvictionPolicy, false)()
}
newPdb := &policy.PodDisruptionBudget{
ObjectMeta: metav1.ObjectMeta{Name: pdb.Name, Namespace: pdb.Namespace},
Spec: pdb.Spec,
}
if tc.updateUnhealthyPodEvictionPolicy != nil {
newPdb.Spec.UnhealthyPodEvictionPolicy = tc.updateUnhealthyPodEvictionPolicy
}
// Nothing in Spec changes: OK
Strategy.PrepareForUpdate(ctx, newPdb, pdb)
errs = Strategy.ValidateUpdate(ctx, newPdb, pdb)
if len(errs) != 0 {
if !tc.expectedValidationUpdateErr {
t.Errorf("Unexpected error updating PodDisruptionBudget %v", errs)
}
return // no point going further when we have invalid PDB
}
if len(errs) == 0 && tc.expectedValidationUpdateErr {
t.Errorf("Expected error updating PodDisruptionBudget")
}
if !reflect.DeepEqual(newPdb.Spec.UnhealthyPodEvictionPolicy, tc.expectedUpdateUnhealthyPodEvictionPolicy) {
t.Errorf("Unexpected UnhealthyPodEvictionPolicy set: expected %v, got %v", tc.expectedUpdateUnhealthyPodEvictionPolicy, newPdb.Spec.UnhealthyPodEvictionPolicy)
}
}
func testPodDisruptionBudgetStrategy(t *testing.T) {
ctx := genericapirequest.NewDefaultContext()
if !Strategy.NamespaceScoped() {
t.Errorf("PodDisruptionBudget must be namespace scoped")
@ -210,3 +371,86 @@ func TestPodDisruptionBudgetStatusValidationByApiVersion(t *testing.T) {
})
}
}
func TestDropDisabledFields(t *testing.T) {
tests := map[string]struct {
oldSpec *policy.PodDisruptionBudgetSpec
newSpec *policy.PodDisruptionBudgetSpec
expectNewSpec *policy.PodDisruptionBudgetSpec
enableUnhealthyPodEvictionPolicy bool
}{
"disabled clears unhealthyPodEvictionPolicy": {
enableUnhealthyPodEvictionPolicy: false,
oldSpec: nil,
newSpec: specWithUnhealthyPodEvictionPolicy(unhealthyPolicyPtr(policy.IfHealthyBudget)),
expectNewSpec: specWithUnhealthyPodEvictionPolicy(nil),
},
"disabled does not allow updating unhealthyPodEvictionPolicy": {
enableUnhealthyPodEvictionPolicy: false,
oldSpec: specWithUnhealthyPodEvictionPolicy(nil),
newSpec: specWithUnhealthyPodEvictionPolicy(unhealthyPolicyPtr(policy.IfHealthyBudget)),
expectNewSpec: specWithUnhealthyPodEvictionPolicy(nil),
},
"disabled preserves old unhealthyPodEvictionPolicy when both old and new have it": {
enableUnhealthyPodEvictionPolicy: false,
oldSpec: specWithUnhealthyPodEvictionPolicy(unhealthyPolicyPtr(policy.IfHealthyBudget)),
newSpec: specWithUnhealthyPodEvictionPolicy(unhealthyPolicyPtr(policy.IfHealthyBudget)),
expectNewSpec: specWithUnhealthyPodEvictionPolicy(unhealthyPolicyPtr(policy.IfHealthyBudget)),
},
"disabled allows updating unhealthyPodEvictionPolicy": {
enableUnhealthyPodEvictionPolicy: false,
oldSpec: specWithUnhealthyPodEvictionPolicy(unhealthyPolicyPtr(policy.IfHealthyBudget)),
newSpec: specWithUnhealthyPodEvictionPolicy(unhealthyPolicyPtr(policy.AlwaysAllow)),
expectNewSpec: specWithUnhealthyPodEvictionPolicy(unhealthyPolicyPtr(policy.AlwaysAllow)),
},
"enabled preserve unhealthyPodEvictionPolicy": {
enableUnhealthyPodEvictionPolicy: true,
oldSpec: nil,
newSpec: specWithUnhealthyPodEvictionPolicy(unhealthyPolicyPtr(policy.IfHealthyBudget)),
expectNewSpec: specWithUnhealthyPodEvictionPolicy(unhealthyPolicyPtr(policy.IfHealthyBudget)),
},
"enabled allows updating unhealthyPodEvictionPolicy": {
enableUnhealthyPodEvictionPolicy: true,
oldSpec: specWithUnhealthyPodEvictionPolicy(nil),
newSpec: specWithUnhealthyPodEvictionPolicy(unhealthyPolicyPtr(policy.IfHealthyBudget)),
expectNewSpec: specWithUnhealthyPodEvictionPolicy(unhealthyPolicyPtr(policy.IfHealthyBudget)),
},
"enabled preserve unhealthyPodEvictionPolicy when both old and new have it": {
enableUnhealthyPodEvictionPolicy: true,
oldSpec: specWithUnhealthyPodEvictionPolicy(unhealthyPolicyPtr(policy.IfHealthyBudget)),
newSpec: specWithUnhealthyPodEvictionPolicy(unhealthyPolicyPtr(policy.IfHealthyBudget)),
expectNewSpec: specWithUnhealthyPodEvictionPolicy(unhealthyPolicyPtr(policy.IfHealthyBudget)),
},
"enabled updates unhealthyPodEvictionPolicy": {
enableUnhealthyPodEvictionPolicy: true,
oldSpec: specWithUnhealthyPodEvictionPolicy(unhealthyPolicyPtr(policy.IfHealthyBudget)),
newSpec: specWithUnhealthyPodEvictionPolicy(unhealthyPolicyPtr(policy.AlwaysAllow)),
expectNewSpec: specWithUnhealthyPodEvictionPolicy(unhealthyPolicyPtr(policy.AlwaysAllow)),
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PDBUnhealthyPodEvictionPolicy, tc.enableUnhealthyPodEvictionPolicy)()
oldSpecBefore := tc.oldSpec.DeepCopy()
dropDisabledFields(tc.newSpec, tc.oldSpec)
if !reflect.DeepEqual(tc.newSpec, tc.expectNewSpec) {
t.Error(cmp.Diff(tc.newSpec, tc.expectNewSpec))
}
if !reflect.DeepEqual(tc.oldSpec, oldSpecBefore) {
t.Error(cmp.Diff(tc.oldSpec, oldSpecBefore))
}
})
}
}
func unhealthyPolicyPtr(unhealthyPodEvictionPolicy policy.UnhealthyPodEvictionPolicyType) *policy.UnhealthyPodEvictionPolicyType {
return &unhealthyPodEvictionPolicy
}
func specWithUnhealthyPodEvictionPolicy(unhealthyPodEvictionPolicy *policy.UnhealthyPodEvictionPolicyType) *policy.PodDisruptionBudgetSpec {
return &policy.PodDisruptionBudgetSpec{
UnhealthyPodEvictionPolicy: unhealthyPodEvictionPolicy,
}
}

View File

@ -201,58 +201,61 @@ func init() {
}
var fileDescriptor_2d50488813b2d18e = []byte{
// 808 bytes of a gzipped FileDescriptorProto
// 854 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x96, 0xcf, 0x8f, 0xdb, 0x44,
0x14, 0xc7, 0xe3, 0xcd, 0x66, 0x59, 0xa6, 0x49, 0xb4, 0x0c, 0x0b, 0x2c, 0x39, 0x38, 0x28, 0xa7,
0x82, 0xd4, 0x31, 0xdb, 0x22, 0xb4, 0xaa, 0x04, 0xa2, 0x6e, 0x56, 0x50, 0xd4, 0x25, 0xd5, 0x2c,
0x08, 0x09, 0x81, 0xc4, 0xc4, 0x7e, 0xcd, 0x0e, 0xb1, 0x3d, 0xd6, 0xcc, 0x38, 0x34, 0x27, 0xf8,
0x13, 0xf8, 0x17, 0xf8, 0x53, 0x38, 0xb1, 0x27, 0xd4, 0x63, 0xc5, 0x21, 0x62, 0xcd, 0x3f, 0x82,
0x3c, 0x76, 0x7e, 0x38, 0xf1, 0xaa, 0x59, 0x0e, 0xbd, 0x79, 0xde, 0x7b, 0xdf, 0xcf, 0x9b, 0xf7,
0xe6, 0xcd, 0xc8, 0xe8, 0x93, 0xf1, 0x89, 0x22, 0x5c, 0x38, 0xe3, 0x64, 0x08, 0x32, 0x02, 0x0d,
0xca, 0x99, 0x40, 0xe4, 0x0b, 0xe9, 0x14, 0x0e, 0x16, 0x73, 0x27, 0x16, 0x01, 0xf7, 0xa6, 0xce,
0xe4, 0xd8, 0x19, 0x41, 0x04, 0x92, 0x69, 0xf0, 0x49, 0x2c, 0x85, 0x16, 0xf8, 0x30, 0x8f, 0x22,
0x2c, 0xe6, 0x24, 0x8f, 0x22, 0x93, 0xe3, 0xce, 0x9d, 0x11, 0xd7, 0x17, 0xc9, 0x90, 0x78, 0x22,
0x74, 0x46, 0x62, 0x24, 0x1c, 0x13, 0x3c, 0x4c, 0x9e, 0x9a, 0x95, 0x59, 0x98, 0xaf, 0x1c, 0xd2,
0xf9, 0x68, 0x99, 0x2a, 0x64, 0xde, 0x05, 0x8f, 0x40, 0x4e, 0x9d, 0x78, 0x3c, 0xca, 0x0c, 0xca,
0x09, 0x41, 0xb3, 0x8a, 0xd4, 0x1d, 0xe7, 0x3a, 0x95, 0x4c, 0x22, 0xcd, 0x43, 0xd8, 0x10, 0x7c,
0xfc, 0x32, 0x81, 0xf2, 0x2e, 0x20, 0x64, 0x1b, 0xba, 0x7b, 0xd7, 0xe9, 0x12, 0xcd, 0x03, 0x87,
0x47, 0x5a, 0x69, 0xb9, 0x2e, 0xea, 0xfd, 0x6d, 0xa1, 0xfd, 0xd3, 0x09, 0xf7, 0x34, 0x17, 0x11,
0xfe, 0x11, 0xed, 0x67, 0x55, 0xf8, 0x4c, 0xb3, 0x23, 0xeb, 0x3d, 0xeb, 0xf6, 0xad, 0xbb, 0x1f,
0x92, 0x65, 0xe3, 0x16, 0x50, 0x12, 0x8f, 0x47, 0x99, 0x41, 0x91, 0x2c, 0x9a, 0x4c, 0x8e, 0xc9,
0x60, 0xf8, 0x13, 0x78, 0xfa, 0x0c, 0x34, 0x73, 0xf1, 0xe5, 0xac, 0x5b, 0x4b, 0x67, 0x5d, 0xb4,
0xb4, 0xd1, 0x05, 0x15, 0x07, 0xa8, 0xe5, 0x43, 0x00, 0x1a, 0x06, 0x71, 0x96, 0x51, 0x1d, 0xed,
0x98, 0x34, 0xf7, 0xb6, 0x4b, 0xd3, 0x5f, 0x95, 0xba, 0x6f, 0xa4, 0xb3, 0x6e, 0xab, 0x64, 0xa2,
0x65, 0x78, 0xef, 0xf7, 0x1d, 0xf4, 0xe6, 0x13, 0xe1, 0xf7, 0xb9, 0x92, 0x89, 0x31, 0xb9, 0x89,
0x3f, 0x02, 0xfd, 0x0a, 0xea, 0x1c, 0xa0, 0x5d, 0x15, 0x83, 0x57, 0x94, 0x77, 0x87, 0x54, 0x8d,
0x1f, 0xa9, 0xd8, 0xda, 0x79, 0x0c, 0x9e, 0xdb, 0x2c, 0xd0, 0xbb, 0xd9, 0x8a, 0x1a, 0x10, 0xfe,
0x16, 0xed, 0x29, 0xcd, 0x74, 0xa2, 0x8e, 0xea, 0x06, 0xe9, 0x6c, 0x8f, 0x34, 0x32, 0xb7, 0x5d,
0x40, 0xf7, 0xf2, 0x35, 0x2d, 0x70, 0xbd, 0x3f, 0x2d, 0xf4, 0x4e, 0x85, 0xea, 0x31, 0x57, 0x1a,
0x7f, 0xbf, 0xd1, 0x27, 0xb2, 0x5d, 0x9f, 0x32, 0xb5, 0xe9, 0xd2, 0x41, 0x91, 0x75, 0x7f, 0x6e,
0x59, 0xe9, 0xd1, 0x57, 0xa8, 0xc1, 0x35, 0x84, 0xd9, 0x0c, 0xd4, 0x6f, 0xdf, 0xba, 0xfb, 0xfe,
0xd6, 0x15, 0xb9, 0xad, 0x82, 0xda, 0x78, 0x94, 0xe9, 0x69, 0x8e, 0xe9, 0xfd, 0xb5, 0x53, 0x59,
0x49, 0xd6, 0x44, 0xfc, 0x14, 0x35, 0x43, 0x1e, 0x3d, 0x98, 0x30, 0x1e, 0xb0, 0x61, 0x00, 0x2f,
0x3d, 0xf5, 0xec, 0xca, 0x90, 0xfc, 0xca, 0x90, 0x47, 0x91, 0x1e, 0xc8, 0x73, 0x2d, 0x79, 0x34,
0x72, 0x0f, 0xd2, 0x59, 0xb7, 0x79, 0xb6, 0x42, 0xa2, 0x25, 0x2e, 0xfe, 0x01, 0xed, 0x2b, 0x08,
0xc0, 0xd3, 0x42, 0xde, 0x6c, 0xb4, 0x1f, 0xb3, 0x21, 0x04, 0xe7, 0x85, 0xd4, 0x6d, 0x66, 0x2d,
0x9b, 0xaf, 0xe8, 0x02, 0x89, 0x03, 0xd4, 0x0e, 0xd9, 0xb3, 0x6f, 0x22, 0xb6, 0x28, 0xa4, 0xfe,
0x3f, 0x0b, 0xc1, 0xe9, 0xac, 0xdb, 0x3e, 0x2b, 0xb1, 0xe8, 0x1a, 0xbb, 0xf7, 0x47, 0x03, 0xbd,
0x7b, 0xed, 0x40, 0xe1, 0x2f, 0x11, 0x16, 0x43, 0x05, 0x72, 0x02, 0xfe, 0xe7, 0xf9, 0xa3, 0xc2,
0x45, 0x64, 0x1a, 0x5b, 0x77, 0x3b, 0xc5, 0x01, 0xe1, 0xc1, 0x46, 0x04, 0xad, 0x50, 0xe1, 0x5f,
0x50, 0xcb, 0xcf, 0xb3, 0x80, 0xff, 0x44, 0xf8, 0xf3, 0x91, 0x70, 0x6f, 0x38, 0xe4, 0xa4, 0xbf,
0x0a, 0x39, 0x8d, 0xb4, 0x9c, 0xba, 0x6f, 0x15, 0x5b, 0x69, 0x95, 0x7c, 0xb4, 0x9c, 0x2f, 0x2b,
0xc6, 0x5f, 0x20, 0xd5, 0x83, 0x20, 0x10, 0x3f, 0x83, 0x6f, 0x9a, 0xdb, 0x58, 0x16, 0xd3, 0xdf,
0x88, 0xa0, 0x15, 0x2a, 0xfc, 0x29, 0x6a, 0x7b, 0x89, 0x94, 0x10, 0xe9, 0x2f, 0x80, 0x05, 0xfa,
0x62, 0x7a, 0xb4, 0x6b, 0x38, 0x6f, 0x17, 0x9c, 0xf6, 0xc3, 0x92, 0x97, 0xae, 0x45, 0x67, 0x7a,
0x1f, 0x14, 0x97, 0xe0, 0xcf, 0xf5, 0x8d, 0xb2, 0xbe, 0x5f, 0xf2, 0xd2, 0xb5, 0x68, 0x7c, 0x82,
0x9a, 0xf0, 0x2c, 0x06, 0x6f, 0xde, 0xcb, 0x3d, 0xa3, 0x3e, 0x2c, 0xd4, 0xcd, 0xd3, 0x15, 0x1f,
0x2d, 0x45, 0x62, 0x0f, 0x21, 0x4f, 0x44, 0x3e, 0xcf, 0x9f, 0xe6, 0xd7, 0xcc, 0x19, 0x38, 0xdb,
0xcd, 0xef, 0xc3, 0xb9, 0x6e, 0xf9, 0x30, 0x2e, 0x4c, 0x8a, 0xae, 0x60, 0x3b, 0x01, 0xc2, 0x9b,
0xc7, 0x84, 0x0f, 0x50, 0x7d, 0x0c, 0x53, 0x33, 0x3e, 0xaf, 0xd3, 0xec, 0x13, 0x7f, 0x86, 0x1a,
0x13, 0x16, 0x24, 0x50, 0xdc, 0xa3, 0x0f, 0xb6, 0xdb, 0xc7, 0xd7, 0x3c, 0x04, 0x9a, 0x0b, 0xef,
0xef, 0x9c, 0x58, 0xee, 0xfd, 0xcb, 0x2b, 0xbb, 0xf6, 0xfc, 0xca, 0xae, 0xbd, 0xb8, 0xb2, 0x6b,
0xbf, 0xa6, 0xb6, 0x75, 0x99, 0xda, 0xd6, 0xf3, 0xd4, 0xb6, 0x5e, 0xa4, 0xb6, 0xf5, 0x4f, 0x6a,
0x5b, 0xbf, 0xfd, 0x6b, 0xd7, 0xbe, 0x3b, 0xac, 0xfa, 0x89, 0xf8, 0x2f, 0x00, 0x00, 0xff, 0xff,
0x8d, 0x9b, 0x69, 0xee, 0x74, 0x08, 0x00, 0x00,
0x14, 0xc7, 0xe3, 0xcd, 0x66, 0xd9, 0x4e, 0x93, 0x68, 0x19, 0x16, 0x58, 0x72, 0x70, 0xaa, 0x9c,
0x16, 0xa4, 0x8e, 0xd9, 0x16, 0xa1, 0x55, 0x25, 0x50, 0xeb, 0x66, 0x05, 0x45, 0x5d, 0xb2, 0x9a,
0x6d, 0x85, 0x84, 0x40, 0x62, 0x62, 0xbf, 0x26, 0x43, 0x6c, 0x8f, 0xe5, 0x19, 0x87, 0xe6, 0x44,
0xff, 0x04, 0xfe, 0x05, 0xfe, 0x14, 0x4e, 0xec, 0xb1, 0xdc, 0x2a, 0x0e, 0x11, 0x6b, 0xfe, 0x0b,
0x4e, 0xc8, 0x63, 0xe7, 0x87, 0x37, 0x0e, 0xcd, 0x72, 0xe8, 0xcd, 0xf3, 0xde, 0xfb, 0x7e, 0x9e,
0xdf, 0x8f, 0x71, 0x82, 0x3e, 0x1b, 0x1d, 0x4b, 0xc2, 0x85, 0x35, 0x8a, 0xfb, 0x10, 0x05, 0xa0,
0x40, 0x5a, 0x63, 0x08, 0x5c, 0x11, 0x59, 0xb9, 0x83, 0x85, 0xdc, 0x0a, 0x85, 0xc7, 0x9d, 0x89,
0x35, 0x3e, 0xb2, 0x06, 0x10, 0x40, 0xc4, 0x14, 0xb8, 0x24, 0x8c, 0x84, 0x12, 0x78, 0x3f, 0x8b,
0x22, 0x2c, 0xe4, 0x24, 0x8b, 0x22, 0xe3, 0xa3, 0xd6, 0xed, 0x01, 0x57, 0xc3, 0xb8, 0x4f, 0x1c,
0xe1, 0x5b, 0x03, 0x31, 0x10, 0x96, 0x0e, 0xee, 0xc7, 0xcf, 0xf4, 0x49, 0x1f, 0xf4, 0x53, 0x06,
0x69, 0x7d, 0xb2, 0x48, 0xe5, 0x33, 0x67, 0xc8, 0x03, 0x88, 0x26, 0x56, 0x38, 0x1a, 0xa4, 0x06,
0x69, 0xf9, 0xa0, 0x58, 0x49, 0xea, 0x96, 0xb5, 0x4e, 0x15, 0xc5, 0x81, 0xe2, 0x3e, 0xac, 0x08,
0x3e, 0x7d, 0x9d, 0x40, 0x3a, 0x43, 0xf0, 0xd9, 0x8a, 0xee, 0xee, 0x3a, 0x5d, 0xac, 0xb8, 0x67,
0xf1, 0x40, 0x49, 0x15, 0x5d, 0x15, 0x75, 0xfe, 0x34, 0xd0, 0xee, 0xc9, 0x98, 0x3b, 0x8a, 0x8b,
0x00, 0xff, 0x80, 0x76, 0xd3, 0x2a, 0x5c, 0xa6, 0xd8, 0x81, 0x71, 0xcb, 0x38, 0xbc, 0x79, 0xe7,
0x63, 0xb2, 0x68, 0xdc, 0x1c, 0x4a, 0xc2, 0xd1, 0x20, 0x35, 0x48, 0x92, 0x46, 0x93, 0xf1, 0x11,
0xe9, 0xf5, 0x7f, 0x04, 0x47, 0x9d, 0x82, 0x62, 0x36, 0xbe, 0x98, 0xb6, 0x2b, 0xc9, 0xb4, 0x8d,
0x16, 0x36, 0x3a, 0xa7, 0x62, 0x0f, 0x35, 0x5c, 0xf0, 0x40, 0x41, 0x2f, 0x4c, 0x33, 0xca, 0x83,
0x2d, 0x9d, 0xe6, 0xee, 0x66, 0x69, 0xba, 0xcb, 0x52, 0xfb, 0xed, 0x64, 0xda, 0x6e, 0x14, 0x4c,
0xb4, 0x08, 0xef, 0xfc, 0xba, 0x85, 0xde, 0x39, 0x13, 0x6e, 0x97, 0xcb, 0x28, 0xd6, 0x26, 0x3b,
0x76, 0x07, 0xa0, 0xde, 0x40, 0x9d, 0x3d, 0xb4, 0x2d, 0x43, 0x70, 0xf2, 0xf2, 0x6e, 0x93, 0xb2,
0xf5, 0x23, 0x25, 0xaf, 0x76, 0x1e, 0x82, 0x63, 0xd7, 0x73, 0xf4, 0x76, 0x7a, 0xa2, 0x1a, 0x84,
0xbf, 0x41, 0x3b, 0x52, 0x31, 0x15, 0xcb, 0x83, 0xaa, 0x46, 0x5a, 0x9b, 0x23, 0xb5, 0xcc, 0x6e,
0xe6, 0xd0, 0x9d, 0xec, 0x4c, 0x73, 0x5c, 0xe7, 0x77, 0x03, 0xbd, 0x5f, 0xa2, 0x7a, 0xcc, 0xa5,
0xc2, 0xdf, 0xad, 0xf4, 0x89, 0x6c, 0xd6, 0xa7, 0x54, 0xad, 0xbb, 0xb4, 0x97, 0x67, 0xdd, 0x9d,
0x59, 0x96, 0x7a, 0xf4, 0x35, 0xaa, 0x71, 0x05, 0x7e, 0xba, 0x03, 0xd5, 0xc3, 0x9b, 0x77, 0x3e,
0xdc, 0xb8, 0x22, 0xbb, 0x91, 0x53, 0x6b, 0x8f, 0x52, 0x3d, 0xcd, 0x30, 0x9d, 0x3f, 0xaa, 0xa5,
0x95, 0xa4, 0x4d, 0xc4, 0xcf, 0x50, 0xdd, 0xe7, 0xc1, 0x83, 0x31, 0xe3, 0x1e, 0xeb, 0x7b, 0xf0,
0xda, 0xa9, 0xa7, 0x57, 0x86, 0x64, 0x57, 0x86, 0x3c, 0x0a, 0x54, 0x2f, 0x3a, 0x57, 0x11, 0x0f,
0x06, 0xf6, 0x5e, 0x32, 0x6d, 0xd7, 0x4f, 0x97, 0x48, 0xb4, 0xc0, 0xc5, 0xdf, 0xa3, 0x5d, 0x09,
0x1e, 0x38, 0x4a, 0x44, 0xd7, 0x5b, 0xed, 0xc7, 0xac, 0x0f, 0xde, 0x79, 0x2e, 0xb5, 0xeb, 0x69,
0xcb, 0x66, 0x27, 0x3a, 0x47, 0x62, 0x0f, 0x35, 0x7d, 0xf6, 0xfc, 0x69, 0xc0, 0xe6, 0x85, 0x54,
0xff, 0x67, 0x21, 0x38, 0x99, 0xb6, 0x9b, 0xa7, 0x05, 0x16, 0xbd, 0xc2, 0xc6, 0x2f, 0x0c, 0xd4,
0x8a, 0x83, 0x21, 0x30, 0x4f, 0x0d, 0x27, 0x67, 0xc2, 0x9d, 0x7d, 0x27, 0xce, 0xf4, 0x70, 0x0e,
0xb6, 0x6f, 0x19, 0x87, 0x37, 0xec, 0xfb, 0xc9, 0xb4, 0xdd, 0x7a, 0xba, 0x36, 0xea, 0x9f, 0x69,
0xdb, 0x5c, 0xef, 0x7d, 0x32, 0x09, 0x81, 0xfe, 0x47, 0x8e, 0xce, 0x6f, 0x35, 0xf4, 0xc1, 0xda,
0x9d, 0xc6, 0x5f, 0x21, 0x2c, 0xfa, 0x12, 0xa2, 0x31, 0xb8, 0x5f, 0x64, 0xdf, 0x35, 0x2e, 0x02,
0x3d, 0xdb, 0xaa, 0xdd, 0xca, 0x77, 0x04, 0xf7, 0x56, 0x22, 0x68, 0x89, 0x0a, 0xff, 0x8c, 0x1a,
0x6e, 0x96, 0x05, 0xdc, 0x33, 0xe1, 0xce, 0xb6, 0xd2, 0xbe, 0xe6, 0x3d, 0x23, 0xdd, 0x65, 0xc8,
0x49, 0xa0, 0xa2, 0x89, 0xfd, 0x6e, 0xfe, 0x2a, 0x8d, 0x82, 0x8f, 0x16, 0xf3, 0xa5, 0xc5, 0xb8,
0x73, 0xa4, 0x7c, 0xe0, 0x79, 0xe2, 0x27, 0x70, 0xf5, 0x7c, 0x6b, 0x8b, 0x62, 0xba, 0x2b, 0x11,
0xb4, 0x44, 0x85, 0x3f, 0x47, 0x4d, 0x27, 0x8e, 0x22, 0x08, 0xd4, 0x97, 0x59, 0x67, 0xf5, 0xb0,
0x6a, 0xf6, 0x7b, 0x39, 0xa7, 0xf9, 0xb0, 0xe0, 0xa5, 0x57, 0xa2, 0x53, 0xbd, 0x0b, 0x92, 0x47,
0xe0, 0xce, 0xf4, 0xb5, 0xa2, 0xbe, 0x5b, 0xf0, 0xd2, 0x2b, 0xd1, 0xf8, 0x18, 0xd5, 0xe1, 0x79,
0x08, 0xce, 0xac, 0x97, 0x3b, 0x5a, 0xbd, 0x9f, 0xab, 0xeb, 0x27, 0x4b, 0x3e, 0x5a, 0x88, 0xc4,
0x0e, 0x42, 0x8e, 0x08, 0x5c, 0x9e, 0xfd, 0x3a, 0xbc, 0xa5, 0x67, 0x60, 0x6d, 0x76, 0x85, 0x1e,
0xce, 0x74, 0x8b, 0x6f, 0xf3, 0xdc, 0x24, 0xe9, 0x12, 0xb6, 0xe5, 0x21, 0xbc, 0x3a, 0x26, 0xbc,
0x87, 0xaa, 0x23, 0x98, 0xe8, 0xf5, 0xb9, 0x41, 0xd3, 0x47, 0x7c, 0x1f, 0xd5, 0xc6, 0xcc, 0x8b,
0x21, 0xbf, 0xca, 0x1f, 0x6d, 0xf6, 0x1e, 0x4f, 0xb8, 0x0f, 0x34, 0x13, 0xde, 0xdb, 0x3a, 0x36,
0xec, 0x7b, 0x17, 0x97, 0x66, 0xe5, 0xe5, 0xa5, 0x59, 0x79, 0x75, 0x69, 0x56, 0x5e, 0x24, 0xa6,
0x71, 0x91, 0x98, 0xc6, 0xcb, 0xc4, 0x34, 0x5e, 0x25, 0xa6, 0xf1, 0x57, 0x62, 0x1a, 0xbf, 0xfc,
0x6d, 0x56, 0xbe, 0xdd, 0x2f, 0xfb, 0x1f, 0xf3, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x79, 0xd7,
0x99, 0xdb, 0xf7, 0x08, 0x00, 0x00,
}
func (m *Eviction) Marshal() (dAtA []byte, err error) {
@ -420,6 +423,13 @@ func (m *PodDisruptionBudgetSpec) MarshalToSizedBuffer(dAtA []byte) (int, error)
_ = i
var l int
_ = l
if m.UnhealthyPodEvictionPolicy != nil {
i -= len(*m.UnhealthyPodEvictionPolicy)
copy(dAtA[i:], *m.UnhealthyPodEvictionPolicy)
i = encodeVarintGenerated(dAtA, i, uint64(len(*m.UnhealthyPodEvictionPolicy)))
i--
dAtA[i] = 0x22
}
if m.MaxUnavailable != nil {
{
size, err := m.MaxUnavailable.MarshalToSizedBuffer(dAtA[:i])
@ -616,6 +626,10 @@ func (m *PodDisruptionBudgetSpec) Size() (n int) {
l = m.MaxUnavailable.Size()
n += 1 + l + sovGenerated(uint64(l))
}
if m.UnhealthyPodEvictionPolicy != nil {
l = len(*m.UnhealthyPodEvictionPolicy)
n += 1 + l + sovGenerated(uint64(l))
}
return n
}
@ -701,6 +715,7 @@ func (this *PodDisruptionBudgetSpec) String() string {
`MinAvailable:` + strings.Replace(fmt.Sprintf("%v", this.MinAvailable), "IntOrString", "intstr.IntOrString", 1) + `,`,
`Selector:` + strings.Replace(fmt.Sprintf("%v", this.Selector), "LabelSelector", "v1.LabelSelector", 1) + `,`,
`MaxUnavailable:` + strings.Replace(fmt.Sprintf("%v", this.MaxUnavailable), "IntOrString", "intstr.IntOrString", 1) + `,`,
`UnhealthyPodEvictionPolicy:` + valueToStringGenerated(this.UnhealthyPodEvictionPolicy) + `,`,
`}`,
}, "")
return s
@ -1266,6 +1281,39 @@ func (m *PodDisruptionBudgetSpec) Unmarshal(dAtA []byte) error {
return err
}
iNdEx = postIndex
case 4:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field UnhealthyPodEvictionPolicy", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthGenerated
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthGenerated
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
s := UnhealthyPodEvictionPolicyType(dAtA[iNdEx:postIndex])
m.UnhealthyPodEvictionPolicy = &s
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipGenerated(dAtA[iNdEx:])

View File

@ -92,6 +92,34 @@ message PodDisruptionBudgetSpec {
// by specifying 0. This is a mutually exclusive setting with "minAvailable".
// +optional
optional k8s.io.apimachinery.pkg.util.intstr.IntOrString maxUnavailable = 3;
// UnhealthyPodEvictionPolicy defines the criteria for when unhealthy pods
// should be considered for eviction. Current implementation considers healthy pods,
// as pods that have status.conditions item with type="Ready",status="True".
//
// Valid policies are IfHealthyBudget and AlwaysAllow.
// If no policy is specified, the default behavior will be used,
// which corresponds to the IfHealthyBudget policy.
//
// IfHealthyBudget policy means that running pods (status.phase="Running"),
// but not yet healthy can be evicted only if the guarded application is not
// disrupted (status.currentHealthy is at least equal to status.desiredHealthy).
// Healthy pods will be subject to the PDB for eviction.
//
// AlwaysAllow policy means that all running pods (status.phase="Running"),
// but not yet healthy are considered disrupted and can be evicted regardless
// of whether the criteria in a PDB is met. This means perspective running
// pods of a disrupted application might not get a chance to become healthy.
// Healthy pods will be subject to the PDB for eviction.
//
// Additional policies may be added in the future.
// Clients making eviction decisions should disallow eviction of unhealthy pods
// if they encounter an unrecognized policy in this field.
//
// This field is alpha-level. The eviction API uses this field when
// the feature gate PDBUnhealthyPodEvictionPolicy is enabled (disabled by default).
// +optional
optional string unhealthyPodEvictionPolicy = 4;
}
// PodDisruptionBudgetStatus represents information about the status of a

View File

@ -47,8 +47,56 @@ type PodDisruptionBudgetSpec struct {
// by specifying 0. This is a mutually exclusive setting with "minAvailable".
// +optional
MaxUnavailable *intstr.IntOrString `json:"maxUnavailable,omitempty" protobuf:"bytes,3,opt,name=maxUnavailable"`
// UnhealthyPodEvictionPolicy defines the criteria for when unhealthy pods
// should be considered for eviction. Current implementation considers healthy pods,
// as pods that have status.conditions item with type="Ready",status="True".
//
// Valid policies are IfHealthyBudget and AlwaysAllow.
// If no policy is specified, the default behavior will be used,
// which corresponds to the IfHealthyBudget policy.
//
// IfHealthyBudget policy means that running pods (status.phase="Running"),
// but not yet healthy can be evicted only if the guarded application is not
// disrupted (status.currentHealthy is at least equal to status.desiredHealthy).
// Healthy pods will be subject to the PDB for eviction.
//
// AlwaysAllow policy means that all running pods (status.phase="Running"),
// but not yet healthy are considered disrupted and can be evicted regardless
// of whether the criteria in a PDB is met. This means perspective running
// pods of a disrupted application might not get a chance to become healthy.
// Healthy pods will be subject to the PDB for eviction.
//
// Additional policies may be added in the future.
// Clients making eviction decisions should disallow eviction of unhealthy pods
// if they encounter an unrecognized policy in this field.
//
// This field is alpha-level. The eviction API uses this field when
// the feature gate PDBUnhealthyPodEvictionPolicy is enabled (disabled by default).
// +optional
UnhealthyPodEvictionPolicy *UnhealthyPodEvictionPolicyType `json:"unhealthyPodEvictionPolicy,omitempty" protobuf:"bytes,4,opt,name=unhealthyPodEvictionPolicy"`
}
// UnhealthyPodEvictionPolicyType defines the criteria for when unhealthy pods
// should be considered for eviction.
// +enum
type UnhealthyPodEvictionPolicyType string
const (
// IfHealthyBudget policy means that running pods (status.phase="Running"),
// but not yet healthy can be evicted only if the guarded application is not
// disrupted (status.currentHealthy is at least equal to status.desiredHealthy).
// Healthy pods will be subject to the PDB for eviction.
IfHealthyBudget UnhealthyPodEvictionPolicyType = "IfHealthyBudget"
// AlwaysAllow policy means that all running pods (status.phase="Running"),
// but not yet healthy are considered disrupted and can be evicted regardless
// of whether the criteria in a PDB is met. This means perspective running
// pods of a disrupted application might not get a chance to become healthy.
// Healthy pods will be subject to the PDB for eviction.
AlwaysAllow UnhealthyPodEvictionPolicyType = "AlwaysAllow"
)
// PodDisruptionBudgetStatus represents information about the status of a
// PodDisruptionBudget. Status may trail the actual state of a system.
type PodDisruptionBudgetStatus struct {

View File

@ -63,6 +63,7 @@ var map_PodDisruptionBudgetSpec = map[string]string{
"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. A null selector will match no pods, while an empty ({}) selector will select all pods within the namespace.",
"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\".",
"unhealthyPodEvictionPolicy": "UnhealthyPodEvictionPolicy defines the criteria for when unhealthy pods should be considered for eviction. Current implementation considers healthy pods, as pods that have status.conditions item with type=\"Ready\",status=\"True\".\n\nValid policies are IfHealthyBudget and AlwaysAllow. If no policy is specified, the default behavior will be used, which corresponds to the IfHealthyBudget policy.\n\nIfHealthyBudget policy means that running pods (status.phase=\"Running\"), but not yet healthy can be evicted only if the guarded application is not disrupted (status.currentHealthy is at least equal to status.desiredHealthy). Healthy pods will be subject to the PDB for eviction.\n\nAlwaysAllow policy means that all running pods (status.phase=\"Running\"), but not yet healthy are considered disrupted and can be evicted regardless of whether the criteria in a PDB is met. This means perspective running pods of a disrupted application might not get a chance to become healthy. Healthy pods will be subject to the PDB for eviction.\n\nAdditional policies may be added in the future. Clients making eviction decisions should disallow eviction of unhealthy pods if they encounter an unrecognized policy in this field.\n\nThis field is alpha-level. The eviction API uses this field when the feature gate PDBUnhealthyPodEvictionPolicy is enabled (disabled by default).",
}
func (PodDisruptionBudgetSpec) SwaggerDoc() map[string]string {

View File

@ -137,6 +137,11 @@ func (in *PodDisruptionBudgetSpec) DeepCopyInto(out *PodDisruptionBudgetSpec) {
*out = new(intstr.IntOrString)
**out = **in
}
if in.UnhealthyPodEvictionPolicy != nil {
in, out := &in.UnhealthyPodEvictionPolicy, &out.UnhealthyPodEvictionPolicy
*out = new(UnhealthyPodEvictionPolicyType)
**out = **in
}
return
}

View File

@ -609,127 +609,129 @@ func init() {
}
var fileDescriptor_014060e454a820dc = []byte{
// 1907 bytes of a gzipped FileDescriptorProto
// 1946 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x59, 0x5b, 0x73, 0xdb, 0xc6,
0xf5, 0x17, 0x4c, 0x5d, 0xa8, 0xd5, 0xc5, 0xe2, 0xea, 0x62, 0x48, 0xf9, 0x87, 0x70, 0x90, 0x99,
0xff, 0xb8, 0x69, 0x0a, 0xc6, 0xb2, 0xe3, 0x7a, 0x9a, 0x5e, 0x22, 0x88, 0x92, 0xad, 0x8c, 0x65,
0xb1, 0x4b, 0x3b, 0xd3, 0x76, 0xdc, 0x4e, 0x97, 0xc0, 0x8a, 0x42, 0x04, 0x02, 0x28, 0x76, 0xc1,
0x88, 0x6f, 0x7d, 0xe8, 0x43, 0x1f, 0xfb, 0x05, 0x32, 0xfd, 0x00, 0x9d, 0x3e, 0xf5, 0x43, 0xd4,
0x99, 0xe9, 0x64, 0xf2, 0x98, 0xe9, 0x03, 0xa7, 0x66, 0xbf, 0x85, 0x9f, 0x3a, 0x58, 0x2e, 0x40,
0x5c, 0x49, 0x3b, 0x33, 0xf6, 0x1b, 0xb1, 0xe7, 0xf7, 0xfb, 0x9d, 0xdd, 0xb3, 0x67, 0xcf, 0x5e,
0x08, 0xf4, 0xcb, 0xfb, 0x54, 0xb3, 0xdc, 0xc6, 0x65, 0xd0, 0x21, 0xbe, 0x43, 0x18, 0xa1, 0x8d,
0x3e, 0x71, 0x4c, 0xd7, 0x6f, 0x08, 0x03, 0xf6, 0xac, 0x86, 0xe7, 0xda, 0x96, 0x31, 0x68, 0xf4,
0x6f, 0x77, 0x08, 0xc3, 0xb7, 0x1b, 0x5d, 0xe2, 0x10, 0x1f, 0x33, 0x62, 0x6a, 0x9e, 0xef, 0x32,
0x17, 0xee, 0x8e, 0xa1, 0x1a, 0xf6, 0x2c, 0x6d, 0x0c, 0xd5, 0x04, 0x74, 0xef, 0x47, 0x5d, 0x8b,
0x5d, 0x04, 0x1d, 0xcd, 0x70, 0x7b, 0x8d, 0xae, 0xdb, 0x75, 0x1b, 0x9c, 0xd1, 0x09, 0xce, 0xf9,
0x17, 0xff, 0xe0, 0xbf, 0xc6, 0x4a, 0x7b, 0x6a, 0xc2, 0xa9, 0xe1, 0xfa, 0xa4, 0xd1, 0xcf, 0x79,
0xdb, 0xbb, 0x3b, 0xc1, 0xf4, 0xb0, 0x71, 0x61, 0x39, 0xc4, 0x1f, 0x34, 0xbc, 0xcb, 0x6e, 0xd8,
0x40, 0x1b, 0x3d, 0xc2, 0x70, 0x11, 0xab, 0x51, 0xc6, 0xf2, 0x03, 0x87, 0x59, 0x3d, 0x92, 0x23,
0xdc, 0x9b, 0x45, 0xa0, 0xc6, 0x05, 0xe9, 0xe1, 0x1c, 0xef, 0x4e, 0x19, 0x2f, 0x60, 0x96, 0xdd,
0xb0, 0x1c, 0x46, 0x99, 0x9f, 0x25, 0xa9, 0x77, 0xc1, 0xc6, 0x81, 0x6d, 0xbb, 0x5f, 0x12, 0xf3,
0xb0, 0x7d, 0xd2, 0xf4, 0xad, 0x3e, 0xf1, 0xe1, 0x4d, 0x30, 0xef, 0xe0, 0x1e, 0x91, 0xa5, 0x9b,
0xd2, 0xad, 0x65, 0x7d, 0xf5, 0xf9, 0x50, 0x99, 0x1b, 0x0d, 0x95, 0xf9, 0xc7, 0xb8, 0x47, 0x10,
0xb7, 0xa8, 0x9f, 0x80, 0x9a, 0x60, 0x1d, 0xdb, 0xe4, 0xea, 0x73, 0xd7, 0x0e, 0x7a, 0x04, 0xfe,
0x3f, 0x58, 0x34, 0xb9, 0x80, 0x20, 0xae, 0x0b, 0xe2, 0xe2, 0x58, 0x16, 0x09, 0xab, 0x4a, 0xc1,
0x75, 0x41, 0x7e, 0xe8, 0x52, 0xd6, 0xc2, 0xec, 0x02, 0xee, 0x03, 0xe0, 0x61, 0x76, 0xd1, 0xf2,
0xc9, 0xb9, 0x75, 0x25, 0xe8, 0x50, 0xd0, 0x41, 0x2b, 0xb6, 0xa0, 0x04, 0x0a, 0x7e, 0x08, 0xaa,
0x3e, 0xc1, 0xe6, 0x99, 0x63, 0x0f, 0xe4, 0x6b, 0x37, 0xa5, 0x5b, 0x55, 0x7d, 0x43, 0x30, 0xaa,
0x48, 0xb4, 0xa3, 0x18, 0xa1, 0xfe, 0x5b, 0x02, 0xd5, 0xa3, 0xbe, 0x65, 0x30, 0xcb, 0x75, 0xe0,
0xef, 0x41, 0x35, 0x9c, 0x2d, 0x13, 0x33, 0xcc, 0x9d, 0xad, 0xec, 0x7f, 0xa4, 0x4d, 0x32, 0x29,
0x0e, 0x9e, 0xe6, 0x5d, 0x76, 0xc3, 0x06, 0xaa, 0x85, 0x68, 0xad, 0x7f, 0x5b, 0x3b, 0xeb, 0x7c,
0x41, 0x0c, 0x76, 0x4a, 0x18, 0x9e, 0x74, 0x6f, 0xd2, 0x86, 0x62, 0x55, 0x68, 0x83, 0x35, 0x93,
0xd8, 0x84, 0x91, 0x33, 0x2f, 0xf4, 0x48, 0x79, 0x0f, 0x57, 0xf6, 0xef, 0xbc, 0x9a, 0x9b, 0x66,
0x92, 0xaa, 0xd7, 0x46, 0x43, 0x65, 0x2d, 0xd5, 0x84, 0xd2, 0xe2, 0xea, 0x57, 0x12, 0xd8, 0x39,
0x6e, 0x3f, 0xf0, 0xdd, 0xc0, 0x6b, 0xb3, 0x70, 0x76, 0xbb, 0x03, 0x61, 0x82, 0x3f, 0x06, 0xf3,
0x7e, 0x60, 0x47, 0x73, 0xf9, 0x7e, 0x34, 0x97, 0x28, 0xb0, 0xc9, 0xcb, 0xa1, 0xb2, 0x99, 0x61,
0x3d, 0x19, 0x78, 0x04, 0x71, 0x02, 0xfc, 0x0c, 0x2c, 0xfa, 0xd8, 0xe9, 0x92, 0xb0, 0xeb, 0x95,
0x5b, 0x2b, 0xfb, 0xaa, 0x56, 0xba, 0xd6, 0xb4, 0x93, 0x26, 0x0a, 0xa1, 0x93, 0x19, 0xe7, 0x9f,
0x14, 0x09, 0x05, 0xf5, 0x14, 0xac, 0xf1, 0xa9, 0x76, 0x7d, 0xc6, 0x2d, 0xf0, 0x5d, 0x50, 0xe9,
0x59, 0x0e, 0xef, 0xd4, 0x82, 0xbe, 0x22, 0x58, 0x95, 0x53, 0xcb, 0x41, 0x61, 0x3b, 0x37, 0xe3,
0x2b, 0x1e, 0xb3, 0xa4, 0x19, 0x5f, 0xa1, 0xb0, 0x5d, 0x7d, 0x00, 0x96, 0x84, 0xc7, 0xa4, 0x50,
0x65, 0xba, 0x50, 0xa5, 0x40, 0xe8, 0x6f, 0xd7, 0xc0, 0x66, 0xcb, 0x35, 0x9b, 0x16, 0xf5, 0x03,
0x1e, 0x2f, 0x3d, 0x30, 0xbb, 0x84, 0xbd, 0x85, 0xfc, 0x78, 0x02, 0xe6, 0xa9, 0x47, 0x0c, 0x91,
0x16, 0xfb, 0x53, 0x62, 0x5b, 0xd0, 0xbf, 0xb6, 0x47, 0x8c, 0xc9, 0xb2, 0x0c, 0xbf, 0x10, 0x57,
0x83, 0xcf, 0xc0, 0x22, 0x65, 0x98, 0x05, 0x54, 0xae, 0x70, 0xdd, 0xbb, 0xaf, 0xa9, 0xcb, 0xb9,
0x93, 0x59, 0x1c, 0x7f, 0x23, 0xa1, 0xa9, 0xfe, 0x4b, 0x02, 0x37, 0x0a, 0x58, 0x8f, 0x2c, 0xca,
0xe0, 0xb3, 0x5c, 0xc4, 0xb4, 0x57, 0x8b, 0x58, 0xc8, 0xe6, 0xf1, 0x8a, 0x17, 0x6f, 0xd4, 0x92,
0x88, 0x56, 0x1b, 0x2c, 0x58, 0x8c, 0xf4, 0xa2, 0x54, 0xd4, 0x5e, 0x6f, 0x58, 0xfa, 0x9a, 0x90,
0x5e, 0x38, 0x09, 0x45, 0xd0, 0x58, 0x4b, 0xfd, 0xe6, 0x5a, 0xe1, 0x70, 0xc2, 0x70, 0xc2, 0x73,
0xb0, 0xda, 0xb3, 0x9c, 0x83, 0x3e, 0xb6, 0x6c, 0xdc, 0x11, 0xab, 0x67, 0x5a, 0x12, 0x84, 0x15,
0x56, 0x1b, 0x57, 0x58, 0xed, 0xc4, 0x61, 0x67, 0x7e, 0x9b, 0xf9, 0x96, 0xd3, 0xd5, 0x37, 0x46,
0x43, 0x65, 0xf5, 0x34, 0xa1, 0x84, 0x52, 0xba, 0xf0, 0xb7, 0xa0, 0x4a, 0x89, 0x4d, 0x0c, 0xe6,
0xfa, 0xaf, 0x57, 0x21, 0x1e, 0xe1, 0x0e, 0xb1, 0xdb, 0x82, 0xaa, 0xaf, 0x86, 0x71, 0x8b, 0xbe,
0x50, 0x2c, 0x09, 0x6d, 0xb0, 0xde, 0xc3, 0x57, 0x4f, 0x1d, 0x1c, 0x0f, 0xa4, 0xf2, 0x3d, 0x07,
0x02, 0x47, 0x43, 0x65, 0xfd, 0x34, 0xa5, 0x85, 0x32, 0xda, 0xea, 0x3f, 0x17, 0xc0, 0x6e, 0x69,
0x56, 0xc1, 0xcf, 0x00, 0x74, 0x3b, 0x94, 0xf8, 0x7d, 0x62, 0x3e, 0x18, 0xef, 0x41, 0x96, 0x1b,
0x2d, 0xdc, 0x3d, 0x31, 0x41, 0xf0, 0x2c, 0x87, 0x40, 0x05, 0x2c, 0xf8, 0x27, 0x09, 0xac, 0x99,
0x63, 0x37, 0xc4, 0x6c, 0xb9, 0x66, 0x94, 0x18, 0x0f, 0xbe, 0x4f, 0xbe, 0x6b, 0xcd, 0xa4, 0xd2,
0x91, 0xc3, 0xfc, 0x81, 0xbe, 0x2d, 0x3a, 0xb4, 0x96, 0xb2, 0xa1, 0xb4, 0xd3, 0x70, 0x48, 0x66,
0x2c, 0x49, 0xc5, 0x9e, 0xc6, 0x43, 0xbc, 0x30, 0x19, 0x52, 0x33, 0x87, 0x40, 0x05, 0x2c, 0xf8,
0x73, 0xb0, 0x6e, 0x04, 0xbe, 0x4f, 0x1c, 0xf6, 0x90, 0x60, 0x9b, 0x5d, 0x0c, 0xe4, 0x79, 0xae,
0xb3, 0x23, 0x74, 0xd6, 0x0f, 0x53, 0x56, 0x94, 0x41, 0x87, 0x7c, 0x93, 0x50, 0xcb, 0x27, 0x66,
0xc4, 0x5f, 0x48, 0xf3, 0x9b, 0x29, 0x2b, 0xca, 0xa0, 0xe1, 0x7d, 0xb0, 0x4a, 0xae, 0x3c, 0x62,
0x44, 0x01, 0x5d, 0xe4, 0xec, 0x2d, 0xc1, 0x5e, 0x3d, 0x4a, 0xd8, 0x50, 0x0a, 0x09, 0x0d, 0x00,
0x0c, 0xd7, 0x31, 0xad, 0xf1, 0x3e, 0xb7, 0xc4, 0x27, 0xa2, 0xf1, 0x6a, 0x59, 0x7c, 0x18, 0xf1,
0x26, 0xd5, 0x32, 0x6e, 0xa2, 0x28, 0x21, 0xbb, 0x67, 0x03, 0x98, 0x9f, 0x26, 0xb8, 0x01, 0x2a,
0x97, 0x64, 0x30, 0xde, 0xdb, 0x50, 0xf8, 0x13, 0x7e, 0x0a, 0x16, 0xfa, 0xd8, 0x0e, 0x88, 0x58,
0x4d, 0x1f, 0xbc, 0x5a, 0x3f, 0x9e, 0x58, 0x3d, 0x82, 0xc6, 0xc4, 0x9f, 0x5c, 0xbb, 0x2f, 0xa9,
0x5f, 0x4b, 0xa0, 0xd6, 0x72, 0xcd, 0x36, 0x31, 0x02, 0xdf, 0x62, 0x83, 0x16, 0xcf, 0xa4, 0xb7,
0xb0, 0x2b, 0xa0, 0xd4, 0xae, 0xf0, 0xd1, 0xf4, 0x6c, 0x4e, 0xf7, 0xae, 0x6c, 0x4f, 0x50, 0x9f,
0x4b, 0x60, 0x3b, 0x87, 0x7e, 0x0b, 0x35, 0xfb, 0x97, 0xe9, 0x9a, 0xfd, 0xe1, 0xeb, 0x0c, 0xa6,
0xa4, 0x62, 0x7f, 0x5d, 0x2b, 0x18, 0x0a, 0xaf, 0xd7, 0xe1, 0xf9, 0xd1, 0xb7, 0xfa, 0x96, 0x4d,
0xba, 0xc4, 0xe4, 0x83, 0xa9, 0x26, 0xce, 0x8f, 0xb1, 0x05, 0x25, 0x50, 0x90, 0x82, 0x1d, 0x93,
0x9c, 0xe3, 0xc0, 0x66, 0x07, 0xa6, 0x79, 0x88, 0x3d, 0xdc, 0xb1, 0x6c, 0x8b, 0x59, 0xe2, 0xc0,
0xb3, 0xac, 0x7f, 0x32, 0x1a, 0x2a, 0x3b, 0xcd, 0x42, 0xc4, 0xcb, 0xa1, 0xf2, 0x6e, 0xfe, 0xbe,
0xa0, 0xc5, 0x90, 0x01, 0x2a, 0x91, 0x86, 0x03, 0x20, 0xfb, 0xe4, 0x0f, 0x41, 0xb8, 0xf2, 0x9a,
0xbe, 0xeb, 0xa5, 0xdc, 0x56, 0xb8, 0xdb, 0x9f, 0x8d, 0x86, 0x8a, 0x8c, 0x4a, 0x30, 0xb3, 0x1d,
0x97, 0xca, 0xc3, 0x2f, 0xc0, 0x26, 0x16, 0x27, 0xfd, 0xa4, 0xd7, 0x79, 0xee, 0xf5, 0xfe, 0x68,
0xa8, 0x6c, 0x1e, 0xe4, 0xcd, 0xb3, 0x1d, 0x16, 0x89, 0xc2, 0x06, 0x58, 0xea, 0xf3, 0x4b, 0x01,
0x95, 0x17, 0xb8, 0xfe, 0xf6, 0x68, 0xa8, 0x2c, 0x8d, 0xef, 0x09, 0xa1, 0xe6, 0xe2, 0x71, 0x9b,
0x1f, 0x35, 0x23, 0x14, 0xfc, 0x18, 0xac, 0x5c, 0xb8, 0x94, 0x3d, 0x26, 0xec, 0x4b, 0xd7, 0xbf,
0xe4, 0xd5, 0xa7, 0xaa, 0x6f, 0x8a, 0x19, 0x5c, 0x79, 0x38, 0x31, 0xa1, 0x24, 0x0e, 0xfe, 0x1a,
0x2c, 0x5f, 0x88, 0x83, 0x65, 0x54, 0x7a, 0x6e, 0x4d, 0x49, 0xb4, 0xd4, 0x21, 0x54, 0xaf, 0x09,
0xf9, 0xe5, 0xa8, 0x99, 0xa2, 0x89, 0x1a, 0xfc, 0x01, 0x58, 0xe2, 0x1f, 0x27, 0x4d, 0xb9, 0xca,
0x7b, 0x73, 0x5d, 0xc0, 0x97, 0x1e, 0x8e, 0x9b, 0x51, 0x64, 0x8f, 0xa0, 0x27, 0xad, 0x43, 0x79,
0x39, 0x0f, 0x3d, 0x69, 0x1d, 0xa2, 0xc8, 0x0e, 0x9f, 0x81, 0x25, 0x4a, 0x1e, 0x59, 0x4e, 0x70,
0x25, 0x03, 0xbe, 0xe4, 0x6e, 0x4f, 0xe9, 0x6e, 0xfb, 0x88, 0x23, 0x33, 0x47, 0xfa, 0x89, 0xba,
0xb0, 0xa3, 0x48, 0x12, 0x9a, 0x60, 0xd9, 0x0f, 0x9c, 0x03, 0xfa, 0x94, 0x12, 0x5f, 0x5e, 0xc9,
0x9d, 0x27, 0xb2, 0xfa, 0x28, 0xc2, 0x66, 0x3d, 0xc4, 0x91, 0x89, 0x11, 0x68, 0x22, 0x0c, 0x4d,
0x00, 0xf8, 0x07, 0xbf, 0x39, 0xc8, 0x3b, 0x33, 0x4f, 0x9a, 0x28, 0x06, 0x67, 0xfd, 0xac, 0x87,
0xcb, 0x73, 0x62, 0x46, 0x09, 0x5d, 0xf8, 0x67, 0x09, 0x40, 0x1a, 0x78, 0x9e, 0x4d, 0x7a, 0xc4,
0x61, 0xd8, 0xe6, 0xad, 0x54, 0x5e, 0xe5, 0xee, 0x7e, 0x3a, 0x2d, 0x6a, 0x39, 0x52, 0xd6, 0x6d,
0xbc, 0x37, 0xe7, 0xa1, 0xa8, 0xc0, 0x67, 0x38, 0x69, 0xe7, 0x62, 0xb4, 0x6b, 0x33, 0x27, 0xad,
0xf8, 0x1e, 0x36, 0x99, 0x34, 0x61, 0x47, 0x91, 0x24, 0xfc, 0x1c, 0xec, 0x44, 0xb7, 0x54, 0xe4,
0xba, 0xec, 0xd8, 0xb2, 0x09, 0x1d, 0x50, 0x46, 0x7a, 0xf2, 0x3a, 0x4f, 0xa6, 0xba, 0x60, 0xee,
0xa0, 0x42, 0x14, 0x2a, 0x61, 0xc3, 0x1e, 0x50, 0xa2, 0x22, 0x14, 0xae, 0xd0, 0xb8, 0x0a, 0x1e,
0x51, 0x03, 0xdb, 0xe3, 0xd3, 0xd7, 0x75, 0xee, 0xe0, 0xfd, 0xd1, 0x50, 0x51, 0x9a, 0xd3, 0xa1,
0x68, 0x96, 0x16, 0xfc, 0x15, 0x90, 0x71, 0x99, 0x9f, 0x0d, 0xee, 0xe7, 0xff, 0xc2, 0xca, 0x56,
0xea, 0xa0, 0x94, 0x0d, 0x3d, 0xb0, 0x81, 0xd3, 0xef, 0x05, 0x54, 0xae, 0xf1, 0xb5, 0xfe, 0xc1,
0x94, 0x79, 0xc8, 0x3c, 0x31, 0xe8, 0xb2, 0x08, 0xe3, 0x46, 0xc6, 0x40, 0x51, 0x4e, 0x1d, 0x5e,
0x01, 0x88, 0xb3, 0xcf, 0x1b, 0x54, 0x86, 0x33, 0x37, 0xb2, 0xdc, 0x9b, 0xc8, 0x24, 0xd5, 0x72,
0x26, 0x8a, 0x0a, 0x7c, 0x40, 0x06, 0x6a, 0x38, 0xf3, 0x1c, 0x43, 0xe5, 0x1b, 0xdc, 0xf1, 0x0f,
0x67, 0x3b, 0x8e, 0x39, 0xfa, 0xae, 0xf0, 0x5b, 0xcb, 0x5a, 0x28, 0xca, 0x3b, 0x80, 0x8f, 0xc0,
0x96, 0x68, 0x7c, 0xea, 0x50, 0x7c, 0x4e, 0xda, 0x03, 0x6a, 0x30, 0x9b, 0xca, 0x9b, 0xbc, 0x76,
0xcb, 0xa3, 0xa1, 0xb2, 0x75, 0x50, 0x60, 0x47, 0x85, 0x2c, 0xf8, 0x29, 0xd8, 0x38, 0x77, 0xfd,
0x8e, 0x65, 0x9a, 0xc4, 0x89, 0x94, 0xb6, 0xb8, 0xd2, 0x56, 0x18, 0xff, 0xe3, 0x8c, 0x0d, 0xe5,
0xd0, 0x90, 0x82, 0x6d, 0xa1, 0xdc, 0xf2, 0x5d, 0xe3, 0xd4, 0x0d, 0x1c, 0x16, 0x6e, 0x17, 0x54,
0xde, 0x8e, 0xb7, 0xc8, 0xed, 0x83, 0x22, 0xc0, 0xcb, 0xa1, 0x72, 0xb3, 0x60, 0xbb, 0x4a, 0x81,
0x50, 0xb1, 0x36, 0xb4, 0xc1, 0xaa, 0x78, 0x60, 0x3b, 0xb4, 0x31, 0xa5, 0xb2, 0xcc, 0x97, 0xfa,
0xbd, 0xe9, 0x85, 0x2d, 0x86, 0x67, 0xd7, 0x3b, 0xbf, 0xf9, 0x25, 0x01, 0x28, 0xa5, 0xae, 0xfe,
0x55, 0x02, 0xbb, 0xa5, 0x85, 0x11, 0xde, 0x4b, 0xbd, 0xda, 0xa8, 0x99, 0x57, 0x1b, 0x98, 0x27,
0xbe, 0x81, 0x47, 0x9b, 0xaf, 0x24, 0x20, 0x97, 0xed, 0x10, 0xf0, 0xe3, 0x54, 0x07, 0xdf, 0xcb,
0x74, 0xb0, 0x96, 0xe3, 0xbd, 0x81, 0xfe, 0x7d, 0x23, 0x81, 0x77, 0xa6, 0xcc, 0x40, 0x5c, 0x90,
0x88, 0x99, 0x44, 0x3d, 0xc6, 0xe1, 0x52, 0x96, 0x78, 0x1e, 0x4d, 0x0a, 0x52, 0x01, 0x06, 0x95,
0xb2, 0xe1, 0x53, 0x70, 0x43, 0x54, 0xc3, 0xac, 0x8d, 0x9f, 0xdc, 0x97, 0xf5, 0x77, 0x46, 0x43,
0xe5, 0x46, 0xb3, 0x18, 0x82, 0xca, 0xb8, 0xea, 0xdf, 0x25, 0xb0, 0x53, 0xbc, 0xe5, 0xc3, 0x3b,
0xa9, 0x70, 0x2b, 0x99, 0x70, 0x5f, 0xcf, 0xb0, 0x44, 0xb0, 0x7f, 0x07, 0xd6, 0xc5, 0xc1, 0x20,
0xfd, 0x08, 0x99, 0x0a, 0x7a, 0xb8, 0x44, 0xc2, 0x33, 0xbd, 0x90, 0x88, 0xd2, 0x97, 0xdf, 0xf7,
0xd3, 0x6d, 0x28, 0xa3, 0xa6, 0xfe, 0x43, 0x02, 0xef, 0xcd, 0xdc, 0x6c, 0xa1, 0x9e, 0xea, 0xba,
0x96, 0xe9, 0x7a, 0xbd, 0x5c, 0xe0, 0xcd, 0xbc, 0x45, 0xea, 0xbf, 0x78, 0xfe, 0xa2, 0x3e, 0xf7,
0xed, 0x8b, 0xfa, 0xdc, 0x77, 0x2f, 0xea, 0x73, 0x7f, 0x1c, 0xd5, 0xa5, 0xe7, 0xa3, 0xba, 0xf4,
0xed, 0xa8, 0x2e, 0x7d, 0x37, 0xaa, 0x4b, 0xff, 0x19, 0xd5, 0xa5, 0xbf, 0xfc, 0xb7, 0x3e, 0xf7,
0x9b, 0xdd, 0xd2, 0xff, 0x20, 0xfe, 0x17, 0x00, 0x00, 0xff, 0xff, 0xec, 0x71, 0xd7, 0x62, 0xb8,
0x18, 0x00, 0x00,
0x15, 0x16, 0x4c, 0x5d, 0xa8, 0xd5, 0xc5, 0xe2, 0xea, 0x62, 0x48, 0x69, 0x08, 0x07, 0x99, 0xe9,
0xb8, 0x69, 0x0a, 0xc6, 0xb2, 0xe3, 0x7a, 0x9a, 0x5e, 0x2c, 0x88, 0x92, 0xad, 0x8c, 0x65, 0xb1,
0x4b, 0x2b, 0xd3, 0x76, 0xdc, 0x4e, 0x97, 0xc0, 0x8a, 0x44, 0x04, 0x02, 0x28, 0x76, 0xc1, 0x88,
0x6f, 0x79, 0xe8, 0x43, 0x1f, 0xfb, 0x07, 0x32, 0xfd, 0x01, 0x9d, 0x3e, 0xf5, 0x47, 0xd4, 0x99,
0xe9, 0x74, 0xd2, 0xb7, 0x4c, 0x1f, 0x38, 0x35, 0xfb, 0x2f, 0xfc, 0xd4, 0xc1, 0x72, 0x01, 0x12,
0x37, 0xd2, 0xce, 0x8c, 0xfd, 0x46, 0xec, 0xf9, 0xbe, 0xef, 0xec, 0x9e, 0xdd, 0x3d, 0x67, 0x77,
0x09, 0xf4, 0xcb, 0xfb, 0x54, 0xb3, 0xdc, 0xda, 0x65, 0xd0, 0x22, 0xbe, 0x43, 0x18, 0xa1, 0xb5,
0x1e, 0x71, 0x4c, 0xd7, 0xaf, 0x09, 0x03, 0xf6, 0xac, 0x9a, 0xe7, 0xda, 0x96, 0xd1, 0xaf, 0xf5,
0x6e, 0xb7, 0x08, 0xc3, 0xb7, 0x6b, 0x6d, 0xe2, 0x10, 0x1f, 0x33, 0x62, 0x6a, 0x9e, 0xef, 0x32,
0x17, 0xee, 0x8e, 0xa0, 0x1a, 0xf6, 0x2c, 0x6d, 0x04, 0xd5, 0x04, 0x74, 0xef, 0x47, 0x6d, 0x8b,
0x75, 0x82, 0x96, 0x66, 0xb8, 0xdd, 0x5a, 0xdb, 0x6d, 0xbb, 0x35, 0xce, 0x68, 0x05, 0x17, 0xfc,
0x8b, 0x7f, 0xf0, 0x5f, 0x23, 0xa5, 0x3d, 0x75, 0xc2, 0xa9, 0xe1, 0xfa, 0xa4, 0xd6, 0xcb, 0x78,
0xdb, 0xbb, 0x3b, 0xc6, 0x74, 0xb1, 0xd1, 0xb1, 0x1c, 0xe2, 0xf7, 0x6b, 0xde, 0x65, 0x3b, 0x6c,
0xa0, 0xb5, 0x2e, 0x61, 0x38, 0x8f, 0x55, 0x2b, 0x62, 0xf9, 0x81, 0xc3, 0xac, 0x2e, 0xc9, 0x10,
0xee, 0xcd, 0x22, 0x50, 0xa3, 0x43, 0xba, 0x38, 0xc3, 0xbb, 0x53, 0xc4, 0x0b, 0x98, 0x65, 0xd7,
0x2c, 0x87, 0x51, 0xe6, 0xa7, 0x49, 0xea, 0x5d, 0xb0, 0x71, 0x60, 0xdb, 0xee, 0x17, 0xc4, 0x3c,
0x6c, 0x9e, 0xd4, 0x7d, 0xab, 0x47, 0x7c, 0x78, 0x13, 0xcc, 0x3b, 0xb8, 0x4b, 0x64, 0xe9, 0xa6,
0x74, 0x6b, 0x59, 0x5f, 0x7d, 0x3e, 0x50, 0xe6, 0x86, 0x03, 0x65, 0xfe, 0x09, 0xee, 0x12, 0xc4,
0x2d, 0xea, 0x27, 0xa0, 0x22, 0x58, 0xc7, 0x36, 0xb9, 0xfa, 0xcc, 0xb5, 0x83, 0x2e, 0x81, 0xdf,
0x07, 0x8b, 0x26, 0x17, 0x10, 0xc4, 0x75, 0x41, 0x5c, 0x1c, 0xc9, 0x22, 0x61, 0x55, 0x29, 0xb8,
0x2e, 0xc8, 0x8f, 0x5c, 0xca, 0x1a, 0x98, 0x75, 0xe0, 0x3e, 0x00, 0x1e, 0x66, 0x9d, 0x86, 0x4f,
0x2e, 0xac, 0x2b, 0x41, 0x87, 0x82, 0x0e, 0x1a, 0xb1, 0x05, 0x4d, 0xa0, 0xe0, 0x87, 0xa0, 0xec,
0x13, 0x6c, 0x9e, 0x39, 0x76, 0x5f, 0xbe, 0x76, 0x53, 0xba, 0x55, 0xd6, 0x37, 0x04, 0xa3, 0x8c,
0x44, 0x3b, 0x8a, 0x11, 0xea, 0x7f, 0x24, 0x50, 0x3e, 0xea, 0x59, 0x06, 0xb3, 0x5c, 0x07, 0xfe,
0x1e, 0x94, 0xc3, 0xd9, 0x32, 0x31, 0xc3, 0xdc, 0xd9, 0xca, 0xfe, 0x47, 0xda, 0x78, 0x25, 0xc5,
0xc1, 0xd3, 0xbc, 0xcb, 0x76, 0xd8, 0x40, 0xb5, 0x10, 0xad, 0xf5, 0x6e, 0x6b, 0x67, 0xad, 0xcf,
0x89, 0xc1, 0x4e, 0x09, 0xc3, 0xe3, 0xee, 0x8d, 0xdb, 0x50, 0xac, 0x0a, 0x6d, 0xb0, 0x66, 0x12,
0x9b, 0x30, 0x72, 0xe6, 0x85, 0x1e, 0x29, 0xef, 0xe1, 0xca, 0xfe, 0x9d, 0x57, 0x73, 0x53, 0x9f,
0xa4, 0xea, 0x95, 0xe1, 0x40, 0x59, 0x4b, 0x34, 0xa1, 0xa4, 0xb8, 0xfa, 0x95, 0x04, 0x76, 0x8e,
0x9b, 0x0f, 0x7d, 0x37, 0xf0, 0x9a, 0x2c, 0x9c, 0xdd, 0x76, 0x5f, 0x98, 0xe0, 0x8f, 0xc1, 0xbc,
0x1f, 0xd8, 0xd1, 0x5c, 0xbe, 0x1f, 0xcd, 0x25, 0x0a, 0x6c, 0xf2, 0x72, 0xa0, 0x6c, 0xa6, 0x58,
0x4f, 0xfb, 0x1e, 0x41, 0x9c, 0x00, 0x3f, 0x05, 0x8b, 0x3e, 0x76, 0xda, 0x24, 0xec, 0x7a, 0xe9,
0xd6, 0xca, 0xbe, 0xaa, 0x15, 0xee, 0x35, 0xed, 0xa4, 0x8e, 0x42, 0xe8, 0x78, 0xc6, 0xf9, 0x27,
0x45, 0x42, 0x41, 0x3d, 0x05, 0x6b, 0x7c, 0xaa, 0x5d, 0x9f, 0x71, 0x0b, 0x7c, 0x17, 0x94, 0xba,
0x96, 0xc3, 0x3b, 0xb5, 0xa0, 0xaf, 0x08, 0x56, 0xe9, 0xd4, 0x72, 0x50, 0xd8, 0xce, 0xcd, 0xf8,
0x8a, 0xc7, 0x6c, 0xd2, 0x8c, 0xaf, 0x50, 0xd8, 0xae, 0x3e, 0x04, 0x4b, 0xc2, 0xe3, 0xa4, 0x50,
0x69, 0xba, 0x50, 0x29, 0x47, 0xe8, 0xaf, 0xd7, 0xc0, 0x66, 0xc3, 0x35, 0xeb, 0x16, 0xf5, 0x03,
0x1e, 0x2f, 0x3d, 0x30, 0xdb, 0x84, 0xbd, 0x85, 0xf5, 0xf1, 0x14, 0xcc, 0x53, 0x8f, 0x18, 0x62,
0x59, 0xec, 0x4f, 0x89, 0x6d, 0x4e, 0xff, 0x9a, 0x1e, 0x31, 0xc6, 0xdb, 0x32, 0xfc, 0x42, 0x5c,
0x0d, 0x3e, 0x03, 0x8b, 0x94, 0x61, 0x16, 0x50, 0xb9, 0xc4, 0x75, 0xef, 0xbe, 0xa6, 0x2e, 0xe7,
0x8e, 0x67, 0x71, 0xf4, 0x8d, 0x84, 0xa6, 0xfa, 0x4f, 0x09, 0xdc, 0xc8, 0x61, 0x3d, 0xb6, 0x28,
0x83, 0xcf, 0x32, 0x11, 0xd3, 0x5e, 0x2d, 0x62, 0x21, 0x9b, 0xc7, 0x2b, 0xde, 0xbc, 0x51, 0xcb,
0x44, 0xb4, 0x9a, 0x60, 0xc1, 0x62, 0xa4, 0x1b, 0x2d, 0x45, 0xed, 0xf5, 0x86, 0xa5, 0xaf, 0x09,
0xe9, 0x85, 0x93, 0x50, 0x04, 0x8d, 0xb4, 0xd4, 0x7f, 0x97, 0x72, 0x87, 0x13, 0x86, 0x13, 0x5e,
0x80, 0xd5, 0xae, 0xe5, 0x1c, 0xf4, 0xb0, 0x65, 0xe3, 0x96, 0xd8, 0x3d, 0xd3, 0x16, 0x41, 0x98,
0x61, 0xb5, 0x51, 0x86, 0xd5, 0x4e, 0x1c, 0x76, 0xe6, 0x37, 0x99, 0x6f, 0x39, 0x6d, 0x7d, 0x63,
0x38, 0x50, 0x56, 0x4f, 0x27, 0x94, 0x50, 0x42, 0x17, 0xfe, 0x16, 0x94, 0x29, 0xb1, 0x89, 0xc1,
0x5c, 0xff, 0xf5, 0x32, 0xc4, 0x63, 0xdc, 0x22, 0x76, 0x53, 0x50, 0xf5, 0xd5, 0x30, 0x6e, 0xd1,
0x17, 0x8a, 0x25, 0xa1, 0x0d, 0xd6, 0xbb, 0xf8, 0xea, 0xdc, 0xc1, 0xf1, 0x40, 0x4a, 0xdf, 0x71,
0x20, 0x70, 0x38, 0x50, 0xd6, 0x4f, 0x13, 0x5a, 0x28, 0xa5, 0x0d, 0xbf, 0x94, 0xc0, 0x5e, 0xe0,
0x74, 0x08, 0xb6, 0x59, 0xa7, 0xdf, 0x70, 0xcd, 0x28, 0xdd, 0x36, 0xf8, 0x0c, 0xc9, 0xf3, 0x3c,
0x03, 0x3d, 0x18, 0x0e, 0x94, 0xbd, 0xf3, 0x42, 0xd4, 0xcb, 0x81, 0x52, 0x2d, 0xb6, 0xf2, 0xf4,
0x34, 0xc5, 0x87, 0xfa, 0x8f, 0x05, 0xb0, 0x5b, 0xb8, 0xb0, 0xe1, 0xa7, 0x00, 0xba, 0x2d, 0x4a,
0xfc, 0x1e, 0x31, 0x1f, 0x8e, 0xca, 0xa0, 0xe5, 0x46, 0xb9, 0x63, 0x4f, 0xac, 0x11, 0x78, 0x96,
0x41, 0xa0, 0x1c, 0x16, 0xfc, 0xa3, 0x04, 0xd6, 0xcc, 0x91, 0x1b, 0x62, 0x36, 0x5c, 0x33, 0x5a,
0x9b, 0x0f, 0xbf, 0xcb, 0x96, 0xd3, 0xea, 0x93, 0x4a, 0x47, 0x0e, 0xf3, 0xfb, 0xfa, 0xb6, 0xe8,
0xd0, 0x5a, 0xc2, 0x86, 0x92, 0x4e, 0xc3, 0x21, 0x99, 0xb1, 0x24, 0x15, 0x65, 0x95, 0xcf, 0xf2,
0xc2, 0x78, 0x48, 0xf5, 0x0c, 0x02, 0xe5, 0xb0, 0xe0, 0xcf, 0xc1, 0xba, 0x11, 0xf8, 0x3e, 0x71,
0xd8, 0xa3, 0x51, 0x7c, 0xf9, 0x94, 0x2d, 0xe8, 0x3b, 0x42, 0x67, 0xfd, 0x30, 0x61, 0x45, 0x29,
0x74, 0xc8, 0x37, 0x09, 0xb5, 0x7c, 0x62, 0x46, 0xfc, 0x85, 0x24, 0xbf, 0x9e, 0xb0, 0xa2, 0x14,
0x1a, 0xde, 0x07, 0xab, 0xe4, 0xca, 0x23, 0x46, 0x14, 0xd0, 0x45, 0xce, 0xde, 0x12, 0xec, 0xd5,
0xa3, 0x09, 0x1b, 0x4a, 0x20, 0xa1, 0x01, 0x80, 0xe1, 0x3a, 0xa6, 0x35, 0x2a, 0xb5, 0x4b, 0x7c,
0x22, 0x6a, 0xaf, 0xb6, 0x91, 0x0e, 0x23, 0xde, 0x38, 0x61, 0xc7, 0x4d, 0x14, 0x4d, 0xc8, 0xee,
0xd9, 0x00, 0x66, 0xa7, 0x09, 0x6e, 0x80, 0xd2, 0x25, 0xe9, 0x8f, 0xca, 0x2b, 0x0a, 0x7f, 0xc2,
0x07, 0x60, 0xa1, 0x87, 0xed, 0x80, 0x88, 0x0d, 0xfd, 0xc1, 0xab, 0xf5, 0xe3, 0xa9, 0xd5, 0x25,
0x68, 0x44, 0xfc, 0xc9, 0xb5, 0xfb, 0x92, 0xfa, 0xb5, 0x04, 0x2a, 0x0d, 0xd7, 0x6c, 0x12, 0x23,
0xf0, 0x2d, 0xd6, 0x1f, 0xad, 0xef, 0xb7, 0x50, 0x98, 0x50, 0xa2, 0x30, 0x7d, 0x34, 0x7d, 0x35,
0x27, 0x7b, 0x57, 0x54, 0x96, 0xd4, 0xe7, 0x12, 0xd8, 0xce, 0xa0, 0xdf, 0x42, 0xd9, 0xf8, 0x65,
0xb2, 0x6c, 0x7c, 0xf8, 0x3a, 0x83, 0x29, 0x28, 0x1a, 0x5f, 0x57, 0x72, 0x86, 0xc2, 0x4b, 0x46,
0x78, 0x84, 0xf5, 0xad, 0x9e, 0x65, 0x93, 0x36, 0x31, 0xf9, 0x60, 0xca, 0x13, 0x47, 0xd8, 0xd8,
0x82, 0x26, 0x50, 0x90, 0x82, 0x1d, 0x93, 0x5c, 0xe0, 0xc0, 0x66, 0x07, 0xa6, 0x79, 0x88, 0x3d,
0xdc, 0xb2, 0x6c, 0x8b, 0x59, 0xe2, 0xcc, 0xb5, 0xac, 0x7f, 0x32, 0x1c, 0x28, 0x3b, 0xf5, 0x5c,
0xc4, 0xcb, 0x81, 0xf2, 0x6e, 0xf6, 0xca, 0xa2, 0xc5, 0x90, 0x3e, 0x2a, 0x90, 0x86, 0x7d, 0x20,
0xfb, 0xe4, 0x0f, 0x41, 0xb8, 0xf3, 0xea, 0xbe, 0xeb, 0x25, 0xdc, 0x96, 0xb8, 0xdb, 0x9f, 0x0d,
0x07, 0x8a, 0x8c, 0x0a, 0x30, 0xb3, 0x1d, 0x17, 0xca, 0xc3, 0xcf, 0xc1, 0x26, 0x16, 0x97, 0x8d,
0x49, 0xaf, 0xf3, 0xdc, 0xeb, 0xfd, 0xe1, 0x40, 0xd9, 0x3c, 0xc8, 0x9a, 0x67, 0x3b, 0xcc, 0x13,
0x85, 0x35, 0xb0, 0xd4, 0xe3, 0xf7, 0x12, 0x2a, 0x2f, 0x70, 0xfd, 0xed, 0xe1, 0x40, 0x59, 0x1a,
0x5d, 0x55, 0x42, 0xcd, 0xc5, 0xe3, 0x26, 0x2f, 0x27, 0x11, 0x0a, 0x7e, 0x0c, 0x56, 0x3a, 0x2e,
0x65, 0x4f, 0x08, 0xfb, 0xc2, 0xf5, 0x2f, 0x79, 0xf6, 0x29, 0xeb, 0x9b, 0x62, 0x06, 0x57, 0x1e,
0x8d, 0x4d, 0x68, 0x12, 0x07, 0x7f, 0x0d, 0x96, 0x3b, 0xe2, 0x6c, 0x1b, 0xa5, 0x9e, 0x5b, 0x53,
0x16, 0x5a, 0xe2, 0x1c, 0xac, 0x57, 0x84, 0xfc, 0x72, 0xd4, 0x4c, 0xd1, 0x58, 0x0d, 0xfe, 0x00,
0x2c, 0xf1, 0x8f, 0x93, 0xba, 0x5c, 0xe6, 0xbd, 0xb9, 0x2e, 0xe0, 0x4b, 0x8f, 0x46, 0xcd, 0x28,
0xb2, 0x47, 0xd0, 0x93, 0xc6, 0xa1, 0xbc, 0x9c, 0x85, 0x9e, 0x34, 0x0e, 0x51, 0x64, 0x87, 0xcf,
0xc0, 0x12, 0x25, 0x8f, 0x2d, 0x27, 0xb8, 0x92, 0x01, 0xdf, 0x72, 0xb7, 0xa7, 0x74, 0xb7, 0x79,
0xc4, 0x91, 0xa9, 0x5b, 0xc5, 0x58, 0x5d, 0xd8, 0x51, 0x24, 0x09, 0x4d, 0xb0, 0xec, 0x07, 0xce,
0x01, 0x3d, 0xa7, 0xc4, 0x97, 0x57, 0x32, 0x47, 0x9a, 0xb4, 0x3e, 0x8a, 0xb0, 0x69, 0x0f, 0x71,
0x64, 0x62, 0x04, 0x1a, 0x0b, 0x43, 0x13, 0x00, 0xfe, 0xc1, 0x2f, 0x2f, 0xf2, 0xce, 0xcc, 0xc3,
0x2e, 0x8a, 0xc1, 0x69, 0x3f, 0xeb, 0xe1, 0xf6, 0x1c, 0x9b, 0xd1, 0x84, 0x2e, 0xfc, 0x93, 0x04,
0x20, 0x0d, 0x3c, 0xcf, 0x26, 0x5d, 0xe2, 0x30, 0x6c, 0xf3, 0x56, 0x2a, 0xaf, 0x72, 0x77, 0x3f,
0x9d, 0x16, 0xb5, 0x0c, 0x29, 0xed, 0x36, 0xae, 0xcd, 0x59, 0x28, 0xca, 0xf1, 0x19, 0x4e, 0xda,
0x85, 0x18, 0xed, 0xda, 0xcc, 0x49, 0xcb, 0xbf, 0x0a, 0x8e, 0x27, 0x4d, 0xd8, 0x51, 0x24, 0x09,
0x3f, 0x03, 0x3b, 0xd1, 0x45, 0x19, 0xb9, 0x2e, 0x3b, 0xb6, 0x6c, 0x42, 0xfb, 0x94, 0x91, 0xae,
0xbc, 0xce, 0x17, 0x53, 0x55, 0x30, 0x77, 0x50, 0x2e, 0x0a, 0x15, 0xb0, 0x61, 0x17, 0x28, 0x51,
0x12, 0x0a, 0x77, 0x68, 0x9c, 0x05, 0x8f, 0xa8, 0x81, 0xed, 0xd1, 0xe9, 0xeb, 0x3a, 0x77, 0xf0,
0xfe, 0x70, 0xa0, 0x28, 0xf5, 0xe9, 0x50, 0x34, 0x4b, 0x0b, 0xfe, 0x0a, 0xc8, 0xb8, 0xc8, 0xcf,
0x06, 0xf7, 0xf3, 0xbd, 0x30, 0xb3, 0x15, 0x3a, 0x28, 0x64, 0x43, 0x0f, 0x6c, 0xe0, 0xe4, 0x93,
0x05, 0x95, 0x2b, 0x7c, 0xaf, 0x7f, 0x30, 0x65, 0x1e, 0x52, 0xaf, 0x1c, 0xba, 0x2c, 0xc2, 0xb8,
0x91, 0x32, 0x50, 0x94, 0x51, 0x87, 0x57, 0x00, 0xe2, 0xf4, 0x0b, 0x0b, 0x95, 0xe1, 0xcc, 0x42,
0x96, 0x79, 0x96, 0x19, 0x2f, 0xb5, 0x8c, 0x89, 0xa2, 0x1c, 0x1f, 0x90, 0x81, 0x0a, 0x4e, 0xbd,
0x08, 0x51, 0xf9, 0x06, 0x77, 0xfc, 0xc3, 0xd9, 0x8e, 0x63, 0x8e, 0xbe, 0x2b, 0xfc, 0x56, 0xd2,
0x16, 0x8a, 0xb2, 0x0e, 0xe0, 0x63, 0xb0, 0x25, 0x1a, 0xcf, 0x1d, 0x8a, 0x2f, 0x48, 0xb3, 0x4f,
0x0d, 0x66, 0x53, 0x79, 0x93, 0xe7, 0x6e, 0x79, 0x38, 0x50, 0xb6, 0x0e, 0x72, 0xec, 0x28, 0x97,
0x05, 0x1f, 0x80, 0x8d, 0x0b, 0xd7, 0x6f, 0x59, 0xa6, 0x49, 0x9c, 0x48, 0x69, 0x8b, 0x2b, 0x6d,
0x85, 0xf1, 0x3f, 0x4e, 0xd9, 0x50, 0x06, 0x0d, 0x29, 0xd8, 0x16, 0xca, 0x0d, 0xdf, 0x35, 0x4e,
0xdd, 0xc0, 0x61, 0x61, 0xb9, 0xa0, 0xf2, 0x76, 0x5c, 0x22, 0xb7, 0x0f, 0xf2, 0x00, 0x2f, 0x07,
0xca, 0xcd, 0x9c, 0x72, 0x95, 0x00, 0xa1, 0x7c, 0x6d, 0x68, 0x83, 0x55, 0xf1, 0xc6, 0x77, 0x68,
0x63, 0x4a, 0x65, 0x99, 0x6f, 0xf5, 0x7b, 0xd3, 0x13, 0x5b, 0x0c, 0x4f, 0xef, 0x77, 0x7e, 0xf9,
0x9c, 0x04, 0xa0, 0x84, 0xba, 0xfa, 0x17, 0x09, 0xec, 0x16, 0x26, 0x46, 0x78, 0x2f, 0xf1, 0x70,
0xa4, 0xa6, 0x1e, 0x8e, 0x60, 0x96, 0xf8, 0x06, 0xde, 0x8d, 0xbe, 0x92, 0x80, 0x5c, 0x54, 0x21,
0xe0, 0xc7, 0x89, 0x0e, 0xbe, 0x97, 0xea, 0x60, 0x25, 0xc3, 0x7b, 0x03, 0xfd, 0xfb, 0x97, 0x04,
0xde, 0x99, 0x32, 0x03, 0x71, 0x42, 0x22, 0xe6, 0x24, 0xea, 0x09, 0x0e, 0xb7, 0xb2, 0xc4, 0xd7,
0xd1, 0x38, 0x21, 0xe5, 0x60, 0x50, 0x21, 0x1b, 0x9e, 0x83, 0x1b, 0x22, 0x1b, 0xa6, 0x6d, 0xfc,
0xe4, 0xbe, 0xac, 0xbf, 0x33, 0x1c, 0x28, 0x37, 0xea, 0xf9, 0x10, 0x54, 0xc4, 0x55, 0xff, 0x26,
0x81, 0x9d, 0xfc, 0x92, 0x0f, 0xef, 0x24, 0xc2, 0xad, 0xa4, 0xc2, 0x7d, 0x3d, 0xc5, 0x12, 0xc1,
0xfe, 0x1d, 0x58, 0x17, 0x07, 0x83, 0xe4, 0x3b, 0x68, 0x22, 0xe8, 0xe1, 0x16, 0x09, 0xcf, 0xf4,
0x42, 0x22, 0x5a, 0xbe, 0xfc, 0xc9, 0x21, 0xd9, 0x86, 0x52, 0x6a, 0xea, 0xdf, 0x25, 0xf0, 0xde,
0xcc, 0x62, 0x0b, 0xf5, 0x44, 0xd7, 0xb5, 0x54, 0xd7, 0xab, 0xc5, 0x02, 0x6f, 0xe6, 0x39, 0x54,
0xff, 0xc5, 0xf3, 0x17, 0xd5, 0xb9, 0x6f, 0x5e, 0x54, 0xe7, 0xbe, 0x7d, 0x51, 0x9d, 0xfb, 0x72,
0x58, 0x95, 0x9e, 0x0f, 0xab, 0xd2, 0x37, 0xc3, 0xaa, 0xf4, 0xed, 0xb0, 0x2a, 0xfd, 0x77, 0x58,
0x95, 0xfe, 0xfc, 0xbf, 0xea, 0xdc, 0x6f, 0x76, 0x0b, 0xff, 0x06, 0xf9, 0x7f, 0x00, 0x00, 0x00,
0xff, 0xff, 0xb4, 0x84, 0x53, 0xfb, 0x3b, 0x19, 0x00, 0x00,
}
func (m *AllowedCSIDriver) Marshal() (dAtA []byte, err error) {
@ -1089,6 +1091,13 @@ func (m *PodDisruptionBudgetSpec) MarshalToSizedBuffer(dAtA []byte) (int, error)
_ = i
var l int
_ = l
if m.UnhealthyPodEvictionPolicy != nil {
i -= len(*m.UnhealthyPodEvictionPolicy)
copy(dAtA[i:], *m.UnhealthyPodEvictionPolicy)
i = encodeVarintGenerated(dAtA, i, uint64(len(*m.UnhealthyPodEvictionPolicy)))
i--
dAtA[i] = 0x22
}
if m.MaxUnavailable != nil {
{
size, err := m.MaxUnavailable.MarshalToSizedBuffer(dAtA[:i])
@ -1937,6 +1946,10 @@ func (m *PodDisruptionBudgetSpec) Size() (n int) {
l = m.MaxUnavailable.Size()
n += 1 + l + sovGenerated(uint64(l))
}
if m.UnhealthyPodEvictionPolicy != nil {
l = len(*m.UnhealthyPodEvictionPolicy)
n += 1 + l + sovGenerated(uint64(l))
}
return n
}
@ -2308,6 +2321,7 @@ func (this *PodDisruptionBudgetSpec) String() string {
`MinAvailable:` + strings.Replace(fmt.Sprintf("%v", this.MinAvailable), "IntOrString", "intstr.IntOrString", 1) + `,`,
`Selector:` + strings.Replace(fmt.Sprintf("%v", this.Selector), "LabelSelector", "v1.LabelSelector", 1) + `,`,
`MaxUnavailable:` + strings.Replace(fmt.Sprintf("%v", this.MaxUnavailable), "IntOrString", "intstr.IntOrString", 1) + `,`,
`UnhealthyPodEvictionPolicy:` + valueToStringGenerated(this.UnhealthyPodEvictionPolicy) + `,`,
`}`,
}, "")
return s
@ -3581,6 +3595,39 @@ func (m *PodDisruptionBudgetSpec) Unmarshal(dAtA []byte) error {
return err
}
iNdEx = postIndex
case 4:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field UnhealthyPodEvictionPolicy", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthGenerated
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthGenerated
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
s := UnhealthyPodEvictionPolicyType(dAtA[iNdEx:postIndex])
m.UnhealthyPodEvictionPolicy = &s
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipGenerated(dAtA[iNdEx:])

View File

@ -153,6 +153,34 @@ message PodDisruptionBudgetSpec {
// by specifying 0. This is a mutually exclusive setting with "minAvailable".
// +optional
optional k8s.io.apimachinery.pkg.util.intstr.IntOrString maxUnavailable = 3;
// UnhealthyPodEvictionPolicy defines the criteria for when unhealthy pods
// should be considered for eviction. Current implementation considers healthy pods,
// as pods that have status.conditions item with type="Ready",status="True".
//
// Valid policies are IfHealthyBudget and AlwaysAllow.
// If no policy is specified, the default behavior will be used,
// which corresponds to the IfHealthyBudget policy.
//
// IfHealthyBudget policy means that running pods (status.phase="Running"),
// but not yet healthy can be evicted only if the guarded application is not
// disrupted (status.currentHealthy is at least equal to status.desiredHealthy).
// Healthy pods will be subject to the PDB for eviction.
//
// AlwaysAllow policy means that all running pods (status.phase="Running"),
// but not yet healthy are considered disrupted and can be evicted regardless
// of whether the criteria in a PDB is met. This means perspective running
// pods of a disrupted application might not get a chance to become healthy.
// Healthy pods will be subject to the PDB for eviction.
//
// Additional policies may be added in the future.
// Clients making eviction decisions should disallow eviction of unhealthy pods
// if they encounter an unrecognized policy in this field.
//
// This field is alpha-level. The eviction API uses this field when
// the feature gate PDBUnhealthyPodEvictionPolicy is enabled (disabled by default).
// +optional
optional string unhealthyPodEvictionPolicy = 4;
}
// PodDisruptionBudgetStatus represents information about the status of a

View File

@ -45,8 +45,56 @@ type PodDisruptionBudgetSpec struct {
// by specifying 0. This is a mutually exclusive setting with "minAvailable".
// +optional
MaxUnavailable *intstr.IntOrString `json:"maxUnavailable,omitempty" protobuf:"bytes,3,opt,name=maxUnavailable"`
// UnhealthyPodEvictionPolicy defines the criteria for when unhealthy pods
// should be considered for eviction. Current implementation considers healthy pods,
// as pods that have status.conditions item with type="Ready",status="True".
//
// Valid policies are IfHealthyBudget and AlwaysAllow.
// If no policy is specified, the default behavior will be used,
// which corresponds to the IfHealthyBudget policy.
//
// IfHealthyBudget policy means that running pods (status.phase="Running"),
// but not yet healthy can be evicted only if the guarded application is not
// disrupted (status.currentHealthy is at least equal to status.desiredHealthy).
// Healthy pods will be subject to the PDB for eviction.
//
// AlwaysAllow policy means that all running pods (status.phase="Running"),
// but not yet healthy are considered disrupted and can be evicted regardless
// of whether the criteria in a PDB is met. This means perspective running
// pods of a disrupted application might not get a chance to become healthy.
// Healthy pods will be subject to the PDB for eviction.
//
// Additional policies may be added in the future.
// Clients making eviction decisions should disallow eviction of unhealthy pods
// if they encounter an unrecognized policy in this field.
//
// This field is alpha-level. The eviction API uses this field when
// the feature gate PDBUnhealthyPodEvictionPolicy is enabled (disabled by default).
// +optional
UnhealthyPodEvictionPolicy *UnhealthyPodEvictionPolicyType `json:"unhealthyPodEvictionPolicy,omitempty" protobuf:"bytes,4,opt,name=unhealthyPodEvictionPolicy"`
}
// UnhealthyPodEvictionPolicyType defines the criteria for when unhealthy pods
// should be considered for eviction.
// +enum
type UnhealthyPodEvictionPolicyType string
const (
// IfHealthyBudget policy means that running pods (status.phase="Running"),
// but not yet healthy can be evicted only if the guarded application is not
// disrupted (status.currentHealthy is at least equal to status.desiredHealthy).
// Healthy pods will be subject to the PDB for eviction.
IfHealthyBudget UnhealthyPodEvictionPolicyType = "IfHealthyBudget"
// AlwaysAllow policy means that all running pods (status.phase="Running"),
// but not yet healthy are considered disrupted and can be evicted regardless
// of whether the criteria in a PDB is met. This means perspective running
// pods of a disrupted application might not get a chance to become healthy.
// Healthy pods will be subject to the PDB for eviction.
AlwaysAllow UnhealthyPodEvictionPolicyType = "AlwaysAllow"
)
// PodDisruptionBudgetStatus represents information about the status of a
// PodDisruptionBudget. Status may trail the actual state of a system.
type PodDisruptionBudgetStatus struct {

View File

@ -121,6 +121,7 @@ var map_PodDisruptionBudgetSpec = map[string]string{
"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. A null selector selects no pods. An empty selector ({}) also selects no pods, which differs from standard behavior of selecting all pods. In policy/v1, an empty selector will select all pods in the namespace.",
"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\".",
"unhealthyPodEvictionPolicy": "UnhealthyPodEvictionPolicy defines the criteria for when unhealthy pods should be considered for eviction. Current implementation considers healthy pods, as pods that have status.conditions item with type=\"Ready\",status=\"True\".\n\nValid policies are IfHealthyBudget and AlwaysAllow. If no policy is specified, the default behavior will be used, which corresponds to the IfHealthyBudget policy.\n\nIfHealthyBudget policy means that running pods (status.phase=\"Running\"), but not yet healthy can be evicted only if the guarded application is not disrupted (status.currentHealthy is at least equal to status.desiredHealthy). Healthy pods will be subject to the PDB for eviction.\n\nAlwaysAllow policy means that all running pods (status.phase=\"Running\"), but not yet healthy are considered disrupted and can be evicted regardless of whether the criteria in a PDB is met. This means perspective running pods of a disrupted application might not get a chance to become healthy. Healthy pods will be subject to the PDB for eviction.\n\nAdditional policies may be added in the future. Clients making eviction decisions should disallow eviction of unhealthy pods if they encounter an unrecognized policy in this field.\n\nThis field is alpha-level. The eviction API uses this field when the feature gate PDBUnhealthyPodEvictionPolicy is enabled (disabled by default).",
}
func (PodDisruptionBudgetSpec) SwaggerDoc() map[string]string {

View File

@ -239,6 +239,11 @@ func (in *PodDisruptionBudgetSpec) DeepCopyInto(out *PodDisruptionBudgetSpec) {
*out = new(intstr.IntOrString)
**out = **in
}
if in.UnhealthyPodEvictionPolicy != nil {
in, out := &in.UnhealthyPodEvictionPolicy, &out.UnhealthyPodEvictionPolicy
*out = new(UnhealthyPodEvictionPolicyType)
**out = **in
}
return
}

View File

@ -59,7 +59,8 @@
}
]
},
"maxUnavailable": "maxUnavailableValue"
"maxUnavailable": "maxUnavailableValue",
"unhealthyPodEvictionPolicy": "unhealthyPodEvictionPolicyValue"
},
"status": {
"observedGeneration": 1,

View File

@ -43,6 +43,7 @@ spec:
- valuesValue
matchLabels:
matchLabelsKey: matchLabelsValue
unhealthyPodEvictionPolicy: unhealthyPodEvictionPolicyValue
status:
conditions:
- lastTransitionTime: "2004-01-01T01:01:01Z"

View File

@ -59,7 +59,8 @@
}
]
},
"maxUnavailable": "maxUnavailableValue"
"maxUnavailable": "maxUnavailableValue",
"unhealthyPodEvictionPolicy": "unhealthyPodEvictionPolicyValue"
},
"status": {
"observedGeneration": 1,

View File

@ -43,6 +43,7 @@ spec:
- valuesValue
matchLabels:
matchLabelsKey: matchLabelsValue
unhealthyPodEvictionPolicy: unhealthyPodEvictionPolicyValue
status:
conditions:
- lastTransitionTime: "2004-01-01T01:01:01Z"

View File

@ -10610,6 +10610,9 @@ var schemaYAML = typed.YAMLObject(`types:
- name: selector
type:
namedType: io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector
- name: unhealthyPodEvictionPolicy
type:
scalar: string
- name: io.k8s.api.policy.v1.PodDisruptionBudgetStatus
map:
fields:
@ -10751,6 +10754,9 @@ var schemaYAML = typed.YAMLObject(`types:
- name: selector
type:
namedType: io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector
- name: unhealthyPodEvictionPolicy
type:
scalar: string
- name: io.k8s.api.policy.v1beta1.PodDisruptionBudgetStatus
map:
fields:

View File

@ -19,6 +19,7 @@ limitations under the License.
package v1
import (
policyv1 "k8s.io/api/policy/v1"
intstr "k8s.io/apimachinery/pkg/util/intstr"
v1 "k8s.io/client-go/applyconfigurations/meta/v1"
)
@ -29,6 +30,7 @@ type PodDisruptionBudgetSpecApplyConfiguration struct {
MinAvailable *intstr.IntOrString `json:"minAvailable,omitempty"`
Selector *v1.LabelSelectorApplyConfiguration `json:"selector,omitempty"`
MaxUnavailable *intstr.IntOrString `json:"maxUnavailable,omitempty"`
UnhealthyPodEvictionPolicy *policyv1.UnhealthyPodEvictionPolicyType `json:"unhealthyPodEvictionPolicy,omitempty"`
}
// PodDisruptionBudgetSpecApplyConfiguration constructs an declarative configuration of the PodDisruptionBudgetSpec type for use with
@ -60,3 +62,11 @@ func (b *PodDisruptionBudgetSpecApplyConfiguration) WithMaxUnavailable(value int
b.MaxUnavailable = &value
return b
}
// WithUnhealthyPodEvictionPolicy sets the UnhealthyPodEvictionPolicy field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the UnhealthyPodEvictionPolicy field is set to the value of the last call.
func (b *PodDisruptionBudgetSpecApplyConfiguration) WithUnhealthyPodEvictionPolicy(value policyv1.UnhealthyPodEvictionPolicyType) *PodDisruptionBudgetSpecApplyConfiguration {
b.UnhealthyPodEvictionPolicy = &value
return b
}

View File

@ -19,6 +19,7 @@ limitations under the License.
package v1beta1
import (
v1beta1 "k8s.io/api/policy/v1beta1"
intstr "k8s.io/apimachinery/pkg/util/intstr"
v1 "k8s.io/client-go/applyconfigurations/meta/v1"
)
@ -29,6 +30,7 @@ type PodDisruptionBudgetSpecApplyConfiguration struct {
MinAvailable *intstr.IntOrString `json:"minAvailable,omitempty"`
Selector *v1.LabelSelectorApplyConfiguration `json:"selector,omitempty"`
MaxUnavailable *intstr.IntOrString `json:"maxUnavailable,omitempty"`
UnhealthyPodEvictionPolicy *v1beta1.UnhealthyPodEvictionPolicyType `json:"unhealthyPodEvictionPolicy,omitempty"`
}
// PodDisruptionBudgetSpecApplyConfiguration constructs an declarative configuration of the PodDisruptionBudgetSpec type for use with
@ -60,3 +62,11 @@ func (b *PodDisruptionBudgetSpecApplyConfiguration) WithMaxUnavailable(value int
b.MaxUnavailable = &value
return b
}
// WithUnhealthyPodEvictionPolicy sets the UnhealthyPodEvictionPolicy field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the UnhealthyPodEvictionPolicy field is set to the value of the last call.
func (b *PodDisruptionBudgetSpecApplyConfiguration) WithUnhealthyPodEvictionPolicy(value v1beta1.UnhealthyPodEvictionPolicyType) *PodDisruptionBudgetSpecApplyConfiguration {
b.UnhealthyPodEvictionPolicy = &value
return b
}

View File

@ -104,7 +104,7 @@ func TestConcurrentEvictionRequests(t *testing.T) {
t.Errorf("Failed to create PodDisruptionBudget: %v", err)
}
waitPDBStable(t, clientSet, numOfEvictions, ns.Name, pdb.Name)
waitPDBStable(t, clientSet, ns.Name, pdb.Name, numOfEvictions)
var numberPodsEvicted uint32
errCh := make(chan error, 3*numOfEvictions)
@ -212,7 +212,7 @@ func TestTerminalPodEviction(t *testing.T) {
t.Errorf("Failed to create PodDisruptionBudget: %v", err)
}
waitPDBStable(t, clientSet, 1, ns.Name, pdb.Name)
waitPDBStable(t, clientSet, ns.Name, pdb.Name, 1)
pdbList, err := clientSet.PolicyV1().PodDisruptionBudgets(ns.Name).List(context.TODO(), metav1.ListOptions{})
if err != nil {
@ -394,13 +394,7 @@ func TestEvictionWithFinalizers(t *testing.T) {
eviction := newV1Eviction(ns.Name, pod.Name, deleteOption)
err := wait.PollImmediate(5*time.Second, 60*time.Second, func() (bool, error) {
e := clientSet.PolicyV1().Evictions(ns.Name).Evict(ctx, eviction)
if e != nil {
return false, e
}
return true, nil
})
err := clientSet.PolicyV1().Evictions(ns.Name).Evict(ctx, eviction)
if err != nil {
t.Fatalf("Eviction of pod failed %v", err)
}
@ -419,6 +413,95 @@ func TestEvictionWithFinalizers(t *testing.T) {
}
}
// TestEvictionWithUnhealthyPodEvictionPolicy tests eviction with a PDB that has a UnhealthyPodEvictionPolicy
func TestEvictionWithUnhealthyPodEvictionPolicy(t *testing.T) {
cases := map[string]struct {
enableUnhealthyPodEvictionPolicy bool
unhealthyPodEvictionPolicy *policyv1.UnhealthyPodEvictionPolicyType
isPodReady bool
}{
"UnhealthyPodEvictionPolicy disabled and policy not set": {
enableUnhealthyPodEvictionPolicy: false,
unhealthyPodEvictionPolicy: nil,
isPodReady: true,
},
"UnhealthyPodEvictionPolicy enabled but policy not set": {
enableUnhealthyPodEvictionPolicy: true,
unhealthyPodEvictionPolicy: nil,
isPodReady: true,
},
"UnhealthyPodEvictionPolicy enabled but policy set to IfHealthyBudget with ready pod": {
enableUnhealthyPodEvictionPolicy: true,
unhealthyPodEvictionPolicy: unhealthyPolicyPtr(policyv1.IfHealthyBudget),
isPodReady: true,
},
"UnhealthyPodEvictionPolicy enabled but policy set to AlwaysAllow with ready pod": {
enableUnhealthyPodEvictionPolicy: true,
unhealthyPodEvictionPolicy: unhealthyPolicyPtr(policyv1.AlwaysAllow),
isPodReady: true,
},
"UnhealthyPodEvictionPolicy enabled but policy set to AlwaysAllow with unready pod": {
enableUnhealthyPodEvictionPolicy: true,
unhealthyPodEvictionPolicy: unhealthyPolicyPtr(policyv1.AlwaysAllow),
isPodReady: false,
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.PDBUnhealthyPodEvictionPolicy, tc.enableUnhealthyPodEvictionPolicy)()
closeFn, rm, informers, _, clientSet := rmSetup(t)
defer closeFn()
ns := framework.CreateNamespaceOrDie(clientSet, "eviction-with-pdb-pod-healthy-policy", t)
defer framework.DeleteNamespaceOrDie(clientSet, ns, t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
informers.Start(ctx.Done())
go rm.Run(ctx)
pod := newPod("pod")
if _, err := clientSet.CoreV1().Pods(ns.Name).Create(context.TODO(), pod, metav1.CreateOptions{}); err != nil {
t.Errorf("Failed to create pod: %v", err)
}
pod.Status.Phase = v1.PodRunning
if tc.isPodReady {
addPodConditionReady(pod)
}
if _, err := clientSet.CoreV1().Pods(ns.Name).UpdateStatus(ctx, pod, metav1.UpdateOptions{}); err != nil {
t.Fatal(err)
}
waitToObservePods(t, informers.Core().V1().Pods().Informer(), 1, v1.PodRunning)
pdb := newPDB()
pdb.Spec.UnhealthyPodEvictionPolicy = tc.unhealthyPodEvictionPolicy
if _, err := clientSet.PolicyV1().PodDisruptionBudgets(ns.Name).Create(context.TODO(), pdb, metav1.CreateOptions{}); err != nil {
t.Errorf("Failed to create PodDisruptionBudget: %v", err)
}
if tc.isPodReady {
waitPDBStable(t, clientSet, ns.Name, pdb.Name, 1)
} else {
waitPDB(t, clientSet, ns.Name, pdb.Name, func(pdb *policyv1.PodDisruptionBudget) bool {
return pdb.Status.ExpectedPods == 1
})
}
deleteOption := metav1.DeleteOptions{}
eviction := newV1Eviction(ns.Name, pod.Name, deleteOption)
err := clientSet.PolicyV1().Evictions(ns.Name).Evict(ctx, eviction)
if err != nil {
t.Fatalf("Eviction of pod failed %v", err)
}
waitToObservePods(t, informers.Core().V1().Pods().Informer(), 0, v1.PodRunning)
waitPDBStable(t, clientSet, ns.Name, pdb.Name, 0)
})
}
}
func newPod(podName string) *v1.Pod {
return &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
@ -533,17 +616,24 @@ func waitToObservePods(t *testing.T, podInformer cache.SharedIndexInformer, podN
}
}
func waitPDBStable(t *testing.T, clientSet clientset.Interface, podNum int32, ns, pdbName string) {
func waitPDBStable(t *testing.T, clientSet clientset.Interface, ns, pdbName string, podNum int32) {
waitPDB(t, clientSet, ns, pdbName, func(pdb *policyv1.PodDisruptionBudget) bool {
return pdb.Status.CurrentHealthy == podNum
})
}
func waitPDB(t *testing.T, clientSet clientset.Interface, ns, pdbName string, condition func(budget *policyv1.PodDisruptionBudget) bool) {
if err := wait.PollImmediate(2*time.Second, 60*time.Second, func() (bool, error) {
pdb, err := clientSet.PolicyV1().PodDisruptionBudgets(ns).Get(context.TODO(), pdbName, metav1.GetOptions{})
if err != nil {
return false, err
}
if pdb.Status.CurrentHealthy != podNum {
return false, nil
}
return true, nil
return condition(pdb), nil
}); err != nil {
t.Fatal(err)
}
}
func unhealthyPolicyPtr(unhealthyPodEvictionPolicy policyv1.UnhealthyPodEvictionPolicyType) *policyv1.UnhealthyPodEvictionPolicyType {
return &unhealthyPodEvictionPolicy
}