1
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 }
298
View as plain text