Add support for so integration 03/85003/2
authorKiran Kamineni <kiran.k.kamineni@intel.com>
Thu, 11 Apr 2019 04:54:41 +0000 (21:54 -0700)
committerKiran Kamineni <kiran.k.kamineni@intel.com>
Thu, 11 Apr 2019 07:21:48 +0000 (00:21 -0700)
Add support for the multicloud-api
that is called by SO for instantiation.

Issue-ID: MULTICLOUD-350
Change-Id: Icf9137dae9796ac256c3319b49af6c30b275a4a9
Signed-off-by: Kiran Kamineni <kiran.k.kamineni@intel.com>
src/k8splugin/api/api.go
src/k8splugin/api/brokerhandler.go [new file with mode: 0644]
src/k8splugin/api/brokerhandler_test.go [new file with mode: 0644]

index 54147d2..4bf8d6a 100644 (file)
@@ -39,6 +39,13 @@ func NewRouter(defClient rb.DefinitionManager,
        // (TODO): Fix update method
        // instRouter.HandleFunc("/{vnfInstanceId}", UpdateHandler).Methods("PUT")
 
+       brokerHandler := brokerInstanceHandler{client: instClient}
+       instRouter.HandleFunc("/{cloud-owner}/{cloud-region}/infra_workload", brokerHandler.createHandler).Methods("POST")
+       instRouter.HandleFunc("/{cloud-owner}/{cloud-region}/infra_workload/{instID}",
+               brokerHandler.getHandler).Methods("GET")
+       instRouter.HandleFunc("/{cloud-owner}/{cloud-region}/infra_workload/{instID}",
+               brokerHandler.deleteHandler).Methods("DELETE")
+
        //Setup resource bundle definition routes
        if defClient == nil {
                defClient = rb.NewDefinitionClient()
diff --git a/src/k8splugin/api/brokerhandler.go b/src/k8splugin/api/brokerhandler.go
new file mode 100644 (file)
index 0000000..28e4423
--- /dev/null
@@ -0,0 +1,164 @@
+/*
+Copyright 2018 Intel Corporation.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+    http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package api
+
+import (
+       "encoding/json"
+       "io"
+       "net/http"
+
+       "k8splugin/internal/app"
+
+       "github.com/gorilla/mux"
+)
+
+// Used to store the backend implementation objects
+// Also simplifies the mocking needed for unit testing
+type brokerInstanceHandler struct {
+       // Interface that implements the Instance operations
+       client app.InstanceManager
+}
+
+type brokerRequest struct {
+       GenericVnfID                 string                 `json:"generic-vnf-id"`
+       VFModuleID                   string                 `json:"vf-module-id"`
+       VFModuleModelInvariantID     string                 `json:"vf-module-model-invariant-id"`
+       VFModuleModelVersionID       string                 `json:"vf-module-model-version-id"`
+       VFModuleModelCustomizationID string                 `json:"vf-module-model-customization-id"`
+       OOFDirectives                map[string]interface{} `json:"oof_directives"`
+       SDNCDirections               map[string]interface{} `json:"sdnc_directives"`
+       UserDirectives               map[string]interface{} `json:"user_directives"`
+       TemplateType                 string                 `json:"template_type"`
+       TemplateData                 map[string]interface{} `json:"template_data"`
+}
+
+type brokerPOSTResponse struct {
+       TemplateType     string              `json:"template_type"`
+       WorkloadID       string              `json:"workload_id"`
+       TemplateResponse map[string][]string `json:"template_response"`
+}
+
+type brokerGETResponse struct {
+       TemplateType   string `json:"template_type"`
+       WorkloadID     string `json:"workload_id"`
+       WorkloadStatus string `json:"workload_status"`
+}
+
+func (b brokerInstanceHandler) createHandler(w http.ResponseWriter, r *http.Request) {
+       vars := mux.Vars(r)
+       cloudRegion := vars["cloud-region"]
+
+       var req brokerRequest
+       err := json.NewDecoder(r.Body).Decode(&req)
+       switch {
+       case err == io.EOF:
+               http.Error(w, "Body empty", http.StatusBadRequest)
+               return
+       case err != nil:
+               http.Error(w, err.Error(), http.StatusUnprocessableEntity)
+               return
+       }
+
+       // Check body for expected parameters
+       if req.VFModuleModelCustomizationID == "" {
+               http.Error(w, "vf-module-model-customization-id is empty", http.StatusBadRequest)
+               return
+       }
+
+       rbName, ok := req.UserDirectives["definition-name"]
+       if !ok {
+               http.Error(w, "definition-name is missing from user-directives", http.StatusBadRequest)
+               return
+       }
+
+       rbVersion, ok := req.UserDirectives["definition-version"]
+       if !ok {
+               http.Error(w, "definition-version is missing from user-directives", http.StatusBadRequest)
+               return
+       }
+
+       profileName, ok := req.UserDirectives["profile-name"]
+       if !ok {
+               http.Error(w, "profile-name is missing from user-directives", http.StatusBadRequest)
+               return
+       }
+
+       // Setup the resource parameters for making the request
+       var instReq app.InstanceRequest
+       instReq.RBName = rbName.(string)
+       instReq.RBVersion = rbVersion.(string)
+       instReq.ProfileName = profileName.(string)
+       instReq.CloudRegion = cloudRegion
+
+       resp, err := b.client.Create(instReq)
+       if err != nil {
+               http.Error(w, err.Error(), http.StatusInternalServerError)
+               return
+       }
+
+       brokerResp := brokerPOSTResponse{
+               TemplateType:     "heat",
+               WorkloadID:       resp.ID,
+               TemplateResponse: resp.Resources,
+       }
+
+       w.Header().Set("Content-Type", "application/json")
+       w.WriteHeader(http.StatusCreated)
+       err = json.NewEncoder(w).Encode(brokerResp)
+       if err != nil {
+               http.Error(w, err.Error(), http.StatusInternalServerError)
+               return
+       }
+}
+
+// getHandler retrieves information about an instance via the ID
+func (b brokerInstanceHandler) getHandler(w http.ResponseWriter, r *http.Request) {
+       vars := mux.Vars(r)
+       instanceID := vars["instID"]
+
+       resp, err := b.client.Get(instanceID)
+       if err != nil {
+               http.Error(w, err.Error(), http.StatusInternalServerError)
+               return
+       }
+
+       brokerResp := brokerGETResponse{
+               TemplateType:   "heat",
+               WorkloadID:     resp.ID,
+               WorkloadStatus: "CREATED",
+       }
+
+       w.Header().Set("Content-Type", "application/json")
+       w.WriteHeader(http.StatusOK)
+       err = json.NewEncoder(w).Encode(brokerResp)
+       if err != nil {
+               http.Error(w, err.Error(), http.StatusInternalServerError)
+               return
+       }
+}
+
+// deleteHandler method terminates an instance via the ID
+func (b brokerInstanceHandler) deleteHandler(w http.ResponseWriter, r *http.Request) {
+       vars := mux.Vars(r)
+       instanceID := vars["instID"]
+
+       err := b.client.Delete(instanceID)
+       if err != nil {
+               http.Error(w, err.Error(), http.StatusInternalServerError)
+               return
+       }
+
+       w.Header().Set("Content-Type", "application/json")
+       w.WriteHeader(http.StatusAccepted)
+}
diff --git a/src/k8splugin/api/brokerhandler_test.go b/src/k8splugin/api/brokerhandler_test.go
new file mode 100644 (file)
index 0000000..f35a835
--- /dev/null
@@ -0,0 +1,224 @@
+/*
+Copyright 2018 Intel Corporation.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+    http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package api
+
+import (
+       "bytes"
+       "encoding/json"
+       "io"
+       "io/ioutil"
+       "net/http"
+       "net/http/httptest"
+       "reflect"
+       "testing"
+
+       "k8splugin/internal/app"
+
+       pkgerrors "github.com/pkg/errors"
+)
+
+func TestBrokerCreateHandler(t *testing.T) {
+       testCases := []struct {
+               label        string
+               input        io.Reader
+               expected     brokerPOSTResponse
+               expectedCode int
+               instClient   *mockInstanceClient
+       }{
+               {
+                       label:        "Missing body failure",
+                       expectedCode: http.StatusBadRequest,
+               },
+               {
+                       label:        "Invalid JSON request format",
+                       input:        bytes.NewBuffer([]byte("invalid")),
+                       expectedCode: http.StatusUnprocessableEntity,
+               },
+               {
+                       label: "Missing parameter failure",
+                       input: bytes.NewBuffer([]byte(`{
+                               "vf-module-model-customization-id": "84sdfkio938",
+                               "user_directives": {
+                                       "definition-name": "test-rbdef",
+                                       "definition-version": "v1"                              }
+                       }`)),
+                       expectedCode: http.StatusBadRequest,
+               },
+               {
+                       label: "Succesfully create an Instance",
+                       input: bytes.NewBuffer([]byte(`{
+                               "vf-module-model-customization-id": "84sdfkio938",
+                               "user_directives": {
+                                       "definition-name": "test-rbdef",
+                                       "definition-version": "v1",
+                                       "profile-name": "profile1"
+                               }
+                       }`)),
+                       expected: brokerPOSTResponse{
+                               WorkloadID:   "HaKpys8e",
+                               TemplateType: "heat",
+                               TemplateResponse: map[string][]string{
+                                       "deployment": []string{"test-deployment"},
+                                       "service":    []string{"test-service"},
+                               },
+                       },
+                       expectedCode: http.StatusCreated,
+                       instClient: &mockInstanceClient{
+                               items: []app.InstanceResponse{
+                                       {
+                                               ID:          "HaKpys8e",
+                                               RBName:      "test-rbdef",
+                                               RBVersion:   "v1",
+                                               ProfileName: "profile1",
+                                               CloudRegion: "region1",
+                                               Namespace:   "testnamespace",
+                                               Resources: map[string][]string{
+                                                       "deployment": []string{"test-deployment"},
+                                                       "service":    []string{"test-service"},
+                                               },
+                                       },
+                               },
+                       },
+               },
+       }
+
+       for _, testCase := range testCases {
+               t.Run(testCase.label, func(t *testing.T) {
+
+                       request := httptest.NewRequest("POST", "/v1/cloudowner/cloudregion/infra_workload", testCase.input)
+                       resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient))
+
+                       if testCase.expectedCode != resp.StatusCode {
+                               body, _ := ioutil.ReadAll(resp.Body)
+                               t.Log(string(body))
+                               t.Fatalf("Request method returned: \n%v\n and it was expected: \n%v", resp.StatusCode, testCase.expectedCode)
+                       }
+
+                       if resp.StatusCode == http.StatusCreated {
+                               var response brokerPOSTResponse
+                               err := json.NewDecoder(resp.Body).Decode(&response)
+                               if err != nil {
+                                       t.Fatalf("Parsing the returned response got an error (%s)", err)
+                               }
+                               if !reflect.DeepEqual(testCase.expected, response) {
+                                       t.Fatalf("TestGetHandler returned:\n result=%v\n expected=%v",
+                                               response, testCase.expected)
+                               }
+                       }
+               })
+       }
+}
+
+func TestBrokerGetHandler(t *testing.T) {
+       testCases := []struct {
+               label            string
+               input            string
+               expectedCode     int
+               expectedResponse brokerGETResponse
+               instClient       *mockInstanceClient
+       }{
+               {
+                       label:        "Fail to retrieve Instance",
+                       input:        "HaKpys8e",
+                       expectedCode: http.StatusInternalServerError,
+                       instClient: &mockInstanceClient{
+                               err: pkgerrors.New("Internal error"),
+                       },
+               },
+               {
+                       label:        "Succesful get an Instance",
+                       input:        "HaKpys8e",
+                       expectedCode: http.StatusOK,
+                       expectedResponse: brokerGETResponse{
+                               TemplateType:   "heat",
+                               WorkloadID:     "HaKpys8e",
+                               WorkloadStatus: "CREATED",
+                       },
+                       instClient: &mockInstanceClient{
+                               items: []app.InstanceResponse{
+                                       {
+                                               ID:          "HaKpys8e",
+                                               RBName:      "test-rbdef",
+                                               RBVersion:   "v1",
+                                               ProfileName: "profile1",
+                                               CloudRegion: "region1",
+                                               Namespace:   "testnamespace",
+                                               Resources: map[string][]string{
+                                                       "deployment": []string{"test-deployment"},
+                                                       "service":    []string{"test-service"},
+                                               },
+                                       },
+                               },
+                       },
+               },
+       }
+
+       for _, testCase := range testCases {
+               t.Run(testCase.label, func(t *testing.T) {
+                       request := httptest.NewRequest("GET", "/v1/cloudowner/cloudregion/infra_workload/"+testCase.input, nil)
+                       resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient))
+
+                       if testCase.expectedCode != resp.StatusCode {
+                               t.Fatalf("Request method returned: %v and it was expected: %v",
+                                       resp.StatusCode, testCase.expectedCode)
+                       }
+                       if resp.StatusCode == http.StatusOK {
+                               var response brokerGETResponse
+                               err := json.NewDecoder(resp.Body).Decode(&response)
+                               if err != nil {
+                                       t.Fatalf("Parsing the returned response got an error (%s)", err)
+                               }
+                               if !reflect.DeepEqual(testCase.expectedResponse, response) {
+                                       t.Fatalf("TestGetHandler returned:\n result=%v\n expected=%v",
+                                               response, testCase.expectedResponse)
+                               }
+                       }
+               })
+       }
+}
+
+func TestBrokerDeleteHandler(t *testing.T) {
+       testCases := []struct {
+               label        string
+               input        string
+               expectedCode int
+               instClient   *mockInstanceClient
+       }{
+               {
+                       label:        "Fail to destroy VNF",
+                       input:        "HaKpys8e",
+                       expectedCode: http.StatusInternalServerError,
+                       instClient: &mockInstanceClient{
+                               err: pkgerrors.New("Internal error"),
+                       },
+               },
+               {
+                       label:        "Succesful delete a VNF",
+                       input:        "HaKpys8e",
+                       expectedCode: http.StatusAccepted,
+                       instClient:   &mockInstanceClient{},
+               },
+       }
+
+       for _, testCase := range testCases {
+               t.Run(testCase.label, func(t *testing.T) {
+                       request := httptest.NewRequest("DELETE", "/v1/cloudowner/cloudregion/infra_workload/"+testCase.input, nil)
+                       resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient))
+
+                       if testCase.expectedCode != resp.StatusCode {
+                               t.Fatalf("Request method returned: %v and it was expected: %v", resp.StatusCode, testCase.expectedCode)
+                       }
+               })
+       }
+}