k8s.io/kubernetes@v1.29.3/pkg/registry/apps/deployment/strategy_test.go (about) 1 /* 2 Copyright 2015 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 deployment 18 19 import ( 20 "reflect" 21 "testing" 22 23 "k8s.io/apimachinery/pkg/api/resource" 24 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 "k8s.io/apimachinery/pkg/runtime" 26 "k8s.io/apimachinery/pkg/util/intstr" 27 "k8s.io/apimachinery/pkg/util/validation/field" 28 genericapirequest "k8s.io/apiserver/pkg/endpoints/request" 29 "k8s.io/kubernetes/pkg/apis/apps" 30 api "k8s.io/kubernetes/pkg/apis/core" 31 ) 32 33 const ( 34 fakeImageName = "fake-name" 35 fakeImage = "fakeimage" 36 deploymentName = "test-deployment" 37 namespace = "test-namespace" 38 ) 39 40 func TestStatusUpdates(t *testing.T) { 41 tests := []struct { 42 old runtime.Object 43 obj runtime.Object 44 expected runtime.Object 45 }{ 46 { 47 old: newDeployment(map[string]string{"test": "label"}, map[string]string{"test": "annotation"}), 48 obj: newDeployment(map[string]string{"test": "label", "sneaky": "label"}, map[string]string{"test": "annotation"}), 49 expected: newDeployment(map[string]string{"test": "label"}, map[string]string{"test": "annotation"}), 50 }, 51 { 52 old: newDeployment(map[string]string{"test": "label"}, map[string]string{"test": "annotation"}), 53 obj: newDeployment(map[string]string{"test": "label"}, map[string]string{"test": "annotation", "sneaky": "annotation"}), 54 expected: newDeployment(map[string]string{"test": "label"}, map[string]string{"test": "annotation", "sneaky": "annotation"}), 55 }, 56 } 57 58 for _, test := range tests { 59 deploymentStatusStrategy{}.PrepareForUpdate(genericapirequest.NewContext(), test.obj, test.old) 60 if !reflect.DeepEqual(test.expected, test.obj) { 61 t.Errorf("Unexpected object mismatch! Expected:\n%#v\ngot:\n%#v", test.expected, test.obj) 62 } 63 } 64 } 65 66 func newDeployment(labels, annotations map[string]string) *apps.Deployment { 67 return &apps.Deployment{ 68 ObjectMeta: metav1.ObjectMeta{ 69 Name: "test", 70 Labels: labels, 71 Annotations: annotations, 72 }, 73 Spec: apps.DeploymentSpec{ 74 Replicas: 1, 75 Strategy: apps.DeploymentStrategy{ 76 Type: apps.RecreateDeploymentStrategyType, 77 }, 78 Template: api.PodTemplateSpec{ 79 Spec: api.PodSpec{ 80 Containers: []api.Container{ 81 { 82 Name: "test", 83 Image: "test", 84 }, 85 }, 86 }, 87 }, 88 }, 89 } 90 } 91 92 func TestSelectorImmutability(t *testing.T) { 93 tests := []struct { 94 requestInfo genericapirequest.RequestInfo 95 oldSelectorLabels map[string]string 96 newSelectorLabels map[string]string 97 expectedErrorList field.ErrorList 98 }{ 99 { 100 genericapirequest.RequestInfo{ 101 APIGroup: "apps", 102 APIVersion: "v1beta2", 103 Resource: "deployments", 104 }, 105 map[string]string{"a": "b"}, 106 map[string]string{"c": "d"}, 107 field.ErrorList{ 108 &field.Error{ 109 Type: field.ErrorTypeInvalid, 110 Field: field.NewPath("spec").Child("selector").String(), 111 BadValue: &metav1.LabelSelector{ 112 MatchLabels: map[string]string{"c": "d"}, 113 MatchExpressions: []metav1.LabelSelectorRequirement{}, 114 }, 115 Detail: "field is immutable", 116 }, 117 }, 118 }, 119 { 120 genericapirequest.RequestInfo{ 121 APIGroup: "apps", 122 APIVersion: "v1beta1", 123 Resource: "deployments", 124 }, 125 map[string]string{"a": "b"}, 126 map[string]string{"c": "d"}, 127 field.ErrorList{}, 128 }, 129 { 130 genericapirequest.RequestInfo{ 131 APIGroup: "extensions", 132 APIVersion: "v1beta1", 133 }, 134 map[string]string{"a": "b"}, 135 map[string]string{"c": "d"}, 136 field.ErrorList{}, 137 }, 138 } 139 140 for _, test := range tests { 141 oldDeployment := newDeploymentWithSelectorLabels(test.oldSelectorLabels) 142 newDeployment := newDeploymentWithSelectorLabels(test.newSelectorLabels) 143 context := genericapirequest.NewContext() 144 context = genericapirequest.WithRequestInfo(context, &test.requestInfo) 145 errorList := deploymentStrategy{}.ValidateUpdate(context, newDeployment, oldDeployment) 146 if len(test.expectedErrorList) == 0 && len(errorList) == 0 { 147 continue 148 } 149 if !reflect.DeepEqual(test.expectedErrorList, errorList) { 150 t.Errorf("Unexpected error list, expected: %v, actual: %v", test.expectedErrorList, errorList) 151 } 152 } 153 } 154 155 func newDeploymentWithSelectorLabels(selectorLabels map[string]string) *apps.Deployment { 156 return &apps.Deployment{ 157 ObjectMeta: metav1.ObjectMeta{ 158 Name: deploymentName, 159 Namespace: namespace, 160 ResourceVersion: "1", 161 }, 162 Spec: apps.DeploymentSpec{ 163 Selector: &metav1.LabelSelector{ 164 MatchLabels: selectorLabels, 165 MatchExpressions: []metav1.LabelSelectorRequirement{}, 166 }, 167 Strategy: apps.DeploymentStrategy{ 168 Type: apps.RollingUpdateDeploymentStrategyType, 169 RollingUpdate: &apps.RollingUpdateDeployment{ 170 MaxSurge: intstr.FromInt32(1), 171 MaxUnavailable: intstr.FromInt32(1), 172 }, 173 }, 174 Template: api.PodTemplateSpec{ 175 ObjectMeta: metav1.ObjectMeta{ 176 Labels: selectorLabels, 177 }, 178 Spec: api.PodSpec{ 179 RestartPolicy: api.RestartPolicyAlways, 180 DNSPolicy: api.DNSDefault, 181 Containers: []api.Container{{Name: fakeImageName, Image: fakeImage, ImagePullPolicy: api.PullNever, TerminationMessagePolicy: api.TerminationMessageReadFile}}, 182 }, 183 }, 184 }, 185 } 186 } 187 188 func newDeploymentWithHugePageValue(resourceName api.ResourceName, value resource.Quantity) *apps.Deployment { 189 return &apps.Deployment{ 190 ObjectMeta: metav1.ObjectMeta{ 191 Name: deploymentName, 192 Namespace: namespace, 193 ResourceVersion: "1", 194 }, 195 Spec: apps.DeploymentSpec{ 196 Selector: &metav1.LabelSelector{ 197 MatchLabels: map[string]string{"foo": "bar"}, 198 MatchExpressions: []metav1.LabelSelectorRequirement{}, 199 }, 200 Strategy: apps.DeploymentStrategy{ 201 Type: apps.RollingUpdateDeploymentStrategyType, 202 RollingUpdate: &apps.RollingUpdateDeployment{ 203 MaxSurge: intstr.FromInt32(1), 204 MaxUnavailable: intstr.FromInt32(1), 205 }, 206 }, 207 Template: api.PodTemplateSpec{ 208 ObjectMeta: metav1.ObjectMeta{ 209 Namespace: "default", 210 Name: "foo", 211 Labels: map[string]string{"foo": "bar"}, 212 }, 213 Spec: api.PodSpec{ 214 RestartPolicy: api.RestartPolicyAlways, 215 DNSPolicy: api.DNSDefault, 216 Containers: []api.Container{{ 217 Name: fakeImageName, 218 Image: fakeImage, 219 ImagePullPolicy: api.PullNever, 220 TerminationMessagePolicy: api.TerminationMessageReadFile, 221 Resources: api.ResourceRequirements{ 222 Requests: api.ResourceList{ 223 api.ResourceName(api.ResourceCPU): resource.MustParse("10"), 224 api.ResourceName(resourceName): value, 225 }, 226 Limits: api.ResourceList{ 227 api.ResourceName(api.ResourceCPU): resource.MustParse("10"), 228 api.ResourceName(resourceName): value, 229 }, 230 }}, 231 }}, 232 }, 233 }, 234 } 235 } 236 237 func TestDeploymentStrategyValidate(t *testing.T) { 238 tests := []struct { 239 name string 240 deployment *apps.Deployment 241 }{ 242 { 243 name: "validation on a new deployment with indivisible hugepages values", 244 deployment: newDeploymentWithHugePageValue(api.ResourceHugePagesPrefix+"2Mi", resource.MustParse("2.1Mi")), 245 }, 246 } 247 248 for _, tc := range tests { 249 t.Run(tc.name, func(t *testing.T) { 250 if errs := Strategy.Validate(genericapirequest.NewContext(), tc.deployment); len(errs) == 0 { 251 t.Error("expected failure") 252 } 253 }) 254 } 255 } 256 257 func TestDeploymentStrategyValidateUpdate(t *testing.T) { 258 tests := []struct { 259 name string 260 newDeployment *apps.Deployment 261 oldDeployment *apps.Deployment 262 }{ 263 { 264 name: "validation on an existing deployment with indivisible hugepages values to a new deployment with indivisible hugepages values", 265 newDeployment: newDeploymentWithHugePageValue(api.ResourceHugePagesPrefix+"2Mi", resource.MustParse("2.1Mi")), 266 oldDeployment: newDeploymentWithHugePageValue(api.ResourceHugePagesPrefix+"1Gi", resource.MustParse("1.1Gi")), 267 }, 268 } 269 270 for _, tc := range tests { 271 t.Run(tc.name, func(t *testing.T) { 272 if errs := Strategy.ValidateUpdate(genericapirequest.NewContext(), tc.newDeployment, tc.oldDeployment); len(errs) != 0 { 273 t.Errorf("unexpected error:%v", errs) 274 } 275 }) 276 } 277 278 errTests := []struct { 279 name string 280 newDeployment *apps.Deployment 281 oldDeployment *apps.Deployment 282 }{ 283 { 284 name: "validation on an existing deployment with divisible hugepages values to a new deployment with indivisible hugepages values", 285 newDeployment: newDeploymentWithHugePageValue(api.ResourceHugePagesPrefix+"2Mi", resource.MustParse("2.1Mi")), 286 oldDeployment: newDeploymentWithHugePageValue(api.ResourceHugePagesPrefix+"1Gi", resource.MustParse("2Gi")), 287 }, 288 } 289 290 for _, tc := range errTests { 291 t.Run(tc.name, func(t *testing.T) { 292 if errs := Strategy.ValidateUpdate(genericapirequest.NewContext(), tc.newDeployment, tc.oldDeployment); len(errs) == 0 { 293 t.Error("expected failure") 294 } 295 }) 296 } 297 }