1
16
17 package apiserver
18
19 import (
20 "context"
21 "encoding/json"
22 "fmt"
23 "path"
24 "strings"
25 "testing"
26
27 apierrors "k8s.io/apimachinery/pkg/api/errors"
28 "k8s.io/apimachinery/pkg/api/meta"
29 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
30 "k8s.io/apimachinery/pkg/runtime/schema"
31 "k8s.io/apimachinery/pkg/types"
32 "k8s.io/apimachinery/pkg/util/managedfields"
33 clientset "k8s.io/client-go/kubernetes"
34 "k8s.io/client-go/kubernetes/scheme"
35 deploymentstorage "k8s.io/kubernetes/pkg/registry/apps/deployment/storage"
36 replicasetstorage "k8s.io/kubernetes/pkg/registry/apps/replicaset/storage"
37 statefulsetstorage "k8s.io/kubernetes/pkg/registry/apps/statefulset/storage"
38 replicationcontrollerstorage "k8s.io/kubernetes/pkg/registry/core/replicationcontroller/storage"
39 )
40
41 type scaleTest struct {
42 kind string
43 resource string
44 path string
45 validObj string
46 }
47
48 func TestScaleAllResources(t *testing.T) {
49 client, closeFn := setup(t)
50 defer closeFn()
51
52 tests := []scaleTest{
53 {
54 kind: "Deployment",
55 resource: "deployments",
56 path: "/apis/apps/v1",
57 validObj: validAppsV1("Deployment"),
58 },
59 {
60 kind: "StatefulSet",
61 resource: "statefulsets",
62 path: "/apis/apps/v1",
63 validObj: validAppsV1("StatefulSet"),
64 },
65 {
66 kind: "ReplicaSet",
67 resource: "replicasets",
68 path: "/apis/apps/v1",
69 validObj: validAppsV1("ReplicaSet"),
70 },
71 {
72 kind: "ReplicationController",
73 resource: "replicationcontrollers",
74 path: "/api/v1",
75 validObj: validV1ReplicationController(),
76 },
77 }
78
79 for _, test := range tests {
80 t.Run(test.kind, func(t *testing.T) {
81 validObject := []byte(test.validObj)
82
83
84 _, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
85 AbsPath(test.path).
86 Namespace("default").
87 Resource(test.resource).
88 Name("test").
89 Param("fieldManager", "apply_test").
90 Body(validObject).
91 Do(context.TODO()).Get()
92 if err != nil {
93 t.Fatalf("Failed to create object using apply: %v", err)
94 }
95 obj := retrieveObject(t, client, test.path, test.resource)
96 assertReplicasValue(t, obj, 1)
97 assertReplicasOwnership(t, obj, "apply_test")
98
99
100 _, err = client.CoreV1().RESTClient().
101 Patch(types.MergePatchType).
102 AbsPath(test.path).
103 Namespace("default").
104 Resource(test.resource).
105 Name("test").
106 SubResource("scale").
107 Param("fieldManager", "scale_test").
108 Body([]byte(`{"spec":{"replicas": 5}}`)).
109 Do(context.TODO()).Get()
110 if err != nil {
111 t.Fatalf("Failed to scale object: %v", err)
112 }
113 obj = retrieveObject(t, client, test.path, test.resource)
114 assertReplicasValue(t, obj, 5)
115 assertReplicasOwnership(t, obj, "scale_test")
116
117
118 _, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
119 AbsPath(test.path).
120 Namespace("default").
121 Resource(test.resource).
122 Name("test").
123 Param("fieldManager", "apply_test").
124 Body(validObject).
125 Do(context.TODO()).Get()
126 if !apierrors.IsConflict(err) {
127 t.Fatalf("Expected conflict when re-applying the original object, but got: %v", err)
128 }
129
130
131 _, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
132 AbsPath(test.path).
133 Namespace("default").
134 Resource(test.resource).
135 Name("test").
136 Param("fieldManager", "apply_test").
137 Param("force", "true").
138 Body(validObject).
139 Do(context.TODO()).Get()
140 if err != nil {
141 t.Fatalf("Error force-updating: %v", err)
142 }
143 obj = retrieveObject(t, client, test.path, test.resource)
144 assertReplicasValue(t, obj, 1)
145 assertReplicasOwnership(t, obj, "apply_test")
146
147
148 _, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
149 AbsPath(test.path).
150 Namespace("default").
151 Resource(test.resource).
152 SubResource("scale").
153 Name("test").
154 Param("fieldManager", "apply_scale").
155 Body([]byte(`{"kind":"Scale","apiVersion":"autoscaling/v1","metadata":{"name":"test","namespace":"default"},"spec":{"replicas":17}}`)).
156 Do(context.TODO()).Get()
157 if !apierrors.IsConflict(err) {
158 t.Fatalf("Expected conflict error but got: %v", err)
159 }
160 if !strings.Contains(err.Error(), "apply_test") {
161 t.Fatalf("Expected conflict with `apply_test` manager when but got: %v", err)
162 }
163
164
165 _, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
166 AbsPath(test.path).
167 Namespace("default").
168 Resource(test.resource).
169 SubResource("scale").
170 Name("test").
171 Param("fieldManager", "apply_scale").
172 Param("force", "true").
173 Body([]byte(`{"kind":"Scale","apiVersion":"autoscaling/v1","metadata":{"name":"test","namespace":"default"},"spec":{"replicas":17}}`)).
174 Do(context.TODO()).Get()
175 if err != nil {
176 t.Fatalf("Error updating object by applying scale and forcing: %v ", err)
177 }
178 obj = retrieveObject(t, client, test.path, test.resource)
179 assertReplicasValue(t, obj, 17)
180 assertReplicasOwnership(t, obj, "apply_scale")
181
182
183 _, err = client.CoreV1().RESTClient().Put().
184 AbsPath(test.path).
185 Namespace("default").
186 Resource(test.resource).
187 SubResource("scale").
188 Name("test").
189 Param("fieldManager", "replace_test").
190 Body([]byte(`{"kind":"Scale","apiVersion":"autoscaling/v1","metadata":{"name":"test","namespace":"default"},"spec":{"replicas":7}}`)).
191 Do(context.TODO()).Get()
192 if err != nil {
193 t.Fatalf("Error replacing object: %v", err)
194 }
195 obj = retrieveObject(t, client, test.path, test.resource)
196 assertReplicasValue(t, obj, 7)
197 assertReplicasOwnership(t, obj, "replace_test")
198
199
200 _, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
201 AbsPath(test.path).
202 Namespace("default").
203 Resource(test.resource).
204 SubResource("scale").
205 Name("test").
206 Param("fieldManager", "co_owning_test").
207 Body([]byte(`{"kind":"Scale","apiVersion":"autoscaling/v1","metadata":{"name":"test","namespace":"default"},"spec":{"replicas":7}}`)).
208 Do(context.TODO()).Get()
209 if err != nil {
210 t.Fatalf("Error updating object: %v", err)
211 }
212 obj = retrieveObject(t, client, test.path, test.resource)
213 assertReplicasValue(t, obj, 7)
214 assertReplicasOwnership(t, obj, "replace_test", "co_owning_test")
215
216
217 _, err = client.CoreV1().RESTClient().Patch(types.MergePatchType).
218 AbsPath(test.path).
219 Namespace("default").
220 Resource(test.resource).
221 SubResource("scale").
222 Name("test").
223 Param("fieldManager", "scale_test").
224 Body([]byte(`{"spec":{"replicas": 5}}`)).
225 Do(context.TODO()).Get()
226 if err != nil {
227 t.Fatalf("Error scaling object: %v", err)
228 }
229 obj = retrieveObject(t, client, test.path, test.resource)
230 assertReplicasValue(t, obj, 5)
231 assertReplicasOwnership(t, obj, "scale_test")
232 })
233 }
234 }
235
236 func TestScaleUpdateOnlyStatus(t *testing.T) {
237 client, closeFn := setup(t)
238 defer closeFn()
239
240 resource := "deployments"
241 path := "/apis/apps/v1"
242 validObject := []byte(validAppsV1("Deployment"))
243
244
245 _, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
246 AbsPath(path).
247 Namespace("default").
248 Resource(resource).
249 Name("test").
250 Param("fieldManager", "apply_test").
251 Body(validObject).
252 Do(context.TODO()).Get()
253 if err != nil {
254 t.Fatalf("Failed to create object using apply: %v", err)
255 }
256 obj := retrieveObject(t, client, path, resource)
257 assertReplicasValue(t, obj, 1)
258 assertReplicasOwnership(t, obj, "apply_test")
259
260
261 _, err = client.CoreV1().RESTClient().
262 Patch(types.MergePatchType).
263 AbsPath(path).
264 Namespace("default").
265 Resource(resource).
266 Name("test").
267 SubResource("scale").
268 Param("fieldManager", "scale_test").
269 Body([]byte(`{"status":{"replicas": 42}}`)).
270 Do(context.TODO()).Get()
271 if err != nil {
272 t.Fatalf("Failed to scale object: %v", err)
273 }
274 obj = retrieveObject(t, client, path, resource)
275 assertReplicasValue(t, obj, 1)
276 assertReplicasOwnership(t, obj, "apply_test")
277 }
278
279 func TestAllKnownVersionsAreInMappings(t *testing.T) {
280 cases := []struct {
281 groupKind schema.GroupKind
282 mappings managedfields.ResourcePathMappings
283 }{
284 {
285 groupKind: schema.GroupKind{Group: "apps", Kind: "ReplicaSet"},
286 mappings: replicasetstorage.ReplicasPathMappings(),
287 },
288 {
289 groupKind: schema.GroupKind{Group: "apps", Kind: "StatefulSet"},
290 mappings: statefulsetstorage.ReplicasPathMappings(),
291 },
292 {
293 groupKind: schema.GroupKind{Group: "apps", Kind: "Deployment"},
294 mappings: deploymentstorage.ReplicasPathMappings(),
295 },
296 {
297 groupKind: schema.GroupKind{Group: "", Kind: "ReplicationController"},
298 mappings: replicationcontrollerstorage.ReplicasPathMappings(),
299 },
300 }
301 for _, c := range cases {
302 knownVersions := scheme.Scheme.VersionsForGroupKind(c.groupKind)
303 for _, version := range knownVersions {
304 if _, ok := c.mappings[version.String()]; !ok {
305 t.Errorf("missing version %v for %v mappings", version, c.groupKind)
306 }
307 }
308
309 if len(knownVersions) != len(c.mappings) {
310 t.Errorf("%v mappings has extra items: %v vs %v", c.groupKind, c.mappings, knownVersions)
311 }
312 }
313 }
314
315 func validAppsV1(kind string) string {
316 return fmt.Sprintf(`{
317 "apiVersion": "apps/v1",
318 "kind": "%s",
319 "metadata": {
320 "name": "test"
321 },
322 "spec": {
323 "replicas": 1,
324 "selector": {
325 "matchLabels": {
326 "app": "nginx"
327 }
328 },
329 "template": {
330 "metadata": {
331 "labels": {
332 "app": "nginx"
333 }
334 },
335 "spec": {
336 "containers": [{
337 "name": "nginx",
338 "image": "nginx:latest"
339 }]
340 }
341 }
342 }
343 }`, kind)
344 }
345
346 func validV1ReplicationController() string {
347 return `{
348 "apiVersion": "v1",
349 "kind": "ReplicationController",
350 "metadata": {
351 "name": "test"
352 },
353 "spec": {
354 "replicas": 1,
355 "selector": {
356 "app": "nginx"
357 },
358 "template": {
359 "metadata": {
360 "labels": {
361 "app": "nginx"
362 }
363 },
364 "spec": {
365 "containers": [{
366 "name": "nginx",
367 "image": "nginx:latest"
368 }]
369 }
370 }
371 }
372 }`
373 }
374
375 func retrieveObject(t *testing.T, client clientset.Interface, prefix, resource string) *unstructured.Unstructured {
376 t.Helper()
377
378 urlPath := path.Join(prefix, "namespaces", "default", resource, "test")
379 bytes, err := client.CoreV1().RESTClient().Get().AbsPath(urlPath).DoRaw(context.TODO())
380 if err != nil {
381 t.Fatalf("Failed to retrieve object: %v", err)
382 }
383 obj := &unstructured.Unstructured{}
384 if err := json.Unmarshal(bytes, obj); err != nil {
385 t.Fatalf("Error unmarshalling the retrieved object: %v", err)
386 }
387 return obj
388 }
389
390 func assertReplicasValue(t *testing.T, obj *unstructured.Unstructured, value int) {
391 actualValue, found, err := unstructured.NestedInt64(obj.Object, "spec", "replicas")
392
393 if err != nil {
394 t.Fatalf("Error when retrieving replicas field: %v", err)
395 }
396 if !found {
397 t.Fatalf("Replicas field not found")
398 }
399
400 if int(actualValue) != value {
401 t.Fatalf("Expected replicas field value to be %d but got %d", value, actualValue)
402 }
403 }
404
405 func assertReplicasOwnership(t *testing.T, obj *unstructured.Unstructured, fieldManagers ...string) {
406 t.Helper()
407
408 accessor, err := meta.Accessor(obj)
409 if err != nil {
410 t.Fatalf("Failed to get meta accessor for object: %v", err)
411 }
412
413 seen := make(map[string]bool)
414 for _, m := range fieldManagers {
415 seen[m] = false
416 }
417
418 for _, managedField := range accessor.GetManagedFields() {
419 var entryJSON map[string]interface{}
420 if err := json.Unmarshal(managedField.FieldsV1.Raw, &entryJSON); err != nil {
421 t.Fatalf("failed to read into json")
422 }
423
424 spec, ok := entryJSON["f:spec"].(map[string]interface{})
425 if !ok {
426
427 continue
428 }
429
430 if _, ok := spec["f:replicas"]; !ok {
431
432 continue
433 }
434
435
436 if _, ok := seen[managedField.Manager]; !ok {
437 t.Fatalf("Unexpected field manager, found %q, expected to be in: %v", managedField.Manager, seen)
438 }
439
440 seen[managedField.Manager] = true
441 }
442
443 var missingManagers []string
444 for manager, managerSeen := range seen {
445 if !managerSeen {
446 missingManagers = append(missingManagers, manager)
447 }
448 }
449 if len(missingManagers) > 0 {
450 t.Fatalf("replicas fields should be owned by %v", missingManagers)
451 }
452 }
453
View as plain text