summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorKiran Kamineni <kiran.k.kamineni@intel.com>2019-04-10 21:54:41 -0700
committerKiran Kamineni <kiran.k.kamineni@intel.com>2019-04-11 00:21:48 -0700
commit875a0d455997e70bd1f7e7f9119516b77d07e8b9 (patch)
tree15268ab9feace73ce3843a236afaf49232401145 /src
parent932322113184862b906f4b82fe83cfcf3403d021 (diff)
Add support for so integration
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>
Diffstat (limited to 'src')
-rw-r--r--src/k8splugin/api/api.go7
-rw-r--r--src/k8splugin/api/brokerhandler.go164
-rw-r--r--src/k8splugin/api/brokerhandler_test.go224
3 files changed, 395 insertions, 0 deletions
diff --git a/src/k8splugin/api/api.go b/src/k8splugin/api/api.go
index 54147d2e..4bf8d6a6 100644
--- a/src/k8splugin/api/api.go
+++ b/src/k8splugin/api/api.go
@@ -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
index 00000000..28e44231
--- /dev/null
+++ b/src/k8splugin/api/brokerhandler.go
@@ -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
index 00000000..f35a835b
--- /dev/null
+++ b/src/k8splugin/api/brokerhandler_test.go
@@ -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)
+ }
+ })
+ }
+}