...

Source file src/github.com/redhat-developer/odo/pkg/component/delete/delete_test.go

Documentation: github.com/redhat-developer/odo/pkg/component/delete

     1  package delete
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"reflect"
     8  	"testing"
     9  
    10  	"github.com/devfile/library/v2/pkg/devfile/parser"
    11  	"github.com/devfile/library/v2/pkg/testingutil/filesystem"
    12  	"github.com/golang/mock/gomock"
    13  	"github.com/google/go-cmp/cmp"
    14  
    15  	appsv1 "k8s.io/api/apps/v1"
    16  	corev1 "k8s.io/api/core/v1"
    17  	kerrors "k8s.io/apimachinery/pkg/api/errors"
    18  	"k8s.io/apimachinery/pkg/api/meta"
    19  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    20  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    21  	"k8s.io/apimachinery/pkg/runtime/schema"
    22  
    23  	"github.com/redhat-developer/odo/pkg/exec"
    24  	"github.com/redhat-developer/odo/pkg/kclient"
    25  	odolabels "github.com/redhat-developer/odo/pkg/labels"
    26  	odocontext "github.com/redhat-developer/odo/pkg/odo/context"
    27  	"github.com/redhat-developer/odo/pkg/platform"
    28  	"github.com/redhat-developer/odo/pkg/podman"
    29  	odoTestingUtil "github.com/redhat-developer/odo/pkg/testingutil"
    30  	"github.com/redhat-developer/odo/pkg/util"
    31  )
    32  
    33  const (
    34  	appName = "app"
    35  )
    36  
    37  func TestDeleteComponentClient_ListClusterResourcesToDelete(t *testing.T) {
    38  	res1 := getUnstructured("dep1", "deployment", "v1", "")
    39  	res2 := getUnstructured("svc1", "service", "v1", "")
    40  
    41  	selectorForMode := func(mode string) string {
    42  		selector := "app.kubernetes.io/instance=my-component,app.kubernetes.io/managed-by=odo,app.kubernetes.io/part-of=app"
    43  		if mode != "" {
    44  			selector += fmt.Sprintf(",odo.dev/mode=%s", mode)
    45  		}
    46  		return selector
    47  	}
    48  
    49  	type fields struct {
    50  		kubeClient func(ctrl *gomock.Controller) kclient.ClientInterface
    51  	}
    52  	type args struct {
    53  		componentName string
    54  		namespace     string
    55  		mode          string
    56  	}
    57  	tests := []struct {
    58  		name    string
    59  		fields  fields
    60  		args    args
    61  		want    []unstructured.Unstructured
    62  		wantErr bool
    63  	}{
    64  		{
    65  			name: "no resource found",
    66  			fields: fields{
    67  				kubeClient: func(ctrl *gomock.Controller) kclient.ClientInterface {
    68  					client := kclient.NewMockClientInterface(ctrl)
    69  					client.EXPECT().GetAllResourcesFromSelector(selectorForMode(odolabels.ComponentAnyMode), "my-ns").Return(nil, nil)
    70  					return client
    71  				},
    72  			},
    73  			args: args{
    74  				componentName: "my-component",
    75  				namespace:     "my-ns",
    76  			},
    77  			wantErr: false,
    78  			want:    nil,
    79  		},
    80  		{
    81  			name: "2 unrelated resources found",
    82  			fields: fields{
    83  				kubeClient: func(ctrl *gomock.Controller) kclient.ClientInterface {
    84  					var resources []unstructured.Unstructured
    85  					resources = append(resources, res1, res2)
    86  					client := kclient.NewMockClientInterface(ctrl)
    87  					client.EXPECT().GetAllResourcesFromSelector(selectorForMode(odolabels.ComponentAnyMode), "my-ns").Return(resources, nil)
    88  					return client
    89  				},
    90  			},
    91  			args: args{
    92  				componentName: "my-component",
    93  				namespace:     "my-ns",
    94  			},
    95  			wantErr: false,
    96  			want:    []unstructured.Unstructured{res1, res2},
    97  		},
    98  		{
    99  			name: "2 resources found, one owned by the other",
   100  			fields: fields{
   101  				kubeClient: func(ctrl *gomock.Controller) kclient.ClientInterface {
   102  					var resources []unstructured.Unstructured
   103  					res1.SetOwnerReferences([]metav1.OwnerReference{
   104  						{
   105  							APIVersion: res2.GetAPIVersion(),
   106  							Kind:       res2.GetKind(),
   107  							Name:       res2.GetName(),
   108  						},
   109  					})
   110  					resources = append(resources, res1, res2)
   111  					client := kclient.NewMockClientInterface(ctrl)
   112  					client.EXPECT().GetAllResourcesFromSelector(selectorForMode(odolabels.ComponentAnyMode), "my-ns").Return(resources, nil)
   113  					return client
   114  				},
   115  			},
   116  			args: args{
   117  				componentName: "my-component",
   118  				namespace:     "my-ns",
   119  			},
   120  			wantErr: false,
   121  			want:    []unstructured.Unstructured{res2},
   122  		},
   123  		{
   124  			name: "returning Dev resources only",
   125  			fields: fields{
   126  				kubeClient: func(ctrl *gomock.Controller) kclient.ClientInterface {
   127  					client := kclient.NewMockClientInterface(ctrl)
   128  					client.EXPECT().GetAllResourcesFromSelector(selectorForMode(odolabels.ComponentDevMode), "my-ns").Return([]unstructured.Unstructured{res1}, nil)
   129  					return client
   130  				},
   131  			},
   132  			args: args{
   133  				componentName: "my-component",
   134  				namespace:     "my-ns",
   135  				mode:          odolabels.ComponentDevMode,
   136  			},
   137  			wantErr: false,
   138  			want:    []unstructured.Unstructured{res1},
   139  		},
   140  		{
   141  			name: "returning Deploy resources only",
   142  			fields: fields{
   143  				kubeClient: func(ctrl *gomock.Controller) kclient.ClientInterface {
   144  					client := kclient.NewMockClientInterface(ctrl)
   145  					client.EXPECT().GetAllResourcesFromSelector(selectorForMode(odolabels.ComponentDeployMode), "my-ns").Return([]unstructured.Unstructured{res2}, nil)
   146  					return client
   147  				},
   148  			},
   149  			args: args{
   150  				componentName: "my-component",
   151  				namespace:     "my-ns",
   152  				mode:          odolabels.ComponentDeployMode,
   153  			},
   154  			wantErr: false,
   155  			want:    []unstructured.Unstructured{res2},
   156  		},
   157  	}
   158  	for _, tt := range tests {
   159  		t.Run(tt.name, func(t *testing.T) {
   160  			ctrl := gomock.NewController(t)
   161  			kubeClient := tt.fields.kubeClient(ctrl)
   162  			execClient := exec.NewExecClient(kubeClient)
   163  			do := NewDeleteComponentClient(kubeClient, nil, execClient, nil)
   164  			ctx := odocontext.WithApplication(context.TODO(), "app")
   165  			got, err := do.ListClusterResourcesToDelete(ctx, tt.args.componentName, tt.args.namespace, tt.args.mode)
   166  			if (err != nil) != tt.wantErr {
   167  				t.Errorf("DeleteComponentClient.ListResourcesToDelete() error = %v, wantErr %v", err, tt.wantErr)
   168  				return
   169  			}
   170  			if diff := cmp.Diff(tt.want, got); diff != "" {
   171  				t.Errorf("DeleteComponentClient.ListClusterResourcesToDelete() mismatch (-want +got):\n%s", diff)
   172  			}
   173  		})
   174  	}
   175  }
   176  
   177  func TestDeleteComponentClient_DeleteResources(t *testing.T) {
   178  	res1 := getUnstructured("dep1", "deployment", "v1", "")
   179  	res2 := getUnstructured("svc1", "service", "v1", "")
   180  
   181  	type fields struct {
   182  		kubeClient func(ctrl *gomock.Controller) kclient.ClientInterface
   183  	}
   184  	type args struct {
   185  		resources []unstructured.Unstructured
   186  	}
   187  	tests := []struct {
   188  		name   string
   189  		fields fields
   190  		args   args
   191  		want   []unstructured.Unstructured
   192  	}{
   193  		{
   194  			name: "2 resources deleted succesfully",
   195  			fields: fields{
   196  				kubeClient: func(ctrl *gomock.Controller) kclient.ClientInterface {
   197  					client := kclient.NewMockClientInterface(ctrl)
   198  					client.EXPECT().GetRestMappingFromUnstructured(res1).Return(&meta.RESTMapping{
   199  						Resource: schema.GroupVersionResource{
   200  							Group:    "",
   201  							Version:  "v1",
   202  							Resource: res1.GetKind(),
   203  						},
   204  					}, nil)
   205  					client.EXPECT().GetRestMappingFromUnstructured(res2).Return(&meta.RESTMapping{
   206  						Resource: schema.GroupVersionResource{
   207  							Group:    "",
   208  							Version:  "v1",
   209  							Resource: res2.GetKind(),
   210  						},
   211  					}, nil)
   212  					client.EXPECT().DeleteDynamicResource(res1.GetName(), getGVR("", "v1", res1.GetKind()), false)
   213  					client.EXPECT().DeleteDynamicResource(res2.GetName(), getGVR("", "v1", res2.GetKind()), false)
   214  					return client
   215  				},
   216  			},
   217  			args: args{
   218  				resources: []unstructured.Unstructured{res1, res2},
   219  			},
   220  			want: nil,
   221  		},
   222  		{
   223  			name: "2 resources, 1 deleted succesfully, 1 failed during restmapping",
   224  			fields: fields{
   225  				kubeClient: func(ctrl *gomock.Controller) kclient.ClientInterface {
   226  					client := kclient.NewMockClientInterface(ctrl)
   227  					client.EXPECT().GetRestMappingFromUnstructured(res1).Return(nil, errors.New("some restmapping error"))
   228  					client.EXPECT().GetRestMappingFromUnstructured(res2).Return(&meta.RESTMapping{
   229  						Resource: schema.GroupVersionResource{
   230  							Group:    "",
   231  							Version:  "v1",
   232  							Resource: res2.GetKind(),
   233  						},
   234  					}, nil)
   235  					client.EXPECT().DeleteDynamicResource(res2.GetName(), getGVR("", "v1", res2.GetKind()), false)
   236  					return client
   237  				},
   238  			},
   239  			args: args{
   240  				resources: []unstructured.Unstructured{res1, res2},
   241  			},
   242  			want: []unstructured.Unstructured{res1},
   243  		},
   244  		{
   245  			name: "2 resources, 1 deleted succesfully, 1 failed during deletion",
   246  			fields: fields{
   247  				kubeClient: func(ctrl *gomock.Controller) kclient.ClientInterface {
   248  					client := kclient.NewMockClientInterface(ctrl)
   249  					client.EXPECT().GetRestMappingFromUnstructured(res1).Return(&meta.RESTMapping{
   250  						Resource: schema.GroupVersionResource{
   251  							Group:    "",
   252  							Version:  "v1",
   253  							Resource: res1.GetKind(),
   254  						},
   255  					}, nil)
   256  					client.EXPECT().GetRestMappingFromUnstructured(res2).Return(&meta.RESTMapping{
   257  						Resource: schema.GroupVersionResource{
   258  							Group:    "",
   259  							Version:  "v1",
   260  							Resource: res2.GetKind(),
   261  						},
   262  					}, nil)
   263  					client.EXPECT().DeleteDynamicResource(res1.GetName(), getGVR("", "v1", res1.GetKind()), false).Return(errors.New("some error"))
   264  					client.EXPECT().DeleteDynamicResource(res2.GetName(), getGVR("", "v1", res2.GetKind()), false)
   265  					return client
   266  				},
   267  			},
   268  			args: args{
   269  				resources: []unstructured.Unstructured{res1, res2},
   270  			},
   271  			want: []unstructured.Unstructured{res1},
   272  		},
   273  		// TODO: Add test cases.
   274  	}
   275  	for _, tt := range tests {
   276  		t.Run(tt.name, func(t *testing.T) {
   277  			ctrl := gomock.NewController(t)
   278  			kubeClient := tt.fields.kubeClient(ctrl)
   279  			execClient := exec.NewExecClient(kubeClient)
   280  			do := NewDeleteComponentClient(kubeClient, nil, execClient, nil)
   281  			got := do.DeleteResources(tt.args.resources, false)
   282  			if diff := cmp.Diff(tt.want, got); diff != "" {
   283  				t.Errorf("DeleteComponentClient.DeleteResources() mismatch (-want +got):\n%s", diff)
   284  			}
   285  		})
   286  	}
   287  }
   288  
   289  func TestDeleteComponentClient_ListClusterResourcesToDeleteFromDevfile(t *testing.T) {
   290  	const compName = "nodejs-prj1-api-abhz"
   291  	innerLoopCoreDeploymentName, _ := util.NamespaceKubernetesObject(compName, appName)
   292  
   293  	// innerLoopCoreDeployment is the deployment created by odo dev for the component
   294  	innerLoopCoreDeployment := odoTestingUtil.CreateFakeDeployment(compName, true)
   295  
   296  	innerLoopCoreDeploymentUnstructured, e := kclient.ConvertK8sResourceToUnstructured(innerLoopCoreDeployment)
   297  	if e != nil {
   298  		t.Errorf("unable to convert deployment to unstructured")
   299  	}
   300  
   301  	// outerLoopResourceUnstructured is the deployment created by odo deploy
   302  	outerLoopResourceUnstructured := unstructured.Unstructured{
   303  		Object: map[string]interface{}{
   304  			"apiVersion": "apps/v1",
   305  			"kind":       "Deployment",
   306  			"metadata": map[string]interface{}{
   307  				"name": "my-component",
   308  			},
   309  			"spec": map[string]interface{}{
   310  				"replicas": float64(1),
   311  				"selector": map[string]interface{}{
   312  					"matchLabels": map[string]interface{}{
   313  						"app": "node-app",
   314  					},
   315  				},
   316  				"template": map[string]interface{}{
   317  					"metadata": map[string]interface{}{
   318  						"labels": map[string]interface{}{
   319  							"app": "node-app",
   320  						},
   321  					},
   322  					"spec": map[string]interface{}{
   323  						"containers": []interface{}{
   324  							map[string]interface{}{
   325  								"image": "quay.io/unknown-account/myimage",
   326  								"name":  "main",
   327  								"resources": map[string]interface{}{
   328  									"limits": map[string]interface{}{
   329  										"cpu":    "500m",
   330  										"memory": "128Mi",
   331  									},
   332  								},
   333  							},
   334  						},
   335  					},
   336  				},
   337  			},
   338  		},
   339  	}
   340  
   341  	// labeledOuterloopResource is the deployment with labels set
   342  	labeledOuterloopResource := *outerLoopResourceUnstructured.DeepCopy()
   343  	labeledOuterloopResource.SetLabels(odolabels.GetLabels(compName, appName, "", odolabels.ComponentDeployMode, false))
   344  
   345  	// innerLoopResourceUnstructured is the deployment that will be deployed by apply command with `odo dev`
   346  	innerLoopResourceUnstructured := *outerLoopResourceUnstructured.DeepCopy()
   347  	innerLoopResourceUnstructured.SetLabels(odolabels.GetLabels(compName, appName, "", odolabels.ComponentDevMode, false))
   348  
   349  	deploymentRESTMapping := meta.RESTMapping{
   350  		Resource: getGVR("apps", "v1", "Deployment"),
   351  	}
   352  
   353  	type fields struct {
   354  		kubeClient func(ctrl *gomock.Controller) kclient.ClientInterface
   355  	}
   356  	type args struct {
   357  		devfileObj parser.DevfileObj
   358  		appName    string
   359  		mode       string
   360  	}
   361  	tests := []struct {
   362  		name                    string
   363  		fields                  fields
   364  		args                    args
   365  		wantIsInnerLoopDeployed bool
   366  		wantResources           []unstructured.Unstructured
   367  		wantErr                 bool
   368  	}{
   369  		{
   370  			name: "list innerloop core resource(deployment), and outerloop resources",
   371  			fields: fields{
   372  				kubeClient: func(ctrl *gomock.Controller) kclient.ClientInterface {
   373  					kubeClient := kclient.NewMockClientInterface(ctrl)
   374  
   375  					kubeClient.EXPECT().GetDeploymentByName(innerLoopCoreDeploymentName).Return(innerLoopCoreDeployment, nil)
   376  
   377  					kubeClient.EXPECT().GetRestMappingFromUnstructured(outerLoopResourceUnstructured).Return(&deploymentRESTMapping, nil)
   378  					kubeClient.EXPECT().
   379  						GetDynamicResource(deploymentRESTMapping.Resource, outerLoopResourceUnstructured.GetName()).
   380  						Return(&labeledOuterloopResource, nil)
   381  
   382  					return kubeClient
   383  				},
   384  			},
   385  			args: args{
   386  				devfileObj: odoTestingUtil.GetTestDevfileObjFromFile("devfile-deploy.yaml"),
   387  				appName:    appName,
   388  				mode:       odolabels.ComponentAnyMode,
   389  			},
   390  			wantIsInnerLoopDeployed: true,
   391  			wantResources:           []unstructured.Unstructured{innerLoopCoreDeploymentUnstructured, labeledOuterloopResource},
   392  			wantErr:                 false,
   393  		},
   394  		{
   395  			name: "list innerloop core resource(deployment) only",
   396  			fields: fields{
   397  				kubeClient: func(ctrl *gomock.Controller) kclient.ClientInterface {
   398  					kubeClient := kclient.NewMockClientInterface(ctrl)
   399  
   400  					kubeClient.EXPECT().GetDeploymentByName(innerLoopCoreDeploymentName).Return(innerLoopCoreDeployment, nil)
   401  					return kubeClient
   402  				},
   403  			},
   404  			args: args{
   405  				devfileObj: func() parser.DevfileObj {
   406  					obj := odoTestingUtil.GetTestDevfileObjFromFile("devfile.yaml")
   407  					// change the metadata name to the desired one since devfile.yaml has a different name
   408  					metadata := obj.Data.GetMetadata()
   409  					metadata.Name = compName
   410  					obj.Data.SetMetadata(metadata)
   411  					return obj
   412  				}(),
   413  				appName: appName,
   414  				mode:    odolabels.ComponentDevMode,
   415  			},
   416  			wantIsInnerLoopDeployed: true,
   417  			wantResources:           []unstructured.Unstructured{innerLoopCoreDeploymentUnstructured},
   418  			wantErr:                 false,
   419  		},
   420  		{
   421  			name: "list innerloop core resources(deployment), another innerloop resources",
   422  			fields: fields{
   423  				kubeClient: func(ctrl *gomock.Controller) kclient.ClientInterface {
   424  					kubeClient := kclient.NewMockClientInterface(ctrl)
   425  
   426  					kubeClient.EXPECT().GetDeploymentByName(innerLoopCoreDeploymentName).Return(innerLoopCoreDeployment, nil)
   427  
   428  					kubeClient.EXPECT().GetRestMappingFromUnstructured(outerLoopResourceUnstructured).Return(&deploymentRESTMapping, nil)
   429  					kubeClient.EXPECT().
   430  						GetDynamicResource(deploymentRESTMapping.Resource, outerLoopResourceUnstructured.GetName()).
   431  						Return(&innerLoopResourceUnstructured, nil)
   432  
   433  					return kubeClient
   434  				},
   435  			},
   436  			args: args{
   437  				devfileObj: func() parser.DevfileObj {
   438  					obj := odoTestingUtil.GetTestDevfileObjFromFile("devfile-composite-apply-commands-unit-test.yaml")
   439  					// change the metadata name to the desired one since devfile.yaml has a different name
   440  					metadata := obj.Data.GetMetadata()
   441  					metadata.Name = compName
   442  					obj.Data.SetMetadata(metadata)
   443  					return obj
   444  				}(),
   445  				appName: appName,
   446  				mode:    odolabels.ComponentDevMode,
   447  			},
   448  			wantIsInnerLoopDeployed: true,
   449  			wantResources:           []unstructured.Unstructured{innerLoopCoreDeploymentUnstructured, innerLoopResourceUnstructured},
   450  			wantErr:                 false,
   451  		},
   452  		{
   453  			name: "list outerloop resources only",
   454  			fields: fields{
   455  				kubeClient: func(ctrl *gomock.Controller) kclient.ClientInterface {
   456  					kubeClient := kclient.NewMockClientInterface(ctrl)
   457  
   458  					kubeClient.EXPECT().GetDeploymentByName(innerLoopCoreDeploymentName).
   459  						Return(&appsv1.Deployment{}, kerrors.NewNotFound(deploymentRESTMapping.Resource.GroupResource(), innerLoopCoreDeploymentName))
   460  					kubeClient.EXPECT().GetRestMappingFromUnstructured(outerLoopResourceUnstructured).Return(&deploymentRESTMapping, nil)
   461  					kubeClient.EXPECT().
   462  						GetDynamicResource(deploymentRESTMapping.Resource, outerLoopResourceUnstructured.GetName()).
   463  						Return(&labeledOuterloopResource, nil)
   464  					return kubeClient
   465  				},
   466  			},
   467  			args: args{
   468  				devfileObj: odoTestingUtil.GetTestDevfileObjFromFile("devfile-deploy.yaml"),
   469  				appName:    appName,
   470  				mode:       odolabels.ComponentAnyMode,
   471  			},
   472  			wantIsInnerLoopDeployed: false,
   473  			wantResources:           []unstructured.Unstructured{labeledOuterloopResource},
   474  			wantErr:                 false,
   475  		},
   476  		{
   477  			name: "list uri-referenced outerloop resources",
   478  			fields: fields{
   479  				kubeClient: func(ctrl *gomock.Controller) kclient.ClientInterface {
   480  					kubeClient := kclient.NewMockClientInterface(ctrl)
   481  					kubeClient.EXPECT().GetDeploymentByName(innerLoopCoreDeploymentName).
   482  						Return(&appsv1.Deployment{}, kerrors.NewNotFound(schema.GroupResource{Group: "apps", Resource: "Deployments"}, innerLoopCoreDeploymentName))
   483  					kubeClient.EXPECT().GetRestMappingFromUnstructured(outerLoopResourceUnstructured).Return(&deploymentRESTMapping, nil)
   484  					kubeClient.EXPECT().
   485  						GetDynamicResource(deploymentRESTMapping.Resource, outerLoopResourceUnstructured.GetName()).
   486  						Return(&labeledOuterloopResource, nil)
   487  					return kubeClient
   488  				},
   489  			},
   490  			args: args{
   491  				devfileObj: odoTestingUtil.GetTestDevfileObjFromFile("devfile-deploy-with-k8s-uri.yaml"),
   492  				appName:    appName,
   493  				mode:       odolabels.ComponentAnyMode,
   494  			},
   495  			wantIsInnerLoopDeployed: false,
   496  			wantResources:           []unstructured.Unstructured{labeledOuterloopResource},
   497  			wantErr:                 false,
   498  		},
   499  		{
   500  			name: "fetching inner loop resource failed due to some error(!NotFoundError)",
   501  			fields: fields{
   502  				kubeClient: func(ctrl *gomock.Controller) kclient.ClientInterface {
   503  					kubeClient := kclient.NewMockClientInterface(ctrl)
   504  					kubeClient.EXPECT().GetDeploymentByName(innerLoopCoreDeploymentName).Return(&appsv1.Deployment{}, errors.New("some error"))
   505  					return kubeClient
   506  				},
   507  			},
   508  			args: args{
   509  				devfileObj: odoTestingUtil.GetTestDevfileObjFromFile("devfile-deploy.yaml"),
   510  				appName:    appName,
   511  				mode:       odolabels.ComponentAnyMode,
   512  			},
   513  			wantIsInnerLoopDeployed: false,
   514  			wantResources:           nil,
   515  			wantErr:                 true,
   516  		},
   517  		{
   518  			name: "failed to add outerloop resource to the list because kubeclient.GetRestMappingFromUnstructured() failed",
   519  			fields: fields{
   520  				kubeClient: func(ctrl *gomock.Controller) kclient.ClientInterface {
   521  					kubeClient := kclient.NewMockClientInterface(ctrl)
   522  					kubeClient.EXPECT().GetDeploymentByName(innerLoopCoreDeploymentName).Return(innerLoopCoreDeployment, nil)
   523  					kubeClient.EXPECT().GetRestMappingFromUnstructured(outerLoopResourceUnstructured).Return(nil, errors.New("some error"))
   524  					return kubeClient
   525  				},
   526  			},
   527  			args: args{
   528  				devfileObj: odoTestingUtil.GetTestDevfileObjFromFile("devfile-deploy.yaml"),
   529  				appName:    appName,
   530  				mode:       odolabels.ComponentAnyMode,
   531  			},
   532  			wantIsInnerLoopDeployed: true,
   533  			wantResources:           []unstructured.Unstructured{innerLoopCoreDeploymentUnstructured},
   534  			wantErr:                 false,
   535  		},
   536  		{
   537  			name: "failed to add outerloop resource to the list because kubeclient.GetDynamicResource() failed",
   538  			fields: fields{
   539  				kubeClient: func(ctrl *gomock.Controller) kclient.ClientInterface {
   540  					kubeClient := kclient.NewMockClientInterface(ctrl)
   541  					kubeClient.EXPECT().GetDeploymentByName(innerLoopCoreDeploymentName).Return(innerLoopCoreDeployment, nil)
   542  					kubeClient.EXPECT().GetRestMappingFromUnstructured(outerLoopResourceUnstructured).Return(&deploymentRESTMapping, nil)
   543  					kubeClient.EXPECT().
   544  						GetDynamicResource(deploymentRESTMapping.Resource, outerLoopResourceUnstructured.GetName()).
   545  						Return(nil, errors.New("some error"))
   546  					return kubeClient
   547  				},
   548  			},
   549  			args: args{
   550  				devfileObj: odoTestingUtil.GetTestDevfileObjFromFile("devfile-deploy.yaml"),
   551  				appName:    appName,
   552  				mode:       odolabels.ComponentAnyMode,
   553  			},
   554  			wantIsInnerLoopDeployed: true,
   555  			wantResources:           []unstructured.Unstructured{innerLoopCoreDeploymentUnstructured},
   556  			wantErr:                 false,
   557  		},
   558  		{
   559  			name: "do not list outerloop resource if Dev mode is asked",
   560  			fields: fields{
   561  				kubeClient: func(ctrl *gomock.Controller) kclient.ClientInterface {
   562  					kubeClient := kclient.NewMockClientInterface(ctrl)
   563  
   564  					kubeClient.EXPECT().GetDeploymentByName(innerLoopCoreDeploymentName).Return(innerLoopCoreDeployment, nil)
   565  
   566  					kubeClient.EXPECT().GetRestMappingFromUnstructured(outerLoopResourceUnstructured).Return(&deploymentRESTMapping, nil)
   567  					kubeClient.EXPECT().
   568  						GetDynamicResource(deploymentRESTMapping.Resource, outerLoopResourceUnstructured.GetName()).
   569  						Return(&outerLoopResourceUnstructured, nil)
   570  
   571  					return kubeClient
   572  				},
   573  			},
   574  			args: args{
   575  				devfileObj: func() parser.DevfileObj {
   576  					obj := odoTestingUtil.GetTestDevfileObjFromFile("devfile-composite-apply-commands-unit-test.yaml")
   577  					// change the metadata name to the desired one since devfile.yaml has a different name
   578  					metadata := obj.Data.GetMetadata()
   579  					metadata.Name = compName
   580  					obj.Data.SetMetadata(metadata)
   581  					return obj
   582  				}(),
   583  				appName: appName,
   584  				mode:    odolabels.ComponentDevMode,
   585  			},
   586  			wantIsInnerLoopDeployed: true,
   587  			wantResources:           []unstructured.Unstructured{innerLoopCoreDeploymentUnstructured},
   588  			wantErr:                 false,
   589  		},
   590  	}
   591  	for _, tt := range tests {
   592  		t.Run(tt.name, func(t *testing.T) {
   593  			ctrl := gomock.NewController(t)
   594  			do := DeleteComponentClient{
   595  				kubeClient: tt.fields.kubeClient(ctrl),
   596  			}
   597  			gotIsInnerLoopDeployed, gotResources, err := do.ListClusterResourcesToDeleteFromDevfile(tt.args.devfileObj, tt.args.appName, tt.args.devfileObj.GetMetadataName(), tt.args.mode)
   598  			if (err != nil) != tt.wantErr {
   599  				t.Errorf("ListResourcesToDeleteFromDevfile() error = %v, wantErr %v", err, tt.wantErr)
   600  				return
   601  			}
   602  			if gotIsInnerLoopDeployed != tt.wantIsInnerLoopDeployed {
   603  				t.Errorf("ListResourcesToDeleteFromDevfile() gotIsInnerLoopDeployed = %v, want %v", gotIsInnerLoopDeployed, tt.wantIsInnerLoopDeployed)
   604  			}
   605  			if diff := cmp.Diff(tt.wantResources, gotResources); diff != "" {
   606  				t.Errorf("DeleteComponentClient.ListResourcesToDeleteFromDevfile() wantResources mismatch (-want +got):\n%s", diff)
   607  			}
   608  		})
   609  	}
   610  }
   611  
   612  func TestDeleteComponentClient_ExecutePreStopEvents(t *testing.T) {
   613  	const componentName = "nodejs-prj1-api-abhz"
   614  	const appName = "app"
   615  	fs := filesystem.NewFakeFs()
   616  
   617  	devfileObjWithPreStopEvents := odoTestingUtil.GetTestDevfileObjWithPreStopEvents(fs, "runtime", "echo \"Hello World!\"")
   618  	metadata := devfileObjWithPreStopEvents.Data.GetMetadata()
   619  	metadata.Name = componentName
   620  	devfileObjWithPreStopEvents.Data.SetMetadata(metadata)
   621  
   622  	type fields struct {
   623  		kubeClient func(ctrl *gomock.Controller) kclient.ClientInterface
   624  	}
   625  	type args struct {
   626  		devfileObj parser.DevfileObj
   627  		appName    string
   628  	}
   629  	tests := []struct {
   630  		name    string
   631  		fields  fields
   632  		args    args
   633  		wantErr bool
   634  	}{
   635  		{
   636  			name: "no preStop event to execute",
   637  			fields: fields{
   638  				kubeClient: func(ctrl *gomock.Controller) kclient.ClientInterface {
   639  					return kclient.NewMockClientInterface(ctrl)
   640  				},
   641  			},
   642  			args: args{
   643  				devfileObj: odoTestingUtil.GetTestDevfileObjFromFile("devfile-deploy.yaml"),
   644  				appName:    appName,
   645  			},
   646  			wantErr: false,
   647  		},
   648  		{
   649  			name: "did not execute preStop event because pod was not found",
   650  			fields: fields{
   651  				kubeClient: func(ctrl *gomock.Controller) kclient.ClientInterface {
   652  					client := kclient.NewMockClientInterface(ctrl)
   653  
   654  					selector := odolabels.GetSelector(componentName, "app", odolabels.ComponentDevMode, false)
   655  					client.EXPECT().GetRunningPodFromSelector(selector).Return(&corev1.Pod{}, &platform.PodNotFoundError{Selector: selector})
   656  					return client
   657  				},
   658  			},
   659  			args: args{
   660  				devfileObj: devfileObjWithPreStopEvents,
   661  				appName:    appName,
   662  			},
   663  			wantErr: false,
   664  		},
   665  		{
   666  			name: "failed to execute preStop event because of an un-ignorable error",
   667  			fields: fields{
   668  				kubeClient: func(ctrl *gomock.Controller) kclient.ClientInterface {
   669  					client := kclient.NewMockClientInterface(ctrl)
   670  
   671  					selector := odolabels.GetSelector(componentName, "app", odolabels.ComponentDevMode, false)
   672  					client.EXPECT().GetRunningPodFromSelector(selector).Return(nil, errors.New("some un-ignorable error"))
   673  					return client
   674  				},
   675  			},
   676  			args: args{
   677  				devfileObj: devfileObjWithPreStopEvents,
   678  				appName:    appName,
   679  			},
   680  			wantErr: true,
   681  		},
   682  		{
   683  			name: "successfully executed preStop events in the running pod",
   684  			fields: fields{
   685  				kubeClient: func(ctrl *gomock.Controller) kclient.ClientInterface {
   686  					client := kclient.NewMockClientInterface(ctrl)
   687  
   688  					selector := odolabels.GetSelector(componentName, "app", odolabels.ComponentDevMode, false)
   689  					client.EXPECT().GetRunningPodFromSelector(selector).Return(odoTestingUtil.CreateFakePod(componentName, "mypod", "runtime"), nil)
   690  
   691  					cmd := []string{"/bin/sh", "-c", "cd /projects/nodejs-starter && (echo \"Hello World!\") 1>>/proc/1/fd/1 2>>/proc/1/fd/2"}
   692  					client.EXPECT().ExecCMDInContainer(gomock.Any(), "runtime", "mypod", cmd, gomock.Any(), gomock.Any(), nil, false).Return(nil)
   693  
   694  					return client
   695  				},
   696  			},
   697  			args: args{
   698  				devfileObj: devfileObjWithPreStopEvents,
   699  				appName:    appName,
   700  			},
   701  			wantErr: false,
   702  		},
   703  		{
   704  			name: "failed to execute PreStopEvents because it failed to execute the command inside the container, but no error returned",
   705  			fields: fields{
   706  				kubeClient: func(ctrl *gomock.Controller) kclient.ClientInterface {
   707  					client := kclient.NewMockClientInterface(ctrl)
   708  
   709  					selector := odolabels.GetSelector(componentName, "app", odolabels.ComponentDevMode, false)
   710  					fakePod := odoTestingUtil.CreateFakePod(componentName, "mypod", "runtime")
   711  					// Expecting this method to be called twice because if the command execution fails, we try to get the pod logs by calling GetOnePodFromSelector again.
   712  					client.EXPECT().GetRunningPodFromSelector(selector).Return(fakePod, nil).Times(2)
   713  
   714  					client.EXPECT().GetPodLogs(fakePod.Name, gomock.Any(), gomock.Any()).Return(nil, errors.New("an error"))
   715  
   716  					cmd := []string{"/bin/sh", "-c", "cd /projects/nodejs-starter && (echo \"Hello World!\") 1>>/proc/1/fd/1 2>>/proc/1/fd/2"}
   717  					client.EXPECT().ExecCMDInContainer(gomock.Any(), "runtime", "mypod", cmd, gomock.Any(), gomock.Any(), nil, false).Return(errors.New("some error"))
   718  
   719  					return client
   720  				},
   721  			},
   722  			args: args{
   723  				devfileObj: devfileObjWithPreStopEvents,
   724  				appName:    appName,
   725  			},
   726  			wantErr: false,
   727  		},
   728  	}
   729  	for _, tt := range tests {
   730  		t.Run(tt.name, func(t *testing.T) {
   731  			ctrl := gomock.NewController(t)
   732  			kubeClient := tt.fields.kubeClient(ctrl)
   733  			execClient := exec.NewExecClient(kubeClient)
   734  			do := NewDeleteComponentClient(kubeClient, nil, execClient, nil)
   735  			ctx := context.Background()
   736  			ctx = odocontext.WithApplication(ctx, appName)
   737  			ctx = odocontext.WithComponentName(ctx, componentName)
   738  			if err := do.ExecutePreStopEvents(ctx, tt.args.devfileObj, tt.args.appName, tt.args.devfileObj.GetMetadataName()); (err != nil) != tt.wantErr {
   739  				t.Errorf("DeleteComponent() error = %v, wantErr %v", err, tt.wantErr)
   740  			}
   741  		})
   742  	}
   743  }
   744  
   745  // getUnstructured returns an unstructured.Unstructured object
   746  func getUnstructured(name, kind, apiVersion, namespace string) (u unstructured.Unstructured) {
   747  	u.SetName(name)
   748  	u.SetKind(kind)
   749  	u.SetAPIVersion(apiVersion)
   750  	u.SetNamespace(namespace)
   751  	return
   752  }
   753  
   754  func getGVR(group, version, resource string) schema.GroupVersionResource {
   755  	return schema.GroupVersionResource{
   756  		Group:    group,
   757  		Version:  version,
   758  		Resource: resource,
   759  	}
   760  }
   761  
   762  func TestDeleteComponentClient_ListPodmanResourcesToDeleteFromDevfile(t *testing.T) {
   763  	type fields struct {
   764  		podmanClient func(ctrl *gomock.Controller) podman.Client
   765  	}
   766  	type args struct {
   767  		appName       string
   768  		componentName string
   769  		mode          string
   770  	}
   771  
   772  	podName := "a-component-an-app"
   773  	podDef := corev1.Pod{}
   774  	podDef.SetName(podName)
   775  
   776  	tests := []struct {
   777  		name                    string
   778  		fields                  fields
   779  		args                    args
   780  		wantIsInnerLoopDeployed bool
   781  		wantPods                []*corev1.Pod
   782  		wantErr                 bool
   783  	}{
   784  		{
   785  			name: "blank name",
   786  			fields: fields{
   787  				podmanClient: func(ctrl *gomock.Controller) podman.Client {
   788  					return podman.NewMockClient(ctrl)
   789  				},
   790  			},
   791  			wantErr: true,
   792  		},
   793  		{
   794  			name: "error get pods on podman",
   795  			fields: fields{
   796  				podmanClient: func(ctrl *gomock.Controller) podman.Client {
   797  					podmanCli := podman.NewMockClient(ctrl)
   798  					podmanCli.EXPECT().PodLs().Return(nil, errors.New("error running PodLs"))
   799  					return podmanCli
   800  				},
   801  			},
   802  			args: args{
   803  				appName:       "an-app",
   804  				componentName: "a-component",
   805  			},
   806  			wantErr: true,
   807  		},
   808  		{
   809  			name: "no pod running on podman",
   810  			fields: fields{
   811  				podmanClient: func(ctrl *gomock.Controller) podman.Client {
   812  					podmanCli := podman.NewMockClient(ctrl)
   813  					podmanCli.EXPECT().PodLs().Return(map[string]bool{}, nil)
   814  					return podmanCli
   815  				},
   816  			},
   817  			args: args{
   818  				appName:       "an-app",
   819  				componentName: "a-component",
   820  			},
   821  			wantErr:                 false,
   822  			wantIsInnerLoopDeployed: false,
   823  			wantPods:                nil,
   824  		},
   825  		{
   826  			name: "another pod running on podman",
   827  			fields: fields{
   828  				podmanClient: func(ctrl *gomock.Controller) podman.Client {
   829  					podmanCli := podman.NewMockClient(ctrl)
   830  					podmanCli.EXPECT().PodLs().Return(map[string]bool{"another-pod": true}, nil)
   831  					return podmanCli
   832  				},
   833  			},
   834  			args: args{
   835  				appName:       "an-app",
   836  				componentName: "a-component",
   837  			},
   838  			wantErr:                 false,
   839  			wantIsInnerLoopDeployed: false,
   840  			wantPods:                nil,
   841  		},
   842  		{
   843  			name: "component's pod running on podman",
   844  			fields: fields{
   845  				podmanClient: func(ctrl *gomock.Controller) podman.Client {
   846  					podmanCli := podman.NewMockClient(ctrl)
   847  					podmanCli.EXPECT().PodLs().Return(map[string]bool{podName: true}, nil)
   848  
   849  					podmanCli.EXPECT().KubeGenerate(podName).Return(&podDef, nil)
   850  					return podmanCli
   851  				},
   852  			},
   853  			args: args{
   854  				appName:       "an-app",
   855  				componentName: "a-component",
   856  			},
   857  			wantErr:                 false,
   858  			wantIsInnerLoopDeployed: true,
   859  			wantPods:                []*corev1.Pod{&podDef},
   860  		},
   861  		{
   862  			name: "component's pod running on podman - dev mode requested",
   863  			fields: fields{
   864  				podmanClient: func(ctrl *gomock.Controller) podman.Client {
   865  					podmanCli := podman.NewMockClient(ctrl)
   866  					podmanCli.EXPECT().PodLs().Return(map[string]bool{podName: true}, nil)
   867  
   868  					podmanCli.EXPECT().KubeGenerate(podName).Return(&podDef, nil)
   869  					return podmanCli
   870  				},
   871  			},
   872  			args: args{
   873  				appName:       "an-app",
   874  				componentName: "a-component",
   875  				mode:          odolabels.ComponentDevMode,
   876  			},
   877  			wantErr:                 false,
   878  			wantIsInnerLoopDeployed: true,
   879  			wantPods:                []*corev1.Pod{&podDef},
   880  		},
   881  		{
   882  			name: "component's pod running on podman - deploy mode requested",
   883  			fields: fields{
   884  				podmanClient: func(ctrl *gomock.Controller) podman.Client {
   885  					podmanCli := podman.NewMockClient(ctrl)
   886  					podmanCli.EXPECT().PodLs().Return(map[string]bool{podName: true}, nil).Times(0)
   887  
   888  					podmanCli.EXPECT().KubeGenerate(podName).Return(&podDef, nil).Times(0)
   889  					return podmanCli
   890  				},
   891  			},
   892  			args: args{
   893  				appName:       "an-app",
   894  				componentName: "a-component",
   895  				mode:          odolabels.ComponentDeployMode,
   896  			},
   897  			wantErr:                 false,
   898  			wantIsInnerLoopDeployed: false,
   899  			wantPods:                nil,
   900  		},
   901  		{
   902  			name: "kube generate fails",
   903  			fields: fields{
   904  				podmanClient: func(ctrl *gomock.Controller) podman.Client {
   905  					podmanCli := podman.NewMockClient(ctrl)
   906  					podmanCli.EXPECT().PodLs().Return(map[string]bool{podName: true}, nil)
   907  
   908  					podmanCli.EXPECT().KubeGenerate(podName).Return(nil, errors.New("error executing KubeGenerate"))
   909  					return podmanCli
   910  				},
   911  			},
   912  			args: args{
   913  				appName:       "an-app",
   914  				componentName: "a-component",
   915  			},
   916  			wantErr: true,
   917  		},
   918  	}
   919  	for _, tt := range tests {
   920  		t.Run(tt.name, func(t *testing.T) {
   921  			ctrl := gomock.NewController(t)
   922  			do := &DeleteComponentClient{
   923  				podmanClient: tt.fields.podmanClient(ctrl),
   924  			}
   925  			gotIsInnerLoopDeployed, gotPods, err := do.ListPodmanResourcesToDelete(tt.args.appName, tt.args.componentName, tt.args.mode)
   926  			if (err != nil) != tt.wantErr {
   927  				t.Errorf("DeleteComponentClient.ListPodmanResourcesToDeleteFromDevfile() error = %v, wantErr %v", err, tt.wantErr)
   928  				return
   929  			}
   930  			if gotIsInnerLoopDeployed != tt.wantIsInnerLoopDeployed {
   931  				t.Errorf("DeleteComponentClient.ListPodmanResourcesToDeleteFromDevfile() gotIsInnerLoopDeployed = %v, want %v", gotIsInnerLoopDeployed, tt.wantIsInnerLoopDeployed)
   932  			}
   933  			if !reflect.DeepEqual(gotPods, tt.wantPods) {
   934  				t.Errorf("DeleteComponentClient.ListPodmanResourcesToDeleteFromDevfile() gotPods = %v, want %v", gotPods, tt.wantPods)
   935  			}
   936  		})
   937  	}
   938  }
   939  

View as plain text