...

Source file src/sigs.k8s.io/cli-utils/test/e2e/mutation_test.go

Documentation: sigs.k8s.io/cli-utils/test/e2e

     1  // Copyright 2020 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package e2e
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  
    10  	. "github.com/onsi/ginkgo/v2"
    11  	. "github.com/onsi/gomega"
    12  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    13  	"sigs.k8s.io/cli-utils/pkg/apply"
    14  	"sigs.k8s.io/cli-utils/pkg/apply/event"
    15  	"sigs.k8s.io/cli-utils/pkg/inventory"
    16  	"sigs.k8s.io/cli-utils/pkg/object"
    17  	"sigs.k8s.io/cli-utils/pkg/testutil"
    18  	"sigs.k8s.io/cli-utils/test/e2e/e2eutil"
    19  	"sigs.k8s.io/cli-utils/test/e2e/invconfig"
    20  	"sigs.k8s.io/controller-runtime/pkg/client"
    21  )
    22  
    23  // High priority use cases:
    24  // - IAMPolicyMember .spec.member injected with apply-time-mutation using a name
    25  //   that contains Project .status.number
    26  //   https://github.com/GoogleCloudPlatform/k8s-config-connector/issues/340
    27  // - Service .spec.loadBalancerIP injected with apply-time-mutation from
    28  //   ComputeAddress .spec.address
    29  //   https://github.com/GoogleCloudPlatform/k8s-config-connector/issues/334
    30  //
    31  // However, since both of these use Config Connector resources, which use CRDs
    32  // that are copyright to Google, we can't use them as e2e tests here. Instead,
    33  // we test a toy example with a pod-a depending on pod-b, injecting the ip and
    34  // port from pod-b into an environment variable of pod-a.
    35  
    36  //nolint:dupl // expEvents similar to CRD tests
    37  func mutationTest(ctx context.Context, c client.Client, invConfig invconfig.InventoryConfig, inventoryName, namespaceName string) {
    38  	By("apply resources in order with substitutions based on apply-time-mutation annotation")
    39  	applier := invConfig.ApplierFactoryFunc()
    40  
    41  	inv := invConfig.InvWrapperFunc(invConfig.FactoryFunc(inventoryName, namespaceName, "test"))
    42  
    43  	fields := struct{ Namespace string }{Namespace: namespaceName}
    44  	podAObj := e2eutil.TemplateToUnstructured(podATemplate, fields)
    45  	podBObj := e2eutil.TemplateToUnstructured(podBTemplate, fields)
    46  
    47  	// Dependency order: podA -> podB
    48  	// Apply order: podB, podA
    49  	resources := []*unstructured.Unstructured{
    50  		podAObj,
    51  		podBObj,
    52  	}
    53  
    54  	applierEvents := e2eutil.RunCollect(applier.Run(ctx, inv, resources, apply.ApplierOptions{
    55  		EmitStatusEvents: false,
    56  	}))
    57  
    58  	expEvents := []testutil.ExpEvent{
    59  		{
    60  			// InitTask
    61  			EventType: event.InitType,
    62  			InitEvent: &testutil.ExpInitEvent{},
    63  		},
    64  		{
    65  			// InvAddTask start
    66  			EventType: event.ActionGroupType,
    67  			ActionGroupEvent: &testutil.ExpActionGroupEvent{
    68  				Action:    event.InventoryAction,
    69  				GroupName: "inventory-add-0",
    70  				Type:      event.Started,
    71  			},
    72  		},
    73  		{
    74  			// InvAddTask finished
    75  			EventType: event.ActionGroupType,
    76  			ActionGroupEvent: &testutil.ExpActionGroupEvent{
    77  				Action:    event.InventoryAction,
    78  				GroupName: "inventory-add-0",
    79  				Type:      event.Finished,
    80  			},
    81  		},
    82  		{
    83  			// ApplyTask start
    84  			EventType: event.ActionGroupType,
    85  			ActionGroupEvent: &testutil.ExpActionGroupEvent{
    86  				Action:    event.ApplyAction,
    87  				GroupName: "apply-0",
    88  				Type:      event.Started,
    89  			},
    90  		},
    91  		{
    92  			// Apply PodB first
    93  			EventType: event.ApplyType,
    94  			ApplyEvent: &testutil.ExpApplyEvent{
    95  				GroupName:  "apply-0",
    96  				Status:     event.ApplySuccessful,
    97  				Identifier: object.UnstructuredToObjMetadata(podBObj),
    98  				Error:      nil,
    99  			},
   100  		},
   101  		{
   102  			// ApplyTask finished
   103  			EventType: event.ActionGroupType,
   104  			ActionGroupEvent: &testutil.ExpActionGroupEvent{
   105  				Action:    event.ApplyAction,
   106  				GroupName: "apply-0",
   107  				Type:      event.Finished,
   108  			},
   109  		},
   110  		{
   111  			// WaitTask start
   112  			EventType: event.ActionGroupType,
   113  			ActionGroupEvent: &testutil.ExpActionGroupEvent{
   114  				Action:    event.WaitAction,
   115  				GroupName: "wait-0",
   116  				Type:      event.Started,
   117  			},
   118  		},
   119  		{
   120  			// PodB reconcile Pending.
   121  			EventType: event.WaitType,
   122  			WaitEvent: &testutil.ExpWaitEvent{
   123  				GroupName:  "wait-0",
   124  				Status:     event.ReconcilePending,
   125  				Identifier: object.UnstructuredToObjMetadata(podBObj),
   126  			},
   127  		},
   128  		{
   129  			// PodB confirmed Current.
   130  			EventType: event.WaitType,
   131  			WaitEvent: &testutil.ExpWaitEvent{
   132  				GroupName:  "wait-0",
   133  				Status:     event.ReconcileSuccessful,
   134  				Identifier: object.UnstructuredToObjMetadata(podBObj),
   135  			},
   136  		},
   137  		{
   138  			// WaitTask finished
   139  			EventType: event.ActionGroupType,
   140  			ActionGroupEvent: &testutil.ExpActionGroupEvent{
   141  				Action:    event.WaitAction,
   142  				GroupName: "wait-0",
   143  				Type:      event.Finished,
   144  			},
   145  		},
   146  		{
   147  			// ApplyTask start
   148  			EventType: event.ActionGroupType,
   149  			ActionGroupEvent: &testutil.ExpActionGroupEvent{
   150  				Action:    event.ApplyAction,
   151  				GroupName: "apply-1",
   152  				Type:      event.Started,
   153  			},
   154  		},
   155  		{
   156  			// Apply PodA second
   157  			EventType: event.ApplyType,
   158  			ApplyEvent: &testutil.ExpApplyEvent{
   159  				GroupName:  "apply-1",
   160  				Status:     event.ApplySuccessful,
   161  				Identifier: object.UnstructuredToObjMetadata(podAObj),
   162  				Error:      nil,
   163  			},
   164  		},
   165  		{
   166  			// ApplyTask finished
   167  			EventType: event.ActionGroupType,
   168  			ActionGroupEvent: &testutil.ExpActionGroupEvent{
   169  				Action:    event.ApplyAction,
   170  				GroupName: "apply-1",
   171  				Type:      event.Finished,
   172  			},
   173  		},
   174  		{
   175  			// WaitTask start
   176  			EventType: event.ActionGroupType,
   177  			ActionGroupEvent: &testutil.ExpActionGroupEvent{
   178  				Action:    event.WaitAction,
   179  				GroupName: "wait-1",
   180  				Type:      event.Started,
   181  			},
   182  		},
   183  		{
   184  			// PodA reconcile Pending.
   185  			EventType: event.WaitType,
   186  			WaitEvent: &testutil.ExpWaitEvent{
   187  				GroupName:  "wait-1",
   188  				Status:     event.ReconcilePending,
   189  				Identifier: object.UnstructuredToObjMetadata(podAObj),
   190  			},
   191  		},
   192  		{
   193  			// PodA confirmed Current.
   194  			EventType: event.WaitType,
   195  			WaitEvent: &testutil.ExpWaitEvent{
   196  				GroupName:  "wait-1",
   197  				Status:     event.ReconcileSuccessful,
   198  				Identifier: object.UnstructuredToObjMetadata(podAObj),
   199  			},
   200  		},
   201  		{
   202  			// WaitTask finished
   203  			EventType: event.ActionGroupType,
   204  			ActionGroupEvent: &testutil.ExpActionGroupEvent{
   205  				Action:    event.WaitAction,
   206  				GroupName: "wait-1",
   207  				Type:      event.Finished,
   208  			},
   209  		},
   210  		{
   211  			// InvSetTask start
   212  			EventType: event.ActionGroupType,
   213  			ActionGroupEvent: &testutil.ExpActionGroupEvent{
   214  				Action:    event.InventoryAction,
   215  				GroupName: "inventory-set-0",
   216  				Type:      event.Started,
   217  			},
   218  		},
   219  		{
   220  			// InvSetTask finished
   221  			EventType: event.ActionGroupType,
   222  			ActionGroupEvent: &testutil.ExpActionGroupEvent{
   223  				Action:    event.InventoryAction,
   224  				GroupName: "inventory-set-0",
   225  				Type:      event.Finished,
   226  			},
   227  		},
   228  	}
   229  	receivedEvents := testutil.EventsToExpEvents(applierEvents)
   230  
   231  	expEvents, receivedEvents = e2eutil.FilterOptionalEvents(expEvents, receivedEvents)
   232  
   233  	Expect(receivedEvents).To(testutil.Equal(expEvents))
   234  
   235  	By("verify podB is created and ready")
   236  	result := e2eutil.AssertUnstructuredExists(ctx, c, podBObj)
   237  
   238  	podIP, found, err := object.NestedField(result.Object, "status", "podIP")
   239  	Expect(err).NotTo(HaveOccurred())
   240  	Expect(found).To(BeTrue())
   241  	Expect(podIP).NotTo(BeEmpty()) // use podIP as proxy for readiness
   242  
   243  	containerPort, found, err := object.NestedField(result.Object, "spec", "containers", 0, "ports", 0, "containerPort")
   244  	Expect(err).NotTo(HaveOccurred())
   245  	Expect(found).To(BeTrue())
   246  	Expect(containerPort).To(Equal(int64(80)))
   247  
   248  	host := fmt.Sprintf("%s:%d", podIP, containerPort)
   249  
   250  	By("verify podA is mutated, created, and ready")
   251  	result = e2eutil.AssertUnstructuredExists(ctx, c, podAObj)
   252  
   253  	podIP, found, err = object.NestedField(result.Object, "status", "podIP")
   254  	Expect(err).NotTo(HaveOccurred())
   255  	Expect(found).To(BeTrue())
   256  	Expect(podIP).NotTo(BeEmpty()) // use podIP as proxy for readiness
   257  
   258  	envValue, found, err := object.NestedField(result.Object, "spec", "containers", 0, "env", 0, "value")
   259  	Expect(err).NotTo(HaveOccurred())
   260  	Expect(found).To(BeTrue())
   261  	Expect(envValue).To(Equal(host))
   262  
   263  	By("destroy resources in opposite order")
   264  	destroyer := invConfig.DestroyerFactoryFunc()
   265  	options := apply.DestroyerOptions{InventoryPolicy: inventory.PolicyAdoptIfNoInventory}
   266  	destroyerEvents := e2eutil.RunCollect(destroyer.Run(ctx, inv, options))
   267  
   268  	expEvents = []testutil.ExpEvent{
   269  		{
   270  			// InitTask
   271  			EventType: event.InitType,
   272  			InitEvent: &testutil.ExpInitEvent{},
   273  		},
   274  		{
   275  			// PruneTask start
   276  			EventType: event.ActionGroupType,
   277  			ActionGroupEvent: &testutil.ExpActionGroupEvent{
   278  				Action:    event.DeleteAction,
   279  				GroupName: "prune-0",
   280  				Type:      event.Started,
   281  			},
   282  		},
   283  		{
   284  			// Delete PodA first
   285  			EventType: event.DeleteType,
   286  			DeleteEvent: &testutil.ExpDeleteEvent{
   287  				GroupName:  "prune-0",
   288  				Status:     event.DeleteSuccessful,
   289  				Identifier: object.UnstructuredToObjMetadata(podAObj),
   290  				Error:      nil,
   291  			},
   292  		},
   293  		{
   294  			// PruneTask finished
   295  			EventType: event.ActionGroupType,
   296  			ActionGroupEvent: &testutil.ExpActionGroupEvent{
   297  				Action:    event.DeleteAction,
   298  				GroupName: "prune-0",
   299  				Type:      event.Finished,
   300  			},
   301  		},
   302  		{
   303  			// WaitTask start
   304  			EventType: event.ActionGroupType,
   305  			ActionGroupEvent: &testutil.ExpActionGroupEvent{
   306  				Action:    event.WaitAction,
   307  				GroupName: "wait-0",
   308  				Type:      event.Started,
   309  			},
   310  		},
   311  		{
   312  			// PodA reconcile Pending.
   313  			EventType: event.WaitType,
   314  			WaitEvent: &testutil.ExpWaitEvent{
   315  				GroupName:  "wait-0",
   316  				Status:     event.ReconcilePending,
   317  				Identifier: object.UnstructuredToObjMetadata(podAObj),
   318  			},
   319  		},
   320  		{
   321  			// PodA confirmed NotFound.
   322  			EventType: event.WaitType,
   323  			WaitEvent: &testutil.ExpWaitEvent{
   324  				GroupName:  "wait-0",
   325  				Status:     event.ReconcileSuccessful,
   326  				Identifier: object.UnstructuredToObjMetadata(podAObj),
   327  			},
   328  		},
   329  		{
   330  			// WaitTask finished
   331  			EventType: event.ActionGroupType,
   332  			ActionGroupEvent: &testutil.ExpActionGroupEvent{
   333  				Action:    event.WaitAction,
   334  				GroupName: "wait-0",
   335  				Type:      event.Finished,
   336  			},
   337  		},
   338  		{
   339  			// PruneTask start
   340  			EventType: event.ActionGroupType,
   341  			ActionGroupEvent: &testutil.ExpActionGroupEvent{
   342  				Action:    event.DeleteAction,
   343  				GroupName: "prune-1",
   344  				Type:      event.Started,
   345  			},
   346  		},
   347  		{
   348  			// Delete PodB second
   349  			EventType: event.DeleteType,
   350  			DeleteEvent: &testutil.ExpDeleteEvent{
   351  				GroupName:  "prune-1",
   352  				Status:     event.DeleteSuccessful,
   353  				Identifier: object.UnstructuredToObjMetadata(podBObj),
   354  				Error:      nil,
   355  			},
   356  		},
   357  		{
   358  			// PruneTask finished
   359  			EventType: event.ActionGroupType,
   360  			ActionGroupEvent: &testutil.ExpActionGroupEvent{
   361  				Action:    event.DeleteAction,
   362  				GroupName: "prune-1",
   363  				Type:      event.Finished,
   364  			},
   365  		},
   366  		{
   367  			// WaitTask start
   368  			EventType: event.ActionGroupType,
   369  			ActionGroupEvent: &testutil.ExpActionGroupEvent{
   370  				Action:    event.WaitAction,
   371  				GroupName: "wait-1",
   372  				Type:      event.Started,
   373  			},
   374  		},
   375  		{
   376  			// PodB reconcile Pending.
   377  			EventType: event.WaitType,
   378  			WaitEvent: &testutil.ExpWaitEvent{
   379  				GroupName:  "wait-1",
   380  				Status:     event.ReconcilePending,
   381  				Identifier: object.UnstructuredToObjMetadata(podBObj),
   382  			},
   383  		},
   384  		{
   385  			// PodB confirmed NotFound.
   386  			EventType: event.WaitType,
   387  			WaitEvent: &testutil.ExpWaitEvent{
   388  				GroupName:  "wait-1",
   389  				Status:     event.ReconcileSuccessful,
   390  				Identifier: object.UnstructuredToObjMetadata(podBObj),
   391  			},
   392  		},
   393  		{
   394  			// WaitTask finished
   395  			EventType: event.ActionGroupType,
   396  			ActionGroupEvent: &testutil.ExpActionGroupEvent{
   397  				Action:    event.WaitAction,
   398  				GroupName: "wait-1",
   399  				Type:      event.Finished,
   400  			},
   401  		},
   402  		{
   403  			// DeleteInvTask start
   404  			EventType: event.ActionGroupType,
   405  			ActionGroupEvent: &testutil.ExpActionGroupEvent{
   406  				Action:    event.InventoryAction,
   407  				GroupName: "delete-inventory-0",
   408  				Type:      event.Started,
   409  			},
   410  		},
   411  		{
   412  			// DeleteInvTask finished
   413  			EventType: event.ActionGroupType,
   414  			ActionGroupEvent: &testutil.ExpActionGroupEvent{
   415  				Action:    event.InventoryAction,
   416  				GroupName: "delete-inventory-0",
   417  				Type:      event.Finished,
   418  			},
   419  		},
   420  	}
   421  	receivedEvents = testutil.EventsToExpEvents(destroyerEvents)
   422  
   423  	expEvents, receivedEvents = e2eutil.FilterOptionalEvents(expEvents, receivedEvents)
   424  
   425  	Expect(receivedEvents).To(testutil.Equal(expEvents))
   426  
   427  	By("verify podB deleted")
   428  	e2eutil.AssertUnstructuredDoesNotExist(ctx, c, podBObj)
   429  
   430  	By("verify podA deleted")
   431  	e2eutil.AssertUnstructuredDoesNotExist(ctx, c, podAObj)
   432  }
   433  

View as plain text