Add suport for query api on root level
[multicloud/k8s.git] / src / k8splugin / api / brokerhandler_test.go
1 /*
2 Copyright 2018 Intel Corporation.
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6         http://www.apache.org/licenses/LICENSE-2.0
7 Unless required by applicable law or agreed to in writing, software
8 distributed under the License is distributed on an "AS IS" BASIS,
9 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 See the License for the specific language governing permissions and
11 limitations under the License.
12 */
13
14 package api
15
16 import (
17         "bytes"
18         "encoding/json"
19         "io"
20         "io/ioutil"
21         "net/http"
22         "net/http/httptest"
23         "reflect"
24         "strings"
25         "testing"
26
27         "github.com/onap/multicloud-k8s/src/k8splugin/internal/app"
28         "github.com/onap/multicloud-k8s/src/k8splugin/internal/helm"
29
30         pkgerrors "github.com/pkg/errors"
31         "k8s.io/apimachinery/pkg/runtime/schema"
32 )
33
34 func TestConvertDirectives(t *testing.T) {
35         testCases := []struct {
36                 label    string
37                 input    brokerRequest
38                 expected map[string]string
39         }{
40                 {
41                         label:    "Single variable",
42                         expected: map[string]string{"test": "true"},
43                         input: brokerRequest{SDNCDirectives: directive{[]attribute{{
44                                 Key:   "test",
45                                 Value: "true",
46                         }}}},
47                 },
48                 {
49                         label:    "Empty parameter",
50                         expected: map[string]string{"test": ""},
51                         input: brokerRequest{OOFDirectives: directive{[]attribute{{
52                                 Key:   "test",
53                                 Value: "",
54                         }}}},
55                 },
56                 {
57                         label:    "Null entry",
58                         input:    brokerRequest{},
59                         expected: make(map[string]string),
60                 },
61                 {
62                         label: "Complex helm overrides",
63                         /*
64                                 String with int will be later treated as int in helm.TemplateClient
65                                 (see helm/pkg/strvals/parser.go)
66                                 If unsure, convert type in helm chart like `{{ toString $value }}` or `{{ int $value }}`
67                                 (see http://masterminds.github.io/sprig/conversion.html)
68                         */
69                         expected: map[string]string{"name": "{a, b, c}", "servers[0].port": "80"},
70                         input: brokerRequest{UserDirectives: directive{[]attribute{
71                                 {
72                                         Key:   "name",
73                                         Value: "{a, b, c}",
74                                 },
75                                 {
76                                         Key:   "servers[0].port",
77                                         Value: "80",
78                                 },
79                         }}},
80                 },
81                 {
82                         label:    "Override variables",
83                         expected: map[string]string{"empty": "", "sdnc": "sdnc", "user": "user", "oof": "oof"},
84                         input: brokerRequest{
85                                 SDNCDirectives: directive{[]attribute{
86                                         {
87                                                 Key:   "empty",
88                                                 Value: "sdnc",
89                                         },
90                                         {
91                                                 Key:   "sdnc",
92                                                 Value: "sdnc",
93                                         },
94                                         {
95                                                 Key:   "oof",
96                                                 Value: "sdnc",
97                                         },
98                                 }},
99                                 OOFDirectives: directive{[]attribute{
100                                         {
101                                                 Key:   "oof",
102                                                 Value: "oof",
103                                         },
104                                         {
105                                                 Key:   "user",
106                                                 Value: "oof",
107                                         },
108                                 }},
109                                 UserDirectives: directive{[]attribute{
110                                         {
111                                                 Key:   "user",
112                                                 Value: "user",
113                                         },
114                                         {
115                                                 Key:   "empty",
116                                                 Value: "",
117                                         },
118                                 }},
119                         },
120                 },
121         }
122
123         for _, testCase := range testCases {
124                 t.Run(testCase.label, func(t *testing.T) {
125                         result := testCase.input.convertDirectives()
126                         if !reflect.DeepEqual(result, testCase.expected) {
127                                 t.Fatalf("Unexpected result. Wanted '%v', retrieved '%v'",
128                                         testCase.expected, result)
129                         }
130                 })
131         }
132 }
133
134 func TestBrokerCreateHandler(t *testing.T) {
135         testCases := []struct {
136                 label         string
137                 input         io.Reader
138                 expected      brokerPOSTResponse
139                 expectedError string
140                 expectedCode  int
141                 instClient    *mockInstanceClient
142         }{
143                 {
144                         label:        "Missing body failure",
145                         expectedCode: http.StatusBadRequest,
146                 },
147                 {
148                         label:        "Invalid JSON request format",
149                         input:        bytes.NewBuffer([]byte("invalid")),
150                         expectedCode: http.StatusUnprocessableEntity,
151                 },
152                 {
153                         label:         "Missing vf-module-*-id parameter",
154                         expectedError: "vf-module-model-customization-id is empty",
155                         expectedCode:  http.StatusBadRequest,
156                         input: bytes.NewBuffer([]byte(`{
157                                 "vf-module-model-invariant-id": "123456qwerty",
158                                 "vf-module-model-version-id": "123qweasdzxc",
159                                 "generic-vnf-id": "dummy-vnf-id",
160                                 "vf-module-id": "dummy-vfm-id",
161                                 "template_data": {
162                                         "stack_name": "dummy-stack-name"
163                                 },
164                                 "sdnc_directives": {
165                                         "attributes": [
166                                                 {
167                                                         "attribute_name": "k8s-rb-profile-name",
168                                                         "attribute_value": "dummy-profile"
169                                                 }
170                                         ]
171                                 }
172                         }`)),
173                 },
174                 {
175                         label:         "Missing stack name parameter",
176                         expectedError: "stack_name is missing from template_data",
177                         expectedCode:  http.StatusBadRequest,
178                         input: bytes.NewBuffer([]byte(`{
179                                 "vf-module-model-customization-id": "84sdfkio938",
180                                 "vf-module-model-invariant-id": "123456qwerty",
181                                 "vf-module-model-version-id": "123qweasdzxc",
182                                 "generic-vnf-id": "dummy-vnf-id",
183                                 "vf-module-id": "dummy-vfm-id",
184                                 "template_data": {
185                                 },
186                                 "sdnc_directives": {
187                                         "attributes": [
188                                                 {
189                                                         "attribute_name": "k8s-rb-profile-name",
190                                                         "attribute_value": "dummy-profile"
191                                                 }
192                                         ]
193                                 }
194                         }`)),
195                 },
196                 {
197                         label:         "Missing profile name directive",
198                         expectedError: "k8s-rb-profile-name is missing from directives",
199                         expectedCode:  http.StatusBadRequest,
200                         input: bytes.NewBuffer([]byte(`{
201                                 "vf-module-model-customization-id": "84sdfkio938",
202                                 "vf-module-model-invariant-id": "123456qwerty",
203                                 "vf-module-model-version-id": "123qweasdzxc",
204                                 "generic-vnf-id": "dummy-vnf-id",
205                                 "vf-module-id": "dummy-vfm-id",
206                                 "template_data": {
207                                         "stack_name": "dummy-stack-name"
208                                 },
209                                 "sdnc_directives": {
210                                         "attributes": [
211                                         ]
212                                 }
213                         }`)),
214                 },
215                 {
216                         label:         "Missing vf-module-id parameter",
217                         expectedError: "vf-module-id is empty",
218                         expectedCode:  http.StatusBadRequest,
219                         input: bytes.NewBuffer([]byte(`{
220                                 "vf-module-model-customization-id": "84sdfkio938",
221                                 "vf-module-model-invariant-id": "123456qwerty",
222                                 "vf-module-model-version-id": "123qweasdzxc",
223                                 "generic-vnf-id": "dummy-vnf-id",
224                                 "template_data": {
225                                         "stack_name": "dummy-stack-name"
226                                 },
227                                 "sdnc_directives": {
228                                         "attributes": [
229                                                 {
230                                                         "attribute_name": "k8s-rb-profile-name",
231                                                         "attribute_value": "dummy-profile"
232                                                 }
233                                         ]
234                                 }
235                         }`)),
236                 },
237                 {
238                         label: "Succesfully create an Instance",
239                         input: bytes.NewBuffer([]byte(`{
240                                 "vf-module-model-customization-id": "84sdfkio938",
241                                 "vf-module-model-invariant-id": "123456qwerty",
242                                 "vf-module-model-version-id": "123qweasdzxc",
243                                 "generic-vnf-id": "dummy-vnf-id",
244                                 "vf-module-id": "dummy-vfm-id",
245                                 "template_data": {
246                                         "stack_name": "dummy-stack-name"
247                                 },
248                                 "sdnc_directives": {
249                                         "attributes": [
250                                                 {
251                                                         "attribute_name": "k8s-rb-profile-name",
252                                                         "attribute_value": "dummy-profile"
253                                                 }
254                                         ]
255                                 }
256                         }`)),
257                         expected: brokerPOSTResponse{
258                                 WorkloadID:     "HaKpys8e",
259                                 TemplateType:   "heat",
260                                 WorkloadStatus: "CREATE_COMPLETE",
261                                 TemplateResponse: []helm.KubernetesResource{
262                                         {
263                                                 GVK: schema.GroupVersionKind{
264                                                         Group:   "apps",
265                                                         Version: "v1",
266                                                         Kind:    "Deployment"},
267                                                 Name: "test-deployment",
268                                         },
269                                         {
270                                                 GVK: schema.GroupVersionKind{
271                                                         Group:   "",
272                                                         Version: "v1",
273                                                         Kind:    "Service"},
274                                                 Name: "test-service",
275                                         },
276                                 },
277                         },
278                         expectedCode: http.StatusCreated,
279                         instClient: &mockInstanceClient{
280                                 items: []app.InstanceResponse{
281                                         {
282                                                 ID: "HaKpys8e",
283                                                 Request: app.InstanceRequest{
284                                                         RBName:      "123456qwerty",
285                                                         RBVersion:   "123qweasdzxc",
286                                                         ProfileName: "profile1",
287                                                         CloudRegion: "region1",
288                                                 },
289                                                 Namespace: "testnamespace",
290                                                 Resources: []helm.KubernetesResource{
291                                                         {
292                                                                 GVK: schema.GroupVersionKind{
293                                                                         Group:   "apps",
294                                                                         Version: "v1",
295                                                                         Kind:    "Deployment"},
296                                                                 Name: "test-deployment",
297                                                         },
298                                                         {
299                                                                 GVK: schema.GroupVersionKind{
300                                                                         Group:   "",
301                                                                         Version: "v1",
302                                                                         Kind:    "Service"},
303                                                                 Name: "test-service",
304                                                         },
305                                                 },
306                                         },
307                                 },
308                         },
309                 },
310         }
311
312         for _, testCase := range testCases {
313                 t.Run(testCase.label, func(t *testing.T) {
314
315                         request := httptest.NewRequest("POST", "/cloudowner/cloudregion/infra_workload", testCase.input)
316                         resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil, nil, nil, nil))
317                         defer resp.Body.Close()
318
319                         if testCase.expectedCode != resp.StatusCode {
320                                 body, _ := ioutil.ReadAll(resp.Body)
321                                 t.Log(string(body))
322                                 t.Fatalf("Request method returned code '%v', but '%v' was expected",
323                                         resp.StatusCode, testCase.expectedCode)
324                         }
325
326                         if resp.StatusCode == http.StatusCreated {
327                                 var response brokerPOSTResponse
328                                 err := json.NewDecoder(resp.Body).Decode(&response)
329                                 if err != nil {
330                                         t.Fatalf("Parsing the returned response got an error (%s)", err)
331                                 }
332                                 if !reflect.DeepEqual(testCase.expected, response) {
333                                         t.Fatalf("TestGetHandler returned:\n result=%v\n expected=%v",
334                                                 response, testCase.expected)
335                                 }
336                         } else if testCase.expectedError != "" {
337                                 body, err := ioutil.ReadAll(resp.Body)
338                                 if err == nil {
339                                         if !strings.Contains(string(body), testCase.expectedError) {
340                                                 t.Fatalf("Request method returned body '%s', but '%s' wasn't found",
341                                                         body, testCase.expectedError)
342                                         }
343                                 } else {
344                                         t.Fatalf("Request method returned malformed body")
345                                 }
346                         }
347                 })
348         }
349 }
350
351 func TestBrokerGetHandler(t *testing.T) {
352         testCases := []struct {
353                 label            string
354                 input            string
355                 expectedCode     int
356                 expectedResponse brokerGETResponse
357                 instClient       *mockInstanceClient
358         }{
359                 {
360                         label:        "Fail to retrieve Instance",
361                         input:        "HaKpys8e",
362                         expectedCode: http.StatusInternalServerError,
363                         instClient: &mockInstanceClient{
364                                 err: pkgerrors.New("Internal error"),
365                         },
366                 },
367                 {
368                         label:        "Succesful get an Instance",
369                         input:        "HaKpys8e",
370                         expectedCode: http.StatusOK,
371                         expectedResponse: brokerGETResponse{
372                                 TemplateType:   "heat",
373                                 WorkloadID:     "HaKpys8e",
374                                 WorkloadStatus: "CREATE_COMPLETE",
375                         },
376                         instClient: &mockInstanceClient{
377                                 items: []app.InstanceResponse{
378                                         {
379                                                 ID: "HaKpys8e",
380                                                 Request: app.InstanceRequest{
381                                                         RBName:      "test-rbdef",
382                                                         RBVersion:   "v1",
383                                                         ProfileName: "profile1",
384                                                         CloudRegion: "region1",
385                                                 },
386                                                 Namespace: "testnamespace",
387                                                 Resources: []helm.KubernetesResource{
388                                                         {
389                                                                 GVK: schema.GroupVersionKind{
390                                                                         Group:   "apps",
391                                                                         Version: "v1",
392                                                                         Kind:    "Deployment"},
393                                                                 Name: "test-deployment",
394                                                         },
395                                                         {
396                                                                 GVK: schema.GroupVersionKind{
397                                                                         Group:   "",
398                                                                         Version: "v1",
399                                                                         Kind:    "Service"},
400                                                                 Name: "test-service",
401                                                         },
402                                                 },
403                                         },
404                                 },
405                         },
406                 },
407         }
408
409         for _, testCase := range testCases {
410                 t.Run(testCase.label, func(t *testing.T) {
411                         request := httptest.NewRequest("GET", "/cloudowner/cloudregion/infra_workload/"+testCase.input, nil)
412                         resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil, nil, nil, nil))
413
414                         if testCase.expectedCode != resp.StatusCode {
415                                 t.Fatalf("Request method returned: %v and it was expected: %v",
416                                         resp.StatusCode, testCase.expectedCode)
417                         }
418                         if resp.StatusCode == http.StatusOK {
419                                 var response brokerGETResponse
420                                 err := json.NewDecoder(resp.Body).Decode(&response)
421                                 if err != nil {
422                                         t.Fatalf("Parsing the returned response got an error (%s)", err)
423                                 }
424                                 if !reflect.DeepEqual(testCase.expectedResponse, response) {
425                                         t.Fatalf("TestGetHandler returned:\n result=%v\n expected=%v",
426                                                 response, testCase.expectedResponse)
427                                 }
428                         }
429                 })
430         }
431 }
432
433 func TestBrokerFindHandler(t *testing.T) {
434         testCases := []struct {
435                 label            string
436                 input            string
437                 expectedCode     int
438                 expectedResponse brokerGETResponse
439                 instClient       *mockInstanceClient
440         }{
441                 {
442                         label:        "Successful find an Instance",
443                         input:        "test-vf-module-name",
444                         expectedCode: http.StatusOK,
445                         expectedResponse: brokerGETResponse{
446                                 TemplateType:   "heat",
447                                 WorkloadID:     "HaKpys8e",
448                                 WorkloadStatus: "CREATE_COMPLETE",
449                                 WorkloadStatusReason: map[string]interface{}{
450                                         "stacks": []map[string]interface{}{
451                                                 {
452                                                         "stack_status": "CREATE_COMPLETE",
453                                                         "id":           "HaKpys8e",
454                                                 },
455                                         },
456                                 },
457                         },
458                         instClient: &mockInstanceClient{
459                                 miniitems: []app.InstanceMiniResponse{
460                                         {
461                                                 ID: "HaKpys8e",
462                                                 Request: app.InstanceRequest{
463                                                         RBName:      "test-rbdef",
464                                                         RBVersion:   "v1",
465                                                         ProfileName: "profile1",
466                                                         CloudRegion: "region1",
467                                                 },
468                                                 Namespace: "testnamespace",
469                                         },
470                                 },
471                         },
472                 },
473                 {
474                         label:        "Fail to find an Instance",
475                         input:        "test-vf-module-name-1",
476                         expectedCode: http.StatusOK,
477                         expectedResponse: brokerGETResponse{
478                                 TemplateType:   "heat",
479                                 WorkloadID:     "",
480                                 WorkloadStatus: "GET_COMPLETE",
481                                 WorkloadStatusReason: map[string]interface{}{
482                                         "stacks": []map[string]interface{}{},
483                                 },
484                         },
485                         instClient: &mockInstanceClient{},
486                 },
487         }
488
489         for _, testCase := range testCases {
490                 t.Run(testCase.label, func(t *testing.T) {
491                         request := httptest.NewRequest("GET", "/cloudowner/cloudregion/infra_workload?name="+testCase.input, nil)
492                         resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil, nil, nil, nil))
493
494                         if testCase.expectedCode != resp.StatusCode {
495                                 t.Fatalf("Request method returned: %v and it was expected: %v",
496                                         resp.StatusCode, testCase.expectedCode)
497                         }
498                         if resp.StatusCode == http.StatusOK {
499                                 var response brokerGETResponse
500                                 err := json.NewDecoder(resp.Body).Decode(&response)
501                                 if err != nil {
502                                         t.Fatalf("Parsing the returned response got an error (%s)", err)
503                                 }
504                                 if testCase.expectedResponse.WorkloadID != response.WorkloadID {
505                                         t.Fatalf("TestGetHandler returned:\n result=%v\n expected=%v",
506                                                 response.WorkloadID, testCase.expectedResponse.WorkloadID)
507                                 }
508                                 tcStacks := testCase.expectedResponse.WorkloadStatusReason["stacks"].([]map[string]interface{})
509                                 if len(tcStacks) != 0 {
510                                         //We expect only one response in this testcase.
511                                         resStacks := response.WorkloadStatusReason["stacks"].([]interface{})[0].(map[string]interface{})
512                                         if !reflect.DeepEqual(tcStacks[0], resStacks) {
513                                                 t.Fatalf("TestGetHandler returned:\n result=%v\n expected=%v",
514                                                         resStacks, tcStacks)
515                                         }
516                                 }
517                         }
518                 })
519         }
520 }
521
522 func TestBrokerDeleteHandler(t *testing.T) {
523         testCases := []struct {
524                 label            string
525                 input            string
526                 expectedCode     int
527                 expectedResponse brokerDELETEResponse
528                 instClient       *mockInstanceClient
529         }{
530                 {
531                         label:        "Fail to destroy VNF",
532                         input:        "HaKpys8e",
533                         expectedCode: http.StatusInternalServerError,
534                         instClient: &mockInstanceClient{
535                                 err: pkgerrors.New("Internal error"),
536                         },
537                 },
538                 {
539                         label:        "Succesful delete a VNF",
540                         input:        "HaKpys8e",
541                         expectedCode: http.StatusAccepted,
542                         expectedResponse: brokerDELETEResponse{
543                                 TemplateType:   "heat",
544                                 WorkloadID:     "HaKpys8e",
545                                 WorkloadStatus: "DELETE_COMPLETE",
546                         },
547                         instClient: &mockInstanceClient{},
548                 },
549         }
550
551         for _, testCase := range testCases {
552                 t.Run(testCase.label, func(t *testing.T) {
553                         request := httptest.NewRequest("DELETE", "/cloudowner/cloudregion/infra_workload/"+testCase.input, nil)
554                         resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil, nil, nil, nil))
555
556                         if testCase.expectedCode != resp.StatusCode {
557                                 t.Fatalf("Request method returned: %v and it was expected: %v", resp.StatusCode, testCase.expectedCode)
558                         }
559                         if resp.StatusCode == http.StatusOK {
560                                 var response brokerGETResponse
561                                 err := json.NewDecoder(resp.Body).Decode(&response)
562                                 if err != nil {
563                                         t.Fatalf("Parsing the returned response got an error (%s)", err)
564                                 }
565                                 if !reflect.DeepEqual(testCase.expectedResponse, response) {
566                                         t.Fatalf("TestGetHandler returned:\n result=%v\n expected=%v",
567                                                 response, testCase.expectedResponse)
568                                 }
569                         }
570                 })
571         }
572 }