k8s.io/kubernetes@v1.29.3/pkg/registry/core/pod/storage/storage_test.go (about) 1 /* 2 Copyright 2014 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 storage 18 19 import ( 20 "context" 21 goerrors "errors" 22 "fmt" 23 "net/url" 24 "strings" 25 "testing" 26 "time" 27 28 "github.com/google/go-cmp/cmp" 29 v1 "k8s.io/api/core/v1" 30 apiequality "k8s.io/apimachinery/pkg/api/equality" 31 "k8s.io/apimachinery/pkg/api/errors" 32 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 33 "k8s.io/apimachinery/pkg/fields" 34 "k8s.io/apimachinery/pkg/labels" 35 "k8s.io/apimachinery/pkg/runtime" 36 "k8s.io/apimachinery/pkg/types" 37 genericapirequest "k8s.io/apiserver/pkg/endpoints/request" 38 "k8s.io/apiserver/pkg/registry/generic" 39 genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" 40 genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing" 41 "k8s.io/apiserver/pkg/registry/rest" 42 apiserverstorage "k8s.io/apiserver/pkg/storage" 43 storeerr "k8s.io/apiserver/pkg/storage/errors" 44 etcd3testing "k8s.io/apiserver/pkg/storage/etcd3/testing" 45 utilfeature "k8s.io/apiserver/pkg/util/feature" 46 featuregatetesting "k8s.io/component-base/featuregate/testing" 47 api "k8s.io/kubernetes/pkg/apis/core" 48 "k8s.io/kubernetes/pkg/features" 49 "k8s.io/kubernetes/pkg/registry/registrytest" 50 "k8s.io/kubernetes/pkg/securitycontext" 51 ) 52 53 func newStorage(t *testing.T) (*REST, *BindingREST, *StatusREST, *etcd3testing.EtcdTestServer) { 54 etcdStorage, server := registrytest.NewEtcdStorage(t, "") 55 restOptions := generic.RESTOptions{ 56 StorageConfig: etcdStorage, 57 Decorator: generic.UndecoratedStorage, 58 DeleteCollectionWorkers: 3, 59 ResourcePrefix: "pods", 60 } 61 storage, err := NewStorage(restOptions, nil, nil, nil) 62 if err != nil { 63 t.Fatalf("unexpected error from REST storage: %v", err) 64 } 65 return storage.Pod, storage.Binding, storage.Status, server 66 } 67 68 func validNewPod() *api.Pod { 69 grace := int64(30) 70 enableServiceLinks := v1.DefaultEnableServiceLinks 71 return &api.Pod{ 72 ObjectMeta: metav1.ObjectMeta{ 73 Name: "foo", 74 Namespace: metav1.NamespaceDefault, 75 }, 76 Spec: api.PodSpec{ 77 RestartPolicy: api.RestartPolicyAlways, 78 DNSPolicy: api.DNSClusterFirst, 79 80 TerminationGracePeriodSeconds: &grace, 81 Containers: []api.Container{ 82 { 83 Name: "foo", 84 Image: "test", 85 ImagePullPolicy: api.PullAlways, 86 87 TerminationMessagePath: api.TerminationMessagePathDefault, 88 TerminationMessagePolicy: api.TerminationMessageReadFile, 89 SecurityContext: securitycontext.ValidInternalSecurityContextWithContainerDefaults(), 90 }, 91 }, 92 SecurityContext: &api.PodSecurityContext{}, 93 SchedulerName: v1.DefaultSchedulerName, 94 EnableServiceLinks: &enableServiceLinks, 95 }, 96 } 97 } 98 99 func validChangedPod() *api.Pod { 100 pod := validNewPod() 101 pod.Labels = map[string]string{ 102 "foo": "bar", 103 } 104 return pod 105 } 106 107 func TestCreate(t *testing.T) { 108 storage, _, _, server := newStorage(t) 109 defer server.Terminate(t) 110 defer storage.Store.DestroyFunc() 111 test := genericregistrytest.New(t, storage.Store) 112 pod := validNewPod() 113 pod.ObjectMeta = metav1.ObjectMeta{} 114 // Make an invalid pod with an incorrect label. 115 invalidPod := validNewPod() 116 invalidPod.Namespace = test.TestNamespace() 117 invalidPod.Labels = map[string]string{ 118 "invalid/label/to/cause/validation/failure": "bar", 119 } 120 test.TestCreate( 121 // valid 122 pod, 123 // invalid (empty contains list) 124 &api.Pod{ 125 Spec: api.PodSpec{ 126 Containers: []api.Container{}, 127 }, 128 }, 129 // invalid (invalid labels) 130 invalidPod, 131 ) 132 } 133 134 func TestUpdate(t *testing.T) { 135 storage, _, _, server := newStorage(t) 136 defer server.Terminate(t) 137 defer storage.Store.DestroyFunc() 138 test := genericregistrytest.New(t, storage.Store) 139 test.TestUpdate( 140 // valid 141 validNewPod(), 142 // updateFunc 143 func(obj runtime.Object) runtime.Object { 144 object := obj.(*api.Pod) 145 object.Labels = map[string]string{"a": "b"} 146 return object 147 }, 148 ) 149 } 150 151 func TestDelete(t *testing.T) { 152 storage, _, _, server := newStorage(t) 153 defer server.Terminate(t) 154 defer storage.Store.DestroyFunc() 155 test := genericregistrytest.New(t, storage.Store).ReturnDeletedObject() 156 test.TestDelete(validNewPod()) 157 158 scheduledPod := validNewPod() 159 scheduledPod.Spec.NodeName = "some-node" 160 test.TestDeleteGraceful(scheduledPod, 30) 161 } 162 163 type FailDeletionStorage struct { 164 apiserverstorage.Interface 165 Called *bool 166 } 167 168 func (f FailDeletionStorage) Delete(_ context.Context, key string, _ runtime.Object, _ *apiserverstorage.Preconditions, _ apiserverstorage.ValidateObjectFunc, _ runtime.Object) error { 169 *f.Called = true 170 return apiserverstorage.NewKeyNotFoundError(key, 0) 171 } 172 173 func newFailDeleteStorage(t *testing.T, called *bool) (*REST, *etcd3testing.EtcdTestServer) { 174 etcdStorage, server := registrytest.NewEtcdStorage(t, "") 175 restOptions := generic.RESTOptions{ 176 StorageConfig: etcdStorage, 177 Decorator: generic.UndecoratedStorage, 178 DeleteCollectionWorkers: 3, 179 ResourcePrefix: "pods", 180 } 181 storage, err := NewStorage(restOptions, nil, nil, nil) 182 if err != nil { 183 t.Fatalf("unexpected error from REST storage: %v", err) 184 } 185 storage.Pod.Store.Storage = genericregistry.DryRunnableStorage{Storage: FailDeletionStorage{storage.Pod.Store.Storage.Storage, called}} 186 return storage.Pod, server 187 } 188 189 func TestIgnoreDeleteNotFound(t *testing.T) { 190 pod := validNewPod() 191 testContext := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault) 192 called := false 193 registry, server := newFailDeleteStorage(t, &called) 194 defer server.Terminate(t) 195 defer registry.Store.DestroyFunc() 196 197 // should fail if pod A is not created yet. 198 _, _, err := registry.Delete(testContext, pod.Name, rest.ValidateAllObjectFunc, nil) 199 if !errors.IsNotFound(err) { 200 t.Errorf("Unexpected error: %v", err) 201 } 202 203 // create pod 204 _, err = registry.Create(testContext, pod, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}) 205 if err != nil { 206 t.Errorf("Unexpected error: %v", err) 207 } 208 209 // delete object with grace period 0, storage will return NotFound, but the 210 // registry shouldn't get any error since we ignore the NotFound error. 211 zero := int64(0) 212 opt := &metav1.DeleteOptions{GracePeriodSeconds: &zero} 213 obj, _, err := registry.Delete(testContext, pod.Name, rest.ValidateAllObjectFunc, opt) 214 if err != nil { 215 t.Fatalf("Unexpected error: %v", err) 216 } 217 218 if !called { 219 t.Fatalf("expect the overriding Delete method to be called") 220 } 221 deletedPod, ok := obj.(*api.Pod) 222 if !ok { 223 t.Fatalf("expect a pod is returned") 224 } 225 if deletedPod.DeletionTimestamp == nil { 226 t.Errorf("expect the DeletionTimestamp to be set") 227 } 228 if deletedPod.DeletionGracePeriodSeconds == nil { 229 t.Fatalf("expect the DeletionGracePeriodSeconds to be set") 230 } 231 if *deletedPod.DeletionGracePeriodSeconds != 0 { 232 t.Errorf("expect the DeletionGracePeriodSeconds to be 0, got %v", *deletedPod.DeletionTimestamp) 233 } 234 } 235 236 func TestCreateSetsFields(t *testing.T) { 237 storage, _, _, server := newStorage(t) 238 defer server.Terminate(t) 239 defer storage.Store.DestroyFunc() 240 pod := validNewPod() 241 _, err := storage.Create(genericapirequest.NewDefaultContext(), pod, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}) 242 if err != nil { 243 t.Fatalf("unexpected error: %v", err) 244 } 245 ctx := genericapirequest.NewDefaultContext() 246 object, err := storage.Get(ctx, "foo", &metav1.GetOptions{}) 247 if err != nil { 248 t.Errorf("unexpected error: %v", err) 249 } 250 actual := object.(*api.Pod) 251 if actual.Name != pod.Name { 252 t.Errorf("unexpected pod: %#v", actual) 253 } 254 if len(actual.UID) == 0 { 255 t.Errorf("expected pod UID to be set: %#v", actual) 256 } 257 } 258 259 func TestResourceLocation(t *testing.T) { 260 expectedIP := "1.2.3.4" 261 expectedIP6 := "fd00:10:244:0:2::6b" 262 testCases := []struct { 263 pod api.Pod 264 query string 265 location string 266 }{ 267 { 268 pod: api.Pod{ 269 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 270 Status: api.PodStatus{PodIPs: []api.PodIP{{IP: expectedIP}}}, 271 }, 272 query: "foo", 273 location: expectedIP, 274 }, 275 { 276 pod: api.Pod{ 277 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 278 Status: api.PodStatus{PodIPs: []api.PodIP{{IP: expectedIP}}}, 279 }, 280 query: "foo:12345", 281 location: expectedIP + ":12345", 282 }, 283 { 284 pod: api.Pod{ 285 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 286 Spec: api.PodSpec{ 287 Containers: []api.Container{ 288 {Name: "ctr"}, 289 }, 290 }, 291 Status: api.PodStatus{PodIPs: []api.PodIP{{IP: expectedIP}}}, 292 }, 293 query: "foo", 294 location: expectedIP, 295 }, 296 { 297 pod: api.Pod{ 298 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 299 Spec: api.PodSpec{ 300 Containers: []api.Container{ 301 {Name: "ctr"}, 302 }, 303 }, 304 Status: api.PodStatus{PodIPs: []api.PodIP{{IP: expectedIP6}}}, 305 }, 306 query: "foo", 307 location: "[" + expectedIP6 + "]", 308 }, 309 { 310 pod: api.Pod{ 311 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 312 Spec: api.PodSpec{ 313 Containers: []api.Container{ 314 {Name: "ctr", Ports: []api.ContainerPort{{ContainerPort: 9376}}}, 315 }, 316 }, 317 Status: api.PodStatus{PodIPs: []api.PodIP{{IP: expectedIP}}}, 318 }, 319 query: "foo", 320 location: expectedIP + ":9376", 321 }, 322 { 323 pod: api.Pod{ 324 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 325 Spec: api.PodSpec{ 326 Containers: []api.Container{ 327 {Name: "ctr", Ports: []api.ContainerPort{{ContainerPort: 9376}}}, 328 }, 329 }, 330 Status: api.PodStatus{PodIPs: []api.PodIP{{IP: expectedIP}}}, 331 }, 332 query: "foo:12345", 333 location: expectedIP + ":12345", 334 }, 335 { 336 pod: api.Pod{ 337 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 338 Spec: api.PodSpec{ 339 Containers: []api.Container{ 340 {Name: "ctr1"}, 341 {Name: "ctr2", Ports: []api.ContainerPort{{ContainerPort: 9376}}}, 342 }, 343 }, 344 Status: api.PodStatus{PodIPs: []api.PodIP{{IP: expectedIP}}}, 345 }, 346 query: "foo", 347 location: expectedIP + ":9376", 348 }, 349 { 350 pod: api.Pod{ 351 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 352 Spec: api.PodSpec{ 353 Containers: []api.Container{ 354 {Name: "ctr1", Ports: []api.ContainerPort{{ContainerPort: 9376}}}, 355 {Name: "ctr2", Ports: []api.ContainerPort{{ContainerPort: 1234}}}, 356 }, 357 }, 358 Status: api.PodStatus{PodIPs: []api.PodIP{{IP: expectedIP}}}, 359 }, 360 query: "foo", 361 location: expectedIP + ":9376", 362 }, 363 { 364 pod: api.Pod{ 365 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 366 Spec: api.PodSpec{ 367 Containers: []api.Container{ 368 {Name: "ctr1", Ports: []api.ContainerPort{{ContainerPort: 9376}}}, 369 {Name: "ctr2", Ports: []api.ContainerPort{{ContainerPort: 1234}}}, 370 }, 371 }, 372 Status: api.PodStatus{PodIPs: []api.PodIP{{IP: expectedIP}, {IP: expectedIP6}}}, 373 }, 374 query: "foo", 375 location: expectedIP + ":9376", 376 }, 377 { 378 pod: api.Pod{ 379 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 380 Spec: api.PodSpec{ 381 Containers: []api.Container{ 382 {Name: "ctr1", Ports: []api.ContainerPort{{ContainerPort: 9376}}}, 383 {Name: "ctr2", Ports: []api.ContainerPort{{ContainerPort: 1234}}}, 384 }, 385 }, 386 Status: api.PodStatus{PodIPs: []api.PodIP{{IP: expectedIP6}, {IP: expectedIP}}}, 387 }, 388 query: "foo", 389 location: "[" + expectedIP6 + "]:9376", 390 }, 391 } 392 393 storage, _, _, server := newStorage(t) 394 defer server.Terminate(t) 395 for i, tc := range testCases { 396 // unique namespace/storage location per test 397 ctx := genericapirequest.WithNamespace(genericapirequest.NewDefaultContext(), fmt.Sprintf("namespace-%d", i)) 398 key, _ := storage.KeyFunc(ctx, tc.pod.Name) 399 if err := storage.Storage.Create(ctx, key, &tc.pod, nil, 0, false); err != nil { 400 t.Fatalf("unexpected error: %v", err) 401 } 402 403 redirector := rest.Redirector(storage) 404 location, _, err := redirector.ResourceLocation(ctx, tc.query) 405 if err != nil { 406 t.Errorf("Unexpected error: %v", err) 407 } 408 if location == nil { 409 t.Errorf("Unexpected nil: %v", location) 410 } 411 412 if location.Scheme != "" { 413 t.Errorf("Expected '%v', but got '%v'", "", location.Scheme) 414 } 415 if location.Host != tc.location { 416 t.Errorf("Expected %v, but got %v", tc.location, location.Host) 417 } 418 if _, err := url.Parse(location.String()); err != nil { 419 t.Errorf("could not parse returned location %s: %v", location.String(), err) 420 } 421 422 } 423 } 424 425 func TestGet(t *testing.T) { 426 storage, _, _, server := newStorage(t) 427 defer server.Terminate(t) 428 defer storage.Store.DestroyFunc() 429 test := genericregistrytest.New(t, storage.Store) 430 test.TestGet(validNewPod()) 431 } 432 433 func TestList(t *testing.T) { 434 storage, _, _, server := newStorage(t) 435 defer server.Terminate(t) 436 defer storage.Store.DestroyFunc() 437 test := genericregistrytest.New(t, storage.Store) 438 test.TestList(validNewPod()) 439 } 440 441 func TestWatch(t *testing.T) { 442 storage, _, _, server := newStorage(t) 443 defer server.Terminate(t) 444 defer storage.Store.DestroyFunc() 445 test := genericregistrytest.New(t, storage.Store) 446 test.TestWatch( 447 validNewPod(), 448 // matching labels 449 []labels.Set{}, 450 // not matching labels 451 []labels.Set{ 452 {"foo": "bar"}, 453 }, 454 // matching fields 455 []fields.Set{ 456 {"metadata.name": "foo"}, 457 }, 458 // not matching fields 459 []fields.Set{ 460 {"metadata.name": "bar"}, 461 }, 462 ) 463 } 464 465 func TestConvertToTableList(t *testing.T) { 466 storage, _, _, server := newStorage(t) 467 defer server.Terminate(t) 468 defer storage.Store.DestroyFunc() 469 ctx := genericapirequest.NewDefaultContext() 470 471 columns := []metav1.TableColumnDefinition{ 472 {Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]}, 473 {Name: "Ready", Type: "string", Description: "The aggregate readiness state of this pod for accepting traffic."}, 474 {Name: "Status", Type: "string", Description: "The aggregate status of the containers in this pod."}, 475 {Name: "Restarts", Type: "string", Description: "The number of times the containers in this pod have been restarted and when the last container in this pod has restarted."}, 476 {Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]}, 477 {Name: "IP", Type: "string", Priority: 1, Description: v1.PodStatus{}.SwaggerDoc()["podIP"]}, 478 {Name: "Node", Type: "string", Priority: 1, Description: v1.PodSpec{}.SwaggerDoc()["nodeName"]}, 479 {Name: "Nominated Node", Type: "string", Priority: 1, Description: v1.PodStatus{}.SwaggerDoc()["nominatedNodeName"]}, 480 {Name: "Readiness Gates", Type: "string", Priority: 1, Description: v1.PodSpec{}.SwaggerDoc()["readinessGates"]}, 481 } 482 483 condition1 := "condition1" 484 condition2 := "condition2" 485 pod1 := &api.Pod{ 486 ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "foo", CreationTimestamp: metav1.NewTime(time.Now().Add(-370 * 24 * time.Hour))}, 487 Spec: api.PodSpec{ 488 Containers: []api.Container{ 489 {Name: "ctr1"}, 490 {Name: "ctr2", Ports: []api.ContainerPort{{ContainerPort: 9376}}}, 491 }, 492 NodeName: "test-node", 493 ReadinessGates: []api.PodReadinessGate{ 494 { 495 ConditionType: api.PodConditionType(condition1), 496 }, 497 { 498 ConditionType: api.PodConditionType(condition2), 499 }, 500 }, 501 }, 502 Status: api.PodStatus{ 503 Conditions: []api.PodCondition{ 504 { 505 Type: api.PodConditionType(condition1), 506 Status: api.ConditionFalse, 507 }, 508 { 509 Type: api.PodConditionType(condition2), 510 Status: api.ConditionTrue, 511 }, 512 }, 513 PodIPs: []api.PodIP{{IP: "10.1.2.3"}}, 514 Phase: api.PodPending, 515 ContainerStatuses: []api.ContainerStatus{ 516 {Name: "ctr1", State: api.ContainerState{Running: &api.ContainerStateRunning{}}, RestartCount: 10, Ready: true}, 517 {Name: "ctr2", State: api.ContainerState{Waiting: &api.ContainerStateWaiting{}}, RestartCount: 0}, 518 }, 519 NominatedNodeName: "nominated-node", 520 }, 521 } 522 523 multiIPsPod := &api.Pod{ 524 ObjectMeta: metav1.ObjectMeta{Namespace: "test", Name: "foo", CreationTimestamp: metav1.NewTime(time.Now().Add(-370 * 24 * time.Hour))}, 525 Spec: api.PodSpec{ 526 Containers: []api.Container{ 527 {Name: "ctr1"}, 528 {Name: "ctr2", Ports: []api.ContainerPort{{ContainerPort: 9376}}}, 529 }, 530 NodeName: "test-node", 531 ReadinessGates: []api.PodReadinessGate{ 532 { 533 ConditionType: api.PodConditionType(condition1), 534 }, 535 { 536 ConditionType: api.PodConditionType(condition2), 537 }, 538 }, 539 }, 540 Status: api.PodStatus{ 541 Conditions: []api.PodCondition{ 542 { 543 Type: api.PodConditionType(condition1), 544 Status: api.ConditionFalse, 545 }, 546 { 547 Type: api.PodConditionType(condition2), 548 Status: api.ConditionTrue, 549 }, 550 }, 551 PodIPs: []api.PodIP{{IP: "10.1.2.3"}, {IP: "2001:db8::"}}, 552 Phase: api.PodPending, 553 ContainerStatuses: []api.ContainerStatus{ 554 {Name: "ctr1", State: api.ContainerState{Running: &api.ContainerStateRunning{}}, RestartCount: 10, Ready: true}, 555 {Name: "ctr2", State: api.ContainerState{Waiting: &api.ContainerStateWaiting{}}, RestartCount: 0}, 556 }, 557 NominatedNodeName: "nominated-node", 558 }, 559 } 560 561 testCases := []struct { 562 in runtime.Object 563 out *metav1.Table 564 err bool 565 }{ 566 { 567 in: nil, 568 err: true, 569 }, 570 { 571 in: &api.Pod{}, 572 out: &metav1.Table{ 573 ColumnDefinitions: columns, 574 Rows: []metav1.TableRow{ 575 {Cells: []interface{}{"", "0/0", "", "0", "<unknown>", "<none>", "<none>", "<none>", "<none>"}, Object: runtime.RawExtension{Object: &api.Pod{}}}, 576 }, 577 }, 578 }, 579 { 580 in: pod1, 581 out: &metav1.Table{ 582 ColumnDefinitions: columns, 583 Rows: []metav1.TableRow{ 584 {Cells: []interface{}{"foo", "1/2", "Pending", "10", "370d", "10.1.2.3", "test-node", "nominated-node", "1/2"}, Object: runtime.RawExtension{Object: pod1}}, 585 }, 586 }, 587 }, 588 { 589 in: &api.PodList{}, 590 out: &metav1.Table{ColumnDefinitions: columns}, 591 }, 592 { 593 in: multiIPsPod, 594 out: &metav1.Table{ 595 ColumnDefinitions: columns, 596 Rows: []metav1.TableRow{ 597 {Cells: []interface{}{"foo", "1/2", "Pending", "10", "370d", "10.1.2.3", "test-node", "nominated-node", "1/2"}, Object: runtime.RawExtension{Object: multiIPsPod}}, 598 }, 599 }, 600 }, 601 } 602 for i, test := range testCases { 603 out, err := storage.ConvertToTable(ctx, test.in, nil) 604 if err != nil { 605 if test.err { 606 continue 607 } 608 t.Errorf("%d: error: %v", i, err) 609 continue 610 } 611 if !apiequality.Semantic.DeepEqual(test.out, out) { 612 t.Errorf("%d: mismatch: %s", i, cmp.Diff(test.out, out)) 613 } 614 } 615 } 616 617 func TestEtcdCreate(t *testing.T) { 618 storage, bindingStorage, _, server := newStorage(t) 619 defer server.Terminate(t) 620 defer storage.Store.DestroyFunc() 621 ctx := genericapirequest.NewDefaultContext() 622 _, err := storage.Create(ctx, validNewPod(), rest.ValidateAllObjectFunc, &metav1.CreateOptions{}) 623 if err != nil { 624 t.Fatalf("unexpected error: %v", err) 625 } 626 627 // Suddenly, a wild scheduler appears: 628 _, err = bindingStorage.Create(ctx, "foo", &api.Binding{ 629 ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault, Name: "foo"}, 630 Target: api.ObjectReference{Name: "machine"}, 631 }, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}) 632 if err != nil { 633 t.Fatalf("unexpected error: %v", err) 634 } 635 636 _, err = storage.Get(ctx, "foo", &metav1.GetOptions{}) 637 if err != nil { 638 t.Fatalf("Unexpected error %v", err) 639 } 640 } 641 642 // Ensure that when scheduler creates a binding for a pod that has already been deleted 643 // by the API server, API server returns not-found error. 644 func TestEtcdCreateBindingNoPod(t *testing.T) { 645 storage, bindingStorage, _, server := newStorage(t) 646 defer server.Terminate(t) 647 defer storage.Store.DestroyFunc() 648 ctx := genericapirequest.NewDefaultContext() 649 650 // Assume that a pod has undergone the following: 651 // - Create (apiserver) 652 // - Schedule (scheduler) 653 // - Delete (apiserver) 654 _, err := bindingStorage.Create(ctx, "foo", &api.Binding{ 655 ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault, Name: "foo"}, 656 Target: api.ObjectReference{Name: "machine"}, 657 }, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}) 658 if err == nil { 659 t.Fatalf("Expected not-found-error but got nothing") 660 } 661 if !errors.IsNotFound(storeerr.InterpretGetError(err, api.Resource("pods"), "foo")) { 662 t.Fatalf("Unexpected error returned: %#v", err) 663 } 664 665 _, err = storage.Get(ctx, "foo", &metav1.GetOptions{}) 666 if err == nil { 667 t.Fatalf("Expected not-found-error but got nothing") 668 } 669 if !errors.IsNotFound(storeerr.InterpretGetError(err, api.Resource("pods"), "foo")) { 670 t.Fatalf("Unexpected error: %v", err) 671 } 672 } 673 674 func TestEtcdCreateFailsWithoutNamespace(t *testing.T) { 675 storage, _, _, server := newStorage(t) 676 defer server.Terminate(t) 677 defer storage.Store.DestroyFunc() 678 pod := validNewPod() 679 pod.Namespace = "" 680 _, err := storage.Create(genericapirequest.NewContext(), pod, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}) 681 // Accept "namespace" or "Namespace". 682 if err == nil || !strings.Contains(err.Error(), "amespace") { 683 t.Fatalf("expected error that namespace was missing from context, got: %v", err) 684 } 685 } 686 687 func TestEtcdCreateWithContainersNotFound(t *testing.T) { 688 storage, bindingStorage, _, server := newStorage(t) 689 defer server.Terminate(t) 690 defer storage.Store.DestroyFunc() 691 ctx := genericapirequest.NewDefaultContext() 692 _, err := storage.Create(ctx, validNewPod(), rest.ValidateAllObjectFunc, &metav1.CreateOptions{}) 693 if err != nil { 694 t.Fatalf("unexpected error: %v", err) 695 } 696 697 // Suddenly, a wild scheduler appears: 698 _, err = bindingStorage.Create(ctx, "foo", &api.Binding{ 699 ObjectMeta: metav1.ObjectMeta{ 700 Namespace: metav1.NamespaceDefault, 701 Name: "foo", 702 Annotations: map[string]string{"label1": "value1"}, 703 }, 704 Target: api.ObjectReference{Name: "machine"}, 705 }, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}) 706 if err != nil { 707 t.Fatalf("unexpected error: %v", err) 708 } 709 710 obj, err := storage.Get(ctx, "foo", &metav1.GetOptions{}) 711 if err != nil { 712 t.Fatalf("Unexpected error %v", err) 713 } 714 pod := obj.(*api.Pod) 715 716 if !(pod.Annotations != nil && pod.Annotations["label1"] == "value1") { 717 t.Fatalf("Pod annotations don't match the expected: %v", pod.Annotations) 718 } 719 } 720 721 func TestEtcdCreateWithConflict(t *testing.T) { 722 storage, bindingStorage, _, server := newStorage(t) 723 defer server.Terminate(t) 724 defer storage.Store.DestroyFunc() 725 ctx := genericapirequest.NewDefaultContext() 726 727 _, err := storage.Create(ctx, validNewPod(), rest.ValidateAllObjectFunc, &metav1.CreateOptions{}) 728 if err != nil { 729 t.Fatalf("unexpected error: %v", err) 730 } 731 732 // Suddenly, a wild scheduler appears: 733 binding := api.Binding{ 734 ObjectMeta: metav1.ObjectMeta{ 735 Namespace: metav1.NamespaceDefault, 736 Name: "foo", 737 Annotations: map[string]string{"label1": "value1"}, 738 }, 739 Target: api.ObjectReference{Name: "machine"}, 740 } 741 _, err = bindingStorage.Create(ctx, binding.Name, &binding, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}) 742 if err != nil { 743 t.Fatalf("unexpected error: %v", err) 744 } 745 746 _, err = bindingStorage.Create(ctx, binding.Name, &binding, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}) 747 if err == nil || !errors.IsConflict(err) { 748 t.Fatalf("expected resource conflict error, not: %v", err) 749 } 750 } 751 752 func TestEtcdCreateWithSchedulingGates(t *testing.T) { 753 tests := []struct { 754 name string 755 featureEnabled bool 756 schedulingGates []api.PodSchedulingGate 757 wantErr error 758 }{ 759 { 760 name: "pod with non-nil schedulingGates, feature disabled", 761 featureEnabled: false, 762 schedulingGates: []api.PodSchedulingGate{ 763 {Name: "foo"}, 764 {Name: "bar"}, 765 }, 766 wantErr: nil, 767 }, 768 { 769 name: "pod with non-nil schedulingGates, feature enabled", 770 featureEnabled: true, 771 schedulingGates: []api.PodSchedulingGate{ 772 {Name: "foo"}, 773 {Name: "bar"}, 774 }, 775 wantErr: goerrors.New(`Operation cannot be fulfilled on pods/binding "foo": pod foo has non-empty .spec.schedulingGates`), 776 }, 777 { 778 name: "pod with nil schedulingGates, feature disabled", 779 featureEnabled: false, 780 schedulingGates: nil, 781 wantErr: nil, 782 }, 783 { 784 name: "pod with nil schedulingGates, feature enabled", 785 featureEnabled: true, 786 schedulingGates: nil, 787 wantErr: nil, 788 }, 789 } 790 791 for _, tt := range tests { 792 for _, flipFeatureGateBeforeBinding := range []bool{false, true} { 793 if flipFeatureGateBeforeBinding { 794 tt.name = fmt.Sprintf("%v and flipped before binding", tt.name) 795 } 796 t.Run(tt.name, func(t *testing.T) { 797 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodSchedulingReadiness, tt.featureEnabled)() 798 storage, bindingStorage, _, server := newStorage(t) 799 defer server.Terminate(t) 800 defer storage.Store.DestroyFunc() 801 ctx := genericapirequest.NewDefaultContext() 802 803 pod := validNewPod() 804 pod.Spec.SchedulingGates = tt.schedulingGates 805 if _, err := storage.Create(ctx, pod, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}); err != nil { 806 t.Fatalf("Unexpected error: %v", err) 807 } 808 if flipFeatureGateBeforeBinding { 809 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodSchedulingReadiness, !tt.featureEnabled)() 810 } 811 _, err := bindingStorage.Create(ctx, "foo", &api.Binding{ 812 ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault, Name: "foo"}, 813 Target: api.ObjectReference{Name: "machine"}, 814 }, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}) 815 if tt.wantErr == nil { 816 if err != nil { 817 t.Errorf("Want nil err, but got %v", err) 818 } 819 } else { 820 if err == nil { 821 t.Errorf("Want %v, but got nil err", tt.wantErr) 822 } else if tt.wantErr.Error() != err.Error() { 823 t.Errorf("Want %v, but got %v", tt.wantErr, err) 824 } 825 } 826 }) 827 } 828 } 829 } 830 831 func validNewBinding() *api.Binding { 832 return &api.Binding{ 833 ObjectMeta: metav1.ObjectMeta{Name: "foo"}, 834 Target: api.ObjectReference{Name: "machine", Kind: "Node"}, 835 } 836 } 837 838 func TestEtcdCreateBindingWithUIDAndResourceVersion(t *testing.T) { 839 originUID := func(pod *api.Pod) types.UID { 840 return pod.UID 841 } 842 emptyUID := func(pod *api.Pod) types.UID { 843 return "" 844 } 845 changedUID := func(pod *api.Pod) types.UID { 846 return pod.UID + "-changed" 847 } 848 849 originResourceVersion := func(pod *api.Pod) string { 850 return pod.ResourceVersion 851 } 852 emptyResourceVersion := func(pod *api.Pod) string { 853 return "" 854 } 855 changedResourceVersion := func(pod *api.Pod) string { 856 return pod.ResourceVersion + "-changed" 857 } 858 859 noError := func(err error) bool { 860 return err == nil 861 } 862 conflictError := func(err error) bool { 863 return err != nil && errors.IsConflict(err) 864 } 865 866 testCases := map[string]struct { 867 podUIDGetter func(pod *api.Pod) types.UID 868 podResourceVersionGetter func(pod *api.Pod) string 869 errOK func(error) bool 870 expectedNodeName string 871 }{ 872 "originUID-originResourceVersion": { 873 podUIDGetter: originUID, 874 podResourceVersionGetter: originResourceVersion, 875 errOK: noError, 876 expectedNodeName: "machine", 877 }, 878 "originUID-emptyResourceVersion": { 879 podUIDGetter: originUID, 880 podResourceVersionGetter: emptyResourceVersion, 881 errOK: noError, 882 expectedNodeName: "machine", 883 }, 884 "originUID-changedResourceVersion": { 885 podUIDGetter: originUID, 886 podResourceVersionGetter: changedResourceVersion, 887 errOK: conflictError, 888 expectedNodeName: "", 889 }, 890 "emptyUID-originResourceVersion": { 891 podUIDGetter: emptyUID, 892 podResourceVersionGetter: originResourceVersion, 893 errOK: noError, 894 expectedNodeName: "machine", 895 }, 896 "emptyUID-emptyResourceVersion": { 897 podUIDGetter: emptyUID, 898 podResourceVersionGetter: emptyResourceVersion, 899 errOK: noError, 900 expectedNodeName: "machine", 901 }, 902 "emptyUID-changedResourceVersion": { 903 podUIDGetter: emptyUID, 904 podResourceVersionGetter: changedResourceVersion, 905 errOK: conflictError, 906 expectedNodeName: "", 907 }, 908 "changedUID-originResourceVersion": { 909 podUIDGetter: changedUID, 910 podResourceVersionGetter: originResourceVersion, 911 errOK: conflictError, 912 expectedNodeName: "", 913 }, 914 "changedUID-emptyResourceVersion": { 915 podUIDGetter: changedUID, 916 podResourceVersionGetter: emptyResourceVersion, 917 errOK: conflictError, 918 expectedNodeName: "", 919 }, 920 "changedUID-changedResourceVersion": { 921 podUIDGetter: changedUID, 922 podResourceVersionGetter: changedResourceVersion, 923 errOK: conflictError, 924 expectedNodeName: "", 925 }, 926 } 927 928 storage, bindingStorage, _, server := newStorage(t) 929 defer server.Terminate(t) 930 defer storage.Store.DestroyFunc() 931 932 for k, testCase := range testCases { 933 pod := validNewPod() 934 pod.Namespace = fmt.Sprintf("namespace-%s", strings.ToLower(k)) 935 ctx := genericapirequest.WithNamespace(genericapirequest.NewDefaultContext(), pod.Namespace) 936 937 podCreated, err := storage.Create(ctx, pod, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}) 938 if err != nil { 939 t.Fatalf("%s: unexpected error: %v", k, err) 940 } 941 942 binding := validNewBinding() 943 binding.UID = testCase.podUIDGetter(podCreated.(*api.Pod)) 944 binding.ResourceVersion = testCase.podResourceVersionGetter(podCreated.(*api.Pod)) 945 946 if _, err := bindingStorage.Create(ctx, binding.Name, binding, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}); !testCase.errOK(err) { 947 t.Errorf("%s: unexpected error: %v", k, err) 948 } 949 950 if pod, err := storage.Get(ctx, pod.Name, &metav1.GetOptions{}); err != nil { 951 t.Errorf("%s: unexpected error: %v", k, err) 952 } else if pod.(*api.Pod).Spec.NodeName != testCase.expectedNodeName { 953 t.Errorf("%s: expected: %v, got: %v", k, pod.(*api.Pod).Spec.NodeName, testCase.expectedNodeName) 954 } 955 } 956 } 957 958 func TestEtcdCreateWithExistingContainers(t *testing.T) { 959 storage, bindingStorage, _, server := newStorage(t) 960 defer server.Terminate(t) 961 defer storage.Store.DestroyFunc() 962 ctx := genericapirequest.NewDefaultContext() 963 _, err := storage.Create(ctx, validNewPod(), rest.ValidateAllObjectFunc, &metav1.CreateOptions{}) 964 if err != nil { 965 t.Fatalf("unexpected error: %v", err) 966 } 967 968 // Suddenly, a wild scheduler appears: 969 _, err = bindingStorage.Create(ctx, "foo", &api.Binding{ 970 ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault, Name: "foo"}, 971 Target: api.ObjectReference{Name: "machine"}, 972 }, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}) 973 if err != nil { 974 t.Fatalf("unexpected error: %v", err) 975 } 976 977 _, err = storage.Get(ctx, "foo", &metav1.GetOptions{}) 978 if err != nil { 979 t.Fatalf("Unexpected error %v", err) 980 } 981 } 982 983 func TestEtcdCreateBinding(t *testing.T) { 984 testCases := map[string]struct { 985 binding api.Binding 986 badNameInURL bool 987 errOK func(error) bool 988 }{ 989 "noName": { 990 binding: api.Binding{ 991 ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault, Name: "foo"}, 992 Target: api.ObjectReference{}, 993 }, 994 errOK: func(err error) bool { return err != nil }, 995 }, 996 "badNameInURL": { 997 binding: api.Binding{ 998 ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault, Name: "foo"}, 999 Target: api.ObjectReference{}, 1000 }, 1001 badNameInURL: true, 1002 errOK: func(err error) bool { return err != nil }, 1003 }, 1004 "badKind": { 1005 binding: api.Binding{ 1006 ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault, Name: "foo"}, 1007 Target: api.ObjectReference{Name: "machine1", Kind: "unknown"}, 1008 }, 1009 errOK: func(err error) bool { return err != nil }, 1010 }, 1011 "emptyKind": { 1012 binding: api.Binding{ 1013 ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault, Name: "foo"}, 1014 Target: api.ObjectReference{Name: "machine2"}, 1015 }, 1016 errOK: func(err error) bool { return err == nil }, 1017 }, 1018 "kindNode": { 1019 binding: api.Binding{ 1020 ObjectMeta: metav1.ObjectMeta{Namespace: metav1.NamespaceDefault, Name: "foo"}, 1021 Target: api.ObjectReference{Name: "machine3", Kind: "Node"}, 1022 }, 1023 errOK: func(err error) bool { return err == nil }, 1024 }, 1025 } 1026 storage, bindingStorage, _, server := newStorage(t) 1027 defer server.Terminate(t) 1028 defer storage.Store.DestroyFunc() 1029 1030 for k, test := range testCases { 1031 pod := validNewPod() 1032 pod.Namespace = fmt.Sprintf("namespace-%s", strings.ToLower(k)) 1033 ctx := genericapirequest.WithNamespace(genericapirequest.NewDefaultContext(), pod.Namespace) 1034 if _, err := storage.Create(ctx, pod, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}); err != nil { 1035 t.Fatalf("%s: unexpected error: %v", k, err) 1036 } 1037 name := test.binding.Name 1038 if test.badNameInURL { 1039 name += "badNameInURL" 1040 } 1041 if _, err := bindingStorage.Create(ctx, name, &test.binding, rest.ValidateAllObjectFunc, &metav1.CreateOptions{}); !test.errOK(err) { 1042 t.Errorf("%s: unexpected error: %v", k, err) 1043 } else if err == nil { 1044 // If bind succeeded, verify Host field in pod's Spec. 1045 pod, err := storage.Get(ctx, pod.ObjectMeta.Name, &metav1.GetOptions{}) 1046 if err != nil { 1047 t.Errorf("%s: unexpected error: %v", k, err) 1048 } else if pod.(*api.Pod).Spec.NodeName != test.binding.Target.Name { 1049 t.Errorf("%s: expected: %v, got: %v", k, pod.(*api.Pod).Spec.NodeName, test.binding.Target.Name) 1050 } 1051 } 1052 } 1053 } 1054 1055 func TestEtcdUpdateNotScheduled(t *testing.T) { 1056 storage, _, _, server := newStorage(t) 1057 defer server.Terminate(t) 1058 defer storage.Store.DestroyFunc() 1059 ctx := genericapirequest.NewDefaultContext() 1060 1061 if _, err := storage.Create(ctx, validNewPod(), rest.ValidateAllObjectFunc, &metav1.CreateOptions{}); err != nil { 1062 t.Fatalf("unexpected error: %v", err) 1063 } 1064 1065 podIn := validChangedPod() 1066 _, _, err := storage.Update(ctx, podIn.Name, rest.DefaultUpdatedObjectInfo(podIn), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}) 1067 if err != nil { 1068 t.Errorf("Unexpected error: %v", err) 1069 } 1070 obj, err := storage.Get(ctx, validNewPod().ObjectMeta.Name, &metav1.GetOptions{}) 1071 if err != nil { 1072 t.Errorf("unexpected error: %v", err) 1073 } 1074 podOut := obj.(*api.Pod) 1075 // validChangedPod only changes the Labels, so were checking the update was valid 1076 if !apiequality.Semantic.DeepEqual(podIn.Labels, podOut.Labels) { 1077 t.Errorf("objects differ: %v", cmp.Diff(podOut, podIn)) 1078 } 1079 } 1080 1081 func TestEtcdUpdateScheduled(t *testing.T) { 1082 storage, _, _, server := newStorage(t) 1083 defer server.Terminate(t) 1084 defer storage.Store.DestroyFunc() 1085 ctx := genericapirequest.NewDefaultContext() 1086 1087 key, _ := storage.KeyFunc(ctx, "foo") 1088 err := storage.Storage.Create(ctx, key, &api.Pod{ 1089 ObjectMeta: metav1.ObjectMeta{ 1090 Name: "foo", 1091 Namespace: metav1.NamespaceDefault, 1092 }, 1093 Spec: api.PodSpec{ 1094 NodeName: "machine", 1095 Containers: []api.Container{ 1096 { 1097 Name: "foobar", 1098 Image: "foo:v1", 1099 SecurityContext: securitycontext.ValidInternalSecurityContextWithContainerDefaults(), 1100 }, 1101 }, 1102 SecurityContext: &api.PodSecurityContext{}, 1103 SchedulerName: v1.DefaultSchedulerName, 1104 }, 1105 }, nil, 1, false) 1106 if err != nil { 1107 t.Errorf("Unexpected error: %v", err) 1108 } 1109 1110 grace := int64(30) 1111 enableServiceLinks := v1.DefaultEnableServiceLinks 1112 podIn := api.Pod{ 1113 ObjectMeta: metav1.ObjectMeta{ 1114 Name: "foo", 1115 Labels: map[string]string{ 1116 "foo": "bar", 1117 }, 1118 }, 1119 Spec: api.PodSpec{ 1120 NodeName: "machine", 1121 Containers: []api.Container{{ 1122 Name: "foobar", 1123 Image: "foo:v2", 1124 ImagePullPolicy: api.PullIfNotPresent, 1125 TerminationMessagePath: api.TerminationMessagePathDefault, 1126 TerminationMessagePolicy: api.TerminationMessageReadFile, 1127 SecurityContext: securitycontext.ValidInternalSecurityContextWithContainerDefaults(), 1128 }}, 1129 RestartPolicy: api.RestartPolicyAlways, 1130 DNSPolicy: api.DNSClusterFirst, 1131 1132 TerminationGracePeriodSeconds: &grace, 1133 SecurityContext: &api.PodSecurityContext{}, 1134 SchedulerName: v1.DefaultSchedulerName, 1135 EnableServiceLinks: &enableServiceLinks, 1136 }, 1137 } 1138 _, _, err = storage.Update(ctx, podIn.Name, rest.DefaultUpdatedObjectInfo(&podIn), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}) 1139 if err != nil { 1140 t.Errorf("Unexpected error: %v", err) 1141 } 1142 1143 obj, err := storage.Get(ctx, "foo", &metav1.GetOptions{}) 1144 if err != nil { 1145 t.Errorf("Unexpected error: %v", err) 1146 } 1147 podOut := obj.(*api.Pod) 1148 // Check to verify the Spec and Label updates match from change above. Those are the fields changed. 1149 if !apiequality.Semantic.DeepEqual(podOut.Spec, podIn.Spec) || !apiequality.Semantic.DeepEqual(podOut.Labels, podIn.Labels) { 1150 t.Errorf("objects differ: %v", cmp.Diff(podOut, podIn)) 1151 } 1152 1153 } 1154 1155 func TestEtcdUpdateStatus(t *testing.T) { 1156 storage, _, statusStorage, server := newStorage(t) 1157 defer server.Terminate(t) 1158 defer storage.Store.DestroyFunc() 1159 ctx := genericapirequest.NewDefaultContext() 1160 1161 key, _ := storage.KeyFunc(ctx, "foo") 1162 podStart := api.Pod{ 1163 ObjectMeta: metav1.ObjectMeta{ 1164 Name: "foo", 1165 Namespace: metav1.NamespaceDefault, 1166 }, 1167 Spec: api.PodSpec{ 1168 NodeName: "machine", 1169 Containers: []api.Container{ 1170 { 1171 Image: "foo:v1", 1172 SecurityContext: securitycontext.ValidInternalSecurityContextWithContainerDefaults(), 1173 }, 1174 }, 1175 SecurityContext: &api.PodSecurityContext{}, 1176 SchedulerName: v1.DefaultSchedulerName, 1177 }, 1178 } 1179 err := storage.Storage.Create(ctx, key, &podStart, nil, 0, false) 1180 if err != nil { 1181 t.Errorf("unexpected error: %v", err) 1182 } 1183 1184 podsIn := []api.Pod{ 1185 { 1186 ObjectMeta: metav1.ObjectMeta{ 1187 Name: "foo", 1188 Labels: map[string]string{ 1189 "foo": "bar", 1190 }, 1191 }, 1192 Spec: api.PodSpec{ 1193 NodeName: "machine", 1194 Containers: []api.Container{ 1195 { 1196 Image: "foo:v2", 1197 ImagePullPolicy: api.PullIfNotPresent, 1198 TerminationMessagePath: api.TerminationMessagePathDefault, 1199 }, 1200 }, 1201 SecurityContext: &api.PodSecurityContext{}, 1202 SchedulerName: v1.DefaultSchedulerName, 1203 }, 1204 Status: api.PodStatus{ 1205 Phase: api.PodRunning, 1206 PodIPs: []api.PodIP{{IP: "127.0.0.1"}}, 1207 Message: "is now scheduled", 1208 }, 1209 }, 1210 { 1211 ObjectMeta: metav1.ObjectMeta{ 1212 Name: "foo", 1213 Labels: map[string]string{ 1214 "foo": "bar", 1215 }, 1216 }, 1217 Spec: api.PodSpec{ 1218 NodeName: "machine", 1219 Containers: []api.Container{ 1220 { 1221 Image: "foo:v2", 1222 ImagePullPolicy: api.PullIfNotPresent, 1223 TerminationMessagePath: api.TerminationMessagePathDefault, 1224 }, 1225 }, 1226 SecurityContext: &api.PodSecurityContext{}, 1227 SchedulerName: v1.DefaultSchedulerName, 1228 }, 1229 Status: api.PodStatus{ 1230 Phase: api.PodRunning, 1231 PodIPs: []api.PodIP{{IP: "127.0.0.1"}, {IP: "2001:db8::"}}, 1232 Message: "is now scheduled", 1233 }, 1234 }, 1235 } 1236 1237 for _, podIn := range podsIn { 1238 expected := podStart 1239 expected.ResourceVersion = "2" 1240 grace := int64(30) 1241 enableServiceLinks := v1.DefaultEnableServiceLinks 1242 expected.Spec.TerminationGracePeriodSeconds = &grace 1243 expected.Spec.RestartPolicy = api.RestartPolicyAlways 1244 expected.Spec.DNSPolicy = api.DNSClusterFirst 1245 expected.Spec.EnableServiceLinks = &enableServiceLinks 1246 expected.Spec.Containers[0].ImagePullPolicy = api.PullIfNotPresent 1247 expected.Spec.Containers[0].TerminationMessagePath = api.TerminationMessagePathDefault 1248 expected.Spec.Containers[0].TerminationMessagePolicy = api.TerminationMessageReadFile 1249 expected.Labels = podIn.Labels 1250 expected.Status = podIn.Status 1251 1252 _, _, err = statusStorage.Update(ctx, podIn.Name, rest.DefaultUpdatedObjectInfo(&podIn), rest.ValidateAllObjectFunc, rest.ValidateAllObjectUpdateFunc, false, &metav1.UpdateOptions{}) 1253 if err != nil { 1254 t.Fatalf("Unexpected error: %v", err) 1255 } 1256 obj, err := storage.Get(ctx, "foo", &metav1.GetOptions{}) 1257 if err != nil { 1258 t.Errorf("unexpected error: %v", err) 1259 } 1260 podOut := obj.(*api.Pod) 1261 // Check to verify the Label, and Status updates match from change above. Those are the fields changed. 1262 if !apiequality.Semantic.DeepEqual(podOut.Spec, expected.Spec) || 1263 !apiequality.Semantic.DeepEqual(podOut.Labels, expected.Labels) || 1264 !apiequality.Semantic.DeepEqual(podOut.Status, expected.Status) { 1265 t.Errorf("objects differ: %v", cmp.Diff(podOut, expected)) 1266 } 1267 } 1268 } 1269 1270 func TestShortNames(t *testing.T) { 1271 storage, _, _, server := newStorage(t) 1272 defer server.Terminate(t) 1273 defer storage.Store.DestroyFunc() 1274 expected := []string{"po"} 1275 registrytest.AssertShortNames(t, storage, expected) 1276 } 1277 1278 func TestCategories(t *testing.T) { 1279 storage, _, _, server := newStorage(t) 1280 defer server.Terminate(t) 1281 defer storage.Store.DestroyFunc() 1282 expected := []string{"all"} 1283 registrytest.AssertCategories(t, storage, expected) 1284 }