k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/integration/client/client_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 client 18 19 import ( 20 "context" 21 "fmt" 22 "log" 23 "reflect" 24 rt "runtime" 25 "strings" 26 "sync" 27 "testing" 28 "time" 29 30 "github.com/google/go-cmp/cmp" 31 appsv1 "k8s.io/api/apps/v1" 32 v1 "k8s.io/api/core/v1" 33 eventsv1 "k8s.io/api/events/v1" 34 "k8s.io/apimachinery/pkg/api/equality" 35 apierrors "k8s.io/apimachinery/pkg/api/errors" 36 "k8s.io/apimachinery/pkg/api/resource" 37 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 38 "k8s.io/apimachinery/pkg/fields" 39 "k8s.io/apimachinery/pkg/labels" 40 "k8s.io/apimachinery/pkg/runtime" 41 "k8s.io/apimachinery/pkg/runtime/schema" 42 "k8s.io/apimachinery/pkg/types" 43 "k8s.io/apimachinery/pkg/util/wait" 44 "k8s.io/apimachinery/pkg/watch" 45 appsv1ac "k8s.io/client-go/applyconfigurations/apps/v1" 46 corev1ac "k8s.io/client-go/applyconfigurations/core/v1" 47 metav1ac "k8s.io/client-go/applyconfigurations/meta/v1" 48 clientset "k8s.io/client-go/kubernetes" 49 "k8s.io/utils/pointer" 50 51 "k8s.io/component-base/version" 52 kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing" 53 "k8s.io/kubernetes/pkg/api/legacyscheme" 54 "k8s.io/kubernetes/test/integration/framework" 55 imageutils "k8s.io/kubernetes/test/utils/image" 56 ) 57 58 func TestClient(t *testing.T) { 59 result := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--disable-admission-plugins", "ServiceAccount"}, framework.SharedEtcd()) 60 defer result.TearDownFn() 61 62 client := clientset.NewForConfigOrDie(result.ClientConfig) 63 64 info, err := client.Discovery().ServerVersion() 65 if err != nil { 66 t.Fatalf("unexpected error: %v", err) 67 } 68 if e, a := version.Get(), *info; !reflect.DeepEqual(e, a) { 69 t.Errorf("expected %#v, got %#v", e, a) 70 } 71 72 pods, err := client.CoreV1().Pods("default").List(context.TODO(), metav1.ListOptions{}) 73 if err != nil { 74 t.Fatalf("unexpected error: %v", err) 75 } 76 if len(pods.Items) != 0 { 77 t.Errorf("expected no pods, got %#v", pods) 78 } 79 80 // get a validation error 81 pod := &v1.Pod{ 82 ObjectMeta: metav1.ObjectMeta{ 83 GenerateName: "test", 84 Namespace: "default", 85 }, 86 Spec: v1.PodSpec{ 87 Containers: []v1.Container{ 88 { 89 Name: "test", 90 }, 91 }, 92 }, 93 } 94 95 got, err := client.CoreV1().Pods("default").Create(context.TODO(), pod, metav1.CreateOptions{}) 96 if err == nil { 97 t.Fatalf("unexpected non-error: %v", got) 98 } 99 100 // get a created pod 101 pod.Spec.Containers[0].Image = "an-image" 102 got, err = client.CoreV1().Pods("default").Create(context.TODO(), pod, metav1.CreateOptions{}) 103 if err != nil { 104 t.Fatalf("unexpected error: %v", err) 105 } 106 if got.Name == "" { 107 t.Errorf("unexpected empty pod Name %v", got) 108 } 109 110 // pod is shown, but not scheduled 111 pods, err = client.CoreV1().Pods("default").List(context.TODO(), metav1.ListOptions{}) 112 if err != nil { 113 t.Fatalf("unexpected error: %v", err) 114 } 115 if len(pods.Items) != 1 { 116 t.Errorf("expected one pod, got %#v", pods) 117 } 118 actual := pods.Items[0] 119 if actual.Name != got.Name { 120 t.Errorf("expected pod %#v, got %#v", got, actual) 121 } 122 if actual.Spec.NodeName != "" { 123 t.Errorf("expected pod to be unscheduled, got %#v", actual) 124 } 125 } 126 127 func TestAtomicPut(t *testing.T) { 128 result := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--disable-admission-plugins", "ServiceAccount"}, framework.SharedEtcd()) 129 defer result.TearDownFn() 130 131 c := clientset.NewForConfigOrDie(result.ClientConfig) 132 133 rcBody := v1.ReplicationController{ 134 TypeMeta: metav1.TypeMeta{ 135 APIVersion: c.CoreV1().RESTClient().APIVersion().String(), 136 }, 137 ObjectMeta: metav1.ObjectMeta{ 138 Name: "atomicrc", 139 Namespace: "default", 140 Labels: map[string]string{ 141 "name": "atomicrc", 142 }, 143 }, 144 Spec: v1.ReplicationControllerSpec{ 145 Replicas: pointer.Int32(0), 146 Selector: map[string]string{ 147 "foo": "bar", 148 }, 149 Template: &v1.PodTemplateSpec{ 150 ObjectMeta: metav1.ObjectMeta{ 151 Labels: map[string]string{ 152 "foo": "bar", 153 }, 154 }, 155 Spec: v1.PodSpec{ 156 Containers: []v1.Container{ 157 {Name: "name", Image: "image"}, 158 }, 159 }, 160 }, 161 }, 162 } 163 rcs := c.CoreV1().ReplicationControllers("default") 164 rc, err := rcs.Create(context.TODO(), &rcBody, metav1.CreateOptions{}) 165 if err != nil { 166 t.Fatalf("Failed creating atomicRC: %v", err) 167 } 168 testLabels := labels.Set{ 169 "foo": "bar", 170 } 171 for i := 0; i < 5; i++ { 172 // a: z, b: y, etc... 173 testLabels[string([]byte{byte('a' + i)})] = string([]byte{byte('z' - i)}) 174 } 175 var wg sync.WaitGroup 176 wg.Add(len(testLabels)) 177 for label, value := range testLabels { 178 go func(l, v string) { 179 defer wg.Done() 180 for { 181 tmpRC, err := rcs.Get(context.TODO(), rc.Name, metav1.GetOptions{}) 182 if err != nil { 183 t.Errorf("Error getting atomicRC: %v", err) 184 continue 185 } 186 if tmpRC.Spec.Selector == nil { 187 tmpRC.Spec.Selector = map[string]string{l: v} 188 tmpRC.Spec.Template.Labels = map[string]string{l: v} 189 } else { 190 tmpRC.Spec.Selector[l] = v 191 tmpRC.Spec.Template.Labels[l] = v 192 } 193 _, err = rcs.Update(context.TODO(), tmpRC, metav1.UpdateOptions{}) 194 if err != nil { 195 if apierrors.IsConflict(err) { 196 // This is what we expect. 197 continue 198 } 199 t.Errorf("Unexpected error putting atomicRC: %v", err) 200 continue 201 } 202 return 203 } 204 }(label, value) 205 } 206 wg.Wait() 207 rc, err = rcs.Get(context.TODO(), rc.Name, metav1.GetOptions{}) 208 if err != nil { 209 t.Fatalf("Failed getting atomicRC after writers are complete: %v", err) 210 } 211 if !reflect.DeepEqual(testLabels, labels.Set(rc.Spec.Selector)) { 212 t.Errorf("Selector PUTs were not atomic: wanted %v, got %v", testLabels, rc.Spec.Selector) 213 } 214 } 215 216 func TestPatch(t *testing.T) { 217 result := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--disable-admission-plugins", "ServiceAccount"}, framework.SharedEtcd()) 218 defer result.TearDownFn() 219 220 c := clientset.NewForConfigOrDie(result.ClientConfig) 221 222 name := "patchpod" 223 resource := "pods" 224 podBody := v1.Pod{ 225 TypeMeta: metav1.TypeMeta{ 226 APIVersion: c.CoreV1().RESTClient().APIVersion().String(), 227 }, 228 ObjectMeta: metav1.ObjectMeta{ 229 Name: name, 230 Namespace: "default", 231 Labels: map[string]string{}, 232 }, 233 Spec: v1.PodSpec{ 234 Containers: []v1.Container{ 235 {Name: "name", Image: "image"}, 236 }, 237 }, 238 } 239 pods := c.CoreV1().Pods("default") 240 _, err := pods.Create(context.TODO(), &podBody, metav1.CreateOptions{}) 241 if err != nil { 242 t.Fatalf("Failed creating patchpods: %v", err) 243 } 244 245 patchBodies := map[schema.GroupVersion]map[types.PatchType]struct { 246 AddLabelBody []byte 247 RemoveLabelBody []byte 248 RemoveAllLabelsBody []byte 249 }{ 250 v1.SchemeGroupVersion: { 251 types.JSONPatchType: { 252 []byte(`[{"op":"add","path":"/metadata/labels","value":{"foo":"bar","baz":"qux"}}]`), 253 []byte(`[{"op":"remove","path":"/metadata/labels/foo"}]`), 254 []byte(`[{"op":"remove","path":"/metadata/labels"}]`), 255 }, 256 types.MergePatchType: { 257 []byte(`{"metadata":{"labels":{"foo":"bar","baz":"qux"}}}`), 258 []byte(`{"metadata":{"labels":{"foo":null}}}`), 259 []byte(`{"metadata":{"labels":null}}`), 260 }, 261 types.StrategicMergePatchType: { 262 []byte(`{"metadata":{"labels":{"foo":"bar","baz":"qux"}}}`), 263 []byte(`{"metadata":{"labels":{"foo":null}}}`), 264 []byte(`{"metadata":{"labels":{"$patch":"replace"}}}`), 265 }, 266 }, 267 } 268 269 pb := patchBodies[c.CoreV1().RESTClient().APIVersion()] 270 271 execPatch := func(pt types.PatchType, body []byte) error { 272 result := c.CoreV1().RESTClient().Patch(pt). 273 Resource(resource). 274 Namespace("default"). 275 Name(name). 276 Body(body). 277 Do(context.TODO()) 278 if result.Error() != nil { 279 return result.Error() 280 } 281 282 // trying to chase flakes, this should give us resource versions of objects as we step through 283 jsonObj, err := result.Raw() 284 if err != nil { 285 t.Log(err) 286 } else { 287 t.Logf("%v", string(jsonObj)) 288 } 289 290 return nil 291 } 292 293 for k, v := range pb { 294 // add label 295 err := execPatch(k, v.AddLabelBody) 296 if err != nil { 297 t.Fatalf("Failed updating patchpod with patch type %s: %v", k, err) 298 } 299 pod, err := pods.Get(context.TODO(), name, metav1.GetOptions{}) 300 if err != nil { 301 t.Fatalf("Failed getting patchpod: %v", err) 302 } 303 if len(pod.Labels) != 2 || pod.Labels["foo"] != "bar" || pod.Labels["baz"] != "qux" { 304 t.Errorf("Failed updating patchpod with patch type %s: labels are: %v", k, pod.Labels) 305 } 306 307 // remove one label 308 err = execPatch(k, v.RemoveLabelBody) 309 if err != nil { 310 t.Fatalf("Failed updating patchpod with patch type %s: %v", k, err) 311 } 312 pod, err = pods.Get(context.TODO(), name, metav1.GetOptions{}) 313 if err != nil { 314 t.Fatalf("Failed getting patchpod: %v", err) 315 } 316 if len(pod.Labels) != 1 || pod.Labels["baz"] != "qux" { 317 t.Errorf("Failed updating patchpod with patch type %s: labels are: %v", k, pod.Labels) 318 } 319 320 // remove all labels 321 err = execPatch(k, v.RemoveAllLabelsBody) 322 if err != nil { 323 t.Fatalf("Failed updating patchpod with patch type %s: %v", k, err) 324 } 325 pod, err = pods.Get(context.TODO(), name, metav1.GetOptions{}) 326 if err != nil { 327 t.Fatalf("Failed getting patchpod: %v", err) 328 } 329 if pod.Labels != nil { 330 t.Errorf("Failed remove all labels from patchpod with patch type %s: %v", k, pod.Labels) 331 } 332 } 333 } 334 335 func TestPatchWithCreateOnUpdate(t *testing.T) { 336 result := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd()) 337 defer result.TearDownFn() 338 339 c := clientset.NewForConfigOrDie(result.ClientConfig) 340 341 endpointTemplate := &v1.Endpoints{ 342 ObjectMeta: metav1.ObjectMeta{ 343 Name: "patchendpoint", 344 Namespace: "default", 345 }, 346 Subsets: []v1.EndpointSubset{ 347 { 348 Addresses: []v1.EndpointAddress{{IP: "1.2.3.4"}}, 349 Ports: []v1.EndpointPort{{Port: 80, Protocol: v1.ProtocolTCP}}, 350 }, 351 }, 352 } 353 354 patchEndpoint := func(json []byte) (runtime.Object, error) { 355 return c.CoreV1().RESTClient().Patch(types.MergePatchType).Resource("endpoints").Namespace("default").Name("patchendpoint").Body(json).Do(context.TODO()).Get() 356 } 357 358 // Make sure patch doesn't get to CreateOnUpdate 359 { 360 endpointJSON, err := runtime.Encode(legacyscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), endpointTemplate) 361 if err != nil { 362 t.Fatalf("Failed creating endpoint JSON: %v", err) 363 } 364 if obj, err := patchEndpoint(endpointJSON); !apierrors.IsNotFound(err) { 365 t.Errorf("Expected notfound creating from patch, got error=%v and object: %#v", err, obj) 366 } 367 } 368 369 // Create the endpoint (endpoints set AllowCreateOnUpdate=true) to get a UID and resource version 370 createdEndpoint, err := c.CoreV1().Endpoints("default").Update(context.TODO(), endpointTemplate, metav1.UpdateOptions{}) 371 if err != nil { 372 t.Fatalf("Failed creating endpoint: %v", err) 373 } 374 375 // Make sure identity patch is accepted 376 { 377 endpointJSON, err := runtime.Encode(legacyscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), createdEndpoint) 378 if err != nil { 379 t.Fatalf("Failed creating endpoint JSON: %v", err) 380 } 381 if _, err := patchEndpoint(endpointJSON); err != nil { 382 t.Errorf("Failed patching endpoint: %v", err) 383 } 384 } 385 386 // Make sure patch complains about a mismatched resourceVersion 387 { 388 endpointTemplate.Name = "" 389 endpointTemplate.UID = "" 390 endpointTemplate.ResourceVersion = "1" 391 endpointJSON, err := runtime.Encode(legacyscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), endpointTemplate) 392 if err != nil { 393 t.Fatalf("Failed creating endpoint JSON: %v", err) 394 } 395 if _, err := patchEndpoint(endpointJSON); !apierrors.IsConflict(err) { 396 t.Errorf("Expected error, got %#v", err) 397 } 398 } 399 400 // Make sure patch complains about mutating the UID 401 { 402 endpointTemplate.Name = "" 403 endpointTemplate.UID = "abc" 404 endpointTemplate.ResourceVersion = "" 405 endpointJSON, err := runtime.Encode(legacyscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), endpointTemplate) 406 if err != nil { 407 t.Fatalf("Failed creating endpoint JSON: %v", err) 408 } 409 if _, err := patchEndpoint(endpointJSON); !apierrors.IsInvalid(err) { 410 t.Errorf("Expected error, got %#v", err) 411 } 412 } 413 414 // Make sure patch complains about a mismatched name 415 { 416 endpointTemplate.Name = "changedname" 417 endpointTemplate.UID = "" 418 endpointTemplate.ResourceVersion = "" 419 endpointJSON, err := runtime.Encode(legacyscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), endpointTemplate) 420 if err != nil { 421 t.Fatalf("Failed creating endpoint JSON: %v", err) 422 } 423 if _, err := patchEndpoint(endpointJSON); !apierrors.IsBadRequest(err) { 424 t.Errorf("Expected error, got %#v", err) 425 } 426 } 427 428 // Make sure patch containing originally submitted JSON is accepted 429 { 430 endpointTemplate.Name = "" 431 endpointTemplate.UID = "" 432 endpointTemplate.ResourceVersion = "" 433 endpointJSON, err := runtime.Encode(legacyscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), endpointTemplate) 434 if err != nil { 435 t.Fatalf("Failed creating endpoint JSON: %v", err) 436 } 437 if _, err := patchEndpoint(endpointJSON); err != nil { 438 t.Errorf("Failed patching endpoint: %v", err) 439 } 440 } 441 } 442 443 func TestAPIVersions(t *testing.T) { 444 result := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd()) 445 defer result.TearDownFn() 446 447 c := clientset.NewForConfigOrDie(result.ClientConfig) 448 449 clientVersion := c.CoreV1().RESTClient().APIVersion().String() 450 g, err := c.Discovery().ServerGroups() 451 if err != nil { 452 t.Fatalf("Failed to get api versions: %v", err) 453 } 454 versions := metav1.ExtractGroupVersions(g) 455 456 // Verify that the server supports the API version used by the client. 457 for _, version := range versions { 458 if version == clientVersion { 459 return 460 } 461 } 462 t.Errorf("Server does not support APIVersion used by client. Server supported APIVersions: '%v', client APIVersion: '%v'", versions, clientVersion) 463 } 464 465 func TestEventValidation(t *testing.T) { 466 result := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd()) 467 defer result.TearDownFn() 468 469 client := clientset.NewForConfigOrDie(result.ClientConfig) 470 471 createNamespace := func(namespace string) string { 472 if namespace == "" { 473 namespace = metav1.NamespaceDefault 474 } 475 return namespace 476 } 477 478 mkCoreEvent := func(ver string, ns string) *v1.Event { 479 name := fmt.Sprintf("%v-%v-event", ver, ns) 480 namespace := createNamespace(ns) 481 return &v1.Event{ 482 ObjectMeta: metav1.ObjectMeta{ 483 Namespace: namespace, 484 Name: name, 485 }, 486 InvolvedObject: v1.ObjectReference{ 487 Namespace: ns, 488 Name: name, 489 }, 490 Count: 2, 491 Type: "Normal", 492 ReportingController: "test-controller", 493 ReportingInstance: "test-1", 494 Reason: fmt.Sprintf("event %v test", name), 495 Action: "Testing", 496 } 497 } 498 mkV1Event := func(ver string, ns string) *eventsv1.Event { 499 name := fmt.Sprintf("%v-%v-event", ver, ns) 500 namespace := createNamespace(ns) 501 return &eventsv1.Event{ 502 ObjectMeta: metav1.ObjectMeta{ 503 Namespace: namespace, 504 Name: name, 505 }, 506 Regarding: v1.ObjectReference{ 507 Namespace: ns, 508 Name: name, 509 }, 510 Series: &eventsv1.EventSeries{ 511 Count: 2, 512 LastObservedTime: metav1.MicroTime{Time: time.Now()}, 513 }, 514 Type: "Normal", 515 EventTime: metav1.MicroTime{Time: time.Now()}, 516 ReportingController: "test-controller", 517 ReportingInstance: "test-2", 518 Reason: fmt.Sprintf("event %v test", name), 519 Action: "Testing", 520 } 521 } 522 523 testcases := []struct { 524 name string 525 namespace string 526 hasError bool 527 }{ 528 { 529 name: "Involved object is namespaced", 530 namespace: "kube-system", 531 hasError: false, 532 }, 533 { 534 name: "Involved object is cluster-scoped", 535 namespace: "", 536 hasError: false, 537 }, 538 } 539 540 for _, test := range testcases { 541 // create test 542 oldEventObj := mkCoreEvent("corev1", test.namespace) 543 corev1Event, err := client.CoreV1().Events(oldEventObj.Namespace).Create(context.TODO(), oldEventObj, metav1.CreateOptions{}) 544 if err != nil && !test.hasError { 545 t.Errorf("%v, call Create failed, expect has error: %v, but got: %v", test.name, test.hasError, err) 546 } 547 newEventObj := mkV1Event("eventsv1", test.namespace) 548 eventsv1Event, err := client.EventsV1().Events(newEventObj.Namespace).Create(context.TODO(), newEventObj, metav1.CreateOptions{}) 549 if err != nil && !test.hasError { 550 t.Errorf("%v, call Create failed, expect has error: %v, but got: %v", test.name, test.hasError, err) 551 } 552 if corev1Event.Namespace != eventsv1Event.Namespace { 553 t.Errorf("%v, events created by different api client have different namespaces that isn't expected", test.name) 554 } 555 // update test 556 corev1Event.Count++ 557 corev1Event, err = client.CoreV1().Events(corev1Event.Namespace).Update(context.TODO(), corev1Event, metav1.UpdateOptions{}) 558 if err != nil && !test.hasError { 559 t.Errorf("%v, call Update failed, expect has error: %v, but got: %v", test.name, test.hasError, err) 560 } 561 eventsv1Event.Series.Count++ 562 eventsv1Event.Series.LastObservedTime = metav1.MicroTime{Time: time.Now()} 563 eventsv1Event, err = client.EventsV1().Events(eventsv1Event.Namespace).Update(context.TODO(), eventsv1Event, metav1.UpdateOptions{}) 564 if err != nil && !test.hasError { 565 t.Errorf("%v, call Update failed, expect has error: %v, but got: %v", test.name, test.hasError, err) 566 } 567 if corev1Event.Namespace != eventsv1Event.Namespace { 568 t.Errorf("%v, events updated by different api client have different namespaces that isn't expected", test.name) 569 } 570 } 571 } 572 573 func TestEventCompatibility(t *testing.T) { 574 result := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd()) 575 defer result.TearDownFn() 576 577 client := clientset.NewForConfigOrDie(result.ClientConfig) 578 579 coreevents := []*v1.Event{ 580 { 581 ObjectMeta: metav1.ObjectMeta{ 582 Name: "pass-core-default-cluster-scoped", 583 Namespace: "default", 584 }, 585 Type: "Normal", 586 Reason: "event test", 587 Action: "Testing", 588 ReportingController: "test-controller", 589 ReportingInstance: "test-controller-1", 590 InvolvedObject: v1.ObjectReference{Kind: "Node", Name: "foo", Namespace: ""}, 591 }, 592 { 593 ObjectMeta: metav1.ObjectMeta{ 594 Name: "fail-core-kube-system-cluster-scoped", 595 Namespace: "kube-system", 596 }, 597 Type: "Normal", 598 Reason: "event test", 599 Action: "Testing", 600 ReportingController: "test-controller", 601 ReportingInstance: "test-controller-1", 602 InvolvedObject: v1.ObjectReference{Kind: "Node", Name: "foo", Namespace: ""}, 603 }, 604 { 605 ObjectMeta: metav1.ObjectMeta{ 606 Name: "fail-core-other-ns-cluster-scoped", 607 Namespace: "test-ns", 608 }, 609 Type: "Normal", 610 Reason: "event test", 611 Action: "Testing", 612 ReportingController: "test-controller", 613 ReportingInstance: "test-controller-1", 614 InvolvedObject: v1.ObjectReference{Kind: "Node", Name: "foo", Namespace: ""}, 615 }, 616 } 617 for _, e := range coreevents { 618 t.Run(e.Name, func(t *testing.T) { 619 _, err := client.CoreV1().Events(e.Namespace).Create(context.TODO(), e, metav1.CreateOptions{}) 620 if err == nil && !strings.HasPrefix(e.Name, "pass-") { 621 t.Fatalf("unexpected pass") 622 } 623 if err != nil && !strings.HasPrefix(e.Name, "fail-") { 624 t.Fatalf("unexpected error: %v", err) 625 } 626 }) 627 } 628 629 v1events := []*eventsv1.Event{ 630 { 631 ObjectMeta: metav1.ObjectMeta{ 632 Name: "pass-events-default-cluster-scoped", 633 Namespace: "default", 634 }, 635 EventTime: metav1.MicroTime{Time: time.Now()}, 636 Type: "Normal", 637 Reason: "event test", 638 Action: "Testing", 639 ReportingController: "test-controller", 640 ReportingInstance: "test-controller-1", 641 Regarding: v1.ObjectReference{Kind: "Node", Name: "foo", Namespace: ""}, 642 }, 643 { 644 ObjectMeta: metav1.ObjectMeta{ 645 Name: "pass-events-kube-system-cluster-scoped", 646 Namespace: "kube-system", 647 }, 648 EventTime: metav1.MicroTime{Time: time.Now()}, 649 Type: "Normal", 650 Reason: "event test", 651 Action: "Testing", 652 ReportingController: "test-controller", 653 ReportingInstance: "test-controller-1", 654 Regarding: v1.ObjectReference{Kind: "Node", Name: "foo", Namespace: ""}, 655 }, 656 { 657 ObjectMeta: metav1.ObjectMeta{ 658 Name: "fail-events-other-ns-cluster-scoped", 659 Namespace: "test-ns", 660 }, 661 EventTime: metav1.MicroTime{Time: time.Now()}, 662 Type: "Normal", 663 Reason: "event test", 664 Action: "Testing", 665 ReportingController: "test-controller", 666 ReportingInstance: "test-controller-1", 667 Regarding: v1.ObjectReference{Kind: "Node", Name: "foo", Namespace: ""}, 668 }, 669 } 670 for _, e := range v1events { 671 t.Run(e.Name, func(t *testing.T) { 672 _, err := client.EventsV1().Events(e.Namespace).Create(context.TODO(), e, metav1.CreateOptions{}) 673 if err == nil && !strings.HasPrefix(e.Name, "pass-") { 674 t.Fatalf("unexpected pass") 675 } 676 if err != nil && !strings.HasPrefix(e.Name, "fail-") { 677 t.Fatalf("unexpected error: %v", err) 678 } 679 }) 680 } 681 } 682 683 func TestSingleWatch(t *testing.T) { 684 result := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd()) 685 defer result.TearDownFn() 686 687 client := clientset.NewForConfigOrDie(result.ClientConfig) 688 689 mkEvent := func(i int) *v1.Event { 690 name := fmt.Sprintf("event-%v", i) 691 return &v1.Event{ 692 ObjectMeta: metav1.ObjectMeta{ 693 Namespace: "default", 694 Name: name, 695 }, 696 InvolvedObject: v1.ObjectReference{ 697 Namespace: "default", 698 Name: name, 699 }, 700 Reason: fmt.Sprintf("event %v", i), 701 } 702 } 703 704 rv1 := "" 705 for i := 0; i < 10; i++ { 706 event := mkEvent(i) 707 got, err := client.CoreV1().Events("default").Create(context.TODO(), event, metav1.CreateOptions{}) 708 if err != nil { 709 t.Fatalf("Failed creating event %#q: %v", event, err) 710 } 711 if rv1 == "" { 712 rv1 = got.ResourceVersion 713 if rv1 == "" { 714 t.Fatal("did not get a resource version.") 715 } 716 } 717 t.Logf("Created event %#v", got.ObjectMeta) 718 } 719 720 w, err := client.CoreV1().RESTClient().Get(). 721 Namespace("default"). 722 Resource("events"). 723 VersionedParams(&metav1.ListOptions{ 724 ResourceVersion: rv1, 725 Watch: true, 726 FieldSelector: fields.OneTermEqualSelector("metadata.name", "event-9").String(), 727 }, metav1.ParameterCodec). 728 Watch(context.TODO()) 729 730 if err != nil { 731 t.Fatalf("Failed watch: %v", err) 732 } 733 defer w.Stop() 734 735 select { 736 case <-time.After(wait.ForeverTestTimeout): 737 t.Fatalf("watch took longer than %s", wait.ForeverTestTimeout.String()) 738 case got, ok := <-w.ResultChan(): 739 if !ok { 740 t.Fatal("Watch channel closed unexpectedly.") 741 } 742 743 // We expect to see an ADD of event-9 and only event-9. (This 744 // catches a bug where all the events would have been sent down 745 // the channel.) 746 if e, a := watch.Added, got.Type; e != a { 747 t.Errorf("Wanted %v, got %v", e, a) 748 } 749 switch o := got.Object.(type) { 750 case *v1.Event: 751 if e, a := "event-9", o.Name; e != a { 752 t.Errorf("Wanted %v, got %v", e, a) 753 } 754 default: 755 t.Fatalf("Unexpected watch event containing object %#q", got) 756 } 757 } 758 } 759 760 func TestMultiWatch(t *testing.T) { 761 // Disable this test as long as it demonstrates a problem. 762 // TODO: Re-enable this test when we get #6059 resolved. 763 t.Skip() 764 765 const watcherCount = 50 766 rt.GOMAXPROCS(watcherCount) 767 768 result := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, framework.SharedEtcd()) 769 defer result.TearDownFn() 770 771 client := clientset.NewForConfigOrDie(result.ClientConfig) 772 773 dummyEvent := func(i int) *v1.Event { 774 name := fmt.Sprintf("unrelated-%v", i) 775 return &v1.Event{ 776 ObjectMeta: metav1.ObjectMeta{ 777 Name: fmt.Sprintf("%v.%x", name, time.Now().UnixNano()), 778 Namespace: "default", 779 }, 780 InvolvedObject: v1.ObjectReference{ 781 Name: name, 782 Namespace: "default", 783 }, 784 Reason: fmt.Sprintf("unrelated change %v", i), 785 } 786 } 787 788 type timePair struct { 789 t time.Time 790 name string 791 } 792 793 receivedTimes := make(chan timePair, watcherCount*2) 794 watchesStarted := sync.WaitGroup{} 795 796 // make a bunch of pods and watch them 797 for i := 0; i < watcherCount; i++ { 798 watchesStarted.Add(1) 799 name := fmt.Sprintf("multi-watch-%v", i) 800 got, err := client.CoreV1().Pods("default").Create(context.TODO(), &v1.Pod{ 801 ObjectMeta: metav1.ObjectMeta{ 802 Name: name, 803 Labels: labels.Set{"watchlabel": name}, 804 }, 805 Spec: v1.PodSpec{ 806 Containers: []v1.Container{{ 807 Name: "pause", 808 Image: imageutils.GetPauseImageName(), 809 }}, 810 }, 811 }, metav1.CreateOptions{}) 812 813 if err != nil { 814 t.Fatalf("Couldn't make %v: %v", name, err) 815 } 816 go func(name, rv string) { 817 options := metav1.ListOptions{ 818 LabelSelector: labels.Set{"watchlabel": name}.AsSelector().String(), 819 ResourceVersion: rv, 820 } 821 w, err := client.CoreV1().Pods("default").Watch(context.TODO(), options) 822 if err != nil { 823 panic(fmt.Sprintf("watch error for %v: %v", name, err)) 824 } 825 defer w.Stop() 826 watchesStarted.Done() 827 e, ok := <-w.ResultChan() // should get the update (that we'll do below) 828 if !ok { 829 panic(fmt.Sprintf("%v ended early?", name)) 830 } 831 if e.Type != watch.Modified { 832 panic(fmt.Sprintf("Got unexpected watch notification:\n%v: %+v %+v", name, e, e.Object)) 833 } 834 receivedTimes <- timePair{time.Now(), name} 835 }(name, got.ObjectMeta.ResourceVersion) 836 } 837 log.Printf("%v: %v pods made and watchers started", time.Now(), watcherCount) 838 839 // wait for watches to start before we start spamming the system with 840 // objects below, otherwise we'll hit the watch window restriction. 841 watchesStarted.Wait() 842 843 const ( 844 useEventsAsUnrelatedType = false 845 usePodsAsUnrelatedType = true 846 ) 847 848 // make a bunch of unrelated changes in parallel 849 if useEventsAsUnrelatedType { 850 const unrelatedCount = 3000 851 var wg sync.WaitGroup 852 defer wg.Wait() 853 changeToMake := make(chan int, unrelatedCount*2) 854 changeMade := make(chan int, unrelatedCount*2) 855 go func() { 856 for i := 0; i < unrelatedCount; i++ { 857 changeToMake <- i 858 } 859 close(changeToMake) 860 }() 861 for i := 0; i < 50; i++ { 862 wg.Add(1) 863 go func() { 864 defer wg.Done() 865 for { 866 i, ok := <-changeToMake 867 if !ok { 868 return 869 } 870 if _, err := client.CoreV1().Events("default").Create(context.TODO(), dummyEvent(i), metav1.CreateOptions{}); err != nil { 871 panic(fmt.Sprintf("couldn't make an event: %v", err)) 872 } 873 changeMade <- i 874 } 875 }() 876 } 877 878 for i := 0; i < 2000; i++ { 879 <-changeMade 880 if (i+1)%50 == 0 { 881 log.Printf("%v: %v unrelated changes made", time.Now(), i+1) 882 } 883 } 884 } 885 if usePodsAsUnrelatedType { 886 const unrelatedCount = 3000 887 var wg sync.WaitGroup 888 defer wg.Wait() 889 changeToMake := make(chan int, unrelatedCount*2) 890 changeMade := make(chan int, unrelatedCount*2) 891 go func() { 892 for i := 0; i < unrelatedCount; i++ { 893 changeToMake <- i 894 } 895 close(changeToMake) 896 }() 897 for i := 0; i < 50; i++ { 898 wg.Add(1) 899 go func() { 900 defer wg.Done() 901 for { 902 i, ok := <-changeToMake 903 if !ok { 904 return 905 } 906 name := fmt.Sprintf("unrelated-%v", i) 907 _, err := client.CoreV1().Pods("default").Create(context.TODO(), &v1.Pod{ 908 ObjectMeta: metav1.ObjectMeta{ 909 Name: name, 910 }, 911 Spec: v1.PodSpec{ 912 Containers: []v1.Container{{ 913 Name: "nothing", 914 Image: imageutils.GetPauseImageName(), 915 }}, 916 }, 917 }, metav1.CreateOptions{}) 918 919 if err != nil { 920 panic(fmt.Sprintf("couldn't make unrelated pod: %v", err)) 921 } 922 changeMade <- i 923 } 924 }() 925 } 926 927 for i := 0; i < 2000; i++ { 928 <-changeMade 929 if (i+1)%50 == 0 { 930 log.Printf("%v: %v unrelated changes made", time.Now(), i+1) 931 } 932 } 933 } 934 935 // Now we still have changes being made in parallel, but at least 1000 have been made. 936 // Make some updates to send down the watches. 937 sentTimes := make(chan timePair, watcherCount*2) 938 for i := 0; i < watcherCount; i++ { 939 go func(i int) { 940 name := fmt.Sprintf("multi-watch-%v", i) 941 pod, err := client.CoreV1().Pods("default").Get(context.TODO(), name, metav1.GetOptions{}) 942 if err != nil { 943 panic(fmt.Sprintf("Couldn't get %v: %v", name, err)) 944 } 945 pod.Spec.Containers[0].Image = imageutils.GetPauseImageName() 946 sentTimes <- timePair{time.Now(), name} 947 if _, err := client.CoreV1().Pods("default").Update(context.TODO(), pod, metav1.UpdateOptions{}); err != nil { 948 panic(fmt.Sprintf("Couldn't make %v: %v", name, err)) 949 } 950 }(i) 951 } 952 953 sent := map[string]time.Time{} 954 for i := 0; i < watcherCount; i++ { 955 tp := <-sentTimes 956 sent[tp.name] = tp.t 957 } 958 log.Printf("all changes made") 959 dur := map[string]time.Duration{} 960 for i := 0; i < watcherCount; i++ { 961 tp := <-receivedTimes 962 delta := tp.t.Sub(sent[tp.name]) 963 dur[tp.name] = delta 964 log.Printf("%v: %v", tp.name, delta) 965 } 966 log.Printf("all watches ended") 967 t.Errorf("durations: %v", dur) 968 } 969 970 func TestApplyWithApplyConfiguration(t *testing.T) { 971 deployment := appsv1ac.Deployment("nginx-deployment-3", "default"). 972 WithSpec(appsv1ac.DeploymentSpec(). 973 WithSelector(metav1ac.LabelSelector(). 974 WithMatchLabels(map[string]string{"app": "nginx"}), 975 ). 976 WithTemplate(corev1ac.PodTemplateSpec(). 977 WithLabels(map[string]string{"app": "nginx"}). 978 WithSpec(corev1ac.PodSpec(). 979 WithContainers(corev1ac.Container(). 980 WithName("nginx"). 981 WithImage("nginx:1.14.2"). 982 WithStdin(true). 983 WithPorts(corev1ac.ContainerPort(). 984 WithContainerPort(8080). 985 WithProtocol(v1.ProtocolTCP), 986 ). 987 WithResources(corev1ac.ResourceRequirements(). 988 WithLimits(v1.ResourceList{ 989 v1.ResourceCPU: resource.MustParse("4"), 990 v1.ResourceMemory: resource.MustParse("32Gi"), 991 }), 992 ), 993 ), 994 ), 995 ), 996 ) 997 testServer := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--disable-admission-plugins", "ServiceAccount"}, framework.SharedEtcd()) 998 defer testServer.TearDownFn() 999 1000 c := clientset.NewForConfigOrDie(testServer.ClientConfig) 1001 1002 // Test apply to spec 1003 obj, err := c.AppsV1().Deployments("default").Apply(context.TODO(), deployment, metav1.ApplyOptions{FieldManager: "test-mgr", Force: true}) 1004 if err != nil { 1005 t.Fatalf("unexpected error when applying manifest for Deployment: %v", err) 1006 } 1007 if obj.Spec.Template.Spec.Containers[0].Image != "nginx:1.14.2" { 1008 t.Errorf("expected image %s but got %s", "nginx:1.14.2", obj.Spec.Template.Spec.Containers[0].Image) 1009 } 1010 cpu := obj.Spec.Template.Spec.Containers[0].Resources.Limits[v1.ResourceCPU] 1011 if cpu.Value() != int64(4) { 1012 t.Errorf("expected resourceCPU limit %d but got %d", 4, cpu.Value()) 1013 } 1014 1015 // Test apply to status 1016 statusApply := appsv1ac.Deployment("nginx-deployment-3", "default"). 1017 WithStatus(appsv1ac.DeploymentStatus(). 1018 WithConditions( 1019 appsv1ac.DeploymentCondition(). 1020 WithType(appsv1.DeploymentReplicaFailure). 1021 WithStatus(v1.ConditionUnknown). 1022 WithLastTransitionTime(metav1.Now()). 1023 WithLastUpdateTime(metav1.Now()). 1024 WithMessage("apply status test"). 1025 WithReason("TestApplyWithApplyConfiguration"), 1026 ), 1027 ) 1028 obj, err = c.AppsV1().Deployments("default").ApplyStatus(context.TODO(), statusApply, metav1.ApplyOptions{FieldManager: "test-mgr", Force: true}) 1029 if err != nil { 1030 t.Fatalf("unexpected error when applying manifest for Deployment: %v", err) 1031 } 1032 var found bool 1033 for _, c := range obj.Status.Conditions { 1034 if c.Type == appsv1.DeploymentReplicaFailure && c.Status == v1.ConditionUnknown && 1035 c.Message == "apply status test" && c.Reason == "TestApplyWithApplyConfiguration" { 1036 found = true 1037 break 1038 } 1039 } 1040 if !found { 1041 t.Error("expected status to contain DeploymentReplicaFailure condition set by apply") 1042 } 1043 } 1044 1045 func TestExtractModifyApply(t *testing.T) { 1046 testCases := []struct { 1047 name string 1048 // modifyFunc modifies deployApply, defined below, after it is applied and "extracted" 1049 // apply is skipped if this func is nil 1050 modifyFunc func(apply *appsv1ac.DeploymentApplyConfiguration) 1051 modifyStatusFunc func(apply *appsv1ac.DeploymentApplyConfiguration) // same but for status 1052 // verifyAppliedFunc verifies the results of applying the applied 1053 // configuration after modifyFunc modifies it. Only called if modifyFunc is provided. 1054 verifyAppliedFunc func(applied *appsv1ac.DeploymentApplyConfiguration) 1055 verifyStatusAppliedFunc func(applied *appsv1ac.DeploymentApplyConfiguration) // same but for status 1056 }{ 1057 { 1058 // With<fieldname>() on a scalar field replaces it with the given value 1059 name: "modify-scalar", 1060 modifyFunc: func(apply *appsv1ac.DeploymentApplyConfiguration) { 1061 apply.Spec.WithReplicas(2) 1062 }, 1063 verifyAppliedFunc: func(applied *appsv1ac.DeploymentApplyConfiguration) { 1064 if *applied.Spec.Replicas != 2 { 1065 t.Errorf("Expected 2 replicas but got: %d", *applied.Spec.Replicas) 1066 } 1067 }, 1068 }, 1069 { 1070 // With<fieldname>() on a non-empty struct field replaces the entire struct 1071 name: "modify-struct", 1072 modifyFunc: func(apply *appsv1ac.DeploymentApplyConfiguration) { 1073 apply.Spec.Template.WithSpec(corev1ac.PodSpec(). // replace the Spec of the existing Template 1074 WithContainers( 1075 corev1ac.Container(). 1076 WithName("modify-struct"). 1077 WithImage("nginx:1.14.3"), 1078 ), 1079 ) 1080 }, 1081 verifyAppliedFunc: func(applied *appsv1ac.DeploymentApplyConfiguration) { 1082 containers := applied.Spec.Template.Spec.Containers 1083 if len(containers) != 1 { 1084 t.Errorf("Expected 1 container but got %d", len(containers)) 1085 } 1086 if *containers[0].Name != "modify-struct" { 1087 t.Errorf("Expected container name modify-struct but got: %s", *containers[0].Name) 1088 } 1089 }, 1090 }, 1091 { 1092 // With<fieldname>() on a non-empty map field puts all the given entries into the existing map 1093 name: "modify-map", 1094 modifyFunc: func(apply *appsv1ac.DeploymentApplyConfiguration) { 1095 apply.WithLabels(map[string]string{"label2": "value2"}) 1096 }, 1097 verifyAppliedFunc: func(applied *appsv1ac.DeploymentApplyConfiguration) { 1098 labels := applied.Labels 1099 if len(labels) != 2 { 1100 t.Errorf("Expected 2 label but got %d", len(labels)) 1101 } 1102 if labels["label2"] != "value2" { 1103 t.Errorf("Expected container name value2 but got: %s", labels["label2"]) 1104 } 1105 }, 1106 }, 1107 { 1108 // With<fieldname>() on a non-empty slice field appends all the given items to the existing slice 1109 name: "modify-slice", 1110 modifyFunc: func(apply *appsv1ac.DeploymentApplyConfiguration) { 1111 apply.Spec.Template.Spec.WithContainers(corev1ac.Container(). 1112 WithName("modify-slice"). 1113 WithImage("nginx:1.14.2"), 1114 ) 1115 }, 1116 verifyAppliedFunc: func(applied *appsv1ac.DeploymentApplyConfiguration) { 1117 containers := applied.Spec.Template.Spec.Containers 1118 if len(containers) != 2 { 1119 t.Errorf("Expected 2 containers but got %d", len(containers)) 1120 } 1121 if *containers[0].Name != "initial-container" { 1122 t.Errorf("Expected container name initial-container but got: %s", *containers[0].Name) 1123 } 1124 if *containers[1].Name != "modify-slice" { 1125 t.Errorf("Expected container name modify-slice but got: %s", *containers[1].Name) 1126 } 1127 }, 1128 }, 1129 { 1130 // Append a condition to the status if the object 1131 name: "modify-status-conditions", 1132 modifyStatusFunc: func(apply *appsv1ac.DeploymentApplyConfiguration) { 1133 apply.WithStatus(appsv1ac.DeploymentStatus(). 1134 WithConditions(appsv1ac.DeploymentCondition(). 1135 WithType(appsv1.DeploymentProgressing). 1136 WithStatus(v1.ConditionUnknown). 1137 WithLastTransitionTime(metav1.Now()). 1138 WithLastUpdateTime(metav1.Now()). 1139 WithMessage("progressing"). 1140 WithReason("TestExtractModifyApply_Status"), 1141 ), 1142 ) 1143 }, 1144 verifyStatusAppliedFunc: func(applied *appsv1ac.DeploymentApplyConfiguration) { 1145 conditions := applied.Status.Conditions 1146 if len(conditions) != 1 { 1147 t.Errorf("Expected 1 conditions but got %d", len(conditions)) 1148 } 1149 if *conditions[0].Type != appsv1.DeploymentProgressing { 1150 t.Errorf("Expected condition name DeploymentProgressing but got: %s", *conditions[0].Type) 1151 } 1152 }, 1153 }, 1154 } 1155 1156 testServer := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--disable-admission-plugins", "ServiceAccount"}, framework.SharedEtcd()) 1157 defer testServer.TearDownFn() 1158 c := clientset.NewForConfigOrDie(testServer.ClientConfig) 1159 deploymentClient := c.AppsV1().Deployments("default") 1160 fieldMgr := "test-mgr" 1161 1162 for _, tc := range testCases { 1163 t.Run(tc.name, func(t *testing.T) { 1164 // Applied at the started of each test 1165 deployApply := appsv1ac.Deployment(tc.name, "default"). 1166 WithLabels(map[string]string{"label1": "value1"}). 1167 WithSpec(appsv1ac.DeploymentSpec(). 1168 WithSelector(metav1ac.LabelSelector(). 1169 WithMatchLabels(map[string]string{"app": tc.name}), 1170 ). 1171 WithTemplate(corev1ac.PodTemplateSpec(). 1172 WithLabels(map[string]string{"app": tc.name}). 1173 WithSpec(corev1ac.PodSpec(). 1174 WithContainers( 1175 corev1ac.Container(). 1176 WithName("initial-container"). 1177 WithImage("nginx:1.14.2"), 1178 ), 1179 ), 1180 ), 1181 ) 1182 actual, err := deploymentClient.Apply(context.TODO(), deployApply, metav1.ApplyOptions{FieldManager: fieldMgr}) 1183 if err != nil { 1184 t.Fatalf("Failed to apply: %v", err) 1185 } 1186 if tc.modifyFunc != nil { 1187 extractedDeployment, err := appsv1ac.ExtractDeployment(actual, fieldMgr) 1188 if err != nil { 1189 t.Fatalf("Failed to extract: %v", err) 1190 } 1191 tc.modifyFunc(extractedDeployment) 1192 result, err := deploymentClient.Apply(context.TODO(), extractedDeployment, metav1.ApplyOptions{FieldManager: fieldMgr}) 1193 if err != nil { 1194 t.Fatalf("Failed to apply extracted apply configuration: %v", err) 1195 } 1196 extractedResult, err := appsv1ac.ExtractDeployment(result, fieldMgr) 1197 if err != nil { 1198 t.Fatalf("Failed to extract: %v", err) 1199 } 1200 if tc.verifyAppliedFunc != nil { 1201 tc.verifyAppliedFunc(extractedResult) 1202 } 1203 } 1204 1205 if tc.modifyStatusFunc != nil { 1206 extractedDeployment, err := appsv1ac.ExtractDeploymentStatus(actual, fieldMgr) 1207 if err != nil { 1208 t.Fatalf("Failed to extract: %v", err) 1209 } 1210 tc.modifyStatusFunc(extractedDeployment) 1211 result, err := deploymentClient.ApplyStatus(context.TODO(), extractedDeployment, metav1.ApplyOptions{FieldManager: fieldMgr}) 1212 if err != nil { 1213 t.Fatalf("Failed to apply extracted apply configuration to status: %v", err) 1214 } 1215 extractedResult, err := appsv1ac.ExtractDeploymentStatus(result, fieldMgr) 1216 if err != nil { 1217 t.Fatalf("Failed to extract: %v", err) 1218 } 1219 if tc.verifyStatusAppliedFunc != nil { 1220 tc.verifyStatusAppliedFunc(extractedResult) 1221 } 1222 } 1223 }) 1224 } 1225 } 1226 1227 func TestExtractModifyApply_ForceOwnership(t *testing.T) { 1228 testServer := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{"--disable-admission-plugins", "ServiceAccount"}, framework.SharedEtcd()) 1229 defer testServer.TearDownFn() 1230 c := clientset.NewForConfigOrDie(testServer.ClientConfig) 1231 deploymentClient := c.AppsV1().Deployments("default") 1232 1233 // apply an initial state with one field manager 1234 createApply := appsv1ac.Deployment("nginx-apply", "default"). 1235 WithSpec(appsv1ac.DeploymentSpec(). 1236 WithSelector(metav1ac.LabelSelector(). 1237 WithMatchLabels(map[string]string{"app": "nginx"}), 1238 ). 1239 WithTemplate(corev1ac.PodTemplateSpec(). 1240 WithLabels(map[string]string{"app": "nginx"}). 1241 WithSpec(corev1ac.PodSpec(). 1242 WithContainers( 1243 corev1ac.Container(). 1244 WithName("nginx"). 1245 WithImage("nginx:1.14.2"). 1246 WithWorkingDir("/tmp/v1"), 1247 ), 1248 ), 1249 ), 1250 ) 1251 1252 _, err := deploymentClient.Apply(context.TODO(), createApply, metav1.ApplyOptions{FieldManager: "create-mgr", Force: true}) 1253 if err != nil { 1254 t.Fatalf("Error creating createApply: %v", err) 1255 } 1256 1257 // apply some non-overlapping fields with another field manager 1258 sidecarApply := appsv1ac.Deployment("nginx-apply", "default"). 1259 WithSpec(appsv1ac.DeploymentSpec(). 1260 WithTemplate(corev1ac.PodTemplateSpec(). 1261 WithSpec(corev1ac.PodSpec(). 1262 WithContainers( 1263 corev1ac.Container(). 1264 WithName("sidecar"). 1265 WithImage("nginx:1.14.2"), 1266 ), 1267 ), 1268 ), 1269 ) 1270 1271 applied, err := deploymentClient.Apply(context.TODO(), sidecarApply, metav1.ApplyOptions{FieldManager: "sidecar-mgr", Force: true}) 1272 if err != nil { 1273 t.Fatalf("Error applying createApply: %v", err) 1274 } 1275 sidecarExtracted, err := appsv1ac.ExtractDeployment(applied, "sidecar-mgr") 1276 if err != nil { 1277 t.Fatalf("Error extracting createApply apply configuration: %v", err) 1278 } 1279 if !equality.Semantic.DeepEqual(sidecarApply, sidecarExtracted) { 1280 t.Errorf("Expected sidecarExtracted apply configuration to match original, but got:\n%s\n", cmp.Diff(sidecarApply, sidecarExtracted)) 1281 } 1282 1283 // modify the extracted apply configuration that was just applied and add some fields that overlap 1284 // with the fields owned by the other field manager to force ownership of them 1285 sidecarExtracted.Spec.Template.Spec.Containers[0].WithImage("nginx:1.14.3") 1286 sidecarExtracted.Spec.Template.Spec.WithContainers(corev1ac.Container(). 1287 WithName("nginx"). 1288 WithWorkingDir("/tmp/v2"), 1289 ) 1290 reapplied, err := deploymentClient.Apply(context.TODO(), sidecarExtracted, metav1.ApplyOptions{FieldManager: "sidecar-mgr", Force: true}) 1291 if err != nil { 1292 t.Fatalf("Unexpected error when applying manifest for Deployment: %v", err) 1293 } 1294 1295 // extract apply configurations for both field managers and check that they are what we expect 1296 reappliedExtracted, err := appsv1ac.ExtractDeployment(reapplied, "sidecar-mgr") 1297 if err != nil { 1298 t.Fatalf("Error extracting sidecarExtracted apply configuration: %v", err) 1299 } 1300 1301 expectedReappliedExtracted := appsv1ac.Deployment("nginx-apply", "default"). 1302 WithSpec(appsv1ac.DeploymentSpec(). 1303 WithTemplate(corev1ac.PodTemplateSpec(). 1304 WithSpec(corev1ac.PodSpec(). 1305 WithContainers( 1306 corev1ac.Container(). 1307 WithName("sidecar"). 1308 WithImage("nginx:1.14.3"), 1309 corev1ac.Container(). 1310 WithName("nginx"). 1311 WithWorkingDir("/tmp/v2"), 1312 ), 1313 ), 1314 ), 1315 ) 1316 if !equality.Semantic.DeepEqual(expectedReappliedExtracted, reappliedExtracted) { 1317 t.Errorf("Reapplied apply configuration did not match expected, got:\n%s\n", cmp.Diff(expectedReappliedExtracted, reappliedExtracted)) 1318 } 1319 1320 createMgrExtracted, err := appsv1ac.ExtractDeployment(reapplied, "create-mgr") 1321 if err != nil { 1322 t.Fatalf("Error extracting createApply apply configuration: %v", err) 1323 } 1324 1325 expectedCreateExtracted := appsv1ac.Deployment("nginx-apply", "default"). 1326 WithSpec(appsv1ac.DeploymentSpec(). 1327 WithSelector(metav1ac.LabelSelector(). 1328 WithMatchLabels(map[string]string{"app": "nginx"}), 1329 ). 1330 WithTemplate(corev1ac.PodTemplateSpec(). 1331 WithLabels(map[string]string{"app": "nginx"}). 1332 WithSpec(corev1ac.PodSpec(). 1333 WithContainers( 1334 corev1ac.Container(). 1335 WithName("nginx"). 1336 WithImage("nginx:1.14.2"), 1337 ), 1338 ), 1339 ), 1340 ) 1341 if !equality.Semantic.DeepEqual(expectedCreateExtracted, createMgrExtracted) { 1342 t.Errorf("createMgrExtracted apply configuration did not match expected, got:\n%s\n", cmp.Diff(expectedCreateExtracted, createMgrExtracted)) 1343 } 1344 }