github.com/aporeto-inc/trireme-lib@v10.358.0+incompatible/monitor/internal/k8s/pod_cache_test.go (about)

     1  package k8smonitor
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"reflect"
     7  	"sync"
     8  	"testing"
     9  	"time"
    10  
    11  	corev1 "k8s.io/api/core/v1"
    12  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    13  	"k8s.io/client-go/kubernetes"
    14  	"k8s.io/client-go/kubernetes/fake"
    15  	//kubernetesTesting "k8s.io/client-go/testing"
    16  )
    17  
    18  func Test_podCache_Delete(t *testing.T) {
    19  	updateEvent := func(context.Context, string) error {
    20  		return nil
    21  	}
    22  
    23  	tests := []struct {
    24  		name      string
    25  		c         *podCache
    26  		sandboxID string
    27  	}{
    28  		{
    29  			name:      "cache uninitialized",
    30  			c:         nil,
    31  			sandboxID: "does-not-matter",
    32  		},
    33  		{
    34  			name:      "cache initialized",
    35  			c:         newPodCache(updateEvent),
    36  			sandboxID: "does-not-mater",
    37  		},
    38  	}
    39  	for _, tt := range tests {
    40  		t.Run(tt.name, func(t *testing.T) {
    41  			tt.c.Delete(tt.sandboxID)
    42  		})
    43  	}
    44  }
    45  
    46  func Test_podCache_Set(t *testing.T) {
    47  	updateEvent := func(context.Context, string) error {
    48  		return nil
    49  	}
    50  	type args struct {
    51  		sandboxID string
    52  		pod       *corev1.Pod
    53  	}
    54  	tests := []struct {
    55  		name         string
    56  		c            *podCache
    57  		args         args
    58  		wantErr      bool
    59  		wantErrError error
    60  	}{
    61  		{
    62  			name:         "cache uninitialized",
    63  			c:            nil,
    64  			wantErr:      true,
    65  			wantErrError: errCacheUninitialized,
    66  		},
    67  		{
    68  			name:         "cache has unintialized map",
    69  			c:            &podCache{},
    70  			wantErr:      true,
    71  			wantErrError: errCacheUninitialized,
    72  			args: args{
    73  				sandboxID: "does-not-matter",
    74  				pod:       &corev1.Pod{},
    75  			},
    76  		},
    77  		{
    78  			name:         "no sandboxID",
    79  			c:            newPodCache(updateEvent),
    80  			wantErr:      true,
    81  			wantErrError: errSandboxEmpty,
    82  			args: args{
    83  				sandboxID: "",
    84  				pod:       &corev1.Pod{},
    85  			},
    86  		},
    87  		{
    88  			name:         "pod is nil",
    89  			c:            newPodCache(updateEvent),
    90  			wantErr:      true,
    91  			wantErrError: errPodNil,
    92  			args: args{
    93  				sandboxID: "does-not-matter",
    94  				pod:       nil,
    95  			},
    96  		},
    97  		{
    98  			name:    "successful update entry",
    99  			c:       newPodCache(updateEvent),
   100  			wantErr: false,
   101  			args: args{
   102  				sandboxID: "does-not-matter",
   103  				pod:       &corev1.Pod{},
   104  			},
   105  		},
   106  	}
   107  	for _, tt := range tests {
   108  		t.Run(tt.name, func(t *testing.T) {
   109  			err := tt.c.Set(tt.args.sandboxID, tt.args.pod)
   110  			if (err != nil) != tt.wantErr {
   111  				t.Errorf("podCache.Set() error = %v, wantErr %v", err, tt.wantErr)
   112  			}
   113  			if tt.wantErr {
   114  				if err != tt.wantErrError {
   115  					t.Errorf("podCache.Set() error = %v, wantErrError %v", err, tt.wantErrError)
   116  				}
   117  			}
   118  		})
   119  	}
   120  }
   121  
   122  func Test_podCache_Get(t *testing.T) {
   123  	updateEvent := func(context.Context, string) error {
   124  		return nil
   125  	}
   126  	cacheWithEntry := newPodCache(updateEvent)
   127  	if err := cacheWithEntry.Set("entry", &corev1.Pod{}); err != nil {
   128  		panic(err)
   129  	}
   130  	tests := []struct {
   131  		name      string
   132  		sandboxID string
   133  		c         *podCache
   134  		want      *corev1.Pod
   135  	}{
   136  		{
   137  			name: "uninitialized podCache",
   138  			c:    nil,
   139  			want: nil,
   140  		},
   141  		{
   142  			name: "uninitialized map in podCache",
   143  			c:    &podCache{},
   144  			want: nil,
   145  		},
   146  		{
   147  			name:      "entry does not exist",
   148  			c:         newPodCache(updateEvent),
   149  			sandboxID: "does-not-exist",
   150  			want:      nil,
   151  		},
   152  		{
   153  			name:      "entry exists",
   154  			c:         cacheWithEntry,
   155  			sandboxID: "entry",
   156  			want:      &corev1.Pod{},
   157  		},
   158  	}
   159  	for _, tt := range tests {
   160  		t.Run(tt.name, func(t *testing.T) {
   161  			if got := tt.c.Get(tt.sandboxID); !reflect.DeepEqual(got, tt.want) {
   162  				t.Errorf("podCache.Get() = %v, want %v", got, tt.want)
   163  			}
   164  		})
   165  	}
   166  }
   167  
   168  func Test_podCache_FindSandboxID(t *testing.T) {
   169  	updateEvent := func(context.Context, string) error {
   170  		return nil
   171  	}
   172  	cacheWithEntry := newPodCache(updateEvent)
   173  	if err := cacheWithEntry.Set("entry", &corev1.Pod{
   174  		ObjectMeta: metav1.ObjectMeta{
   175  			Name:      "my-pod",
   176  			Namespace: "default",
   177  		},
   178  	}); err != nil {
   179  		panic(err)
   180  	}
   181  	type args struct {
   182  		name      string
   183  		namespace string
   184  	}
   185  	tests := []struct {
   186  		name         string
   187  		c            *podCache
   188  		args         args
   189  		want         string
   190  		wantErr      bool
   191  		wantErrError error
   192  	}{
   193  		{
   194  			name:         "cache uninitialized",
   195  			c:            nil,
   196  			wantErr:      true,
   197  			wantErrError: errCacheUninitialized,
   198  		},
   199  		{
   200  			name:         "pods uninitialized",
   201  			c:            &podCache{},
   202  			wantErr:      true,
   203  			wantErrError: errCacheUninitialized,
   204  		},
   205  		{
   206  			name: "pod name empty",
   207  			c:    newPodCache(updateEvent),
   208  			args: args{
   209  				name:      "",
   210  				namespace: "default",
   211  			},
   212  			wantErr:      true,
   213  			wantErrError: errPodNameEmpty,
   214  		},
   215  		{
   216  			name: "pod namespace empty",
   217  			c:    newPodCache(updateEvent),
   218  			args: args{
   219  				name:      "my-pod",
   220  				namespace: "",
   221  			},
   222  			wantErr:      true,
   223  			wantErrError: errPodNamespaceEmpty,
   224  		},
   225  		{
   226  			name: "sandbox not found",
   227  			c:    newPodCache(updateEvent),
   228  			args: args{
   229  				name:      "my-pod",
   230  				namespace: "default",
   231  			},
   232  			wantErr:      true,
   233  			wantErrError: errSandboxNotFound,
   234  		},
   235  		{
   236  			name: "sandbox found",
   237  			c:    cacheWithEntry,
   238  			args: args{
   239  				name:      "my-pod",
   240  				namespace: "default",
   241  			},
   242  			wantErr: false,
   243  			want:    "entry",
   244  		},
   245  	}
   246  	for _, tt := range tests {
   247  		t.Run(tt.name, func(t *testing.T) {
   248  			got, err := tt.c.FindSandboxID(tt.args.name, tt.args.namespace)
   249  			if (err != nil) != tt.wantErr {
   250  				t.Errorf("podCache.FindSandboxID() error = %v, wantErr %v", err, tt.wantErr)
   251  				return
   252  			}
   253  			if got != tt.want {
   254  				t.Errorf("podCache.FindSandboxID() = %v, want %v", got, tt.want)
   255  			}
   256  			if tt.wantErr {
   257  				if err != tt.wantErrError {
   258  					t.Errorf("podCache.FindSandboxID() error = %v, wantErrError %v", err, tt.wantErrError)
   259  				}
   260  			}
   261  		})
   262  	}
   263  }
   264  
   265  type unitTestUpdateEvent interface {
   266  	f() updateEventFunc
   267  	wait()
   268  	called() bool
   269  }
   270  type unitTestUpdateEventHandler struct {
   271  	sync.RWMutex
   272  	wg        sync.WaitGroup
   273  	wgCounter int
   274  	wasCalled bool
   275  	err       error
   276  }
   277  
   278  func (h *unitTestUpdateEventHandler) updateEvent(context.Context, string) error {
   279  	h.Lock()
   280  	defer h.Unlock()
   281  	h.wasCalled = true
   282  	if h.wgCounter > 0 {
   283  		h.wgCounter--
   284  	}
   285  	if h.wgCounter >= 0 {
   286  		h.wg.Done()
   287  	}
   288  	return h.err
   289  }
   290  
   291  func (h *unitTestUpdateEventHandler) f() updateEventFunc {
   292  	return h.updateEvent
   293  }
   294  
   295  func (h *unitTestUpdateEventHandler) wait() {
   296  	h.wg.Wait()
   297  }
   298  
   299  func (h *unitTestUpdateEventHandler) called() bool {
   300  	h.RLock()
   301  	defer h.RUnlock()
   302  	return h.wasCalled
   303  }
   304  
   305  func newUnitTestUpdateEventHandler(n int, err error) unitTestUpdateEvent {
   306  	h := &unitTestUpdateEventHandler{
   307  		err:       err,
   308  		wgCounter: n,
   309  	}
   310  	h.wg.Add(n)
   311  	return h
   312  }
   313  
   314  func Test_podCache_SetupInformer(t *testing.T) {
   315  	podTemplate := &corev1.Pod{
   316  		ObjectMeta: metav1.ObjectMeta{
   317  			Name:      "my-pod",
   318  			Namespace: "default",
   319  		},
   320  		Spec: corev1.PodSpec{
   321  			NodeName: "test",
   322  		},
   323  	}
   324  	running := corev1.ContainerStateRunning{}
   325  	pending := corev1.ContainerStateWaiting{}
   326  
   327  	hostpodTemplate := &corev1.Pod{
   328  		ObjectMeta: metav1.ObjectMeta{
   329  			Name:      "my-host-pod",
   330  			Namespace: "default",
   331  		},
   332  		Spec: corev1.PodSpec{
   333  			NodeName:    "test",
   334  			HostNetwork: true,
   335  		},
   336  		Status: corev1.PodStatus{
   337  			Phase: corev1.PodRunning,
   338  			InitContainerStatuses: []corev1.ContainerStatus{
   339  				{
   340  					ContainerID: "testing://containerID",
   341  					Ready:       true,
   342  					State: corev1.ContainerState{
   343  						Waiting: &pending,
   344  					},
   345  				},
   346  			},
   347  			ContainerStatuses: []corev1.ContainerStatus{
   348  				{
   349  					ContainerID: "broken-container-id-needs-to-be-skipped",
   350  					Ready:       true,
   351  					State: corev1.ContainerState{
   352  						Waiting: &pending,
   353  					},
   354  				},
   355  			},
   356  		},
   357  	}
   358  	updateHostPodTemplate := hostpodTemplate.DeepCopy()
   359  	updateHostPodTemplate.Status.InitContainerStatuses[0].State.Running = &running
   360  	updateHostPodTemplate2 := updateHostPodTemplate.DeepCopy()
   361  	updateHostPodTemplate2.Labels = map[string]string{
   362  		"a": "b",
   363  	}
   364  
   365  	updatedPodTemplate := podTemplate.DeepCopy()
   366  	updatedPodTemplate.Labels = map[string]string{
   367  		"a": "b",
   368  	}
   369  	updatedPodTemplate2 := updatedPodTemplate.DeepCopy()
   370  	updatedPodTemplate2.Annotations = map[string]string{
   371  		"annotated": "",
   372  	}
   373  
   374  	untrackedPodOnSameHostTemplate := &corev1.Pod{
   375  		ObjectMeta: metav1.ObjectMeta{
   376  			Name:      "untracked-same-host",
   377  			Namespace: "default",
   378  		},
   379  		Spec: corev1.PodSpec{
   380  			NodeName: "test",
   381  		},
   382  	}
   383  	untrackedPodOnDifferentHostTemplate := &corev1.Pod{
   384  		ObjectMeta: metav1.ObjectMeta{
   385  			Name:      "untracked-different-host",
   386  			Namespace: "default",
   387  		},
   388  		Spec: corev1.PodSpec{
   389  			NodeName: "different",
   390  		},
   391  	}
   392  
   393  	c := fake.NewSimpleClientset(
   394  		podTemplate.DeepCopy(),
   395  		untrackedPodOnSameHostTemplate.DeepCopy(),
   396  		untrackedPodOnDifferentHostTemplate.DeepCopy(),
   397  		hostpodTemplate.DeepCopy(),
   398  	)
   399  
   400  	type fields struct {
   401  		pods map[string]*corev1.Pod
   402  	}
   403  	type args struct {
   404  		ctx         context.Context
   405  		kubeClient  kubernetes.Interface
   406  		nodeName    string
   407  		needsUpdate needsUpdateFunc
   408  	}
   409  	tests := []struct {
   410  		name                string
   411  		updateEventHandler  unitTestUpdateEvent
   412  		fields              fields
   413  		args                args
   414  		action              func(*testing.T, *podCache)
   415  		expectedUpdateEvent bool
   416  		expectedPods        map[string]*corev1.Pod
   417  	}{
   418  		{
   419  			name:               "update to a pod which we have in cache which requires update event",
   420  			updateEventHandler: newUnitTestUpdateEventHandler(1, fmt.Errorf("increase coverage")),
   421  			fields: fields{
   422  				pods: map[string]*corev1.Pod{
   423  					"entry": podTemplate.DeepCopy(),
   424  				},
   425  			},
   426  			args: args{
   427  				ctx:         context.Background(),
   428  				kubeClient:  c,
   429  				nodeName:    "test",
   430  				needsUpdate: defaultNeedsUpdate,
   431  			},
   432  			action: func(_ *testing.T, _ *podCache) {
   433  				_, err := c.CoreV1().Pods("default").Update(context.Background(), updatedPodTemplate.DeepCopy(), metav1.UpdateOptions{})
   434  				if err != nil {
   435  					panic(err)
   436  				}
   437  			},
   438  			expectedUpdateEvent: true,
   439  			expectedPods: map[string]*corev1.Pod{
   440  				"entry": updatedPodTemplate.DeepCopy(),
   441  			},
   442  		},
   443  		{
   444  			name:               "update to a pod which we have in cache which does not require an update event",
   445  			updateEventHandler: newUnitTestUpdateEventHandler(0, nil),
   446  			fields: fields{
   447  				pods: map[string]*corev1.Pod{
   448  					"entry": podTemplate.DeepCopy(),
   449  				},
   450  			},
   451  			args: args{
   452  				ctx:         context.Background(),
   453  				kubeClient:  c,
   454  				nodeName:    "test",
   455  				needsUpdate: defaultNeedsUpdate,
   456  			},
   457  			action: func(_ *testing.T, _ *podCache) {
   458  				_, err := c.CoreV1().Pods("default").Update(context.Background(), updatedPodTemplate2.DeepCopy(), metav1.UpdateOptions{})
   459  				if err != nil {
   460  					panic(err)
   461  				}
   462  				time.Sleep(time.Millisecond * 100)
   463  			},
   464  			expectedUpdateEvent: false,
   465  			expectedPods: map[string]*corev1.Pod{
   466  				"entry": updatedPodTemplate2.DeepCopy(),
   467  			},
   468  		},
   469  		{
   470  			name:               "update to a pod from different host which we do not track",
   471  			updateEventHandler: newUnitTestUpdateEventHandler(0, nil),
   472  			fields: fields{
   473  				pods: map[string]*corev1.Pod{
   474  					"entry": podTemplate.DeepCopy(),
   475  				},
   476  			},
   477  			args: args{
   478  				ctx:         context.Background(),
   479  				kubeClient:  c,
   480  				nodeName:    "test",
   481  				needsUpdate: defaultNeedsUpdate,
   482  			},
   483  			action: func(_ *testing.T, _ *podCache) {
   484  				updated := untrackedPodOnDifferentHostTemplate.DeepCopy()
   485  				updated.Labels = map[string]string{
   486  					"update": "",
   487  				}
   488  				_, err := c.CoreV1().Pods("default").Update(context.Background(), updated.DeepCopy(), metav1.UpdateOptions{})
   489  				if err != nil {
   490  					panic(err)
   491  				}
   492  			},
   493  			expectedUpdateEvent: false,
   494  			expectedPods: map[string]*corev1.Pod{
   495  				"entry": podTemplate.DeepCopy(),
   496  			},
   497  		},
   498  		{
   499  			name:               "update to a pod from the same host which we do not track",
   500  			updateEventHandler: newUnitTestUpdateEventHandler(0, nil),
   501  			fields: fields{
   502  				pods: map[string]*corev1.Pod{
   503  					"entry": podTemplate.DeepCopy(),
   504  				},
   505  			},
   506  			args: args{
   507  				ctx:         context.Background(),
   508  				kubeClient:  c,
   509  				nodeName:    "test",
   510  				needsUpdate: defaultNeedsUpdate,
   511  			},
   512  			action: func(_ *testing.T, _ *podCache) {
   513  				updated := untrackedPodOnSameHostTemplate.DeepCopy()
   514  				updated.Labels = map[string]string{
   515  					"update": "",
   516  				}
   517  				_, err := c.CoreV1().Pods("default").Update(context.Background(), updated.DeepCopy(), metav1.UpdateOptions{})
   518  				if err != nil {
   519  					panic(err)
   520  				}
   521  			},
   522  			expectedUpdateEvent: false,
   523  			expectedPods: map[string]*corev1.Pod{
   524  				"entry": podTemplate.DeepCopy(),
   525  			},
   526  		},
   527  	}
   528  	for _, tt := range tests {
   529  		t.Run(tt.name, func(t *testing.T) {
   530  			c := &podCache{
   531  				pods:        tt.fields.pods,
   532  				updateEvent: tt.updateEventHandler.f(),
   533  			}
   534  			ctx, cancel := context.WithCancel(tt.args.ctx)
   535  			c.SetupInformer(ctx, tt.args.kubeClient, tt.args.nodeName, tt.args.needsUpdate)
   536  			tt.action(t, c)
   537  			tt.updateEventHandler.wait()
   538  			c.RLock()
   539  			if !reflect.DeepEqual(c.pods, tt.expectedPods) {
   540  				t.Errorf("c.pods = %v, want %v", c.pods, tt.expectedPods)
   541  			}
   542  			c.RUnlock()
   543  			if tt.expectedUpdateEvent != tt.updateEventHandler.called() {
   544  				t.Errorf("updateEventHandler.called() = %v, want %v", tt.updateEventHandler.called(), tt.expectedUpdateEvent)
   545  			}
   546  			cancel()
   547  		})
   548  	}
   549  }
   550  
   551  func Test_defaultNeedsUpdate(t *testing.T) {
   552  	type args struct {
   553  		prev *corev1.Pod
   554  		obj  *corev1.Pod
   555  	}
   556  	tests := []struct {
   557  		name string
   558  		args args
   559  		want bool
   560  	}{
   561  		{
   562  			name: "labels are both nil",
   563  			args: args{
   564  				prev: &corev1.Pod{},
   565  				obj:  &corev1.Pod{},
   566  			},
   567  			want: false,
   568  		},
   569  		{
   570  			name: "labels are empty",
   571  			args: args{
   572  				prev: &corev1.Pod{
   573  					ObjectMeta: metav1.ObjectMeta{
   574  						Labels: map[string]string{},
   575  					},
   576  				},
   577  				obj: &corev1.Pod{
   578  					ObjectMeta: metav1.ObjectMeta{
   579  						Labels: map[string]string{},
   580  					},
   581  				},
   582  			},
   583  			want: false,
   584  		},
   585  		{
   586  			name: "labels are the same",
   587  			args: args{
   588  				prev: &corev1.Pod{
   589  					ObjectMeta: metav1.ObjectMeta{
   590  						Labels: map[string]string{
   591  							"a": "b",
   592  						},
   593  					},
   594  				},
   595  				obj: &corev1.Pod{
   596  					ObjectMeta: metav1.ObjectMeta{
   597  						Labels: map[string]string{
   598  							"a": "b",
   599  						},
   600  					},
   601  				},
   602  			},
   603  			want: false,
   604  		},
   605  		{
   606  			name: "labels are the same, but one has annotations",
   607  			args: args{
   608  				prev: &corev1.Pod{
   609  					ObjectMeta: metav1.ObjectMeta{
   610  						Labels: map[string]string{
   611  							"a": "b",
   612  						},
   613  					},
   614  				},
   615  				obj: &corev1.Pod{
   616  					ObjectMeta: metav1.ObjectMeta{
   617  						Labels: map[string]string{
   618  							"a": "b",
   619  						},
   620  						Annotations: map[string]string{
   621  							"a": "b",
   622  						},
   623  					},
   624  				},
   625  			},
   626  			want: false,
   627  		},
   628  		{
   629  			name: "labels are nil in prev, but set on new",
   630  			args: args{
   631  				prev: &corev1.Pod{},
   632  				obj: &corev1.Pod{
   633  					ObjectMeta: metav1.ObjectMeta{
   634  						Labels: map[string]string{
   635  							"a": "b",
   636  						},
   637  					},
   638  				},
   639  			},
   640  			want: true,
   641  		},
   642  		{
   643  			name: "labels are empty in prev, but set on new",
   644  			args: args{
   645  				prev: &corev1.Pod{
   646  					ObjectMeta: metav1.ObjectMeta{
   647  						Labels: map[string]string{},
   648  					},
   649  				},
   650  				obj: &corev1.Pod{
   651  					ObjectMeta: metav1.ObjectMeta{
   652  						Labels: map[string]string{
   653  							"a": "b",
   654  						},
   655  					},
   656  				},
   657  			},
   658  			want: true,
   659  		},
   660  		{
   661  			name: "labels have same key but different value",
   662  			args: args{
   663  				prev: &corev1.Pod{
   664  					ObjectMeta: metav1.ObjectMeta{
   665  						Labels: map[string]string{
   666  							"a": "a",
   667  						},
   668  					},
   669  				},
   670  				obj: &corev1.Pod{
   671  					ObjectMeta: metav1.ObjectMeta{
   672  						Labels: map[string]string{
   673  							"a": "b",
   674  						},
   675  					},
   676  				},
   677  			},
   678  			want: true,
   679  		},
   680  		{
   681  			name: "prev has one label more",
   682  			args: args{
   683  				prev: &corev1.Pod{
   684  					ObjectMeta: metav1.ObjectMeta{
   685  						Labels: map[string]string{
   686  							"a": "b",
   687  							"b": "b",
   688  						},
   689  					},
   690  				},
   691  				obj: &corev1.Pod{
   692  					ObjectMeta: metav1.ObjectMeta{
   693  						Labels: map[string]string{
   694  							"a": "b",
   695  						},
   696  					},
   697  				},
   698  			},
   699  			want: true,
   700  		},
   701  		{
   702  			name: "prev has different labels",
   703  			args: args{
   704  				prev: &corev1.Pod{
   705  					ObjectMeta: metav1.ObjectMeta{
   706  						Labels: map[string]string{
   707  							"b": "b",
   708  						},
   709  					},
   710  				},
   711  				obj: &corev1.Pod{
   712  					ObjectMeta: metav1.ObjectMeta{
   713  						Labels: map[string]string{
   714  							"a": "b",
   715  						},
   716  					},
   717  				},
   718  			},
   719  			want: true,
   720  		},
   721  	}
   722  	for _, tt := range tests {
   723  		t.Run(tt.name, func(t *testing.T) {
   724  			if got := defaultNeedsUpdate(tt.args.prev, tt.args.obj); got != tt.want {
   725  				t.Errorf("defaultNeedsUpdate() = %v, want %v", got, tt.want)
   726  			}
   727  		})
   728  	}
   729  }