...

Source file src/sigs.k8s.io/cli-utils/pkg/kstatus/watcher/default_status_watcher_test.go

Documentation: sigs.k8s.io/cli-utils/pkg/kstatus/watcher

     1  // Copyright 2022 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package watcher
     5  
     6  import (
     7  	"context"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/stretchr/testify/require"
    12  	appsv1 "k8s.io/api/apps/v1"
    13  	v1 "k8s.io/api/core/v1"
    14  	"k8s.io/apimachinery/pkg/api/meta"
    15  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    16  	"k8s.io/apimachinery/pkg/runtime"
    17  	"k8s.io/apimachinery/pkg/runtime/schema"
    18  	"k8s.io/apimachinery/pkg/util/yaml"
    19  	"k8s.io/apimachinery/pkg/watch"
    20  	dynamicfake "k8s.io/client-go/dynamic/fake"
    21  	clienttesting "k8s.io/client-go/testing"
    22  	"k8s.io/klog/v2"
    23  	"k8s.io/kubectl/pkg/scheme"
    24  	"sigs.k8s.io/cli-utils/pkg/kstatus/polling/event"
    25  	"sigs.k8s.io/cli-utils/pkg/kstatus/status"
    26  	"sigs.k8s.io/cli-utils/pkg/object"
    27  	"sigs.k8s.io/cli-utils/pkg/testutil"
    28  )
    29  
    30  var deployment1Yaml = `
    31  apiVersion: apps/v1
    32  kind: Deployment
    33  metadata:
    34    name: nginx
    35    generation: 1
    36  spec:
    37    replicas: 1
    38    selector:
    39      matchLabels:
    40        app: nginx
    41    template:
    42      metadata:
    43        labels:
    44          app: nginx
    45      spec:
    46        containers:
    47        - name: nginx
    48          image: nginx:1.19.6
    49          ports:
    50          - containerPort: 80
    51  `
    52  
    53  var deployment1InProgress1Yaml = `
    54  apiVersion: apps/v1
    55  kind: Deployment
    56  metadata:
    57    name: nginx
    58    generation: 1
    59  spec:
    60    replicas: 1
    61    selector:
    62      matchLabels:
    63        app: nginx
    64    template:
    65      metadata:
    66        labels:
    67          app: nginx
    68      spec:
    69        containers:
    70        - name: nginx
    71          image: nginx:1.19.6
    72          ports:
    73          - containerPort: 80
    74  status:
    75    observedGeneration: 1
    76    updatedReplicas: 0
    77    readyReplicas: 0
    78    availableReplicas: 0
    79    replicas: 0
    80    conditions:
    81    - reason: NewReplicaSetAvailable
    82      status: "True"
    83      type: Progressing
    84      message: ReplicaSet "nginx-1" is progressing.
    85    - reason: MinimumReplicasUnavailable
    86      type: Available
    87      status: "False"
    88      message: Deployment does not have minimum availability.
    89  `
    90  
    91  var deployment1InProgress2Yaml = `
    92  apiVersion: apps/v1
    93  kind: Deployment
    94  metadata:
    95    name: nginx
    96    generation: 1
    97  spec:
    98    replicas: 1
    99    selector:
   100      matchLabels:
   101        app: nginx
   102    template:
   103      metadata:
   104        labels:
   105          app: nginx
   106      spec:
   107        containers:
   108        - name: nginx
   109          image: nginx:1.19.6
   110          ports:
   111          - containerPort: 80
   112  status:
   113    observedGeneration: 1
   114    updatedReplicas: 1
   115    readyReplicas: 0
   116    availableReplicas: 0
   117    replicas: 1
   118    conditions:
   119    - reason: NewReplicaSetAvailable
   120      status: "True"
   121      type: Progressing
   122      message: ReplicaSet "nginx-1" is progressing.
   123    - reason: MinimumReplicasUnavailable
   124      type: Available
   125      status: "False"
   126      message: Deployment does not have minimum availability.
   127  `
   128  
   129  var deployment1CurrentYaml = `
   130  apiVersion: apps/v1
   131  kind: Deployment
   132  metadata:
   133    name: nginx
   134    generation: 1
   135  spec:
   136    replicas: 1
   137    selector:
   138      matchLabels:
   139        app: nginx
   140    template:
   141      metadata:
   142        labels:
   143          app: nginx
   144      spec:
   145        containers:
   146        - name: nginx
   147          image: nginx:1.19.6
   148          ports:
   149          - containerPort: 80
   150  status:
   151    observedGeneration: 1
   152    updatedReplicas: 1
   153    readyReplicas: 1
   154    availableReplicas: 1
   155    replicas: 1
   156    conditions:
   157    - reason: NewReplicaSetAvailable
   158      status: "True"
   159      type: Progressing
   160      message: ReplicaSet "nginx-1" has successfully progressed.
   161    - reason: MinimumReplicasAvailable
   162      type: Available
   163      status: "True"
   164      message: Deployment has minimum availability.
   165  `
   166  
   167  var replicaset1Yaml = `
   168  apiVersion: apps/v1
   169  kind: ReplicaSet
   170  metadata:
   171    name: nginx-1
   172    generation: 1
   173    labels:
   174      app: nginx
   175  spec:
   176    replicas: 1
   177    selector:
   178      matchLabels:
   179        app: nginx
   180  `
   181  
   182  var replicaset1InProgress1Yaml = `
   183  apiVersion: apps/v1
   184  kind: ReplicaSet
   185  metadata:
   186    name: nginx-1
   187    generation: 1
   188    labels:
   189      app: nginx
   190  spec:
   191    replicas: 1
   192    selector:
   193      matchLabels:
   194        app: nginx
   195  status:
   196    observedGeneration: 1
   197    replicas: 0
   198    readyReplicas: 0
   199    availableReplicas: 0
   200    fullyLabeledReplicas: 0
   201  `
   202  
   203  var replicaset1InProgress2Yaml = `
   204  apiVersion: apps/v1
   205  kind: ReplicaSet
   206  metadata:
   207    name: nginx-1
   208    generation: 1
   209    labels:
   210      app: nginx
   211  spec:
   212    replicas: 1
   213    selector:
   214      matchLabels:
   215        app: nginx
   216  status:
   217    observedGeneration: 1
   218    replicas: 1
   219    readyReplicas: 0
   220    availableReplicas: 0
   221    fullyLabeledReplicas: 1
   222  `
   223  
   224  var replicaset1CurrentYaml = `
   225  apiVersion: apps/v1
   226  kind: ReplicaSet
   227  metadata:
   228    name: nginx-1
   229    generation: 1
   230    labels:
   231      app: nginx
   232  spec:
   233    replicas: 1
   234    selector:
   235      matchLabels:
   236        app: nginx
   237  status:
   238    observedGeneration: 1
   239    replicas: 1
   240    readyReplicas: 1
   241    availableReplicas: 1
   242    fullyLabeledReplicas: 1
   243  `
   244  
   245  var pod1Yaml = `
   246  apiVersion: v1
   247  kind: Pod
   248  metadata:
   249    generation: 1
   250    name: test
   251    labels:
   252      app: nginx
   253  `
   254  
   255  var pod1CurrentYaml = `
   256  apiVersion: v1
   257  kind: Pod
   258  metadata:
   259    generation: 1
   260    name: test
   261    labels:
   262      app: nginx
   263  status:
   264    conditions:
   265    - type: Ready 
   266      status: "True"
   267    phase: Running
   268  `
   269  
   270  func yamlToUnstructured(t *testing.T, yml string) *unstructured.Unstructured {
   271  	m := make(map[string]interface{})
   272  	err := yaml.Unmarshal([]byte(yml), &m)
   273  	if err != nil {
   274  		t.Fatalf("error parsing yaml: %v", err)
   275  		return nil
   276  	}
   277  	return &unstructured.Unstructured{Object: m}
   278  }
   279  
   280  func TestDefaultStatusWatcher(t *testing.T) {
   281  	deployment1 := yamlToUnstructured(t, deployment1Yaml)
   282  	deployment1ID := object.UnstructuredToObjMetadata(deployment1)
   283  	deployment1InProgress1 := yamlToUnstructured(t, deployment1InProgress1Yaml)
   284  	deployment1InProgress2 := yamlToUnstructured(t, deployment1InProgress2Yaml)
   285  	deployment1Current := yamlToUnstructured(t, deployment1CurrentYaml)
   286  
   287  	replicaset1 := yamlToUnstructured(t, replicaset1Yaml)
   288  	replicaset1ID := object.UnstructuredToObjMetadata(replicaset1)
   289  	replicaset1InProgress1 := yamlToUnstructured(t, replicaset1InProgress1Yaml)
   290  	replicaset1InProgress2 := yamlToUnstructured(t, replicaset1InProgress2Yaml)
   291  	replicaset1Current := yamlToUnstructured(t, replicaset1CurrentYaml)
   292  
   293  	pod1 := yamlToUnstructured(t, pod1Yaml)
   294  	pod1ID := object.UnstructuredToObjMetadata(pod1)
   295  	pod1Current := yamlToUnstructured(t, pod1CurrentYaml)
   296  
   297  	fakeMapper := testutil.NewFakeRESTMapper(
   298  		appsv1.SchemeGroupVersion.WithKind("Deployment"),
   299  		appsv1.SchemeGroupVersion.WithKind("ReplicaSet"),
   300  		v1.SchemeGroupVersion.WithKind("Pod"),
   301  	)
   302  	deployment1GVR := getGVR(t, fakeMapper, deployment1)
   303  	replicaset1GVR := getGVR(t, fakeMapper, replicaset1)
   304  	pod1GVR := getGVR(t, fakeMapper, pod1)
   305  
   306  	// namespace2 := "ns-2"
   307  	// namespace3 := "ns-3"
   308  
   309  	pod2 := pod1.DeepCopy()
   310  	pod2.SetNamespace("ns-2")
   311  	pod2.SetName("pod-2")
   312  	pod2ID := object.UnstructuredToObjMetadata(pod2)
   313  	pod2Current := yamlToUnstructured(t, pod1CurrentYaml)
   314  	pod2Current.SetNamespace("ns-2")
   315  	pod2Current.SetName("pod-2")
   316  	pod2GVR := getGVR(t, fakeMapper, pod2)
   317  
   318  	pod3 := pod1.DeepCopy()
   319  	pod3.SetNamespace("ns-3")
   320  	pod3.SetName("pod-3")
   321  	pod3ID := object.UnstructuredToObjMetadata(pod3)
   322  	pod3Current := yamlToUnstructured(t, pod1CurrentYaml)
   323  	pod3Current.SetNamespace("ns-3")
   324  	pod3Current.SetName("pod-3")
   325  	pod3GVR := getGVR(t, fakeMapper, pod3)
   326  
   327  	testCases := []struct {
   328  		name           string
   329  		ids            object.ObjMetadataSet
   330  		opts           Options
   331  		clusterUpdates []func(*dynamicfake.FakeDynamicClient)
   332  		expectedEvents []event.Event
   333  	}{
   334  		{
   335  			name: "single-namespace pod creation",
   336  			ids: object.ObjMetadataSet{
   337  				pod1ID,
   338  			},
   339  			clusterUpdates: []func(fakeClient *dynamicfake.FakeDynamicClient){
   340  				func(fakeClient *dynamicfake.FakeDynamicClient) {
   341  					// Empty cluster before synchronization.
   342  				},
   343  				func(fakeClient *dynamicfake.FakeDynamicClient) {
   344  					require.NoError(t, fakeClient.Tracker().Create(pod1GVR, pod1, pod1.GetNamespace()))
   345  				},
   346  				func(fakeClient *dynamicfake.FakeDynamicClient) {
   347  					require.NoError(t, fakeClient.Tracker().Update(pod1GVR, pod1Current, pod1Current.GetNamespace()))
   348  				},
   349  			},
   350  			expectedEvents: []event.Event{
   351  				{
   352  					Type: event.SyncEvent,
   353  				},
   354  				{
   355  					Resource: &event.ResourceStatus{
   356  						Identifier:         pod1ID,
   357  						Status:             status.InProgressStatus,
   358  						Resource:           pod1,
   359  						Message:            "Pod phase not available",
   360  						GeneratedResources: nil,
   361  					},
   362  				},
   363  				{
   364  					Resource: &event.ResourceStatus{
   365  						Identifier:         pod1ID,
   366  						Status:             status.CurrentStatus,
   367  						Resource:           pod1Current,
   368  						Message:            "Pod is Ready",
   369  						GeneratedResources: nil,
   370  					},
   371  				},
   372  			},
   373  		},
   374  		{
   375  			name: "single-namespace replicaset creation",
   376  			ids: object.ObjMetadataSet{
   377  				replicaset1ID,
   378  			},
   379  			clusterUpdates: []func(fakeClient *dynamicfake.FakeDynamicClient){
   380  				func(fakeClient *dynamicfake.FakeDynamicClient) {
   381  					// Empty cluster before synchronization.
   382  				},
   383  				func(fakeClient *dynamicfake.FakeDynamicClient) {
   384  					require.NoError(t, fakeClient.Tracker().Create(replicaset1GVR, replicaset1, replicaset1.GetNamespace()))
   385  				},
   386  				func(fakeClient *dynamicfake.FakeDynamicClient) {
   387  					require.NoError(t, fakeClient.Tracker().Update(replicaset1GVR, replicaset1InProgress1, replicaset1InProgress1.GetNamespace()))
   388  				},
   389  				func(fakeClient *dynamicfake.FakeDynamicClient) {
   390  					require.NoError(t, fakeClient.Tracker().Create(pod1GVR, pod1, pod1.GetNamespace()))
   391  					require.NoError(t, fakeClient.Tracker().Update(replicaset1GVR, replicaset1InProgress2, replicaset1InProgress2.GetNamespace()))
   392  				},
   393  				func(fakeClient *dynamicfake.FakeDynamicClient) {
   394  					require.NoError(t, fakeClient.Tracker().Update(pod1GVR, pod1Current, pod1Current.GetNamespace()))
   395  					require.NoError(t, fakeClient.Tracker().Update(replicaset1GVR, replicaset1Current, replicaset1Current.GetNamespace()))
   396  				},
   397  			},
   398  			expectedEvents: []event.Event{
   399  				{
   400  					Type: event.SyncEvent,
   401  				},
   402  				{
   403  					Resource: &event.ResourceStatus{
   404  						Identifier:         replicaset1ID,
   405  						Status:             status.InProgressStatus,
   406  						Resource:           replicaset1,
   407  						Message:            "Labelled: 0/1",
   408  						GeneratedResources: nil,
   409  					},
   410  				},
   411  				{
   412  					Resource: &event.ResourceStatus{
   413  						Identifier:         replicaset1ID,
   414  						Status:             status.InProgressStatus,
   415  						Resource:           replicaset1InProgress1,
   416  						Message:            "Labelled: 0/1",
   417  						GeneratedResources: nil,
   418  					},
   419  				},
   420  				{
   421  					Resource: &event.ResourceStatus{
   422  						Identifier: replicaset1ID,
   423  						Status:     status.InProgressStatus,
   424  						Resource:   replicaset1InProgress2,
   425  						Message:    "Available: 0/1",
   426  						GeneratedResources: event.ResourceStatuses{
   427  							{
   428  								Identifier:         pod1ID,
   429  								Status:             status.InProgressStatus,
   430  								Resource:           pod1,
   431  								Message:            "Pod phase not available",
   432  								GeneratedResources: nil,
   433  							},
   434  						},
   435  					},
   436  				},
   437  				{
   438  					Resource: &event.ResourceStatus{
   439  						Identifier: replicaset1ID,
   440  						Status:     status.CurrentStatus,
   441  						Resource:   replicaset1Current,
   442  						Message:    "ReplicaSet is available. Replicas: 1",
   443  						GeneratedResources: event.ResourceStatuses{
   444  							{
   445  								Identifier:         pod1ID,
   446  								Status:             status.CurrentStatus,
   447  								Resource:           pod1Current,
   448  								Message:            "Pod is Ready",
   449  								GeneratedResources: nil,
   450  							},
   451  						},
   452  					},
   453  				},
   454  			},
   455  		},
   456  		{
   457  			name: "single-namespace deployment creation",
   458  			ids: object.ObjMetadataSet{
   459  				deployment1ID,
   460  			},
   461  			clusterUpdates: []func(fakeClient *dynamicfake.FakeDynamicClient){
   462  				func(fakeClient *dynamicfake.FakeDynamicClient) {
   463  					// Empty cluster before synchronization.
   464  				},
   465  				func(fakeClient *dynamicfake.FakeDynamicClient) {
   466  					require.NoError(t, fakeClient.Tracker().Create(deployment1GVR, deployment1, deployment1.GetNamespace()))
   467  				},
   468  				func(fakeClient *dynamicfake.FakeDynamicClient) {
   469  					require.NoError(t, fakeClient.Tracker().Create(replicaset1GVR, replicaset1, replicaset1.GetNamespace()))
   470  					require.NoError(t, fakeClient.Tracker().Update(replicaset1GVR, replicaset1InProgress1, replicaset1InProgress1.GetNamespace()))
   471  					require.NoError(t, fakeClient.Tracker().Update(deployment1GVR, deployment1InProgress1, deployment1InProgress1.GetNamespace()))
   472  				},
   473  				func(fakeClient *dynamicfake.FakeDynamicClient) {
   474  					require.NoError(t, fakeClient.Tracker().Create(pod1GVR, pod1, pod1.GetNamespace()))
   475  					require.NoError(t, fakeClient.Tracker().Update(replicaset1GVR, replicaset1InProgress2, replicaset1InProgress2.GetNamespace()))
   476  					require.NoError(t, fakeClient.Tracker().Update(deployment1GVR, deployment1InProgress2, deployment1InProgress2.GetNamespace()))
   477  				},
   478  				func(fakeClient *dynamicfake.FakeDynamicClient) {
   479  					require.NoError(t, fakeClient.Tracker().Update(pod1GVR, pod1Current, pod1Current.GetNamespace()))
   480  					require.NoError(t, fakeClient.Tracker().Update(replicaset1GVR, replicaset1Current, replicaset1Current.GetNamespace()))
   481  					require.NoError(t, fakeClient.Tracker().Update(deployment1GVR, deployment1Current, deployment1Current.GetNamespace()))
   482  				},
   483  			},
   484  			expectedEvents: []event.Event{
   485  				{
   486  					Type: event.SyncEvent,
   487  				},
   488  				{
   489  					Resource: &event.ResourceStatus{
   490  						Identifier:         deployment1ID,
   491  						Status:             status.InProgressStatus,
   492  						Resource:           deployment1,
   493  						Message:            "Replicas: 0/1",
   494  						GeneratedResources: nil,
   495  					},
   496  				},
   497  				{
   498  					Resource: &event.ResourceStatus{
   499  						Identifier: deployment1ID,
   500  						Status:     status.InProgressStatus,
   501  						Resource:   deployment1InProgress1,
   502  						Message:    "Replicas: 0/1",
   503  						GeneratedResources: event.ResourceStatuses{
   504  							{
   505  								Identifier:         replicaset1ID,
   506  								Status:             status.InProgressStatus,
   507  								Resource:           replicaset1InProgress1,
   508  								Message:            "Labelled: 0/1",
   509  								GeneratedResources: nil,
   510  							},
   511  						},
   512  					},
   513  				},
   514  				{
   515  					Resource: &event.ResourceStatus{
   516  						Identifier: deployment1ID,
   517  						Status:     status.InProgressStatus,
   518  						Resource:   deployment1InProgress2,
   519  						Message:    "Available: 0/1",
   520  						GeneratedResources: event.ResourceStatuses{
   521  							{
   522  								Identifier: replicaset1ID,
   523  								Status:     status.InProgressStatus,
   524  								Resource:   replicaset1InProgress2,
   525  								Message:    "Available: 0/1",
   526  								GeneratedResources: event.ResourceStatuses{
   527  									{
   528  										Identifier:         pod1ID,
   529  										Status:             status.InProgressStatus,
   530  										Resource:           pod1,
   531  										Message:            "Pod phase not available",
   532  										GeneratedResources: nil,
   533  									},
   534  								},
   535  							},
   536  						},
   537  					},
   538  				},
   539  				{
   540  					Resource: &event.ResourceStatus{
   541  						Identifier: deployment1ID,
   542  						Status:     status.CurrentStatus,
   543  						Resource:   deployment1Current,
   544  						Message:    "Deployment is available. Replicas: 1",
   545  						GeneratedResources: event.ResourceStatuses{
   546  							{
   547  								Identifier: replicaset1ID,
   548  								Status:     status.CurrentStatus,
   549  								Resource:   replicaset1Current,
   550  								Message:    "ReplicaSet is available. Replicas: 1",
   551  								GeneratedResources: event.ResourceStatuses{
   552  									{
   553  										Identifier:         pod1ID,
   554  										Status:             status.CurrentStatus,
   555  										Resource:           pod1Current,
   556  										Message:            "Pod is Ready",
   557  										GeneratedResources: nil,
   558  									},
   559  								},
   560  							},
   561  						},
   562  					},
   563  				},
   564  			},
   565  		},
   566  		{
   567  			name: "single-namespace deployment deletion",
   568  			ids: object.ObjMetadataSet{
   569  				deployment1ID,
   570  			},
   571  			clusterUpdates: []func(fakeClient *dynamicfake.FakeDynamicClient){
   572  				func(fakeClient *dynamicfake.FakeDynamicClient) {
   573  					// Empty cluster before synchronization.
   574  				},
   575  				func(fakeClient *dynamicfake.FakeDynamicClient) {
   576  					require.NoError(t, fakeClient.Tracker().Create(pod1GVR, pod1Current, pod1Current.GetNamespace()))
   577  					require.NoError(t, fakeClient.Tracker().Create(replicaset1GVR, replicaset1Current, replicaset1Current.GetNamespace()))
   578  					require.NoError(t, fakeClient.Tracker().Create(deployment1GVR, deployment1Current, deployment1Current.GetNamespace()))
   579  				},
   580  				func(fakeClient *dynamicfake.FakeDynamicClient) {
   581  					require.NoError(t, fakeClient.Tracker().Delete(pod1GVR, pod1Current.GetNamespace(), pod1Current.GetName()))
   582  					require.NoError(t, fakeClient.Tracker().Delete(replicaset1GVR, replicaset1Current.GetNamespace(), replicaset1Current.GetName()))
   583  					require.NoError(t, fakeClient.Tracker().Delete(deployment1GVR, deployment1Current.GetNamespace(), deployment1Current.GetName()))
   584  				},
   585  			},
   586  			expectedEvents: []event.Event{
   587  				{
   588  					Type: event.SyncEvent,
   589  				},
   590  				{
   591  					Resource: &event.ResourceStatus{
   592  						Identifier: deployment1ID,
   593  						Status:     status.CurrentStatus,
   594  						Resource:   deployment1Current,
   595  						Message:    "Deployment is available. Replicas: 1",
   596  						GeneratedResources: event.ResourceStatuses{
   597  							{
   598  								Identifier: replicaset1ID,
   599  								Status:     status.CurrentStatus,
   600  								Resource:   replicaset1Current,
   601  								Message:    "ReplicaSet is available. Replicas: 1",
   602  								GeneratedResources: event.ResourceStatuses{
   603  									{
   604  										Identifier:         pod1ID,
   605  										Status:             status.CurrentStatus,
   606  										Resource:           pod1Current,
   607  										Message:            "Pod is Ready",
   608  										GeneratedResources: nil,
   609  									},
   610  								},
   611  							},
   612  						},
   613  					},
   614  				},
   615  				{
   616  					Resource: &event.ResourceStatus{
   617  						Identifier:         deployment1ID,
   618  						Status:             status.NotFoundStatus,
   619  						Resource:           nil,
   620  						Message:            "Resource not found",
   621  						GeneratedResources: nil,
   622  					},
   623  				},
   624  			},
   625  		},
   626  		{
   627  			name: "multi-namespace pod creation with automatic scope",
   628  			opts: Options{
   629  				RESTScopeStrategy: RESTScopeAutomatic,
   630  			},
   631  			ids: object.ObjMetadataSet{
   632  				pod2ID,
   633  				pod3ID,
   634  			},
   635  			clusterUpdates: []func(fakeClient *dynamicfake.FakeDynamicClient){
   636  				func(fakeClient *dynamicfake.FakeDynamicClient) {
   637  					// Empty cluster before synchronization.
   638  				},
   639  				func(fakeClient *dynamicfake.FakeDynamicClient) {
   640  					require.NoError(t, fakeClient.Tracker().Create(pod2GVR, pod2, pod2.GetNamespace()))
   641  				},
   642  				func(fakeClient *dynamicfake.FakeDynamicClient) {
   643  					require.NoError(t, fakeClient.Tracker().Create(pod3GVR, pod3, pod3.GetNamespace()))
   644  				},
   645  				func(fakeClient *dynamicfake.FakeDynamicClient) {
   646  					require.NoError(t, fakeClient.Tracker().Update(pod2GVR, pod2Current, pod2Current.GetNamespace()))
   647  				},
   648  				func(fakeClient *dynamicfake.FakeDynamicClient) {
   649  					require.NoError(t, fakeClient.Tracker().Update(pod3GVR, pod3Current, pod3Current.GetNamespace()))
   650  				},
   651  			},
   652  			expectedEvents: []event.Event{
   653  				{
   654  					Type: event.SyncEvent,
   655  				},
   656  				{
   657  					Resource: &event.ResourceStatus{
   658  						Identifier:         pod2ID,
   659  						Status:             status.InProgressStatus,
   660  						Resource:           pod2,
   661  						Message:            "Pod phase not available",
   662  						GeneratedResources: nil,
   663  					},
   664  				},
   665  				{
   666  					Resource: &event.ResourceStatus{
   667  						Identifier:         pod3ID,
   668  						Status:             status.InProgressStatus,
   669  						Resource:           pod3,
   670  						Message:            "Pod phase not available",
   671  						GeneratedResources: nil,
   672  					},
   673  				},
   674  				{
   675  					Resource: &event.ResourceStatus{
   676  						Identifier:         pod2ID,
   677  						Status:             status.CurrentStatus,
   678  						Resource:           pod2Current,
   679  						Message:            "Pod is Ready",
   680  						GeneratedResources: nil,
   681  					},
   682  				},
   683  				{
   684  					Resource: &event.ResourceStatus{
   685  						Identifier:         pod3ID,
   686  						Status:             status.CurrentStatus,
   687  						Resource:           pod3Current,
   688  						Message:            "Pod is Ready",
   689  						GeneratedResources: nil,
   690  					},
   691  				},
   692  			},
   693  		},
   694  		{
   695  			name: "multi-namespace pod creation with root scope",
   696  			opts: Options{
   697  				RESTScopeStrategy: RESTScopeRoot,
   698  			},
   699  			ids: object.ObjMetadataSet{
   700  				pod2ID,
   701  				pod3ID,
   702  			},
   703  			clusterUpdates: []func(fakeClient *dynamicfake.FakeDynamicClient){
   704  				func(fakeClient *dynamicfake.FakeDynamicClient) {
   705  					// Empty cluster before synchronization.
   706  				},
   707  				func(fakeClient *dynamicfake.FakeDynamicClient) {
   708  					require.NoError(t, fakeClient.Tracker().Create(pod2GVR, pod2, pod2.GetNamespace()))
   709  				},
   710  				func(fakeClient *dynamicfake.FakeDynamicClient) {
   711  					require.NoError(t, fakeClient.Tracker().Create(pod3GVR, pod3, pod3.GetNamespace()))
   712  				},
   713  				func(fakeClient *dynamicfake.FakeDynamicClient) {
   714  					require.NoError(t, fakeClient.Tracker().Update(pod2GVR, pod2Current, pod2Current.GetNamespace()))
   715  				},
   716  				func(fakeClient *dynamicfake.FakeDynamicClient) {
   717  					require.NoError(t, fakeClient.Tracker().Update(pod3GVR, pod3Current, pod3Current.GetNamespace()))
   718  				},
   719  			},
   720  			expectedEvents: []event.Event{
   721  				{
   722  					Type: event.SyncEvent,
   723  				},
   724  				{
   725  					Resource: &event.ResourceStatus{
   726  						Identifier:         pod2ID,
   727  						Status:             status.InProgressStatus,
   728  						Resource:           pod2,
   729  						Message:            "Pod phase not available",
   730  						GeneratedResources: nil,
   731  					},
   732  				},
   733  				{
   734  					Resource: &event.ResourceStatus{
   735  						Identifier:         pod3ID,
   736  						Status:             status.InProgressStatus,
   737  						Resource:           pod3,
   738  						Message:            "Pod phase not available",
   739  						GeneratedResources: nil,
   740  					},
   741  				},
   742  				{
   743  					Resource: &event.ResourceStatus{
   744  						Identifier:         pod2ID,
   745  						Status:             status.CurrentStatus,
   746  						Resource:           pod2Current,
   747  						Message:            "Pod is Ready",
   748  						GeneratedResources: nil,
   749  					},
   750  				},
   751  				{
   752  					Resource: &event.ResourceStatus{
   753  						Identifier:         pod3ID,
   754  						Status:             status.CurrentStatus,
   755  						Resource:           pod3Current,
   756  						Message:            "Pod is Ready",
   757  						GeneratedResources: nil,
   758  					},
   759  				},
   760  			},
   761  		},
   762  		{
   763  			name: "multi-namespace pod creation with namespace scope",
   764  			opts: Options{
   765  				RESTScopeStrategy: RESTScopeNamespace,
   766  			},
   767  			ids: object.ObjMetadataSet{
   768  				pod2ID,
   769  				pod3ID,
   770  			},
   771  			clusterUpdates: []func(fakeClient *dynamicfake.FakeDynamicClient){
   772  				func(fakeClient *dynamicfake.FakeDynamicClient) {
   773  					// Empty cluster before synchronization.
   774  				},
   775  				func(fakeClient *dynamicfake.FakeDynamicClient) {
   776  					require.NoError(t, fakeClient.Tracker().Create(pod2GVR, pod2, pod2.GetNamespace()))
   777  				},
   778  				func(fakeClient *dynamicfake.FakeDynamicClient) {
   779  					require.NoError(t, fakeClient.Tracker().Create(pod3GVR, pod3, pod3.GetNamespace()))
   780  				},
   781  				func(fakeClient *dynamicfake.FakeDynamicClient) {
   782  					require.NoError(t, fakeClient.Tracker().Update(pod2GVR, pod2Current, pod2Current.GetNamespace()))
   783  				},
   784  				func(fakeClient *dynamicfake.FakeDynamicClient) {
   785  					require.NoError(t, fakeClient.Tracker().Update(pod3GVR, pod3Current, pod3Current.GetNamespace()))
   786  				},
   787  			},
   788  			expectedEvents: []event.Event{
   789  				{
   790  					Type: event.SyncEvent,
   791  				},
   792  				{
   793  					Resource: &event.ResourceStatus{
   794  						Identifier:         pod2ID,
   795  						Status:             status.InProgressStatus,
   796  						Resource:           pod2,
   797  						Message:            "Pod phase not available",
   798  						GeneratedResources: nil,
   799  					},
   800  				},
   801  				{
   802  					Resource: &event.ResourceStatus{
   803  						Identifier:         pod3ID,
   804  						Status:             status.InProgressStatus,
   805  						Resource:           pod3,
   806  						Message:            "Pod phase not available",
   807  						GeneratedResources: nil,
   808  					},
   809  				},
   810  				{
   811  					Resource: &event.ResourceStatus{
   812  						Identifier:         pod2ID,
   813  						Status:             status.CurrentStatus,
   814  						Resource:           pod2Current,
   815  						Message:            "Pod is Ready",
   816  						GeneratedResources: nil,
   817  					},
   818  				},
   819  				{
   820  					Resource: &event.ResourceStatus{
   821  						Identifier:         pod3ID,
   822  						Status:             status.CurrentStatus,
   823  						Resource:           pod3Current,
   824  						Message:            "Pod is Ready",
   825  						GeneratedResources: nil,
   826  					},
   827  				},
   828  			},
   829  		},
   830  	}
   831  
   832  	testTimeout := 10 * time.Second
   833  
   834  	for _, tc := range testCases {
   835  		t.Run(tc.name, func(t *testing.T) {
   836  			ctx, cancel := context.WithTimeout(context.Background(), testTimeout)
   837  			defer cancel()
   838  
   839  			fakeClient := dynamicfake.NewSimpleDynamicClient(scheme.Scheme)
   840  
   841  			// log fakeClient calls
   842  			fakeClient.PrependReactor("*", "*", func(a clienttesting.Action) (bool, runtime.Object, error) {
   843  				klog.V(3).Infof("FakeDynamicClient: %T{ Verb: %q, Resource: %q, Namespace: %q }",
   844  					a, a.GetVerb(), a.GetResource().Resource, a.GetNamespace())
   845  				return false, nil, nil
   846  			})
   847  			fakeClient.PrependWatchReactor("*", func(a clienttesting.Action) (bool, watch.Interface, error) {
   848  				klog.V(3).Infof("FakeDynamicClient: %T{ Verb: %q, Resource: %q, Namespace: %q }",
   849  					a, a.GetVerb(), a.GetResource().Resource, a.GetNamespace())
   850  				return false, nil, nil
   851  			})
   852  
   853  			statusWatcher := NewDefaultStatusWatcher(fakeClient, fakeMapper)
   854  			eventCh := statusWatcher.Watch(ctx, tc.ids, tc.opts)
   855  
   856  			nextCh := make(chan struct{})
   857  			defer close(nextCh)
   858  
   859  			// Synchronize event consumption and production for predictable test results.
   860  			go func() {
   861  				for _, update := range tc.clusterUpdates {
   862  					<-nextCh
   863  					update(fakeClient)
   864  				}
   865  				// Wait for final event to be handled
   866  				<-nextCh
   867  				// Stop the watcher
   868  				cancel()
   869  			}()
   870  
   871  			// Trigger first server update
   872  			nextCh <- struct{}{}
   873  
   874  			receivedEvents := []event.Event{}
   875  			for e := range eventCh {
   876  				receivedEvents = append(receivedEvents, e)
   877  				// Trigger next server update
   878  				nextCh <- struct{}{}
   879  			}
   880  			testutil.AssertEqual(t, tc.expectedEvents, receivedEvents)
   881  		})
   882  	}
   883  }
   884  
   885  func getGVR(t *testing.T, mapper meta.RESTMapper, obj *unstructured.Unstructured) schema.GroupVersionResource {
   886  	gvk := obj.GroupVersionKind()
   887  	mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
   888  	require.NoError(t, err)
   889  	return mapping.Resource
   890  }
   891  

View as plain text