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
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
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