k8s.io/kubernetes@v1.29.3/pkg/apis/apps/v1beta1/defaults_test.go (about) 1 /* 2 Copyright 2017 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 v1beta1_test 18 19 import ( 20 "reflect" 21 "testing" 22 23 appsv1beta1 "k8s.io/api/apps/v1beta1" 24 25 v1 "k8s.io/api/core/v1" 26 apiequality "k8s.io/apimachinery/pkg/api/equality" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/runtime" 29 "k8s.io/apimachinery/pkg/util/intstr" 30 utilfeature "k8s.io/apiserver/pkg/util/feature" 31 featuregatetesting "k8s.io/component-base/featuregate/testing" 32 "k8s.io/kubernetes/pkg/api/legacyscheme" 33 _ "k8s.io/kubernetes/pkg/apis/apps/install" 34 . "k8s.io/kubernetes/pkg/apis/apps/v1beta1" 35 _ "k8s.io/kubernetes/pkg/apis/core/install" 36 "k8s.io/kubernetes/pkg/features" 37 "k8s.io/utils/ptr" 38 ) 39 40 func TestSetDefaultDeployment(t *testing.T) { 41 defaultIntOrString := intstr.FromString("25%") 42 differentIntOrString := intstr.FromInt32(5) 43 period := int64(v1.DefaultTerminationGracePeriodSeconds) 44 defaultTemplate := v1.PodTemplateSpec{ 45 Spec: v1.PodSpec{ 46 DNSPolicy: v1.DNSClusterFirst, 47 RestartPolicy: v1.RestartPolicyAlways, 48 SecurityContext: &v1.PodSecurityContext{}, 49 TerminationGracePeriodSeconds: &period, 50 SchedulerName: v1.DefaultSchedulerName, 51 }, 52 } 53 tests := []struct { 54 original *appsv1beta1.Deployment 55 expected *appsv1beta1.Deployment 56 }{ 57 { 58 original: &appsv1beta1.Deployment{}, 59 expected: &appsv1beta1.Deployment{ 60 Spec: appsv1beta1.DeploymentSpec{ 61 Replicas: ptr.To[int32](1), 62 Strategy: appsv1beta1.DeploymentStrategy{ 63 Type: appsv1beta1.RollingUpdateDeploymentStrategyType, 64 RollingUpdate: &appsv1beta1.RollingUpdateDeployment{ 65 MaxSurge: &defaultIntOrString, 66 MaxUnavailable: &defaultIntOrString, 67 }, 68 }, 69 RevisionHistoryLimit: ptr.To[int32](2), 70 ProgressDeadlineSeconds: ptr.To[int32](600), 71 Template: defaultTemplate, 72 }, 73 }, 74 }, 75 { 76 original: &appsv1beta1.Deployment{ 77 Spec: appsv1beta1.DeploymentSpec{ 78 Replicas: ptr.To[int32](5), 79 Strategy: appsv1beta1.DeploymentStrategy{ 80 RollingUpdate: &appsv1beta1.RollingUpdateDeployment{ 81 MaxSurge: &differentIntOrString, 82 }, 83 }, 84 }, 85 }, 86 expected: &appsv1beta1.Deployment{ 87 Spec: appsv1beta1.DeploymentSpec{ 88 Replicas: ptr.To[int32](5), 89 Strategy: appsv1beta1.DeploymentStrategy{ 90 Type: appsv1beta1.RollingUpdateDeploymentStrategyType, 91 RollingUpdate: &appsv1beta1.RollingUpdateDeployment{ 92 MaxSurge: &differentIntOrString, 93 MaxUnavailable: &defaultIntOrString, 94 }, 95 }, 96 RevisionHistoryLimit: ptr.To[int32](2), 97 ProgressDeadlineSeconds: ptr.To[int32](600), 98 Template: defaultTemplate, 99 }, 100 }, 101 }, 102 { 103 original: &appsv1beta1.Deployment{ 104 Spec: appsv1beta1.DeploymentSpec{ 105 Replicas: ptr.To[int32](3), 106 Strategy: appsv1beta1.DeploymentStrategy{ 107 Type: appsv1beta1.RollingUpdateDeploymentStrategyType, 108 RollingUpdate: nil, 109 }, 110 }, 111 }, 112 expected: &appsv1beta1.Deployment{ 113 Spec: appsv1beta1.DeploymentSpec{ 114 Replicas: ptr.To[int32](3), 115 Strategy: appsv1beta1.DeploymentStrategy{ 116 Type: appsv1beta1.RollingUpdateDeploymentStrategyType, 117 RollingUpdate: &appsv1beta1.RollingUpdateDeployment{ 118 MaxSurge: &defaultIntOrString, 119 MaxUnavailable: &defaultIntOrString, 120 }, 121 }, 122 RevisionHistoryLimit: ptr.To[int32](2), 123 ProgressDeadlineSeconds: ptr.To[int32](600), 124 Template: defaultTemplate, 125 }, 126 }, 127 }, 128 { 129 original: &appsv1beta1.Deployment{ 130 Spec: appsv1beta1.DeploymentSpec{ 131 Replicas: ptr.To[int32](5), 132 Strategy: appsv1beta1.DeploymentStrategy{ 133 Type: appsv1beta1.RecreateDeploymentStrategyType, 134 }, 135 RevisionHistoryLimit: ptr.To[int32](0), 136 }, 137 }, 138 expected: &appsv1beta1.Deployment{ 139 Spec: appsv1beta1.DeploymentSpec{ 140 Replicas: ptr.To[int32](5), 141 Strategy: appsv1beta1.DeploymentStrategy{ 142 Type: appsv1beta1.RecreateDeploymentStrategyType, 143 }, 144 RevisionHistoryLimit: ptr.To[int32](0), 145 ProgressDeadlineSeconds: ptr.To[int32](600), 146 Template: defaultTemplate, 147 }, 148 }, 149 }, 150 { 151 original: &appsv1beta1.Deployment{ 152 Spec: appsv1beta1.DeploymentSpec{ 153 Replicas: ptr.To[int32](5), 154 Strategy: appsv1beta1.DeploymentStrategy{ 155 Type: appsv1beta1.RecreateDeploymentStrategyType, 156 }, 157 ProgressDeadlineSeconds: ptr.To[int32](30), 158 RevisionHistoryLimit: ptr.To[int32](2), 159 }, 160 }, 161 expected: &appsv1beta1.Deployment{ 162 Spec: appsv1beta1.DeploymentSpec{ 163 Replicas: ptr.To[int32](5), 164 Strategy: appsv1beta1.DeploymentStrategy{ 165 Type: appsv1beta1.RecreateDeploymentStrategyType, 166 }, 167 ProgressDeadlineSeconds: ptr.To[int32](30), 168 RevisionHistoryLimit: ptr.To[int32](2), 169 Template: defaultTemplate, 170 }, 171 }, 172 }, 173 } 174 175 for _, test := range tests { 176 original := test.original 177 expected := test.expected 178 obj2 := roundTrip(t, runtime.Object(original)) 179 got, ok := obj2.(*appsv1beta1.Deployment) 180 if !ok { 181 t.Errorf("unexpected object: %v", got) 182 t.FailNow() 183 } 184 if !apiequality.Semantic.DeepEqual(got.Spec, expected.Spec) { 185 t.Errorf("object mismatch!\nexpected:\n\t%+v\ngot:\n\t%+v", got.Spec, expected.Spec) 186 } 187 } 188 } 189 190 func TestDefaultDeploymentAvailability(t *testing.T) { 191 d := roundTrip(t, runtime.Object(&appsv1beta1.Deployment{})).(*appsv1beta1.Deployment) 192 193 maxUnavailable, err := intstr.GetScaledValueFromIntOrPercent(d.Spec.Strategy.RollingUpdate.MaxUnavailable, int(*(d.Spec.Replicas)), false) 194 if err != nil { 195 t.Fatalf("unexpected error: %v", err) 196 } 197 198 if *(d.Spec.Replicas)-int32(maxUnavailable) <= 0 { 199 t.Fatalf("the default value of maxUnavailable can lead to no active replicas during rolling update") 200 } 201 } 202 203 func TestSetDefaultStatefulSet(t *testing.T) { 204 defaultLabels := map[string]string{"foo": "bar"} 205 var defaultPartition int32 = 0 206 var notTheDefaultPartition int32 = 42 207 var defaultReplicas int32 = 1 208 209 period := int64(v1.DefaultTerminationGracePeriodSeconds) 210 defaultTemplate := v1.PodTemplateSpec{ 211 Spec: v1.PodSpec{ 212 DNSPolicy: v1.DNSClusterFirst, 213 RestartPolicy: v1.RestartPolicyAlways, 214 SecurityContext: &v1.PodSecurityContext{}, 215 TerminationGracePeriodSeconds: &period, 216 SchedulerName: v1.DefaultSchedulerName, 217 }, 218 ObjectMeta: metav1.ObjectMeta{ 219 Labels: defaultLabels, 220 }, 221 } 222 223 tests := []struct { 224 name string 225 original *appsv1beta1.StatefulSet 226 expected *appsv1beta1.StatefulSet 227 enableMaxUnavailablePolicy bool 228 enableStatefulSetAutoDelete bool 229 }{ 230 { 231 name: "labels and default update strategy", 232 original: &appsv1beta1.StatefulSet{ 233 Spec: appsv1beta1.StatefulSetSpec{ 234 Template: defaultTemplate, 235 }, 236 }, 237 expected: &appsv1beta1.StatefulSet{ 238 Spec: appsv1beta1.StatefulSetSpec{ 239 Replicas: &defaultReplicas, 240 MinReadySeconds: int32(0), 241 Template: defaultTemplate, 242 PodManagementPolicy: appsv1beta1.OrderedReadyPodManagement, 243 UpdateStrategy: appsv1beta1.StatefulSetUpdateStrategy{ 244 Type: appsv1beta1.OnDeleteStatefulSetStrategyType, 245 RollingUpdate: nil, 246 }, 247 RevisionHistoryLimit: ptr.To[int32](10), 248 Selector: &metav1.LabelSelector{ 249 MatchLabels: map[string]string{"foo": "bar"}, 250 MatchExpressions: []metav1.LabelSelectorRequirement{}, 251 }, 252 }, 253 }, 254 }, 255 { 256 name: "Alternate update strategy", 257 original: &appsv1beta1.StatefulSet{ 258 Spec: appsv1beta1.StatefulSetSpec{ 259 Template: defaultTemplate, 260 UpdateStrategy: appsv1beta1.StatefulSetUpdateStrategy{ 261 Type: appsv1beta1.RollingUpdateStatefulSetStrategyType, 262 }, 263 }, 264 }, 265 expected: &appsv1beta1.StatefulSet{ 266 Spec: appsv1beta1.StatefulSetSpec{ 267 Replicas: &defaultReplicas, 268 MinReadySeconds: int32(0), 269 Template: defaultTemplate, 270 PodManagementPolicy: appsv1beta1.OrderedReadyPodManagement, 271 UpdateStrategy: appsv1beta1.StatefulSetUpdateStrategy{ 272 Type: appsv1beta1.RollingUpdateStatefulSetStrategyType, 273 RollingUpdate: nil, 274 }, 275 RevisionHistoryLimit: ptr.To[int32](10), 276 Selector: &metav1.LabelSelector{ 277 MatchLabels: map[string]string{"foo": "bar"}, 278 MatchExpressions: []metav1.LabelSelectorRequirement{}, 279 }, 280 }, 281 }, 282 }, 283 { 284 name: "Parallel pod management policy.", 285 original: &appsv1beta1.StatefulSet{ 286 Spec: appsv1beta1.StatefulSetSpec{ 287 Template: defaultTemplate, 288 PodManagementPolicy: appsv1beta1.ParallelPodManagement, 289 }, 290 }, 291 expected: &appsv1beta1.StatefulSet{ 292 Spec: appsv1beta1.StatefulSetSpec{ 293 Replicas: &defaultReplicas, 294 MinReadySeconds: int32(0), 295 Template: defaultTemplate, 296 PodManagementPolicy: appsv1beta1.ParallelPodManagement, 297 UpdateStrategy: appsv1beta1.StatefulSetUpdateStrategy{ 298 Type: appsv1beta1.OnDeleteStatefulSetStrategyType, 299 RollingUpdate: nil, 300 }, 301 RevisionHistoryLimit: ptr.To[int32](10), 302 Selector: &metav1.LabelSelector{ 303 MatchLabels: map[string]string{"foo": "bar"}, 304 MatchExpressions: []metav1.LabelSelectorRequirement{}, 305 }, 306 }, 307 }, 308 }, 309 { 310 name: "MaxUnavailable disabled, with maxUnavailable not specified", 311 original: &appsv1beta1.StatefulSet{ 312 Spec: appsv1beta1.StatefulSetSpec{ 313 Template: defaultTemplate, 314 }, 315 }, 316 expected: &appsv1beta1.StatefulSet{ 317 Spec: appsv1beta1.StatefulSetSpec{ 318 Replicas: &defaultReplicas, 319 Template: defaultTemplate, 320 PodManagementPolicy: appsv1beta1.OrderedReadyPodManagement, 321 UpdateStrategy: appsv1beta1.StatefulSetUpdateStrategy{ 322 Type: appsv1beta1.OnDeleteStatefulSetStrategyType, 323 RollingUpdate: nil, 324 }, 325 RevisionHistoryLimit: ptr.To[int32](10), 326 Selector: &metav1.LabelSelector{ 327 MatchLabels: map[string]string{"foo": "bar"}, 328 MatchExpressions: []metav1.LabelSelectorRequirement{}, 329 }, 330 }, 331 }, 332 enableMaxUnavailablePolicy: false, 333 }, 334 { 335 name: "MaxUnavailable disabled, with default maxUnavailable specified", 336 original: &appsv1beta1.StatefulSet{ 337 Spec: appsv1beta1.StatefulSetSpec{ 338 Template: defaultTemplate, 339 UpdateStrategy: appsv1beta1.StatefulSetUpdateStrategy{ 340 RollingUpdate: &appsv1beta1.RollingUpdateStatefulSetStrategy{ 341 Partition: &defaultPartition, 342 MaxUnavailable: ptr.To(intstr.FromInt32(1)), 343 }, 344 }, 345 }, 346 }, 347 expected: &appsv1beta1.StatefulSet{ 348 Spec: appsv1beta1.StatefulSetSpec{ 349 Replicas: &defaultReplicas, 350 Template: defaultTemplate, 351 PodManagementPolicy: appsv1beta1.OrderedReadyPodManagement, 352 UpdateStrategy: appsv1beta1.StatefulSetUpdateStrategy{ 353 Type: appsv1beta1.OnDeleteStatefulSetStrategyType, 354 RollingUpdate: &appsv1beta1.RollingUpdateStatefulSetStrategy{ 355 Partition: ptr.To[int32](0), 356 MaxUnavailable: ptr.To(intstr.FromInt32(1)), 357 }, 358 }, 359 RevisionHistoryLimit: ptr.To[int32](10), 360 Selector: &metav1.LabelSelector{ 361 MatchLabels: map[string]string{"foo": "bar"}, 362 MatchExpressions: []metav1.LabelSelectorRequirement{}, 363 }, 364 }, 365 }, 366 enableMaxUnavailablePolicy: false, 367 }, 368 { 369 name: "MaxUnavailable disabled, with non default maxUnavailable specified", 370 original: &appsv1beta1.StatefulSet{ 371 Spec: appsv1beta1.StatefulSetSpec{ 372 Template: defaultTemplate, 373 UpdateStrategy: appsv1beta1.StatefulSetUpdateStrategy{ 374 RollingUpdate: &appsv1beta1.RollingUpdateStatefulSetStrategy{ 375 Partition: ¬TheDefaultPartition, 376 MaxUnavailable: ptr.To(intstr.FromInt32(3)), 377 }, 378 }, 379 }, 380 }, 381 expected: &appsv1beta1.StatefulSet{ 382 Spec: appsv1beta1.StatefulSetSpec{ 383 Replicas: &defaultReplicas, 384 Template: defaultTemplate, 385 PodManagementPolicy: appsv1beta1.OrderedReadyPodManagement, 386 UpdateStrategy: appsv1beta1.StatefulSetUpdateStrategy{ 387 Type: appsv1beta1.OnDeleteStatefulSetStrategyType, 388 RollingUpdate: &appsv1beta1.RollingUpdateStatefulSetStrategy{ 389 Partition: ptr.To[int32](42), 390 MaxUnavailable: ptr.To(intstr.FromInt32(3)), 391 }, 392 }, 393 RevisionHistoryLimit: ptr.To[int32](10), 394 Selector: &metav1.LabelSelector{ 395 MatchLabels: map[string]string{"foo": "bar"}, 396 MatchExpressions: []metav1.LabelSelectorRequirement{}, 397 }, 398 }, 399 }, 400 enableMaxUnavailablePolicy: false, 401 }, 402 { 403 name: "MaxUnavailable enabled, with no maxUnavailable specified", 404 original: &appsv1beta1.StatefulSet{ 405 Spec: appsv1beta1.StatefulSetSpec{ 406 Template: defaultTemplate, 407 }, 408 }, 409 expected: &appsv1beta1.StatefulSet{ 410 Spec: appsv1beta1.StatefulSetSpec{ 411 Replicas: &defaultReplicas, 412 Template: defaultTemplate, 413 PodManagementPolicy: appsv1beta1.OrderedReadyPodManagement, 414 UpdateStrategy: appsv1beta1.StatefulSetUpdateStrategy{ 415 Type: appsv1beta1.OnDeleteStatefulSetStrategyType, 416 RollingUpdate: nil, 417 }, 418 RevisionHistoryLimit: ptr.To[int32](10), 419 Selector: &metav1.LabelSelector{ 420 MatchLabels: map[string]string{"foo": "bar"}, 421 MatchExpressions: []metav1.LabelSelectorRequirement{}, 422 }, 423 }, 424 }, 425 enableMaxUnavailablePolicy: true, 426 }, 427 { 428 name: "MaxUnavailable enabled, with non default maxUnavailable specified", 429 original: &appsv1beta1.StatefulSet{ 430 Spec: appsv1beta1.StatefulSetSpec{ 431 Template: defaultTemplate, 432 UpdateStrategy: appsv1beta1.StatefulSetUpdateStrategy{ 433 RollingUpdate: &appsv1beta1.RollingUpdateStatefulSetStrategy{ 434 Partition: ¬TheDefaultPartition, 435 MaxUnavailable: ptr.To(intstr.FromInt32(3)), 436 }, 437 }, 438 }, 439 }, 440 expected: &appsv1beta1.StatefulSet{ 441 Spec: appsv1beta1.StatefulSetSpec{ 442 Replicas: &defaultReplicas, 443 Template: defaultTemplate, 444 PodManagementPolicy: appsv1beta1.OrderedReadyPodManagement, 445 UpdateStrategy: appsv1beta1.StatefulSetUpdateStrategy{ 446 Type: appsv1beta1.OnDeleteStatefulSetStrategyType, 447 RollingUpdate: &appsv1beta1.RollingUpdateStatefulSetStrategy{ 448 Partition: ptr.To[int32](42), 449 MaxUnavailable: ptr.To(intstr.FromInt32(3)), 450 }, 451 }, 452 RevisionHistoryLimit: ptr.To[int32](10), 453 Selector: &metav1.LabelSelector{ 454 MatchLabels: map[string]string{"foo": "bar"}, 455 MatchExpressions: []metav1.LabelSelectorRequirement{}, 456 }, 457 }, 458 }, 459 enableMaxUnavailablePolicy: true, 460 }, 461 { 462 name: "StatefulSetAutoDeletePVC enabled", 463 original: &appsv1beta1.StatefulSet{ 464 Spec: appsv1beta1.StatefulSetSpec{ 465 Template: defaultTemplate, 466 }, 467 }, 468 expected: &appsv1beta1.StatefulSet{ 469 Spec: appsv1beta1.StatefulSetSpec{ 470 Replicas: &defaultReplicas, 471 Template: defaultTemplate, 472 PodManagementPolicy: appsv1beta1.OrderedReadyPodManagement, 473 UpdateStrategy: appsv1beta1.StatefulSetUpdateStrategy{ 474 Type: appsv1beta1.OnDeleteStatefulSetStrategyType, 475 RollingUpdate: nil, 476 }, 477 RevisionHistoryLimit: ptr.To[int32](10), 478 PersistentVolumeClaimRetentionPolicy: &appsv1beta1.StatefulSetPersistentVolumeClaimRetentionPolicy{ 479 WhenDeleted: appsv1beta1.RetainPersistentVolumeClaimRetentionPolicyType, 480 WhenScaled: appsv1beta1.RetainPersistentVolumeClaimRetentionPolicyType, 481 }, 482 Selector: &metav1.LabelSelector{ 483 MatchLabels: map[string]string{"foo": "bar"}, 484 MatchExpressions: []metav1.LabelSelectorRequirement{}, 485 }, 486 }, 487 }, 488 enableStatefulSetAutoDelete: true, 489 }, 490 } 491 492 for _, test := range tests { 493 test := test 494 t.Run(test.name, func(t *testing.T) { 495 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.MaxUnavailableStatefulSet, test.enableMaxUnavailablePolicy)() 496 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StatefulSetAutoDeletePVC, test.enableStatefulSetAutoDelete)() 497 obj2 := roundTrip(t, runtime.Object(test.original)) 498 got, ok := obj2.(*appsv1beta1.StatefulSet) 499 if !ok { 500 t.Errorf("unexpected object: %v", got) 501 t.FailNow() 502 } 503 if !apiequality.Semantic.DeepEqual(got.Spec, test.expected.Spec) { 504 t.Errorf("got different than expected\ngot:\n\t%+v\nexpected:\n\t%+v", got.Spec, test.expected.Spec) 505 } 506 }) 507 } 508 } 509 510 func roundTrip(t *testing.T, obj runtime.Object) runtime.Object { 511 data, err := runtime.Encode(legacyscheme.Codecs.LegacyCodec(SchemeGroupVersion), obj) 512 if err != nil { 513 t.Errorf("%v\n %#v", err, obj) 514 return nil 515 } 516 obj2, err := runtime.Decode(legacyscheme.Codecs.UniversalDecoder(), data) 517 if err != nil { 518 t.Errorf("%v\nData: %s\nSource: %#v", err, string(data), obj) 519 return nil 520 } 521 obj3 := reflect.New(reflect.TypeOf(obj).Elem()).Interface().(runtime.Object) 522 err = legacyscheme.Scheme.Convert(obj2, obj3, nil) 523 if err != nil { 524 t.Errorf("%v\nSource: %#v", err, obj2) 525 return nil 526 } 527 return obj3 528 }