...

Source file src/github.com/redhat-developer/odo/pkg/dev/kubedev/push_test.go

Documentation: github.com/redhat-developer/odo/pkg/dev/kubedev

     1  package kubedev
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"testing"
     7  
     8  	"github.com/golang/mock/gomock"
     9  	"github.com/google/go-cmp/cmp"
    10  
    11  	devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
    12  	"github.com/devfile/library/v2/pkg/devfile/generator"
    13  	devfileParser "github.com/devfile/library/v2/pkg/devfile/parser"
    14  	"github.com/devfile/library/v2/pkg/devfile/parser/data"
    15  
    16  	"github.com/redhat-developer/odo/pkg/configAutomount"
    17  	"github.com/redhat-developer/odo/pkg/dev/common"
    18  	"github.com/redhat-developer/odo/pkg/kclient"
    19  	odolabels "github.com/redhat-developer/odo/pkg/labels"
    20  	"github.com/redhat-developer/odo/pkg/libdevfile"
    21  	odocontext "github.com/redhat-developer/odo/pkg/odo/context"
    22  	"github.com/redhat-developer/odo/pkg/preference"
    23  	odoTestingUtil "github.com/redhat-developer/odo/pkg/testingutil"
    24  	"github.com/redhat-developer/odo/pkg/util"
    25  
    26  	v1 "k8s.io/api/apps/v1"
    27  	corev1 "k8s.io/api/core/v1"
    28  	kerrors "k8s.io/apimachinery/pkg/api/errors"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    31  	"k8s.io/apimachinery/pkg/runtime"
    32  	"k8s.io/apimachinery/pkg/runtime/schema"
    33  	ktesting "k8s.io/client-go/testing"
    34  )
    35  
    36  func TestCreateOrUpdateComponent(t *testing.T) {
    37  
    38  	testComponentName := "test"
    39  	testAppName := "app"
    40  	deployment := v1.Deployment{
    41  		TypeMeta: metav1.TypeMeta{
    42  			Kind:       kclient.DeploymentKind,
    43  			APIVersion: kclient.DeploymentAPIVersion,
    44  		},
    45  		ObjectMeta: metav1.ObjectMeta{
    46  			Name:        testComponentName,
    47  			Labels:      odolabels.Builder().WithComponentName(testComponentName).WithAppName(testAppName).Labels(),
    48  			Annotations: odolabels.Builder().WithProjectType("").Labels(),
    49  		},
    50  	}
    51  
    52  	tests := []struct {
    53  		name          string
    54  		componentType devfilev1.ComponentType
    55  		running       bool
    56  		wantErr       bool
    57  	}{
    58  		{
    59  			name:          "Case 1: Invalid devfile",
    60  			componentType: "",
    61  			running:       false,
    62  			wantErr:       true,
    63  		},
    64  		{
    65  			name:          "Case 2: Valid devfile",
    66  			componentType: devfilev1.ContainerComponentType,
    67  			running:       false,
    68  			wantErr:       false,
    69  		},
    70  		{
    71  			name:          "Case 3: Invalid devfile, already running component",
    72  			componentType: "",
    73  			running:       true,
    74  			wantErr:       true,
    75  		},
    76  		{
    77  			name:          "Case 4: Valid devfile, already running component",
    78  			componentType: devfilev1.ContainerComponentType,
    79  			running:       true,
    80  			wantErr:       false,
    81  		},
    82  	}
    83  	for _, tt := range tests {
    84  		t.Run(tt.name, func(t *testing.T) {
    85  			var comp devfilev1.Component
    86  			if tt.componentType != "" {
    87  				odolabels.SetProjectType(deployment.Annotations, string(tt.componentType))
    88  				comp = odoTestingUtil.GetFakeContainerComponent("component")
    89  			}
    90  			devObj := devfileParser.DevfileObj{
    91  				Data: func() data.DevfileData {
    92  					devfileData, err := data.NewDevfileData(string(data.APISchemaVersion200))
    93  					if err != nil {
    94  						t.Error(err)
    95  					}
    96  					metadata := devfileData.GetMetadata()
    97  					metadata.ProjectType = string(tt.componentType)
    98  					err = devfileData.AddComponents([]devfilev1.Component{comp})
    99  					if err != nil {
   100  						t.Error(err)
   101  					}
   102  					err = devfileData.AddCommands([]devfilev1.Command{getExecCommand("run", devfilev1.RunCommandGroupKind)})
   103  					if err != nil {
   104  						t.Error(err)
   105  					}
   106  					return devfileData
   107  				}(),
   108  			}
   109  
   110  			fkclient, fkclientset := kclient.FakeNew()
   111  
   112  			fkclientset.Kubernetes.PrependReactor("patch", "deployments", func(action ktesting.Action) (bool, runtime.Object, error) {
   113  				return true, &deployment, nil
   114  			})
   115  
   116  			if tt.running {
   117  				fkclientset.Kubernetes.PrependReactor("get", "deployments", func(action ktesting.Action) (bool, runtime.Object, error) {
   118  					return true, &deployment, nil
   119  				})
   120  			}
   121  
   122  			fkclientset.Kubernetes.PrependReactor("get", "namespaces", func(action ktesting.Action) (bool, runtime.Object, error) {
   123  				ns := &corev1.Namespace{}
   124  				ns.SetName("my-ns")
   125  				return true, ns, nil
   126  			})
   127  			ctrl := gomock.NewController(t)
   128  			fakePrefClient := preference.NewMockClient(ctrl)
   129  			fakePrefClient.EXPECT().GetEphemeralSourceVolume().AnyTimes()
   130  			fakeConfigAutomount := configAutomount.NewMockClient(ctrl)
   131  			fakeConfigAutomount.EXPECT().GetAutomountingVolumes().AnyTimes()
   132  			client := NewDevClient(fkclient, fakePrefClient, nil, nil, nil, nil, nil, nil, nil, fakeConfigAutomount)
   133  			ctx := context.Background()
   134  			ctx = odocontext.WithApplication(ctx, "app")
   135  			ctx = odocontext.WithComponentName(ctx, "my-component")
   136  			ctx = odocontext.WithDevfilePath(ctx, "/path/to/devfile")
   137  			_, _, err := client.createOrUpdateComponent(ctx, common.PushParameters{
   138  				Devfile: devObj,
   139  			}, tt.running, libdevfile.DevfileCommands{}, nil)
   140  
   141  			// Checks for unexpected error cases
   142  			if !tt.wantErr == (err != nil) {
   143  				t.Errorf("component adapter create unexpected error %v, wantErr %v", err, tt.wantErr)
   144  			}
   145  		})
   146  	}
   147  
   148  }
   149  
   150  func getExecCommand(id string, group devfilev1.CommandGroupKind) devfilev1.Command {
   151  
   152  	commands := [...]string{"ls -la", "pwd"}
   153  	component := "component"
   154  	workDir := [...]string{"/", "/root"}
   155  
   156  	return devfilev1.Command{
   157  		Id: id,
   158  		CommandUnion: devfilev1.CommandUnion{
   159  			Exec: &devfilev1.ExecCommand{
   160  				LabeledCommand: devfilev1.LabeledCommand{
   161  					BaseCommand: devfilev1.BaseCommand{
   162  						Group: &devfilev1.CommandGroup{Kind: group},
   163  					},
   164  				},
   165  				CommandLine: commands[0],
   166  				Component:   component,
   167  				WorkingDir:  workDir[0],
   168  			},
   169  		},
   170  	}
   171  
   172  }
   173  
   174  func TestAdapter_generateDeploymentObjectMeta(t *testing.T) {
   175  	namespacedKubernetesName, err := util.NamespaceKubernetesObject("nodejs", "app")
   176  	if err != nil {
   177  		t.Errorf("unexpected error: %v", err)
   178  	}
   179  
   180  	type fields struct {
   181  		componentName string
   182  		appName       string
   183  		deployment    *v1.Deployment
   184  	}
   185  	type args struct {
   186  		labels      map[string]string
   187  		annotations map[string]string
   188  	}
   189  	tests := []struct {
   190  		name    string
   191  		fields  fields
   192  		args    args
   193  		want    metav1.ObjectMeta
   194  		wantErr bool
   195  	}{
   196  		{
   197  			name: "case 1: deployment exists",
   198  			fields: fields{
   199  				componentName: "nodejs",
   200  				appName:       "app",
   201  				deployment:    odoTestingUtil.CreateFakeDeployment("nodejs", false),
   202  			},
   203  			args: args{
   204  				labels:      odoTestingUtil.CreateFakeDeployment("nodejs", false).Labels,
   205  				annotations: nil,
   206  			},
   207  			want:    generator.GetObjectMeta("nodejs", "project-0", odoTestingUtil.CreateFakeDeployment("nodejs", false).Labels, nil),
   208  			wantErr: false,
   209  		},
   210  		{
   211  			name: "case 2: deployment doesn't exists",
   212  			fields: fields{
   213  				componentName: "nodejs",
   214  				appName:       "app",
   215  				deployment:    nil,
   216  			},
   217  			args: args{
   218  				labels:      odoTestingUtil.CreateFakeDeployment("nodejs", false).Labels,
   219  				annotations: nil,
   220  			},
   221  			want:    generator.GetObjectMeta(namespacedKubernetesName, "project-0", odoTestingUtil.CreateFakeDeployment("nodejs", false).Labels, nil),
   222  			wantErr: false,
   223  		},
   224  		{
   225  			name: "case 3: deployment exists and there is annotations successfully passed in",
   226  			fields: fields{
   227  				componentName: "nodejs",
   228  				appName:       "app",
   229  				deployment:    odoTestingUtil.CreateFakeDeployment("nodejs", false),
   230  			},
   231  			args: args{
   232  				labels:      odoTestingUtil.CreateFakeDeployment("nodejs", false).Labels,
   233  				annotations: odolabels.Builder().WithMode(odolabels.ComponentDevMode).Labels(),
   234  			},
   235  			want:    generator.GetObjectMeta("nodejs", "project-0", odoTestingUtil.CreateFakeDeployment("nodejs", false).Labels, odolabels.Builder().WithMode(odolabels.ComponentDevMode).Labels()),
   236  			wantErr: false,
   237  		},
   238  	}
   239  	for _, tt := range tests {
   240  		t.Run(tt.name, func(t *testing.T) {
   241  			fakeClient, _ := kclient.FakeNew()
   242  			fakeClient.Namespace = "project-0"
   243  
   244  			a := DevClient{
   245  				kubernetesClient: fakeClient,
   246  			}
   247  			ctx := context.Background()
   248  			ctx = odocontext.WithApplication(ctx, "app")
   249  			ctx = odocontext.WithComponentName(ctx, "nodejs")
   250  			ctx = odocontext.WithDevfilePath(ctx, "/path/to/devfile")
   251  			got, err := a.generateDeploymentObjectMeta(ctx, tt.fields.deployment, tt.args.labels, tt.args.annotations)
   252  			if (err != nil) != tt.wantErr {
   253  				t.Errorf("generateDeploymentObjectMeta() error = %v, wantErr %v", err, tt.wantErr)
   254  				return
   255  			}
   256  			if diff := cmp.Diff(tt.want, got); diff != "" {
   257  				t.Errorf("Adapter.generateDeploymentObjectMeta() mismatch (-want +got):\n%s", diff)
   258  			}
   259  		})
   260  	}
   261  }
   262  
   263  func TestAdapter_deleteRemoteResources(t *testing.T) {
   264  	type fields struct {
   265  		kubeClientCustomizer func(kubeClient *kclient.MockClientInterface)
   266  	}
   267  	type args struct {
   268  		objectsToRemove []unstructured.Unstructured
   269  	}
   270  
   271  	var u1 unstructured.Unstructured
   272  	u1.SetGroupVersionKind(schema.GroupVersionKind{
   273  		Group:   "metrics.k8s.io",
   274  		Version: "v1beta1",
   275  		Kind:    "PodMetrics",
   276  	})
   277  	u1.SetName("my-pod-metrics")
   278  
   279  	var u2 unstructured.Unstructured
   280  	u2.SetGroupVersionKind(schema.GroupVersionKind{
   281  		Group:   "postgresql.k8s.enterprisedb.io",
   282  		Version: "v1",
   283  		Kind:    "Cluster",
   284  	})
   285  	u2.SetName("my-pg-cluster")
   286  	toRemove := []unstructured.Unstructured{u1, u2}
   287  
   288  	tests := []struct {
   289  		name    string
   290  		fields  fields
   291  		args    args
   292  		wantErr bool
   293  	}{
   294  		{
   295  			name: "nothing to delete - nil list",
   296  			args: args{
   297  				objectsToRemove: nil,
   298  			},
   299  			fields: fields{
   300  				kubeClientCustomizer: func(kubeClient *kclient.MockClientInterface) {
   301  					kubeClient.EXPECT().DeleteDynamicResource(gomock.Any(), gomock.Any(), gomock.Any()).Times(0)
   302  				},
   303  			},
   304  			wantErr: false,
   305  		},
   306  		{
   307  			name: "nothing to delete - empty list",
   308  			args: args{
   309  				objectsToRemove: []unstructured.Unstructured{},
   310  			},
   311  			fields: fields{
   312  				kubeClientCustomizer: func(kubeClient *kclient.MockClientInterface) {
   313  					kubeClient.EXPECT().DeleteDynamicResource(gomock.Any(), gomock.Any(), gomock.Any()).Times(0)
   314  				},
   315  			},
   316  			wantErr: false,
   317  		},
   318  		{
   319  			name: "error getting information about resource",
   320  			args: args{
   321  				objectsToRemove: toRemove,
   322  			},
   323  			fields: fields{
   324  				kubeClientCustomizer: func(kubeClient *kclient.MockClientInterface) {
   325  					kubeClient.EXPECT().GetGVRFromGVK(gomock.Eq(u1.GroupVersionKind())).Return(schema.GroupVersionResource{}, nil)
   326  					kubeClient.EXPECT().GetGVRFromGVK(gomock.Eq(u2.GroupVersionKind())).Return(schema.GroupVersionResource{}, errors.New("error on GetGVRFromGVK(u2)"))
   327  					// Only u1 should be deleted
   328  					kubeClient.EXPECT().DeleteDynamicResource(gomock.Eq(u1.GetName()), gomock.Any(), gomock.Any()).Return(nil).Times(1)
   329  					kubeClient.EXPECT().DeleteDynamicResource(gomock.Eq(u2.GetName()), gomock.Any(), gomock.Any()).Times(0)
   330  				},
   331  			},
   332  			wantErr: true,
   333  		},
   334  		{
   335  			name: "generic error deleting resource",
   336  			args: args{
   337  				objectsToRemove: toRemove,
   338  			},
   339  			fields: fields{
   340  				kubeClientCustomizer: func(kubeClient *kclient.MockClientInterface) {
   341  					kubeClient.EXPECT().GetGVRFromGVK(gomock.Eq(u1.GroupVersionKind())).Return(schema.GroupVersionResource{}, nil)
   342  					kubeClient.EXPECT().GetGVRFromGVK(gomock.Eq(u2.GroupVersionKind())).Return(schema.GroupVersionResource{}, nil)
   343  					kubeClient.EXPECT().DeleteDynamicResource(gomock.Eq(u1.GetName()), gomock.Any(), gomock.Any()).Return(nil)
   344  					kubeClient.EXPECT().DeleteDynamicResource(gomock.Eq(u2.GetName()), gomock.Any(), gomock.Any()).Return(errors.New("generic error while deleting u1"))
   345  				},
   346  			},
   347  			wantErr: true,
   348  		},
   349  		{
   350  			name: "generic error deleting all resources",
   351  			args: args{
   352  				objectsToRemove: toRemove,
   353  			},
   354  			fields: fields{
   355  				kubeClientCustomizer: func(kubeClient *kclient.MockClientInterface) {
   356  					kubeClient.EXPECT().GetGVRFromGVK(gomock.Eq(u1.GroupVersionKind())).Return(schema.GroupVersionResource{}, nil)
   357  					kubeClient.EXPECT().GetGVRFromGVK(gomock.Eq(u2.GroupVersionKind())).Return(schema.GroupVersionResource{}, nil)
   358  					kubeClient.EXPECT().DeleteDynamicResource(gomock.Eq(u1.GetName()), gomock.Any(), gomock.Any()).
   359  						Return(errors.New("generic error while deleting u1"))
   360  					kubeClient.EXPECT().DeleteDynamicResource(gomock.Eq(u2.GetName()), gomock.Any(), gomock.Any()).
   361  						Return(errors.New("generic error while deleting u2"))
   362  				},
   363  			},
   364  			wantErr: true,
   365  		},
   366  		{
   367  			name: "not found error deleting resource",
   368  			args: args{
   369  				objectsToRemove: toRemove,
   370  			},
   371  			fields: fields{
   372  				kubeClientCustomizer: func(kubeClient *kclient.MockClientInterface) {
   373  					kubeClient.EXPECT().GetGVRFromGVK(gomock.Eq(u1.GroupVersionKind())).Return(schema.GroupVersionResource{}, nil)
   374  					kubeClient.EXPECT().GetGVRFromGVK(gomock.Eq(u2.GroupVersionKind())).Return(schema.GroupVersionResource{}, nil)
   375  					kubeClient.EXPECT().DeleteDynamicResource(gomock.Eq(u1.GetName()), gomock.Any(), gomock.Any()).Return(nil)
   376  					kubeClient.EXPECT().DeleteDynamicResource(gomock.Eq(u2.GetName()), gomock.Any(), gomock.Any()).
   377  						Return(kerrors.NewNotFound(schema.GroupResource{}, "u2"))
   378  				},
   379  			},
   380  			wantErr: false,
   381  		},
   382  		{
   383  			name: "method not allowed error deleting resource",
   384  			args: args{
   385  				objectsToRemove: toRemove,
   386  			},
   387  			fields: fields{
   388  				kubeClientCustomizer: func(kubeClient *kclient.MockClientInterface) {
   389  					kubeClient.EXPECT().GetGVRFromGVK(gomock.Eq(u1.GroupVersionKind())).Return(schema.GroupVersionResource{}, nil)
   390  					kubeClient.EXPECT().GetGVRFromGVK(gomock.Eq(u2.GroupVersionKind())).Return(schema.GroupVersionResource{}, nil)
   391  					kubeClient.EXPECT().DeleteDynamicResource(gomock.Eq(u1.GetName()), gomock.Any(), gomock.Any()).Return(nil)
   392  					kubeClient.EXPECT().DeleteDynamicResource(gomock.Eq(u2.GetName()), gomock.Any(), gomock.Any()).
   393  						Return(kerrors.NewMethodNotSupported(schema.GroupResource{Resource: "PodMetrics"}, "DELETE"))
   394  				},
   395  			},
   396  			wantErr: false,
   397  		},
   398  		{
   399  			name: "not found error deleting all resources",
   400  			args: args{
   401  				objectsToRemove: toRemove,
   402  			},
   403  			fields: fields{
   404  				kubeClientCustomizer: func(kubeClient *kclient.MockClientInterface) {
   405  					kubeClient.EXPECT().GetGVRFromGVK(gomock.Eq(u1.GroupVersionKind())).Return(schema.GroupVersionResource{}, nil)
   406  					kubeClient.EXPECT().GetGVRFromGVK(gomock.Eq(u2.GroupVersionKind())).Return(schema.GroupVersionResource{}, nil)
   407  					kubeClient.EXPECT().DeleteDynamicResource(gomock.Eq(u1.GetName()), gomock.Any(), gomock.Any()).
   408  						Return(kerrors.NewNotFound(schema.GroupResource{}, "u1"))
   409  					kubeClient.EXPECT().DeleteDynamicResource(gomock.Eq(u2.GetName()), gomock.Any(), gomock.Any()).
   410  						Return(kerrors.NewNotFound(schema.GroupResource{}, "u2"))
   411  				},
   412  			},
   413  			wantErr: false,
   414  		},
   415  		{
   416  			name: "method not allowed error deleting all resources",
   417  			args: args{
   418  				objectsToRemove: toRemove,
   419  			},
   420  			fields: fields{
   421  				kubeClientCustomizer: func(kubeClient *kclient.MockClientInterface) {
   422  					kubeClient.EXPECT().GetGVRFromGVK(gomock.Eq(u1.GroupVersionKind())).Return(schema.GroupVersionResource{}, nil)
   423  					kubeClient.EXPECT().GetGVRFromGVK(gomock.Eq(u2.GroupVersionKind())).Return(schema.GroupVersionResource{}, nil)
   424  					kubeClient.EXPECT().DeleteDynamicResource(gomock.Eq(u1.GetName()), gomock.Any(), gomock.Any()).
   425  						Return(kerrors.NewMethodNotSupported(schema.GroupResource{}, "DELETE"))
   426  					kubeClient.EXPECT().DeleteDynamicResource(gomock.Eq(u2.GetName()), gomock.Any(), gomock.Any()).
   427  						Return(kerrors.NewMethodNotSupported(schema.GroupResource{}, "DELETE"))
   428  				},
   429  			},
   430  			wantErr: false,
   431  		},
   432  	}
   433  	for _, tt := range tests {
   434  		t.Run(tt.name, func(t *testing.T) {
   435  			ctrl := gomock.NewController(t)
   436  			kubeClient := kclient.NewMockClientInterface(ctrl)
   437  			if tt.fields.kubeClientCustomizer != nil {
   438  				tt.fields.kubeClientCustomizer(kubeClient)
   439  			}
   440  			a := DevClient{
   441  				kubernetesClient: kubeClient,
   442  			}
   443  			if err := a.deleteRemoteResources(tt.args.objectsToRemove); (err != nil) != tt.wantErr {
   444  				t.Errorf("deleteRemoteResources() error = %v, wantErr %v", err, tt.wantErr)
   445  			}
   446  		})
   447  	}
   448  }
   449  

View as plain text