1
16
17 package storage
18
19 import (
20 "context"
21 "fmt"
22 "net/http"
23 "reflect"
24 "strings"
25 "sync"
26 "testing"
27
28 "github.com/google/go-cmp/cmp"
29 apiequality "k8s.io/apimachinery/pkg/api/equality"
30 apierrors "k8s.io/apimachinery/pkg/api/errors"
31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
32 "k8s.io/apimachinery/pkg/fields"
33 "k8s.io/apimachinery/pkg/labels"
34 "k8s.io/apimachinery/pkg/runtime"
35 "k8s.io/apimachinery/pkg/util/intstr"
36 genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
37 "k8s.io/apiserver/pkg/registry/generic"
38 genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing"
39 "k8s.io/apiserver/pkg/registry/rest"
40 storeerr "k8s.io/apiserver/pkg/storage/errors"
41 etcd3testing "k8s.io/apiserver/pkg/storage/etcd3/testing"
42 "k8s.io/kubernetes/pkg/apis/apps"
43 "k8s.io/kubernetes/pkg/apis/autoscaling"
44 api "k8s.io/kubernetes/pkg/apis/core"
45 "k8s.io/kubernetes/pkg/registry/registrytest"
46 )
47
48 const defaultReplicas = 100
49
50 func newStorage(t *testing.T) (*DeploymentStorage, *etcd3testing.EtcdTestServer) {
51 etcdStorage, server := registrytest.NewEtcdStorage(t, apps.GroupName)
52 restOptions := generic.RESTOptions{StorageConfig: etcdStorage, Decorator: generic.UndecoratedStorage, DeleteCollectionWorkers: 1, ResourcePrefix: "deployments"}
53 deploymentStorage, err := NewStorage(restOptions)
54 if err != nil {
55 t.Fatalf("unexpected error from REST storage: %v", err)
56 }
57 return &deploymentStorage, server
58 }
59
60 var namespace = "foo-namespace"
61 var name = "foo-deployment"
62
63 func validNewDeployment() *apps.Deployment {
64 return &apps.Deployment{
65 ObjectMeta: metav1.ObjectMeta{
66 Name: name,
67 Namespace: namespace,
68 },
69 Spec: apps.DeploymentSpec{
70 Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}},
71 Strategy: apps.DeploymentStrategy{
72 Type: apps.RollingUpdateDeploymentStrategyType,
73 RollingUpdate: &apps.RollingUpdateDeployment{
74 MaxSurge: intstr.FromInt32(1),
75 MaxUnavailable: intstr.FromInt32(1),
76 },
77 },
78 Template: api.PodTemplateSpec{
79 ObjectMeta: metav1.ObjectMeta{
80 Labels: map[string]string{"a": "b"},
81 },
82 Spec: api.PodSpec{
83 Containers: []api.Container{
84 {
85 Name: "test",
86 Image: "test_image",
87 ImagePullPolicy: api.PullIfNotPresent,
88 TerminationMessagePolicy: api.TerminationMessageReadFile,
89 },
90 },
91 RestartPolicy: api.RestartPolicyAlways,
92 DNSPolicy: api.DNSClusterFirst,
93 },
94 },
95 Replicas: 7,
96 },
97 Status: apps.DeploymentStatus{
98 Replicas: 5,
99 },
100 }
101 }
102
103 var validDeployment = *validNewDeployment()
104
105 func TestCreate(t *testing.T) {
106 storage, server := newStorage(t)
107 defer server.Terminate(t)
108 defer storage.Deployment.Store.DestroyFunc()
109 test := genericregistrytest.New(t, storage.Deployment.Store)
110 deployment := validNewDeployment()
111 deployment.ObjectMeta = metav1.ObjectMeta{}
112 test.TestCreate(
113
114 deployment,
115
116 &apps.Deployment{
117 Spec: apps.DeploymentSpec{
118 Selector: &metav1.LabelSelector{MatchLabels: map[string]string{}},
119 Template: validDeployment.Spec.Template,
120 },
121 },
122 )
123 }
124
125 func TestUpdate(t *testing.T) {
126 storage, server := newStorage(t)
127 defer server.Terminate(t)
128 defer storage.Deployment.Store.DestroyFunc()
129 test := genericregistrytest.New(t, storage.Deployment.Store)
130 test.TestUpdate(
131
132 validNewDeployment(),
133
134 func(obj runtime.Object) runtime.Object {
135 object := obj.(*apps.Deployment)
136 object.Spec.Template.Spec.NodeSelector = map[string]string{"c": "d"}
137 return object
138 },
139
140 func(obj runtime.Object) runtime.Object {
141 object := obj.(*apps.Deployment)
142 object.Name = ""
143 return object
144 },
145 func(obj runtime.Object) runtime.Object {
146 object := obj.(*apps.Deployment)
147 object.Spec.Template.Spec.RestartPolicy = api.RestartPolicyOnFailure
148 return object
149 },
150 func(obj runtime.Object) runtime.Object {
151 object := obj.(*apps.Deployment)
152 object.Spec.Selector = &metav1.LabelSelector{MatchLabels: map[string]string{}}
153 return object
154 },
155 )
156 }
157
158 func TestDelete(t *testing.T) {
159 storage, server := newStorage(t)
160 defer server.Terminate(t)
161 defer storage.Deployment.Store.DestroyFunc()
162 test := genericregistrytest.New(t, storage.Deployment.Store)
163 test.TestDelete(validNewDeployment())
164 }
165
166 func TestGet(t *testing.T) {
167 storage, server := newStorage(t)
168 defer server.Terminate(t)
169 defer storage.Deployment.Store.DestroyFunc()
170 test := genericregistrytest.New(t, storage.Deployment.Store)
171 test.TestGet(validNewDeployment())
172 }
173
174 func TestList(t *testing.T) {
175 storage, server := newStorage(t)
176 defer server.Terminate(t)
177 defer storage.Deployment.Store.DestroyFunc()
178 test := genericregistrytest.New(t, storage.Deployment.Store)
179 test.TestList(validNewDeployment())
180 }
181
182 func TestWatch(t *testing.T) {
183 storage, server := newStorage(t)
184 defer server.Terminate(t)
185 defer storage.Deployment.Store.DestroyFunc()
186 test := genericregistrytest.New(t, storage.Deployment.Store)
187 test.TestWatch(
188 validNewDeployment(),
189
190 []labels.Set{},
191
192 []labels.Set{
193 {"a": "c"},
194 {"foo": "bar"},
195 },
196
197 []fields.Set{
198 {"metadata.name": name},
199 },
200
201 []fields.Set{
202 {"metadata.name": "bar"},
203 {"name": name},
204 },
205 )
206 }
207
208 func TestScaleGet(t *testing.T) {
209 storage, server := newStorage(t)
210 defer server.Terminate(t)
211 defer storage.Deployment.Store.DestroyFunc()
212 var deployment apps.Deployment
213 ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), namespace)
214 key := "/deployments/" + namespace + "/" + name
215 if err := storage.Deployment.Storage.Create(ctx, key, &validDeployment, &deployment, 0, false); err != nil {
216 t.Fatalf("error setting new deployment (key: %s) %v: %v", key, validDeployment, err)
217 }
218
219 selector, err := metav1.LabelSelectorAsSelector(validDeployment.Spec.Selector)
220 if err != nil {
221 t.Fatal(err)
222 }
223 want := &autoscaling.Scale{
224 ObjectMeta: metav1.ObjectMeta{
225 Name: name,
226 Namespace: namespace,
227 UID: deployment.UID,
228 ResourceVersion: deployment.ResourceVersion,
229 CreationTimestamp: deployment.CreationTimestamp,
230 },
231 Spec: autoscaling.ScaleSpec{
232 Replicas: validDeployment.Spec.Replicas,
233 },
234 Status: autoscaling.ScaleStatus{
235 Replicas: validDeployment.Status.Replicas,
236 Selector: selector.String(),
237 },
238 }
239 obj, err := storage.Scale.Get(ctx, name, &metav1.GetOptions{})
240 if err != nil {
241 t.Fatalf("error fetching scale for %s: %v", name, err)
242 }
243 got := obj.(*autoscaling.Scale)
244 if !apiequality.Semantic.DeepEqual(want, got) {
245 t.Errorf("unexpected scale: %s", cmp.Diff(want, got))
246 }
247 }
248
249 func TestScaleUpdate(t *testing.T) {
250 storage, server := newStorage(t)
251 defer server.Terminate(t)
252 defer storage.Deployment.Store.DestroyFunc()
253 var deployment apps.Deployment
254 ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), namespace)
255 key := "/deployments/" + namespace + "/" + name
256 if err := storage.Deployment.Storage.Create(ctx, key, &validDeployment, &deployment, 0, false); err != nil {
257 t.Fatalf("error setting new deployment (key: %s) %v: %v", key, validDeployment, err)
258 }
259 replicas := int32(12)
260 update := autoscaling.Scale{
261 ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace},
262 Spec: autoscaling.ScaleSpec{
263 Replicas: replicas,
264 },
265 }
266
267 if _, _, err := storage.Scale.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}); err != nil {
268 t.Fatalf("error updating scale %v: %v", update, err)
269 }
270 obj, err := storage.Scale.Get(ctx, name, &metav1.GetOptions{})
271 if err != nil {
272 t.Fatalf("error fetching scale for %s: %v", name, err)
273 }
274 scale := obj.(*autoscaling.Scale)
275 if scale.Spec.Replicas != replicas {
276 t.Errorf("wrong replicas count expected: %d got: %d", replicas, deployment.Spec.Replicas)
277 }
278
279 update.ResourceVersion = deployment.ResourceVersion
280 update.Spec.Replicas = 15
281
282 if _, _, err = storage.Scale.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}); err != nil && !apierrors.IsConflict(err) {
283 t.Fatalf("unexpected error, expecting an update conflict but got %v", err)
284 }
285 }
286
287 func TestStatusUpdate(t *testing.T) {
288 storage, server := newStorage(t)
289 defer server.Terminate(t)
290 defer storage.Deployment.Store.DestroyFunc()
291 ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), namespace)
292 key := "/deployments/" + namespace + "/" + name
293 if err := storage.Deployment.Storage.Create(ctx, key, &validDeployment, nil, 0, false); err != nil {
294 t.Fatalf("unexpected error: %v", err)
295 }
296 update := apps.Deployment{
297 ObjectMeta: validDeployment.ObjectMeta,
298 Spec: apps.DeploymentSpec{
299 Replicas: defaultReplicas,
300 },
301 Status: apps.DeploymentStatus{
302 Replicas: defaultReplicas,
303 },
304 }
305
306 if _, _, err := storage.Status.Update(ctx, update.Name, rest.DefaultUpdatedObjectInfo(&update), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}); err != nil {
307 t.Fatalf("unexpected error: %v", err)
308 }
309 obj, err := storage.Deployment.Get(ctx, name, &metav1.GetOptions{})
310 if err != nil {
311 t.Fatalf("unexpected error: %v", err)
312 }
313
314 deployment := obj.(*apps.Deployment)
315 if deployment.Spec.Replicas != 7 {
316 t.Errorf("we expected .spec.replicas to not be updated but it was updated to %v", deployment.Spec.Replicas)
317 }
318 if deployment.Status.Replicas != defaultReplicas {
319 t.Errorf("we expected .status.replicas to be updated to %d but it was %v", defaultReplicas, deployment.Status.Replicas)
320 }
321 }
322
323 func TestEtcdCreateDeploymentRollback(t *testing.T) {
324 testCases := map[string]struct {
325 rollback apps.DeploymentRollback
326 errOK func(error) bool
327 }{
328 "normal": {
329 rollback: apps.DeploymentRollback{
330 Name: name,
331 UpdatedAnnotations: map[string]string{},
332 RollbackTo: apps.RollbackConfig{Revision: 1},
333 },
334 errOK: func(err error) bool { return err == nil },
335 },
336 "noAnnotation": {
337 rollback: apps.DeploymentRollback{
338 Name: name,
339 RollbackTo: apps.RollbackConfig{Revision: 1},
340 },
341 errOK: func(err error) bool { return err == nil },
342 },
343 "noName": {
344 rollback: apps.DeploymentRollback{
345 UpdatedAnnotations: map[string]string{},
346 RollbackTo: apps.RollbackConfig{Revision: 1},
347 },
348 errOK: func(err error) bool { return err != nil },
349 },
350 }
351 storage, server := newStorage(t)
352 defer server.Terminate(t)
353 defer storage.Deployment.Store.DestroyFunc()
354 for k, test := range testCases {
355 rollbackStorage := storage.Rollback
356 deployment := validNewDeployment()
357 deployment.Namespace = fmt.Sprintf("namespace-%s", strings.ToLower(k))
358 ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), deployment.Namespace)
359
360 if _, err := storage.Deployment.Create(ctx, deployment, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}); err != nil {
361 t.Fatalf("%s: unexpected error: %v", k, err)
362 }
363 rollbackRespStatus, err := rollbackStorage.Create(ctx, test.rollback.Name, &test.rollback, rest.ValidateAllObjectFunc, &metav1.CreateOptions{})
364 if !test.errOK(err) {
365 t.Errorf("%s: unexpected error: %v", k, err)
366 } else if err == nil {
367
368 status, ok := rollbackRespStatus.(*metav1.Status)
369 if !ok {
370 t.Errorf("%s: unexpected response format", k)
371 }
372 if status.Code != http.StatusOK || status.Status != metav1.StatusSuccess {
373 t.Errorf("%s: unexpected response, code: %d, status: %s", k, status.Code, status.Status)
374 }
375 d, err := storage.Deployment.Get(ctx, deployment.ObjectMeta.Name, &metav1.GetOptions{})
376 if err != nil {
377 t.Errorf("%s: unexpected error: %v", k, err)
378 } else if !reflect.DeepEqual(*d.(*apps.Deployment).Spec.RollbackTo, test.rollback.RollbackTo) {
379 t.Errorf("%s: expected: %v, got: %v", k, *d.(*apps.Deployment).Spec.RollbackTo, test.rollback.RollbackTo)
380 }
381 }
382 }
383 }
384
385 func TestCreateDeploymentRollbackValidation(t *testing.T) {
386 storage, server := newStorage(t)
387 rollbackStorage := storage.Rollback
388 rollback := apps.DeploymentRollback{
389 Name: name,
390 UpdatedAnnotations: map[string]string{},
391 RollbackTo: apps.RollbackConfig{Revision: 1},
392 }
393
394 ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), namespace)
395
396 if _, err := storage.Deployment.Create(ctx, validNewDeployment(), rest.ValidateAllObjectFunc, &metav1.CreateOptions{}); err != nil {
397 t.Fatalf("Unexpected error: %v", err)
398 }
399
400 validationError := fmt.Errorf("admission deny")
401 alwaysDenyValidationFunc := func(ctx context.Context, obj runtime.Object) error { return validationError }
402 _, err := rollbackStorage.Create(ctx, rollback.Name, &rollback, alwaysDenyValidationFunc, &metav1.CreateOptions{})
403
404 if err == nil || validationError != err {
405 t.Errorf("expected: %v, got: %v", validationError, err)
406 }
407
408 storage.Deployment.Store.DestroyFunc()
409 server.Terminate(t)
410 }
411
412
413
414 func TestEtcdCreateDeploymentRollbackNoDeployment(t *testing.T) {
415 storage, server := newStorage(t)
416 defer server.Terminate(t)
417 defer storage.Deployment.Store.DestroyFunc()
418 rollbackStorage := storage.Rollback
419 ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), namespace)
420
421 _, err := rollbackStorage.Create(ctx, name, &apps.DeploymentRollback{
422 Name: name,
423 UpdatedAnnotations: map[string]string{},
424 RollbackTo: apps.RollbackConfig{Revision: 1},
425 }, rest.ValidateAllObjectFunc, &metav1.CreateOptions{})
426 if err == nil {
427 t.Fatalf("Expected not-found-error but got nothing")
428 }
429 if !apierrors.IsNotFound(storeerr.InterpretGetError(err, apps.Resource("deployments"), name)) {
430 t.Fatalf("Unexpected error returned: %#v", err)
431 }
432
433 _, err = storage.Deployment.Get(ctx, name, &metav1.GetOptions{})
434 if err == nil {
435 t.Fatalf("Expected not-found-error but got nothing")
436 }
437 if !apierrors.IsNotFound(storeerr.InterpretGetError(err, apps.Resource("deployments"), name)) {
438 t.Fatalf("Unexpected error: %v", err)
439 }
440 }
441
442 func TestShortNames(t *testing.T) {
443 storage, server := newStorage(t)
444 defer server.Terminate(t)
445 defer storage.Deployment.Store.DestroyFunc()
446 expected := []string{"deploy"}
447 registrytest.AssertShortNames(t, storage.Deployment, expected)
448 }
449
450 func TestCategories(t *testing.T) {
451 storage, server := newStorage(t)
452 defer server.Terminate(t)
453 defer storage.Deployment.Store.DestroyFunc()
454 expected := []string{"all"}
455 registrytest.AssertCategories(t, storage.Deployment, expected)
456 }
457
458 func TestScalePatchErrors(t *testing.T) {
459 storage, server := newStorage(t)
460 defer server.Terminate(t)
461 validObj := validNewDeployment()
462 resourceStore := storage.Deployment.Store
463 scaleStore := storage.Scale
464
465 defer resourceStore.DestroyFunc()
466 ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), namespace)
467
468 {
469 applyNotFoundPatch := func() rest.TransformFunc {
470 return func(_ context.Context, _, currentObject runtime.Object) (objToUpdate runtime.Object, patchErr error) {
471 t.Errorf("notfound patch called")
472 return currentObject, nil
473 }
474 }
475 _, _, err := scaleStore.Update(ctx, "bad-name", rest.DefaultUpdatedObjectInfo(nil, applyNotFoundPatch()), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{})
476 if !apierrors.IsNotFound(err) {
477 t.Errorf("expected notfound, got %v", err)
478 }
479 }
480
481 if _, err := resourceStore.Create(ctx, validObj, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}); err != nil {
482 t.Errorf("Unexpected error: %v", err)
483 }
484
485 {
486 applyBadUIDPatch := func() rest.TransformFunc {
487 return func(_ context.Context, _, currentObject runtime.Object) (objToUpdate runtime.Object, patchErr error) {
488 currentObject.(*autoscaling.Scale).UID = "123"
489 return currentObject, nil
490 }
491 }
492 _, _, err := scaleStore.Update(ctx, name, rest.DefaultUpdatedObjectInfo(nil, applyBadUIDPatch()), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{})
493 if !apierrors.IsConflict(err) {
494 t.Errorf("expected conflict, got %v", err)
495 }
496 }
497
498 {
499 applyBadResourceVersionPatch := func() rest.TransformFunc {
500 return func(_ context.Context, _, currentObject runtime.Object) (objToUpdate runtime.Object, patchErr error) {
501 currentObject.(*autoscaling.Scale).ResourceVersion = "123"
502 return currentObject, nil
503 }
504 }
505 _, _, err := scaleStore.Update(ctx, name, rest.DefaultUpdatedObjectInfo(nil, applyBadResourceVersionPatch()), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{})
506 if !apierrors.IsConflict(err) {
507 t.Errorf("expected conflict, got %v", err)
508 }
509 }
510 }
511
512 func TestScalePatchConflicts(t *testing.T) {
513 storage, server := newStorage(t)
514 defer server.Terminate(t)
515 validObj := validNewDeployment()
516 resourceStore := storage.Deployment.Store
517 scaleStore := storage.Scale
518
519 defer resourceStore.DestroyFunc()
520 ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), namespace)
521 if _, err := resourceStore.Create(ctx, validObj, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}); err != nil {
522 t.Fatalf("Unexpected error: %v", err)
523 }
524 applyLabelPatch := func(labelName, labelValue string) rest.TransformFunc {
525 return func(_ context.Context, _, currentObject runtime.Object) (objToUpdate runtime.Object, patchErr error) {
526 currentObject.(metav1.Object).SetLabels(map[string]string{labelName: labelValue})
527 return currentObject, nil
528 }
529 }
530 stopCh := make(chan struct{})
531 wg := &sync.WaitGroup{}
532 wg.Add(1)
533 go func() {
534 defer wg.Done()
535
536 labelName := "timestamp"
537 for i := 0; ; i++ {
538 select {
539 case <-stopCh:
540 return
541 default:
542 expectedLabelValue := fmt.Sprint(i)
543 updated, _, err := resourceStore.Update(ctx, name, rest.DefaultUpdatedObjectInfo(nil, applyLabelPatch(labelName, fmt.Sprint(i))), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{})
544 if err != nil {
545 t.Errorf("error patching main resource: %v", err)
546 return
547 }
548 gotLabelValue := updated.(metav1.Object).GetLabels()[labelName]
549 if gotLabelValue != expectedLabelValue {
550 t.Errorf("wrong label value: expected: %s, got: %s", expectedLabelValue, gotLabelValue)
551 return
552 }
553 }
554 }
555 }()
556
557
558 applyReplicaPatch := func(replicas int) rest.TransformFunc {
559 return func(_ context.Context, _, currentObject runtime.Object) (objToUpdate runtime.Object, patchErr error) {
560 currentObject.(*autoscaling.Scale).Spec.Replicas = int32(replicas)
561 return currentObject, nil
562 }
563 }
564 for i := 0; i < 100; i++ {
565 result, _, err := scaleStore.Update(ctx, name, rest.DefaultUpdatedObjectInfo(nil, applyReplicaPatch(i)), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{})
566 if err != nil {
567 t.Fatalf("error patching scale: %v", err)
568 }
569 scale := result.(*autoscaling.Scale)
570 if scale.Spec.Replicas != int32(i) {
571 t.Errorf("wrong replicas count: expected: %d got: %d", i, scale.Spec.Replicas)
572 }
573 }
574 close(stopCh)
575 wg.Wait()
576 }
577
View as plain text