1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package dynamic_test
19
20 import (
21 "context"
22 "flag"
23 "fmt"
24 "net/http"
25 "os"
26 "path/filepath"
27 "reflect"
28 "regexp"
29 "strings"
30 "testing"
31 "time"
32
33 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/apis/core/v1alpha1"
34 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/dynamic"
35 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/resourceactuation"
36 dclextension "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/dcl/extension"
37 dclmetadata "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/dcl/metadata"
38 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/gcp"
39 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/k8s"
40 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/servicemapping/servicemappingloader"
41 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test"
42 testcontroller "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/controller"
43 testreconciler "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/controller/reconciler"
44 testgcp "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/gcp"
45 testk8s "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/k8s"
46 testmain "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/main"
47 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/resourcefixture"
48 "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/resourcefixture/contexts"
49 testrunner "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/runner"
50 testservicemapping "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/servicemapping"
51
52 "github.com/cenkalti/backoff"
53 "github.com/ghodss/yaml"
54 transport_tpg "github.com/hashicorp/terraform-provider-google-beta/google-beta/transport"
55 "k8s.io/apimachinery/pkg/api/errors"
56 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
57 "sigs.k8s.io/controller-runtime/pkg/client"
58 "sigs.k8s.io/controller-runtime/pkg/log"
59 "sigs.k8s.io/controller-runtime/pkg/manager"
60 )
61
62 type httpRoundTripperKeyType int
63
64
65 var httpRoundTripperKey httpRoundTripperKeyType
66
67 func init() {
68
69
70
71 flag.StringVar(&runTestsRegex, "run-tests", "", "run only the tests whose names match the given regex")
72 flag.StringVar(&skipTestsRegex, "skip-tests", "", "skip the tests whose names match the given regex, even those that match the run-tests regex")
73
74
75
76
77
78 flag.BoolVar(&cleanupResources, "cleanup-resources", true, "when enabled, "+
79 "cloud resources created by tests will be cleaned up at the end of a test")
80
81
82 transport_tpg.DefaultHTTPClientTransformer = func(ctx context.Context, inner *http.Client) *http.Client {
83 ret := inner
84 if t := ctx.Value(httpRoundTripperKey); t != nil {
85 ret = &http.Client{Transport: t.(http.RoundTripper)}
86 }
87 if artifacts := os.Getenv("ARTIFACTS"); artifacts == "" {
88 log := log.FromContext(ctx)
89 log.Info("env var ARTIFACTS is not set; will not record http log")
90 } else {
91 outputDir := filepath.Join(artifacts, "http-logs")
92 t := test.NewHTTPRecorder(ret.Transport, outputDir)
93 ret = &http.Client{Transport: t}
94 }
95 return ret
96 }
97 }
98
99 var (
100 mgr manager.Manager
101 runTestsRegex string
102 skipTestsRegex string
103 cleanupResources bool
104 )
105
106 const resourceIDTestVar = "${resourceId}"
107
108 func shouldRunBasedOnRunAndSkipRegexes(parentTestName string, fixture resourcefixture.ResourceFixture) bool {
109 testName := formatTestName(parentTestName, fixture)
110
111
112 if skipTestsRegex != "" {
113 if regexp.MustCompile(skipTestsRegex).MatchString(testName) {
114 return false
115 }
116 }
117
118
119 if runTestsRegex != "" {
120 if !regexp.MustCompile(runTestsRegex).MatchString(testName) {
121 return false
122 }
123 }
124
125 return true
126 }
127
128 func TestAcquire(t *testing.T) {
129 t.Parallel()
130 shouldRun := func(fixture resourcefixture.ResourceFixture, mgr manager.Manager) bool {
131 if !shouldRunBasedOnRunAndSkipRegexes("TestAcquire", fixture) {
132 return false
133 }
134
135
136
137
138
139
140
141
142
143 if fixture.Type == resourcefixture.ContainerAnnotations {
144 return false
145 }
146
147
148
149 if fixture.Type == resourcefixture.ResourceID {
150 return true
151 }
152
153
154
155
156
157
158 kinds := map[string]bool{
159
160 "PubSubTopic": true,
161
162 "PubSubSubscription": true,
163
164 "BigQueryTable": true,
165
166
167 "Folder": true,
168
169
170 "ComputeNetwork": true,
171 }
172 return kinds[fixture.GVK.Kind]
173 }
174 testFunc := func(t *testing.T, testContext testrunner.TestContext, systemContext testrunner.SystemContext) {
175 context := contexts.GetResourceContext(testContext.ResourceFixture, systemContext.DCLConverter.MetadataLoader, systemContext.DCLConverter.SchemaLoader)
176 testReconcileAcquire(t, testContext, systemContext, context)
177 }
178 testrunner.RunAllWithDependenciesCreatedButNotObject(t, mgr, shouldRun, testFunc)
179 }
180
181 func TestCreateNoChangeUpdateDelete(t *testing.T) {
182 t.Parallel()
183 shouldRun := func(fixture resourcefixture.ResourceFixture, mgr manager.Manager) bool {
184 switch fixture.Type {
185 case resourcefixture.IAMExternalOnlyRef, resourcefixture.IAMMemberReferences:
186 return false
187 }
188
189
190
191
192 if fixture.Type == resourcefixture.ResourceID {
193 switch fixture.Name {
194 case "servergeneratedresourceid":
195 return false
196 case "servergeneratedresourceidfordcl":
197 return false
198 }
199 }
200
201 return shouldRunBasedOnRunAndSkipRegexes("TestCreateNoChangeUpdateDelete", fixture)
202 }
203 testFunc := func(t *testing.T, testContext testrunner.TestContext, systemContext testrunner.SystemContext) {
204 context := contexts.GetResourceContext(testContext.ResourceFixture, systemContext.DCLConverter.MetadataLoader, systemContext.DCLConverter.SchemaLoader)
205 testReconcileCreateNoChangeUpdateDelete(t, testContext, systemContext, context)
206 }
207 testrunner.RunAllWithDependenciesCreatedButNotObject(t, mgr, shouldRun, testFunc)
208 }
209
210 func formatTestName(parentTestName string, fixture resourcefixture.ResourceFixture) string {
211 return fmt.Sprintf("%v/%v", parentTestName, resourcefixture.FormatTestName(fixture))
212 }
213 func testCreate(t *testing.T, testContext testrunner.TestContext, systemContext testrunner.SystemContext, resourceContext contexts.ResourceContext) {
214 kubeClient := systemContext.Manager.GetClient()
215 initialUnstruct := testContext.CreateUnstruct.DeepCopy()
216 if err := kubeClient.Create(context.TODO(), initialUnstruct); err != nil {
217 t.Fatalf("error creating %v resource %v: %v", initialUnstruct.GetKind(), initialUnstruct.GetName(), err)
218 }
219 t.Logf("resource created with %v\r", initialUnstruct)
220 systemContext.Reconciler.Reconcile(initialUnstruct, testreconciler.ExpectedSuccessfulReconcileResultFor(systemContext.Reconciler, initialUnstruct), nil)
221 validateCreate(t, testContext, systemContext, resourceContext, initialUnstruct.GetGeneration())
222 }
223
224 func validateCreate(t *testing.T, testContext testrunner.TestContext, systemContext testrunner.SystemContext,
225 resourceContext contexts.ResourceContext, preReconcileGeneration int64) {
226 kubeClient := systemContext.Manager.GetClient()
227 initialUnstruct := testContext.CreateUnstruct.DeepCopy()
228
229 reconciledUnstruct := &unstructured.Unstructured{
230 Object: map[string]interface{}{
231 "kind": initialUnstruct.GetKind(),
232 "apiVersion": initialUnstruct.GetAPIVersion(),
233 },
234 }
235 if err := kubeClient.Get(context.TODO(), testContext.NamespacedName, reconciledUnstruct); err != nil {
236 t.Fatalf("unexpected error getting k8s resource: %v", err)
237 }
238 gcpUnstruct, err := resourceContext.Get(t, reconciledUnstruct, systemContext.TFProvider, kubeClient, systemContext.SMLoader, systemContext.DCLConfig, systemContext.DCLConverter)
239 if err != nil {
240 t.Fatalf("unexpected error when GETting '%v': %v", initialUnstruct.GetName(), err)
241 }
242 t.Logf("created resource is %v\r", gcpUnstruct)
243 if resourceContext.SupportsLabels(systemContext.SMLoader) {
244 testcontroller.AssertLabelsMatchAndHaveManagedLabel(t, gcpUnstruct.GetLabels(), reconciledUnstruct.GetLabels())
245 }
246
247
248
249 testcontroller.AssertEventRecordedforUnstruct(t, kubeClient, reconciledUnstruct, k8s.Updating)
250
251
252
253 conditions := dynamic.GetConditions(t, reconciledUnstruct)
254 testcontroller.AssertReadyCondition(t, conditions)
255 testcontroller.AssertEventRecordedforUnstruct(t, kubeClient, reconciledUnstruct, k8s.UpToDate)
256
257 verifyResourceIDIfSupported(t, systemContext, resourceContext, reconciledUnstruct, initialUnstruct)
258
259 generationIncrease := reconciledUnstruct.GetGeneration() - preReconcileGeneration
260 if generationIncrease > 1 {
261
262 t.Fatalf("unexpected generation increase %v", generationIncrease)
263 }
264
265
266 testcontroller.AssertObservedGenerationEquals(t, reconciledUnstruct, preReconcileGeneration)
267 }
268
269
270
271 func testNoChange(t *testing.T, testContext testrunner.TestContext, systemContext testrunner.SystemContext, resourceContext contexts.ResourceContext) {
272 if resourceContext.SkipNoChange {
273 return
274 }
275 kubeClient := systemContext.Manager.GetClient()
276 initialUnstruct := testContext.CreateUnstruct.DeepCopy()
277 if err := kubeClient.Get(context.TODO(), testContext.NamespacedName, initialUnstruct); err != nil {
278 t.Fatalf("unexpected error getting k8s resource: %v", err)
279 }
280 preReconcileGeneration := initialUnstruct.GetGeneration()
281
282
283
284 testcontroller.DeleteAllEventsForUnstruct(t, kubeClient, initialUnstruct)
285
286
287 reconciledUnstruct := &unstructured.Unstructured{
288 Object: map[string]interface{}{
289 "kind": initialUnstruct.GetKind(),
290 "apiVersion": initialUnstruct.GetAPIVersion(),
291 },
292 }
293 systemContext.Reconciler.Reconcile(initialUnstruct, testreconciler.ExpectedSuccessfulReconcileResultFor(systemContext.Reconciler, initialUnstruct), nil)
294 if err := kubeClient.Get(context.TODO(), testContext.NamespacedName, reconciledUnstruct); err != nil {
295 t.Fatalf("unexpected error getting k8s resource: %v", err)
296 }
297
298 if reconciledUnstruct.GetGeneration() != initialUnstruct.GetGeneration() {
299 t.Errorf("generation incremented during expected no-op")
300 }
301
302 if testContext.ResourceFixture.GVK.Kind == "DataflowJob" {
303
304
305
306
307 checkDataflowJobNoChange(t, initialUnstruct, reconciledUnstruct)
308 }
309
310
311 testcontroller.AssertEventNotRecordedforUnstruct(t, kubeClient, initialUnstruct, k8s.Updating)
312
313
314 testcontroller.AssertObservedGenerationEquals(t, reconciledUnstruct, preReconcileGeneration)
315 }
316
317 func testUpdate(t *testing.T, testContext testrunner.TestContext, systemContext testrunner.SystemContext, resourceContext contexts.ResourceContext) {
318
319
320 if resourceContext.SkipUpdate || resourceContext.IsAutoGenerated(systemContext.SMLoader) {
321 return
322 }
323 kubeClient := systemContext.Manager.GetClient()
324 initialUnstruct := testContext.CreateUnstruct.DeepCopy()
325 if err := kubeClient.Get(context.TODO(), testContext.NamespacedName, initialUnstruct); err != nil {
326 t.Fatalf("unexpected error getting k8s resource: %v", err)
327 }
328
329
330
331 testcontroller.DeleteAllEventsForUnstruct(t, kubeClient, initialUnstruct)
332
333
334 updateUnstruct := testContext.UpdateUnstruct.DeepCopy()
335 if updateUnstruct == nil {
336 t.Fatalf("updateUnstruct is nil for '%v'. should SkipUpdate be set to true in resourcefixture/contexts?", testContext.ResourceFixture.Name)
337 }
338 updateUnstruct.SetResourceVersion(initialUnstruct.GetResourceVersion())
339
340 status := initialUnstruct.Object["status"]
341 if err := unstructured.SetNestedField(updateUnstruct.Object, status, "status"); err != nil {
342 t.Fatalf("error setting status on updateUnstruct: %v", err)
343 }
344 patch := client.MergeFrom(testContext.CreateUnstruct)
345 t.Logf("patching %v with %v\r", updateUnstruct, patch)
346 if err := kubeClient.Patch(context.TODO(), updateUnstruct, patch); err != nil {
347 t.Fatalf("unexpected error when updating '%v': %v", initialUnstruct.GetName(), err)
348 }
349 preReconcileGeneration := updateUnstruct.GetGeneration()
350 systemContext.Reconciler.Reconcile(updateUnstruct, testreconciler.ExpectedSuccessfulReconcileResultFor(systemContext.Reconciler, updateUnstruct), nil)
351
352 reconciledUnstruct := &unstructured.Unstructured{
353 Object: map[string]interface{}{
354 "kind": updateUnstruct.GetKind(),
355 "apiVersion": updateUnstruct.GetAPIVersion(),
356 },
357 }
358 if err := kubeClient.Get(context.TODO(), testContext.NamespacedName, reconciledUnstruct); err != nil {
359 t.Fatalf("unexpected error getting k8s resource: %v", err)
360 }
361
362 generationIncrease := reconciledUnstruct.GetGeneration() - updateUnstruct.GetGeneration()
363 if generationIncrease > 1 {
364
365 t.Fatalf("unexpected generation increase %v", generationIncrease)
366 }
367
368
369 gcpUnstruct, err := resourceContext.Get(t, reconciledUnstruct, systemContext.TFProvider, kubeClient, systemContext.SMLoader, systemContext.DCLConfig, systemContext.DCLConverter)
370 if err != nil {
371 t.Fatalf("unexpected error when GETting '%v': %v", updateUnstruct.GetName(), err)
372 }
373 if resourceContext.SupportsLabels(systemContext.SMLoader) {
374 testcontroller.AssertLabelsMatchAndHaveManagedLabel(t, gcpUnstruct.GetLabels(), testContext.UpdateUnstruct.GetLabels())
375 }
376
377
378
379 if initialUnstruct.Object["spec"] != nil {
380 if gcpUnstruct.Object["spec"] == nil {
381 t.Fatalf("GCP resource has a nil spec even though it was created using a resource with a non-nil spec")
382 }
383 changedFields := getChangedFields(initialUnstruct.Object, reconciledUnstruct.Object, "spec")
384 assertObjectContains(t, gcpUnstruct.Object["spec"].(map[string]interface{}), changedFields)
385 }
386
387
388
389 testcontroller.AssertEventRecordedforUnstruct(t, kubeClient, reconciledUnstruct, k8s.Updating)
390
391
392 conditions := dynamic.GetConditions(t, reconciledUnstruct)
393 testcontroller.AssertReadyCondition(t, conditions)
394 testcontroller.AssertEventRecordedforUnstruct(t, kubeClient, reconciledUnstruct, k8s.UpToDate)
395
396
397 testcontroller.AssertObservedGenerationEquals(t, reconciledUnstruct, preReconcileGeneration)
398
399 verifyResourceIDIfSupported(t, systemContext, resourceContext, reconciledUnstruct, updateUnstruct)
400 }
401
402
403 func testDriftCorrection(t *testing.T, testContext testrunner.TestContext, systemContext testrunner.SystemContext, resourceContext contexts.ResourceContext) {
404 if shouldSkipDriftDetection(t, resourceContext, systemContext.SMLoader, systemContext.DCLConverter.MetadataLoader, testContext.CreateUnstruct) {
405 return
406 }
407 kubeClient := systemContext.Manager.GetClient()
408 testUnstruct := testContext.CreateUnstruct.DeepCopy()
409 if err := kubeClient.Get(context.TODO(), testContext.NamespacedName, testUnstruct); err != nil {
410 t.Fatalf("unexpected error getting k8s resource: %v", err)
411 }
412
413 if skip, _ := resourceactuation.ShouldSkip(testUnstruct); skip {
414 return
415 }
416
417
418 testcontroller.DeleteAllEventsForUnstruct(t, kubeClient, testUnstruct)
419
420 if err := resourceContext.Delete(t, testUnstruct, systemContext.TFProvider, systemContext.Manager.GetClient(), systemContext.SMLoader, systemContext.DCLConfig, systemContext.DCLConverter); err != nil {
421 t.Fatalf("error deleting: %v", err)
422 }
423
424
425 time.Sleep(time.Second * 10)
426
427
428 t.Logf("reconcile with %v\r", testUnstruct)
429 systemContext.Reconciler.Reconcile(testUnstruct, testreconciler.ExpectedSuccessfulReconcileResultFor(systemContext.Reconciler, testUnstruct), nil)
430 t.Logf("reconciled with %v\r", testUnstruct)
431 validateCreate(t, testContext, systemContext, resourceContext, testUnstruct.GetGeneration())
432 }
433
434 func shouldSkipDriftDetection(t *testing.T, resourceContext contexts.ResourceContext, smLoader *servicemappingloader.ServiceMappingLoader,
435 serviceMetadataLoader dclmetadata.ServiceMetadataLoader, u *unstructured.Unstructured) bool {
436 if !testgcp.ResourceSupportsDeletion(u.GetKind()) {
437
438 return true
439 }
440 if resourceContext.SkipDriftDetection {
441 return true
442 }
443
444
445 if resourceContext.DCLBased {
446 s, found := dclextension.GetNameFieldSchema(resourceContext.DCLSchema)
447 if !found {
448
449 return false
450 }
451 isServerGenerated, err := dclextension.IsResourceIDFieldServerGenerated(s)
452 if err != nil {
453 t.Fatalf("error parsing `resourceID` field schema: %v", err)
454 }
455 return isServerGenerated
456 }
457
458 rc := testservicemapping.GetResourceConfig(t, smLoader, u)
459 return hasServerGeneratedId(*rc)
460 }
461
462 func hasServerGeneratedId(rc v1alpha1.ResourceConfig) bool {
463 return rc.ServerGeneratedIDField != ""
464 }
465
466 func testDelete(t *testing.T, testContext testrunner.TestContext, systemContext testrunner.SystemContext, resourceContext contexts.ResourceContext) {
467 if resourceContext.SkipDelete {
468 return
469 }
470 kubeClient := systemContext.Manager.GetClient()
471 testReconciler := systemContext.Reconciler
472 initialUnstruct := testContext.CreateUnstruct.DeepCopy()
473 if err := kubeClient.Delete(context.TODO(), initialUnstruct); err != nil {
474 t.Fatalf("error deleting resource: %v", err)
475 }
476
477
478
479 reconciledUnstruct := testContext.CreateUnstruct.DeepCopy()
480 testReconciler.Reconcile(reconciledUnstruct, testreconciler.ExpectedRequeueReconcileStruct, nil)
481 if err := kubeClient.Get(context.TODO(), testContext.NamespacedName, reconciledUnstruct); err != nil {
482 t.Fatalf("unexpected error getting k8s resource: %v", err)
483 }
484 if _, err := resourceContext.Get(t, reconciledUnstruct, systemContext.TFProvider, kubeClient, systemContext.SMLoader, systemContext.DCLConfig, systemContext.DCLConverter); err != nil {
485 t.Errorf("expected resource %s to not be deleted with deletion defender finalizer, but got error: %s",
486 initialUnstruct.GetName(), err)
487 }
488
489
490 testk8s.RemoveDeletionDefenderFinalizerForUnstructured(t, reconciledUnstruct, kubeClient)
491 testReconciler.Reconcile(reconciledUnstruct, testreconciler.ExpectedSuccessfulReconcileResultFor(systemContext.Reconciler, reconciledUnstruct), nil)
492
493 if !testgcp.ResourceSupportsDeletion(testContext.ResourceFixture.GVK.Kind) {
494 _, err := resourceContext.Get(t, reconciledUnstruct, systemContext.TFProvider, kubeClient, systemContext.SMLoader, systemContext.DCLConfig, systemContext.DCLConverter)
495 if err != nil {
496 t.Errorf("expected resource %s to exist after deletion, but got error: %s", initialUnstruct.GetName(), err)
497 }
498 } else {
499 getFunc := func() error {
500
501 _, err := resourceContext.Get(t, reconciledUnstruct, systemContext.TFProvider, kubeClient, systemContext.SMLoader, systemContext.DCLConfig, systemContext.DCLConverter)
502 if err == nil {
503 return fmt.Errorf("expected error, instead got 'nil'")
504 }
505 return backoff.Permanent(err)
506 }
507 expBackoff := backoff.NewExponentialBackOff()
508 expBackoff.MaxElapsedTime = 60 * time.Second
509 expBackoff.MaxInterval = 10 * time.Second
510 err := backoff.Retry(getFunc, expBackoff)
511
512 if !gcp.IsNotFoundError(err) && !contexts.IsNotFoundError(err) {
513 t.Errorf("expected GCP client to return NotFound for '%v', instead got: %v", initialUnstruct.GetName(), err)
514 }
515
516 err = kubeClient.Get(context.TODO(), testContext.NamespacedName, initialUnstruct)
517 if err == nil || !errors.IsNotFound(err) {
518 t.Errorf("unexpected error value: '%v'", err)
519 }
520 }
521
522
523 testcontroller.AssertEventRecordedforUnstruct(t, kubeClient, initialUnstruct, k8s.Deleted)
524 }
525
526 func testReconcileCreateNoChangeUpdateDelete(t *testing.T, testContext testrunner.TestContext, systemContext testrunner.SystemContext, resourceContext contexts.ResourceContext) {
527 resourceCleanup := systemContext.Reconciler.BuildCleanupFunc(testContext.CreateUnstruct, getResourceCleanupPolicy())
528 defer resourceCleanup()
529 testCreate(t, testContext, systemContext, resourceContext)
530 testNoChange(t, testContext, systemContext, resourceContext)
531 testUpdate(t, testContext, systemContext, resourceContext)
532 testDriftCorrection(t, testContext, systemContext, resourceContext)
533 testDelete(t, testContext, systemContext, resourceContext)
534 }
535
536 func checkComputeNetworkUpdate(t *testing.T, updateUnstruct *unstructured.Unstructured, gcpUnstruct *unstructured.Unstructured) {
537 expect, _, err := unstructured.NestedString(updateUnstruct.Object, "spec", "routingMode")
538 if err != nil {
539 t.Error(err)
540 }
541 actual, _, err := unstructured.NestedString(gcpUnstruct.Object, "routingConfig", "routingMode")
542 if err != nil {
543 t.Error(err)
544 }
545 if expect != actual {
546 t.Errorf("unexpected value for routingMode: got %v, want %v", actual, expect)
547 }
548 }
549
550 func checkDataflowJobNoChange(t *testing.T, initialUnstruct, reconciledUnstruct *unstructured.Unstructured) {
551 expectJobID, found, err := unstructured.NestedString(initialUnstruct.Object, "status", "jobId")
552 if err != nil {
553 t.Error(err)
554 }
555 if !found {
556 t.Errorf("initial unstruct does not have a status.jobId field")
557 }
558 actualJobID, found, err := unstructured.NestedString(reconciledUnstruct.Object, "status", "jobId")
559 if err != nil {
560 t.Error(err)
561 }
562 if !found {
563 t.Errorf("reconciled unstruct does not have a status.jobId field")
564 }
565 if expectJobID != actualJobID {
566 t.Errorf("expected status.jobId to be unchanged: got %v, want %v", actualJobID, expectJobID)
567 }
568 }
569
570 func testReconcileAcquire(t *testing.T, testContext testrunner.TestContext, systemContext testrunner.SystemContext, resourceContext contexts.ResourceContext) {
571 kubeClient := systemContext.Manager.GetClient()
572 initialUnstruct := testContext.CreateUnstruct.DeepCopy()
573
574
575
576
577
578
579 unstructToCreate := initialUnstruct.DeepCopy()
580 if containsResourceIDTestVar(t, unstructToCreate) {
581 testcontroller.RemoveResourceID(unstructToCreate)
582 }
583 var gcpUnstruct *unstructured.Unstructured
584 var err error
585 gcpUnstruct, err = resourceContext.Get(t, unstructToCreate, systemContext.TFProvider, kubeClient, systemContext.SMLoader, systemContext.DCLConfig, systemContext.DCLConverter)
586 if err != nil {
587 if !strings.Contains(err.Error(), "not found") {
588 t.Fatalf("unexpected error when GETting '%v': %v", unstructToCreate.GetName(), err)
589 }
590 if gcpUnstruct, err = resourceContext.Create(t, unstructToCreate, systemContext.TFProvider, kubeClient, systemContext.SMLoader, systemContext.DCLConfig, systemContext.DCLConverter); err != nil {
591 t.Fatalf("unexpected error when creating GCP resource '%v': %v", unstructToCreate.GetName(), err)
592 }
593 }
594
595
596
597
598
599
600 if containsResourceIDTestVar(t, initialUnstruct) {
601 resourceID, ok := testcontroller.GetResourceID(t, gcpUnstruct)
602 if !ok || resourceID == "" {
603 t.Fatalf("GCP resource does not have a %v field", k8s.ResourceIDFieldPath)
604 }
605 testcontroller.SetResourceID(t, initialUnstruct, resourceID)
606 }
607
608
609
610 if testContext.ResourceFixture.GVK.Kind == "ComputeNetwork" {
611 unstructured.RemoveNestedField(initialUnstruct.Object, "spec", "autoCreateSubnetworks")
612 }
613 if err := kubeClient.Create(context.TODO(), initialUnstruct); err != nil {
614 t.Fatalf("error creating resource: %v", err)
615 }
616 preReconcileGeneration := initialUnstruct.GetGeneration()
617 resourceCleanup := systemContext.Reconciler.BuildCleanupFunc(initialUnstruct, getResourceCleanupPolicy())
618 defer resourceCleanup()
619 systemContext.Reconciler.Reconcile(initialUnstruct, testreconciler.ExpectedSuccessfulReconcileResultFor(systemContext.Reconciler, initialUnstruct), nil)
620
621
622 if resourceContext.SupportsLabels(systemContext.SMLoader) {
623 gcpUnstruct, err := resourceContext.Get(t, initialUnstruct, systemContext.TFProvider, kubeClient, systemContext.SMLoader, systemContext.DCLConfig, systemContext.DCLConverter)
624 if err != nil {
625 t.Fatalf("unexpected error when GETting '%v': %v", initialUnstruct.GetName(), err)
626 }
627 testcontroller.AssertLabelsMatchAndHaveManagedLabel(t, gcpUnstruct.GetLabels(), initialUnstruct.GetLabels())
628 }
629
630 reconciledUnstruct := &unstructured.Unstructured{
631 Object: map[string]interface{}{
632 "kind": initialUnstruct.GetKind(),
633 "apiVersion": initialUnstruct.GetAPIVersion(),
634 },
635 }
636 if err := kubeClient.Get(context.TODO(), testContext.NamespacedName, reconciledUnstruct); err != nil {
637 t.Fatalf("unexpected error getting k8s resource: %v", err)
638 }
639
640
641 conditions := dynamic.GetConditions(t, reconciledUnstruct)
642 testcontroller.AssertReadyCondition(t, conditions)
643 testcontroller.AssertEventRecordedforUnstruct(t, kubeClient, reconciledUnstruct, k8s.UpToDate)
644
645
646 testcontroller.AssertObservedGenerationEquals(t, reconciledUnstruct, preReconcileGeneration)
647
648 verifyResourceIDIfSupported(t, systemContext, resourceContext, reconciledUnstruct, initialUnstruct)
649 }
650
651
652 func verifyResourceIDIfSupported(t *testing.T, systemContext testrunner.SystemContext, resourceContext contexts.ResourceContext, reconciledUnstruct, appliedUnstruct *unstructured.Unstructured) {
653 if resourceContext.DCLBased {
654 s, found := dclextension.GetNameFieldSchema(resourceContext.DCLSchema)
655 if !found {
656
657 return
658 }
659 isServerGeneratedID, err := dclextension.IsResourceIDFieldServerGenerated(s)
660 if err != nil {
661 t.Fatalf("error parsing `resourceID` field schema: %v", err)
662 }
663 verifyResourceID(t, isServerGeneratedID, reconciledUnstruct, appliedUnstruct)
664 } else {
665 rc, err := systemContext.SMLoader.GetResourceConfig(reconciledUnstruct)
666 if err != nil {
667 t.Fatalf("error getting resource config for Kind '%s', "+
668 "Namespace '%s', Name '%s'", reconciledUnstruct.GetKind(),
669 reconciledUnstruct.GetNamespace(), reconciledUnstruct.GetName())
670 }
671 if !testcontroller.SupportsResourceIDField(rc) {
672 return
673 }
674 isServerGeneratedId := testcontroller.IsResourceIDFieldServerGenerated(rc)
675 verifyResourceID(t, isServerGeneratedId, reconciledUnstruct, appliedUnstruct)
676 }
677 }
678
679 func verifyResourceID(t *testing.T, isServerGeneratedID bool, reconciledUnstruct, appliedUnstruct *unstructured.Unstructured) {
680 reconciledResourceID, found := testcontroller.GetResourceID(t, reconciledUnstruct)
681 if !found {
682 t.Fatalf("'%s' not found", k8s.ResourceIDFieldPath)
683 }
684 if reconciledResourceID == "" {
685 t.Fatalf("invalid value for '%s': empty string",
686 k8s.ResourceIDFieldPath)
687 }
688
689 if isServerGeneratedID {
690 testcontroller.AssertServerGeneratedResourceIDMatch(t, reconciledResourceID, appliedUnstruct)
691 return
692 }
693 testcontroller.AssertUserSpecifiedResourceIDMatch(t, reconciledResourceID, appliedUnstruct)
694 }
695
696 func getResourceCleanupPolicy() testreconciler.ResourceCleanupPolicy {
697 if cleanupResources {
698 return testreconciler.CleanupPolicyAlways
699 }
700 return testreconciler.CleanupPolicyOnSuccess
701 }
702
703 func assertObjectContains(t *testing.T, obj, changedFields map[string]interface{}) {
704 for changedKey, changedVal := range changedFields {
705 objVal, ok := obj[changedKey]
706 if !ok {
707 t.Fatalf("object is missing the field %v", changedKey)
708 }
709
710 switch changedVal.(type) {
711 case map[string]interface{}:
712 if _, ok := objVal.(map[string]interface{}); !ok {
713 t.Fatalf("expected object to have a map at %v, but got %v", changedKey, objVal)
714 }
715 assertObjectContains(t, objVal.(map[string]interface{}), changedVal.(map[string]interface{}))
716 default:
717 if !reflect.DeepEqual(objVal, changedVal) {
718 t.Fatalf("unexpected value for %v: got %v, want %v", changedKey, objVal, changedVal)
719 }
720 }
721 }
722 }
723
724 func getChangedFields(initialObject, updatedObject map[string]interface{}, field string) map[string]interface{} {
725 changedFields := make(map[string]interface{})
726 initial, _ := initialObject[field].(map[string]interface{})
727 updated := updatedObject[field].(map[string]interface{})
728 if !reflect.DeepEqual(initial, updated) {
729 for k, v := range updated {
730 if !reflect.DeepEqual(initial[k], v) {
731 if _, ok := v.(map[string]interface{}); ok {
732 changedFields[k] = getChangedFields(initial, updated, k)
733 } else {
734 changedFields[k] = updated[k]
735 }
736 }
737 }
738 }
739 return changedFields
740 }
741
742
743
744
745
746
747
748
749 func stripInternalKeysFromManagedFields(t *testing.T, unstruct *unstructured.Unstructured) {
750 managedFields, found, err := unstructured.NestedFieldNoCopy(unstruct.Object, "metadata", "managedFields")
751 if err != nil {
752 t.Fatalf("error getting managed fields: %v", err)
753 }
754 if !found {
755 return
756 }
757 for _, managedField := range managedFields.([]interface{}) {
758 managedField := managedField.(map[string]interface{})
759 delete(managedField, "time")
760 }
761 }
762
763 func containsResourceIDTestVar(t *testing.T, u *unstructured.Unstructured) bool {
764 b, err := yaml.Marshal(u)
765 if err != nil {
766 t.Fatalf("error marshalling unstruct to bytes: %v", err)
767 }
768 return strings.Contains(string(b), resourceIDTestVar)
769 }
770
771 func TestMain(m *testing.M) {
772 testmain.TestMainForIntegrationTests(m, &mgr)
773 }
774
View as plain text