k8s.io/kubernetes@v1.29.3/pkg/registry/policy/poddisruptionbudget/strategy_test.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package poddisruptionbudget 18 19 import ( 20 "reflect" 21 "testing" 22 23 "github.com/google/go-cmp/cmp" 24 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 "k8s.io/apimachinery/pkg/util/intstr" 27 genericapirequest "k8s.io/apiserver/pkg/endpoints/request" 28 utilfeature "k8s.io/apiserver/pkg/util/feature" 29 featuregatetesting "k8s.io/component-base/featuregate/testing" 30 "k8s.io/kubernetes/pkg/apis/policy" 31 "k8s.io/kubernetes/pkg/features" 32 ) 33 34 type unhealthyPodEvictionPolicyStrategyTestCase struct { 35 name string 36 enableUnhealthyPodEvictionPolicy bool 37 disablePDBUnhealthyPodEvictionPolicyFeatureGateAfterCreate bool 38 unhealthyPodEvictionPolicy *policy.UnhealthyPodEvictionPolicyType 39 expectedUnhealthyPodEvictionPolicy *policy.UnhealthyPodEvictionPolicyType 40 expectedValidationErr bool 41 updateUnhealthyPodEvictionPolicy *policy.UnhealthyPodEvictionPolicyType 42 expectedUpdateUnhealthyPodEvictionPolicy *policy.UnhealthyPodEvictionPolicyType 43 expectedValidationUpdateErr bool 44 } 45 46 func TestPodDisruptionBudgetStrategy(t *testing.T) { 47 tests := map[string]bool{ 48 "PodDisruptionBudget strategy with PDBUnhealthyPodEvictionPolicy feature gate disabled": false, 49 "PodDisruptionBudget strategy with PDBUnhealthyPodEvictionPolicy feature gate enabled": true, 50 } 51 52 for name, enableUnhealthyPodEvictionPolicy := range tests { 53 t.Run(name, func(t *testing.T) { 54 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PDBUnhealthyPodEvictionPolicy, enableUnhealthyPodEvictionPolicy)() 55 testPodDisruptionBudgetStrategy(t) 56 }) 57 } 58 59 healthyPolicyTests := []unhealthyPodEvictionPolicyStrategyTestCase{ 60 { 61 name: "PodDisruptionBudget strategy with FeatureGate disabled should remove unhealthyPodEvictionPolicy", 62 enableUnhealthyPodEvictionPolicy: false, 63 unhealthyPodEvictionPolicy: unhealthyPolicyPtr(policy.IfHealthyBudget), 64 updateUnhealthyPodEvictionPolicy: unhealthyPolicyPtr(policy.IfHealthyBudget), 65 }, 66 { 67 name: "PodDisruptionBudget strategy with FeatureGate disabled should remove invalid unhealthyPodEvictionPolicy", 68 enableUnhealthyPodEvictionPolicy: false, 69 unhealthyPodEvictionPolicy: unhealthyPolicyPtr("Invalid"), 70 updateUnhealthyPodEvictionPolicy: unhealthyPolicyPtr("Invalid"), 71 }, 72 { 73 name: "PodDisruptionBudget strategy with FeatureGate enabled", 74 enableUnhealthyPodEvictionPolicy: true, 75 }, 76 { 77 name: "PodDisruptionBudget strategy with FeatureGate enabled should respect unhealthyPodEvictionPolicy", 78 enableUnhealthyPodEvictionPolicy: true, 79 unhealthyPodEvictionPolicy: unhealthyPolicyPtr(policy.AlwaysAllow), 80 expectedUnhealthyPodEvictionPolicy: unhealthyPolicyPtr(policy.AlwaysAllow), 81 updateUnhealthyPodEvictionPolicy: unhealthyPolicyPtr(policy.IfHealthyBudget), 82 expectedUpdateUnhealthyPodEvictionPolicy: unhealthyPolicyPtr(policy.IfHealthyBudget), 83 }, 84 { 85 name: "PodDisruptionBudget strategy with FeatureGate enabled should fail invalid unhealthyPodEvictionPolicy", 86 enableUnhealthyPodEvictionPolicy: true, 87 unhealthyPodEvictionPolicy: unhealthyPolicyPtr("Invalid"), 88 expectedValidationErr: true, 89 }, 90 { 91 name: "PodDisruptionBudget strategy with FeatureGate enabled should fail invalid unhealthyPodEvictionPolicy when updated", 92 enableUnhealthyPodEvictionPolicy: true, 93 updateUnhealthyPodEvictionPolicy: unhealthyPolicyPtr("Invalid"), 94 expectedValidationUpdateErr: true, 95 }, 96 { 97 name: "PodDisruptionBudget strategy with unhealthyPodEvictionPolicy should be updated when feature gate is disabled", 98 enableUnhealthyPodEvictionPolicy: true, 99 disablePDBUnhealthyPodEvictionPolicyFeatureGateAfterCreate: true, 100 unhealthyPodEvictionPolicy: unhealthyPolicyPtr(policy.AlwaysAllow), 101 expectedUnhealthyPodEvictionPolicy: unhealthyPolicyPtr(policy.AlwaysAllow), 102 updateUnhealthyPodEvictionPolicy: unhealthyPolicyPtr(policy.IfHealthyBudget), 103 expectedUpdateUnhealthyPodEvictionPolicy: unhealthyPolicyPtr(policy.IfHealthyBudget), 104 }, 105 { 106 name: "PodDisruptionBudget strategy with unhealthyPodEvictionPolicy should not be updated to invalid when feature gate is disabled", 107 enableUnhealthyPodEvictionPolicy: true, 108 disablePDBUnhealthyPodEvictionPolicyFeatureGateAfterCreate: true, 109 unhealthyPodEvictionPolicy: unhealthyPolicyPtr(policy.AlwaysAllow), 110 expectedUnhealthyPodEvictionPolicy: unhealthyPolicyPtr(policy.AlwaysAllow), 111 updateUnhealthyPodEvictionPolicy: unhealthyPolicyPtr("Invalid"), 112 expectedValidationUpdateErr: true, 113 expectedUpdateUnhealthyPodEvictionPolicy: unhealthyPolicyPtr(policy.AlwaysAllow), 114 }, 115 } 116 117 for _, tc := range healthyPolicyTests { 118 t.Run(tc.name, func(t *testing.T) { 119 testPodDisruptionBudgetStrategyWithUnhealthyPodEvictionPolicy(t, tc) 120 }) 121 } 122 } 123 124 func testPodDisruptionBudgetStrategyWithUnhealthyPodEvictionPolicy(t *testing.T, tc unhealthyPodEvictionPolicyStrategyTestCase) { 125 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PDBUnhealthyPodEvictionPolicy, tc.enableUnhealthyPodEvictionPolicy)() 126 ctx := genericapirequest.NewDefaultContext() 127 if !Strategy.NamespaceScoped() { 128 t.Errorf("PodDisruptionBudget must be namespace scoped") 129 } 130 if Strategy.AllowCreateOnUpdate() { 131 t.Errorf("PodDisruptionBudget should not allow create on update") 132 } 133 134 validSelector := map[string]string{"a": "b"} 135 minAvailable := intstr.FromInt32(3) 136 pdb := &policy.PodDisruptionBudget{ 137 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 138 Spec: policy.PodDisruptionBudgetSpec{ 139 MinAvailable: &minAvailable, 140 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 141 UnhealthyPodEvictionPolicy: tc.unhealthyPodEvictionPolicy, 142 }, 143 } 144 145 Strategy.PrepareForCreate(ctx, pdb) 146 errs := Strategy.Validate(ctx, pdb) 147 if len(errs) != 0 { 148 if !tc.expectedValidationErr { 149 t.Errorf("Unexpected error validating %v", errs) 150 } 151 return // no point going further when we have invalid PDB 152 } 153 if len(errs) == 0 && tc.expectedValidationErr { 154 t.Errorf("Expected error validating") 155 } 156 if !reflect.DeepEqual(pdb.Spec.UnhealthyPodEvictionPolicy, tc.expectedUnhealthyPodEvictionPolicy) { 157 t.Errorf("Unexpected UnhealthyPodEvictionPolicy set: expected %v, got %v", tc.expectedUnhealthyPodEvictionPolicy, pdb.Spec.UnhealthyPodEvictionPolicy) 158 } 159 if tc.disablePDBUnhealthyPodEvictionPolicyFeatureGateAfterCreate { 160 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PDBUnhealthyPodEvictionPolicy, false)() 161 } 162 163 newPdb := &policy.PodDisruptionBudget{ 164 ObjectMeta: metav1.ObjectMeta{Name: pdb.Name, Namespace: pdb.Namespace}, 165 Spec: pdb.Spec, 166 } 167 if tc.updateUnhealthyPodEvictionPolicy != nil { 168 newPdb.Spec.UnhealthyPodEvictionPolicy = tc.updateUnhealthyPodEvictionPolicy 169 } 170 171 // Nothing in Spec changes: OK 172 Strategy.PrepareForUpdate(ctx, newPdb, pdb) 173 errs = Strategy.ValidateUpdate(ctx, newPdb, pdb) 174 175 if len(errs) != 0 { 176 if !tc.expectedValidationUpdateErr { 177 t.Errorf("Unexpected error updating PodDisruptionBudget %v", errs) 178 } 179 return // no point going further when we have invalid PDB 180 } 181 if len(errs) == 0 && tc.expectedValidationUpdateErr { 182 t.Errorf("Expected error updating PodDisruptionBudget") 183 } 184 if !reflect.DeepEqual(newPdb.Spec.UnhealthyPodEvictionPolicy, tc.expectedUpdateUnhealthyPodEvictionPolicy) { 185 t.Errorf("Unexpected UnhealthyPodEvictionPolicy set: expected %v, got %v", tc.expectedUpdateUnhealthyPodEvictionPolicy, newPdb.Spec.UnhealthyPodEvictionPolicy) 186 } 187 } 188 189 func testPodDisruptionBudgetStrategy(t *testing.T) { 190 ctx := genericapirequest.NewDefaultContext() 191 if !Strategy.NamespaceScoped() { 192 t.Errorf("PodDisruptionBudget must be namespace scoped") 193 } 194 if Strategy.AllowCreateOnUpdate() { 195 t.Errorf("PodDisruptionBudget should not allow create on update") 196 } 197 198 validSelector := map[string]string{"a": "b"} 199 minAvailable := intstr.FromInt32(3) 200 pdb := &policy.PodDisruptionBudget{ 201 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault}, 202 Spec: policy.PodDisruptionBudgetSpec{ 203 MinAvailable: &minAvailable, 204 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 205 }, 206 } 207 208 Strategy.PrepareForCreate(ctx, pdb) 209 errs := Strategy.Validate(ctx, pdb) 210 if len(errs) != 0 { 211 t.Errorf("Unexpected error validating %v", errs) 212 } 213 214 newPdb := &policy.PodDisruptionBudget{ 215 ObjectMeta: metav1.ObjectMeta{Name: pdb.Name, Namespace: pdb.Namespace}, 216 Spec: pdb.Spec, 217 Status: policy.PodDisruptionBudgetStatus{ 218 DisruptionsAllowed: 1, 219 CurrentHealthy: 3, 220 DesiredHealthy: 3, 221 ExpectedPods: 3, 222 }, 223 } 224 225 // Nothing in Spec changes: OK 226 Strategy.PrepareForUpdate(ctx, newPdb, pdb) 227 errs = Strategy.ValidateUpdate(ctx, newPdb, pdb) 228 if len(errs) != 0 { 229 t.Errorf("Unexpected error updating PodDisruptionBudget.") 230 } 231 232 // Changing the selector? OK 233 newPdb.Spec.Selector = &metav1.LabelSelector{MatchLabels: map[string]string{"a": "bar"}} 234 Strategy.PrepareForUpdate(ctx, newPdb, pdb) 235 errs = Strategy.ValidateUpdate(ctx, newPdb, pdb) 236 if len(errs) != 0 { 237 t.Errorf("Expected no error on changing selector on poddisruptionbudgets.") 238 } 239 newPdb.Spec.Selector = pdb.Spec.Selector 240 241 // Changing MinAvailable? OK 242 newMinAvailable := intstr.FromString("28%") 243 newPdb.Spec.MinAvailable = &newMinAvailable 244 Strategy.PrepareForUpdate(ctx, newPdb, pdb) 245 errs = Strategy.ValidateUpdate(ctx, newPdb, pdb) 246 if len(errs) != 0 { 247 t.Errorf("Expected no error updating MinAvailable on poddisruptionbudgets.") 248 } 249 250 // Changing MinAvailable to MaxAvailable? OK 251 maxUnavailable := intstr.FromString("28%") 252 newPdb.Spec.MaxUnavailable = &maxUnavailable 253 newPdb.Spec.MinAvailable = nil 254 Strategy.PrepareForUpdate(ctx, newPdb, pdb) 255 errs = Strategy.ValidateUpdate(ctx, newPdb, pdb) 256 if len(errs) != 0 { 257 t.Errorf("Expected no error updating replacing MinAvailable with MaxUnavailable on poddisruptionbudgets.") 258 } 259 } 260 261 func TestPodDisruptionBudgetStatusStrategy(t *testing.T) { 262 ctx := genericapirequest.NewDefaultContext() 263 if !StatusStrategy.NamespaceScoped() { 264 t.Errorf("PodDisruptionBudgetStatus must be namespace scoped") 265 } 266 if StatusStrategy.AllowCreateOnUpdate() { 267 t.Errorf("PodDisruptionBudgetStatus should not allow create on update") 268 } 269 270 oldMinAvailable := intstr.FromInt32(3) 271 newMinAvailable := intstr.FromInt32(2) 272 273 validSelector := map[string]string{"a": "b"} 274 oldPdb := &policy.PodDisruptionBudget{ 275 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault, ResourceVersion: "10"}, 276 Spec: policy.PodDisruptionBudgetSpec{ 277 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 278 MinAvailable: &oldMinAvailable, 279 }, 280 Status: policy.PodDisruptionBudgetStatus{ 281 DisruptionsAllowed: 1, 282 CurrentHealthy: 3, 283 DesiredHealthy: 3, 284 ExpectedPods: 3, 285 }, 286 } 287 newPdb := &policy.PodDisruptionBudget{ 288 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault, ResourceVersion: "9"}, 289 Spec: policy.PodDisruptionBudgetSpec{ 290 Selector: &metav1.LabelSelector{MatchLabels: validSelector}, 291 MinAvailable: &newMinAvailable, 292 }, 293 Status: policy.PodDisruptionBudgetStatus{ 294 DisruptionsAllowed: 0, 295 CurrentHealthy: 2, 296 DesiredHealthy: 3, 297 ExpectedPods: 3, 298 }, 299 } 300 StatusStrategy.PrepareForUpdate(ctx, newPdb, oldPdb) 301 if newPdb.Status.CurrentHealthy != 2 { 302 t.Errorf("PodDisruptionBudget status updates should allow change of CurrentHealthy: %v", newPdb.Status.CurrentHealthy) 303 } 304 if newPdb.Spec.MinAvailable.IntValue() != 3 { 305 t.Errorf("PodDisruptionBudget status updates should not clobber spec: %v", newPdb.Spec) 306 } 307 errs := StatusStrategy.ValidateUpdate(ctx, newPdb, oldPdb) 308 if len(errs) != 0 { 309 t.Errorf("Unexpected error %v", errs) 310 } 311 } 312 313 func TestPodDisruptionBudgetStatusValidationByApiVersion(t *testing.T) { 314 testCases := map[string]struct { 315 apiVersion string 316 validation bool 317 }{ 318 "policy/v1beta1 should not do update validation": { 319 apiVersion: "v1beta1", 320 validation: false, 321 }, 322 "policy/v1 should do update validation": { 323 apiVersion: "v1", 324 validation: true, 325 }, 326 "policy/some-version should do update validation": { 327 apiVersion: "some-version", 328 validation: true, 329 }, 330 } 331 332 for tn, tc := range testCases { 333 t.Run(tn, func(t *testing.T) { 334 ctx := genericapirequest.WithRequestInfo(genericapirequest.NewDefaultContext(), 335 &genericapirequest.RequestInfo{ 336 APIGroup: "policy", 337 APIVersion: tc.apiVersion, 338 }) 339 340 oldMaxUnavailable := intstr.FromInt32(2) 341 newMaxUnavailable := intstr.FromInt32(3) 342 oldPdb := &policy.PodDisruptionBudget{ 343 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault, ResourceVersion: "10"}, 344 Spec: policy.PodDisruptionBudgetSpec{ 345 Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}}, 346 MaxUnavailable: &oldMaxUnavailable, 347 }, 348 Status: policy.PodDisruptionBudgetStatus{ 349 DisruptionsAllowed: 1, 350 }, 351 } 352 newPdb := &policy.PodDisruptionBudget{ 353 ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: metav1.NamespaceDefault, ResourceVersion: "9"}, 354 Spec: policy.PodDisruptionBudgetSpec{ 355 Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}}, 356 MinAvailable: &newMaxUnavailable, 357 }, 358 Status: policy.PodDisruptionBudgetStatus{ 359 DisruptionsAllowed: -1, // This is not allowed, so should trigger validation error. 360 }, 361 } 362 363 errs := StatusStrategy.ValidateUpdate(ctx, newPdb, oldPdb) 364 hasErrors := len(errs) > 0 365 if !tc.validation && hasErrors { 366 t.Errorf("Validation failed when no validation should happen") 367 } 368 if tc.validation && !hasErrors { 369 t.Errorf("Expected validation errors but didn't get any") 370 } 371 }) 372 } 373 } 374 375 func TestDropDisabledFields(t *testing.T) { 376 tests := map[string]struct { 377 oldSpec *policy.PodDisruptionBudgetSpec 378 newSpec *policy.PodDisruptionBudgetSpec 379 expectNewSpec *policy.PodDisruptionBudgetSpec 380 enableUnhealthyPodEvictionPolicy bool 381 }{ 382 "disabled clears unhealthyPodEvictionPolicy": { 383 enableUnhealthyPodEvictionPolicy: false, 384 oldSpec: nil, 385 newSpec: specWithUnhealthyPodEvictionPolicy(unhealthyPolicyPtr(policy.IfHealthyBudget)), 386 expectNewSpec: specWithUnhealthyPodEvictionPolicy(nil), 387 }, 388 "disabled does not allow updating unhealthyPodEvictionPolicy": { 389 enableUnhealthyPodEvictionPolicy: false, 390 oldSpec: specWithUnhealthyPodEvictionPolicy(nil), 391 newSpec: specWithUnhealthyPodEvictionPolicy(unhealthyPolicyPtr(policy.IfHealthyBudget)), 392 expectNewSpec: specWithUnhealthyPodEvictionPolicy(nil), 393 }, 394 "disabled preserves old unhealthyPodEvictionPolicy when both old and new have it": { 395 enableUnhealthyPodEvictionPolicy: false, 396 oldSpec: specWithUnhealthyPodEvictionPolicy(unhealthyPolicyPtr(policy.IfHealthyBudget)), 397 newSpec: specWithUnhealthyPodEvictionPolicy(unhealthyPolicyPtr(policy.IfHealthyBudget)), 398 expectNewSpec: specWithUnhealthyPodEvictionPolicy(unhealthyPolicyPtr(policy.IfHealthyBudget)), 399 }, 400 "disabled allows updating unhealthyPodEvictionPolicy": { 401 enableUnhealthyPodEvictionPolicy: false, 402 oldSpec: specWithUnhealthyPodEvictionPolicy(unhealthyPolicyPtr(policy.IfHealthyBudget)), 403 newSpec: specWithUnhealthyPodEvictionPolicy(unhealthyPolicyPtr(policy.AlwaysAllow)), 404 expectNewSpec: specWithUnhealthyPodEvictionPolicy(unhealthyPolicyPtr(policy.AlwaysAllow)), 405 }, 406 "enabled preserve unhealthyPodEvictionPolicy": { 407 enableUnhealthyPodEvictionPolicy: true, 408 oldSpec: nil, 409 newSpec: specWithUnhealthyPodEvictionPolicy(unhealthyPolicyPtr(policy.IfHealthyBudget)), 410 expectNewSpec: specWithUnhealthyPodEvictionPolicy(unhealthyPolicyPtr(policy.IfHealthyBudget)), 411 }, 412 "enabled allows updating unhealthyPodEvictionPolicy": { 413 enableUnhealthyPodEvictionPolicy: true, 414 oldSpec: specWithUnhealthyPodEvictionPolicy(nil), 415 newSpec: specWithUnhealthyPodEvictionPolicy(unhealthyPolicyPtr(policy.IfHealthyBudget)), 416 expectNewSpec: specWithUnhealthyPodEvictionPolicy(unhealthyPolicyPtr(policy.IfHealthyBudget)), 417 }, 418 "enabled preserve unhealthyPodEvictionPolicy when both old and new have it": { 419 enableUnhealthyPodEvictionPolicy: true, 420 oldSpec: specWithUnhealthyPodEvictionPolicy(unhealthyPolicyPtr(policy.IfHealthyBudget)), 421 newSpec: specWithUnhealthyPodEvictionPolicy(unhealthyPolicyPtr(policy.IfHealthyBudget)), 422 expectNewSpec: specWithUnhealthyPodEvictionPolicy(unhealthyPolicyPtr(policy.IfHealthyBudget)), 423 }, 424 "enabled updates unhealthyPodEvictionPolicy": { 425 enableUnhealthyPodEvictionPolicy: true, 426 oldSpec: specWithUnhealthyPodEvictionPolicy(unhealthyPolicyPtr(policy.IfHealthyBudget)), 427 newSpec: specWithUnhealthyPodEvictionPolicy(unhealthyPolicyPtr(policy.AlwaysAllow)), 428 expectNewSpec: specWithUnhealthyPodEvictionPolicy(unhealthyPolicyPtr(policy.AlwaysAllow)), 429 }, 430 } 431 432 for name, tc := range tests { 433 t.Run(name, func(t *testing.T) { 434 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PDBUnhealthyPodEvictionPolicy, tc.enableUnhealthyPodEvictionPolicy)() 435 436 oldSpecBefore := tc.oldSpec.DeepCopy() 437 dropDisabledFields(tc.newSpec, tc.oldSpec) 438 if !reflect.DeepEqual(tc.newSpec, tc.expectNewSpec) { 439 t.Error(cmp.Diff(tc.newSpec, tc.expectNewSpec)) 440 } 441 if !reflect.DeepEqual(tc.oldSpec, oldSpecBefore) { 442 t.Error(cmp.Diff(tc.oldSpec, oldSpecBefore)) 443 } 444 }) 445 } 446 } 447 448 func unhealthyPolicyPtr(unhealthyPodEvictionPolicy policy.UnhealthyPodEvictionPolicyType) *policy.UnhealthyPodEvictionPolicyType { 449 return &unhealthyPodEvictionPolicy 450 } 451 452 func specWithUnhealthyPodEvictionPolicy(unhealthyPodEvictionPolicy *policy.UnhealthyPodEvictionPolicyType) *policy.PodDisruptionBudgetSpec { 453 return &policy.PodDisruptionBudgetSpec{ 454 UnhealthyPodEvictionPolicy: unhealthyPodEvictionPolicy, 455 } 456 }