...

Source file src/github.com/redhat-developer/odo/pkg/storage/kubernetes_test.go

Documentation: github.com/redhat-developer/odo/pkg/storage

     1  package storage
     2  
     3  import (
     4  	"strings"
     5  	"testing"
     6  
     7  	"github.com/golang/mock/gomock"
     8  	"github.com/google/go-cmp/cmp"
     9  	appsv1 "k8s.io/api/apps/v1"
    10  	corev1 "k8s.io/api/core/v1"
    11  	"k8s.io/apimachinery/pkg/api/resource"
    12  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    13  	"k8s.io/apimachinery/pkg/runtime"
    14  	ktesting "k8s.io/client-go/testing"
    15  
    16  	"github.com/redhat-developer/odo/pkg/kclient"
    17  	odolabels "github.com/redhat-developer/odo/pkg/labels"
    18  	"github.com/redhat-developer/odo/pkg/testingutil"
    19  	"github.com/redhat-developer/odo/pkg/util"
    20  )
    21  
    22  func Test_kubernetesClient_List(t *testing.T) {
    23  	type fields struct {
    24  		generic generic
    25  	}
    26  	tests := []struct {
    27  		name                string
    28  		fields              fields
    29  		returnedDeployments *appsv1.DeploymentList
    30  		returnedPVCs        *corev1.PersistentVolumeClaimList
    31  		want                StorageList
    32  		wantErr             bool
    33  	}{
    34  		{
    35  			name: "case 1: should error out for multiple pods returned",
    36  			fields: fields{
    37  				generic: generic{
    38  					appName:       "app",
    39  					componentName: "nodejs",
    40  				},
    41  			},
    42  			returnedDeployments: &appsv1.DeploymentList{
    43  				Items: []appsv1.Deployment{
    44  					*testingutil.CreateFakeDeployment("nodejs", true),
    45  					*testingutil.CreateFakeDeployment("nodejs", true),
    46  				},
    47  			},
    48  			wantErr: true,
    49  		},
    50  		{
    51  			name: "case 2: pod not found",
    52  			fields: fields{
    53  				generic: generic{
    54  					appName:       "app",
    55  					componentName: "nodejs",
    56  				},
    57  			},
    58  			returnedDeployments: &appsv1.DeploymentList{
    59  				Items: []appsv1.Deployment{},
    60  			},
    61  			want:    StorageList{},
    62  			wantErr: false,
    63  		},
    64  		{
    65  			name: "case 3: no volume mounts on pod",
    66  			fields: fields{
    67  				generic: generic{
    68  					componentName: "nodejs",
    69  					appName:       "app",
    70  				},
    71  			},
    72  			returnedDeployments: &appsv1.DeploymentList{
    73  				Items: []appsv1.Deployment{
    74  					*testingutil.CreateFakeDeployment("nodejs", true),
    75  				},
    76  			},
    77  			want:    StorageList{},
    78  			wantErr: false,
    79  		},
    80  		{
    81  			name: "case 4: two volumes mounted on a single container",
    82  			fields: fields{
    83  				generic: generic{
    84  					componentName: "nodejs",
    85  					appName:       "app",
    86  				},
    87  			},
    88  			returnedDeployments: &appsv1.DeploymentList{
    89  				Items: []appsv1.Deployment{
    90  					*testingutil.CreateFakeDeploymentsWithContainers("nodejs", []corev1.Container{
    91  						testingutil.CreateFakeContainerWithVolumeMounts("container-0", []corev1.VolumeMount{
    92  							{Name: "volume-0-vol", MountPath: "/data"},
    93  							{Name: "volume-1-vol", MountPath: "/path"},
    94  						}),
    95  					}, []corev1.Container{}, true),
    96  				},
    97  			},
    98  			returnedPVCs: &corev1.PersistentVolumeClaimList{
    99  				Items: []corev1.PersistentVolumeClaim{
   100  					*testingutil.FakePVC("volume-0", "5Gi", odolabels.Builder().WithComponent("nodejs").WithDevfileStorageName("volume-0").Labels()),
   101  					*testingutil.FakePVC("volume-1", "10Gi", odolabels.Builder().WithComponent("nodejs").WithDevfileStorageName("volume-1").Labels()),
   102  				},
   103  			},
   104  			want: StorageList{
   105  				Items: []Storage{
   106  					generateStorage(NewStorage("volume-0", "5Gi", "/data", nil), "", "container-0"),
   107  					generateStorage(NewStorage("volume-1", "10Gi", "/path", nil), "", "container-0"),
   108  				},
   109  			},
   110  			wantErr: false,
   111  		},
   112  		{
   113  			name: "case 5: one volume is mounted on a single container and another on both",
   114  			fields: fields{
   115  				generic: generic{
   116  					appName:       "app",
   117  					componentName: "nodejs",
   118  				},
   119  			},
   120  			returnedDeployments: &appsv1.DeploymentList{
   121  				Items: []appsv1.Deployment{
   122  					*testingutil.CreateFakeDeploymentsWithContainers("nodejs", []corev1.Container{
   123  						testingutil.CreateFakeContainerWithVolumeMounts("container-0", []corev1.VolumeMount{
   124  							{Name: "volume-0-vol", MountPath: "/data"},
   125  							{Name: "volume-1-vol", MountPath: "/path"},
   126  						}),
   127  						testingutil.CreateFakeContainerWithVolumeMounts("container-1", []corev1.VolumeMount{
   128  							{Name: "volume-1-vol", MountPath: "/path"},
   129  						}),
   130  					}, []corev1.Container{}, true),
   131  				},
   132  			},
   133  			returnedPVCs: &corev1.PersistentVolumeClaimList{
   134  				Items: []corev1.PersistentVolumeClaim{
   135  					*testingutil.FakePVC("volume-0", "5Gi", odolabels.Builder().WithComponent("nodejs").WithDevfileStorageName("volume-0").Labels()),
   136  					*testingutil.FakePVC("volume-1", "10Gi", odolabels.Builder().WithComponent("nodejs").WithDevfileStorageName("volume-1").Labels()),
   137  				},
   138  			},
   139  			want: StorageList{
   140  				Items: []Storage{
   141  					generateStorage(NewStorage("volume-0", "5Gi", "/data", nil), "", "container-0"),
   142  					generateStorage(NewStorage("volume-1", "10Gi", "/path", nil), "", "container-0"),
   143  					generateStorage(NewStorage("volume-1", "10Gi", "/path", nil), "", "container-1"),
   144  				},
   145  			},
   146  			wantErr: false,
   147  		},
   148  		{
   149  			name: "case 6: pvc for volumeMount not found",
   150  			fields: fields{
   151  				generic: generic{
   152  					componentName: "nodejs",
   153  					appName:       "app",
   154  				},
   155  			},
   156  			returnedDeployments: &appsv1.DeploymentList{
   157  				Items: []appsv1.Deployment{
   158  					*testingutil.CreateFakeDeploymentsWithContainers("nodejs", []corev1.Container{
   159  						testingutil.CreateFakeContainerWithVolumeMounts("container-0", []corev1.VolumeMount{
   160  							{Name: "volume-0-vol", MountPath: "/data"},
   161  						}),
   162  						testingutil.CreateFakeContainer("container-1"),
   163  					}, []corev1.Container{}, true),
   164  				},
   165  			},
   166  			returnedPVCs: &corev1.PersistentVolumeClaimList{
   167  				Items: []corev1.PersistentVolumeClaim{
   168  					*testingutil.FakePVC("volume-0", "5Gi", odolabels.Builder().WithComponent("nodejs").WithDevfileStorageName("volume-0").Labels()),
   169  					*testingutil.FakePVC("volume-1", "5Gi", odolabels.Builder().WithComponent("nodejs").WithDevfileStorageName("volume-1").Labels()),
   170  				},
   171  			},
   172  			wantErr: true,
   173  		},
   174  		{
   175  			name: "case 7: the storage label should be used as the name of the storage",
   176  			fields: fields{
   177  				generic: generic{
   178  					componentName: "nodejs",
   179  					appName:       "app",
   180  				},
   181  			},
   182  			returnedDeployments: &appsv1.DeploymentList{
   183  				Items: []appsv1.Deployment{
   184  					*testingutil.CreateFakeDeploymentsWithContainers("nodejs", []corev1.Container{
   185  						testingutil.CreateFakeContainerWithVolumeMounts("container-0", []corev1.VolumeMount{
   186  							{Name: "volume-0-nodejs-vol", MountPath: "/data"},
   187  						}),
   188  					}, []corev1.Container{}, true),
   189  				},
   190  			},
   191  			returnedPVCs: &corev1.PersistentVolumeClaimList{
   192  				Items: []corev1.PersistentVolumeClaim{
   193  					*testingutil.FakePVC("volume-0-nodejs", "5Gi", odolabels.Builder().WithComponent("nodejs").WithDevfileStorageName("volume-0").Labels()),
   194  				},
   195  			},
   196  			want: StorageList{
   197  				Items: []Storage{
   198  					generateStorage(NewStorage("volume-0", "5Gi", "/data", nil), "", "container-0"),
   199  				},
   200  			},
   201  			wantErr: false,
   202  		},
   203  		{
   204  			name: "case 8: no pvc found for mount path",
   205  			fields: fields{
   206  				generic: generic{
   207  					appName:       "app",
   208  					componentName: "nodejs",
   209  				},
   210  			},
   211  			returnedDeployments: &appsv1.DeploymentList{
   212  				Items: []appsv1.Deployment{
   213  					*testingutil.CreateFakeDeploymentsWithContainers("nodejs", []corev1.Container{
   214  						testingutil.CreateFakeContainerWithVolumeMounts("container-0", []corev1.VolumeMount{
   215  							{Name: "volume-0-vol", MountPath: "/data"},
   216  							{Name: "volume-1-vol", MountPath: "/path"},
   217  						}),
   218  						testingutil.CreateFakeContainerWithVolumeMounts("container-1", []corev1.VolumeMount{
   219  							{Name: "volume-1-vol", MountPath: "/path"},
   220  							{Name: "volume-vol", MountPath: "/path1"},
   221  						}),
   222  					}, []corev1.Container{}, true),
   223  				},
   224  			},
   225  			returnedPVCs: &corev1.PersistentVolumeClaimList{
   226  				Items: []corev1.PersistentVolumeClaim{
   227  					*testingutil.FakePVC("volume-0", "5Gi", odolabels.Builder().WithComponent("nodejs").WithDevfileStorageName("volume-0").Labels()),
   228  					*testingutil.FakePVC("volume-1", "10Gi", odolabels.Builder().WithComponent("nodejs").WithDevfileStorageName("volume-1").Labels()),
   229  				},
   230  			},
   231  			want:    StorageList{},
   232  			wantErr: true,
   233  		},
   234  		{
   235  			name: "case 9: avoid the source volume's mount",
   236  			fields: fields{
   237  				generic: generic{
   238  					appName:       "app",
   239  					componentName: "nodejs",
   240  				},
   241  			},
   242  			returnedDeployments: &appsv1.DeploymentList{
   243  				Items: []appsv1.Deployment{
   244  					*testingutil.CreateFakeDeploymentsWithContainers("nodejs", []corev1.Container{
   245  						testingutil.CreateFakeContainerWithVolumeMounts("container-0", []corev1.VolumeMount{
   246  							{Name: "volume-0-vol", MountPath: "/data"},
   247  							{Name: "volume-1-vol", MountPath: "/path"},
   248  						}),
   249  						testingutil.CreateFakeContainerWithVolumeMounts("container-1", []corev1.VolumeMount{
   250  							{Name: "volume-1-vol", MountPath: "/path"},
   251  							{Name: OdoSourceVolume, MountPath: "/path1"},
   252  						}),
   253  					}, []corev1.Container{}, true),
   254  				},
   255  			},
   256  			returnedPVCs: &corev1.PersistentVolumeClaimList{
   257  				Items: []corev1.PersistentVolumeClaim{
   258  					*testingutil.FakePVC("volume-0", "5Gi", odolabels.Builder().WithComponent("nodejs").WithDevfileStorageName("volume-0").Labels()),
   259  					*testingutil.FakePVC("volume-1", "10Gi", odolabels.Builder().WithComponent("nodejs").WithDevfileStorageName("volume-1").Labels()),
   260  				},
   261  			},
   262  			want: StorageList{
   263  				Items: []Storage{
   264  					generateStorage(NewStorage("volume-0", "5Gi", "/data", nil), "", "container-0"),
   265  					generateStorage(NewStorage("volume-1", "10Gi", "/path", nil), "", "container-0"),
   266  					generateStorage(NewStorage("volume-1", "10Gi", "/path", nil), "", "container-1"),
   267  				},
   268  			},
   269  			wantErr: false,
   270  		},
   271  		{
   272  			name: "case 10: avoid the mandatory volume mounts used by odo",
   273  			fields: fields{
   274  				generic: generic{
   275  					appName:       "app",
   276  					componentName: "nodejs",
   277  				},
   278  			},
   279  			returnedDeployments: &appsv1.DeploymentList{
   280  				Items: []appsv1.Deployment{
   281  					{
   282  						ObjectMeta: metav1.ObjectMeta{
   283  							Name: "nodejs",
   284  							Labels: map[string]string{
   285  								"component": "nodejs",
   286  							},
   287  						},
   288  						Spec: appsv1.DeploymentSpec{
   289  							Template: corev1.PodTemplateSpec{
   290  								Spec: corev1.PodSpec{
   291  									InitContainers: []corev1.Container{
   292  										{
   293  											Name: "my-container-with-shared-project",
   294  											VolumeMounts: []corev1.VolumeMount{
   295  												{
   296  													Name:      "odo-shared-project",
   297  													MountPath: "/opt/",
   298  												},
   299  											},
   300  										},
   301  									},
   302  								},
   303  							},
   304  						},
   305  					},
   306  				},
   307  			},
   308  			returnedPVCs: &corev1.PersistentVolumeClaimList{
   309  				Items: []corev1.PersistentVolumeClaim{},
   310  			},
   311  			want:    StorageList{},
   312  			wantErr: false,
   313  		},
   314  	}
   315  
   316  	for _, tt := range tests {
   317  		t.Run(tt.name, func(t *testing.T) {
   318  			fakeClient, fakeClientSet := kclient.FakeNew()
   319  
   320  			fakeClientSet.Kubernetes.PrependReactor("list", "persistentvolumeclaims", func(action ktesting.Action) (bool, runtime.Object, error) {
   321  				return true, tt.returnedPVCs, nil
   322  			})
   323  
   324  			fakeClientSet.Kubernetes.PrependReactor("list", "deployments", func(action ktesting.Action) (bool, runtime.Object, error) {
   325  				return true, tt.returnedDeployments, nil
   326  			})
   327  
   328  			ctrl := gomock.NewController(t)
   329  			defer ctrl.Finish()
   330  
   331  			k := kubernetesClient{
   332  				generic: tt.fields.generic,
   333  				client:  fakeClient,
   334  			}
   335  			got, err := k.List()
   336  			if (err != nil) != tt.wantErr {
   337  				t.Errorf("List() error = %v, wantErr %v", err, tt.wantErr)
   338  				return
   339  			}
   340  			if diff := cmp.Diff(tt.want, got); diff != "" {
   341  				t.Errorf("kubernetesClient.List() mismatch (-want +got):\n%s", diff)
   342  			}
   343  		})
   344  	}
   345  }
   346  
   347  func Test_kubernetesClient_Create(t *testing.T) {
   348  	type fields struct {
   349  		generic generic
   350  	}
   351  	type args struct {
   352  		storage Storage
   353  	}
   354  	tests := []struct {
   355  		name    string
   356  		fields  fields
   357  		args    args
   358  		wantErr bool
   359  	}{
   360  		{
   361  			name: "case 1: valid storage",
   362  			fields: fields{
   363  				generic: generic{
   364  					appName:       "app",
   365  					componentName: "nodejs",
   366  				},
   367  			},
   368  			args: args{
   369  				storage: NewStorageWithContainer("storage-0", "5Gi", "/data", "runtime", util.GetBool(false)),
   370  			},
   371  		},
   372  		{
   373  			name: "case 2: invalid storage size",
   374  			fields: fields{
   375  				generic: generic{
   376  					appName:       "app",
   377  					componentName: "nodejs",
   378  				},
   379  			},
   380  			args: args{
   381  				storage: NewStorageWithContainer("storage-0", "example", "/data", "runtime", util.GetBool(false)),
   382  			},
   383  			wantErr: true,
   384  		},
   385  		{
   386  			name: "case 3: valid odo project related storage",
   387  			fields: fields{
   388  				generic: generic{
   389  					appName:       "app",
   390  					componentName: "nodejs",
   391  				},
   392  			},
   393  			args: args{
   394  				storage: NewStorageWithContainer("odo-projects-vol", "5Gi", "/data", "runtime", util.GetBool(false)),
   395  			},
   396  		},
   397  	}
   398  	for _, tt := range tests {
   399  		t.Run(tt.name, func(t *testing.T) {
   400  			fkclient, fkclientset := kclient.FakeNew()
   401  
   402  			k := kubernetesClient{
   403  				generic: tt.fields.generic,
   404  				client:  fkclient,
   405  			}
   406  			if err := k.Create(tt.args.storage); (err != nil) != tt.wantErr {
   407  				t.Errorf("Create() error = %v, wantErr %v", err, tt.wantErr)
   408  				return
   409  			}
   410  			if tt.wantErr == true {
   411  				return
   412  			}
   413  
   414  			// Check for validating actions performed
   415  			if (len(fkclientset.Kubernetes.Actions()) != 1) && (tt.wantErr != true) {
   416  				t.Errorf("expected 1 action in CreatePVC got: %v", fkclientset.Kubernetes.Actions())
   417  				return
   418  			}
   419  
   420  			createdPVC := fkclientset.Kubernetes.Actions()[0].(ktesting.CreateAction).GetObject().(*corev1.PersistentVolumeClaim)
   421  			quantity, err := resource.ParseQuantity(tt.args.storage.Spec.Size)
   422  			if err != nil {
   423  				t.Errorf("failed to create quantity by calling resource.ParseQuantity(%v)", tt.args.storage.Spec.Size)
   424  			}
   425  
   426  			wantLabels := odolabels.GetLabels(tt.fields.generic.componentName, tt.fields.generic.appName, "", odolabels.ComponentDevMode, false)
   427  			odolabels.AddStorageInfo(wantLabels, tt.args.storage.Name, strings.Contains(tt.args.storage.Name, OdoSourceVolume))
   428  
   429  			// created PVC should be labeled with labels passed to CreatePVC
   430  			if diff := cmp.Diff(wantLabels, createdPVC.Labels); diff != "" {
   431  				t.Errorf("kubernetesClient.Create() wantLabels mismatch (-want +got):\n%s", diff)
   432  			}
   433  			// name, size of createdPVC should be matching to size, name passed to CreatePVC
   434  			if diff := cmp.Diff(quantity, createdPVC.Spec.Resources.Requests["storage"]); diff != "" {
   435  				t.Errorf("kubernetesClient.Create() quantity mismatch (-want +got):\n%s", diff)
   436  			}
   437  
   438  			wantedPVCName, err := generatePVCName(tt.args.storage.Name, tt.fields.generic.componentName, tt.fields.generic.appName)
   439  			if err != nil {
   440  				t.Errorf("unexpected error: %v", err)
   441  			}
   442  			if diff := cmp.Diff(wantedPVCName, createdPVC.Name); diff != "" {
   443  				t.Errorf("kubernetesClient.Create() wantedPVCName mismatch (-want +got):\n%s", diff)
   444  			}
   445  		})
   446  	}
   447  }
   448  
   449  func Test_kubernetesClient_Delete(t *testing.T) {
   450  	pvcName := "pvc-0"
   451  	returnedPVCs := corev1.PersistentVolumeClaimList{
   452  		Items: []corev1.PersistentVolumeClaim{
   453  			*testingutil.FakePVC(pvcName, "5Gi", getStorageLabels("storage-0", "nodejs", "app")),
   454  		},
   455  	}
   456  
   457  	type fields struct {
   458  		generic generic
   459  	}
   460  	type args struct {
   461  		name string
   462  	}
   463  	tests := []struct {
   464  		name    string
   465  		fields  fields
   466  		args    args
   467  		wantErr bool
   468  	}{
   469  		{
   470  			name: "case 1: delete successful",
   471  			fields: fields{
   472  				generic: generic{
   473  					appName:       "app",
   474  					componentName: "nodejs",
   475  				},
   476  			},
   477  			args: args{
   478  				"storage-0",
   479  			},
   480  			wantErr: false,
   481  		},
   482  		{
   483  			name: "case 2: pvc not found",
   484  			fields: fields{
   485  				generic: generic{
   486  					appName:       "app",
   487  					componentName: "nodejs",
   488  				},
   489  			},
   490  			args: args{
   491  				"storage-example",
   492  			},
   493  			wantErr: true,
   494  		},
   495  	}
   496  	for _, tt := range tests {
   497  		t.Run(tt.name, func(t *testing.T) {
   498  			fkclient, fkclientset := kclient.FakeNew()
   499  
   500  			fkclientset.Kubernetes.PrependReactor("list", "persistentvolumeclaims", func(action ktesting.Action) (bool, runtime.Object, error) {
   501  				return true, &returnedPVCs, nil
   502  			})
   503  
   504  			fkclientset.Kubernetes.PrependReactor("delete", "persistentvolumeclaims", func(action ktesting.Action) (bool, runtime.Object, error) {
   505  				if action.(ktesting.DeleteAction).GetName() != pvcName {
   506  					t.Errorf("delete called with = %v, want %v", action.(ktesting.DeleteAction).GetName(), pvcName)
   507  				}
   508  				return true, nil, nil
   509  			})
   510  
   511  			k := kubernetesClient{
   512  				generic: tt.fields.generic,
   513  				client:  fkclient,
   514  			}
   515  			if err := k.Delete(tt.args.name); (err != nil) != tt.wantErr {
   516  				t.Errorf("Delete() error = %v, wantErr %v", err, tt.wantErr)
   517  				return
   518  			}
   519  
   520  			if tt.wantErr {
   521  				return
   522  			}
   523  
   524  			if len(fkclientset.Kubernetes.Actions()) != 2 {
   525  				t.Errorf("expected 2 action, got %v", len(fkclientset.Kubernetes.Actions()))
   526  			}
   527  		})
   528  	}
   529  }
   530  

View as plain text