import (
"encoding/json"
"io"
- "log"
"net/http"
"github.com/onap/multicloud-k8s/src/k8splugin/internal/app"
"github.com/onap/multicloud-k8s/src/k8splugin/internal/helm"
+ log "github.com/onap/multicloud-k8s/src/k8splugin/internal/logutils"
"github.com/gorilla/mux"
)
}
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"`
- SDNCDirectives 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"`
+ 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 directive `json:"oof_directives"`
+ SDNCDirectives directive `json:"sdnc_directives"`
+ UserDirectives directive `json:"user_directives"`
+ TemplateType string `json:"template_type"`
+ TemplateData templateData `json:"template_data"`
+}
+
+type directive struct {
+ Attributes []attribute `json:"attributes"`
+}
+
+type attribute struct {
+ Key string `json:"attribute_name"`
+ Value string `json:"attribute_value"`
+}
+
+type templateData struct {
+ StackName string `json:"stack_name"` //Only this property is relevant (exported)
+ disableRollback string `json:"disable_rollback"`
+ environment string `json:"environment"`
+ parameters string `json:"parameters"`
+ template string `json:"template"`
+ timeoutMins string `json:"timeout_mins"`
}
type brokerPOSTResponse struct {
WorkloadStatusReason map[string]interface{} `json:"workload_status_reason"`
}
-// getUserDirectiveValue parses the following kind of json
-// "user_attributes": {
-// "attributes": [
-// {
-// "attribute_value": "foo",
-// "attribute_name": "bar"
-// },
-// {
-// "attribute_value": "value2",
-// "attribute_name": "name2"
-// }
-// ]
-// }
-func (b brokerRequest) getAttributeValue(directives map[string]interface{}, inp string) string {
- attributes, ok := directives["attributes"].([]interface{})
- if !ok {
- log.Println("Unable to cast attributes to []interface{}")
- return ""
- }
-
- for _, value := range attributes {
-
- attribute, ok := value.(map[string]interface{})
- if !ok {
- log.Println("Unable to cast attribute to map[string]interface{}")
- return ""
- }
-
- attributeName, ok := attribute["attribute_name"].(string)
- if !ok {
- log.Println("Unable to cast attribute_name to string")
- return ""
- }
- if attributeName == inp {
- attributevalue, ok := attribute["attribute_value"].(string)
- if !ok {
- log.Println("Unable to cast attribute_value to string")
- return ""
- }
-
- return attributevalue
+// Convert directives stored in broker request to map[string]string format with
+// merge including precedence provided
+func (b brokerRequest) convertDirectives() map[string]string {
+ extractedAttributes := make(map[string]string)
+ for _, section := range [3]directive{b.SDNCDirectives, b.OOFDirectives, b.UserDirectives} {
+ for _, attribute := range section.Attributes {
+ extractedAttributes[attribute.Key] = attribute.Value
}
}
- return ""
+ return extractedAttributes
}
func (b brokerInstanceHandler) createHandler(w http.ResponseWriter, r *http.Request) {
var req brokerRequest
err := json.NewDecoder(r.Body).Decode(&req)
+ log.Info("Broker API Payload", log.Fields{
+ "Body": r.Body,
+ "Payload": req,
+ })
switch {
case err == io.EOF:
http.Error(w, "Body empty", http.StatusBadRequest)
return
}
- profileName := req.getAttributeValue(req.SDNCDirectives, "k8s-rb-profile-name")
- if profileName == "" {
- http.Error(w, "k8s-rb-profile-name is missing from sdnc-directives", http.StatusBadRequest)
+ if req.GenericVnfID == "" {
+ http.Error(w, "generic-vnf-id is empty", http.StatusBadRequest)
+ return
+ }
+ if req.VFModuleID == "" {
+ http.Error(w, "vf-module-id is empty", http.StatusBadRequest)
+ return
+ }
+
+ if req.TemplateData.StackName == "" {
+ http.Error(w, "stack_name is missing from template_data", http.StatusBadRequest)
return
}
- vfModuleName := req.getAttributeValue(req.SDNCDirectives, "vf_module_name")
- if vfModuleName == "" {
- http.Error(w, "vf_module_name is missing from sdnc-directives", http.StatusBadRequest)
+ directives := req.convertDirectives()
+ profileName, ok := directives["k8s-rb-profile-name"]
+ if !ok {
+ http.Error(w, "k8s-rb-profile-name is missing from directives", http.StatusBadRequest)
return
}
instReq.ProfileName = profileName
instReq.CloudRegion = cloudRegion
instReq.Labels = map[string]string{
- "vf_module_name": vfModuleName,
+ "stack-name": req.TemplateData.StackName,
}
+ instReq.OverrideValues = directives
+ log.Info("Instance API Payload", log.Fields{
+ "payload": instReq,
+ })
resp, err := b.client.Create(instReq)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
TemplateResponse: resp.Resources,
WorkloadStatus: "CREATE_COMPLETE",
}
+ log.Info("Broker API Response", log.Fields{
+ "response": brokerResp,
+ })
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
WorkloadStatus: "CREATE_COMPLETE",
}
+ log.Info("Broker API Response", log.Fields{
+ "response": brokerResp,
+ })
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
err = json.NewEncoder(w).Encode(brokerResp)
}
}
-// getHandler retrieves information about an instance via the ID
+// findHandler retrieves information about an instance via the ID
func (b brokerInstanceHandler) findHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
- //name is an alias for vf_module_name from the so adapter
+ //name is an alias for stack-name from the so adapter
name := vars["name"]
- responses, _ := b.client.Find("", "", "", map[string]string{"vf_module_name": name})
+ responses, _ := b.client.Find("", "", "", map[string]string{"stack-name": name})
brokerResp := brokerGETResponse{
TemplateType: "heat",
}
}
+ log.Info("Broker API Response", log.Fields{
+ "response": brokerResp,
+ })
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
err := json.NewEncoder(w).Encode(brokerResp)
WorkloadID: instanceID,
WorkloadStatus: "DELETE_COMPLETE",
}
+ log.Info("Broker API Response", log.Fields{
+ "response": brokerResp,
+ })
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusAccepted)
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
+ 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.
"net/http"
"net/http/httptest"
"reflect"
+ "strings"
"testing"
"github.com/onap/multicloud-k8s/src/k8splugin/internal/app"
"k8s.io/apimachinery/pkg/runtime/schema"
)
+func TestConvertDirectives(t *testing.T) {
+ testCases := []struct {
+ label string
+ input brokerRequest
+ expected map[string]string
+ }{
+ {
+ label: "Single variable",
+ expected: map[string]string{"test": "true"},
+ input: brokerRequest{SDNCDirectives: directive{[]attribute{{
+ Key: "test",
+ Value: "true",
+ }}}},
+ },
+ {
+ label: "Empty parameter",
+ expected: map[string]string{"test": ""},
+ input: brokerRequest{OOFDirectives: directive{[]attribute{{
+ Key: "test",
+ Value: "",
+ }}}},
+ },
+ {
+ label: "Null entry",
+ input: brokerRequest{},
+ expected: make(map[string]string),
+ },
+ {
+ label: "Complex helm overrides",
+ /*
+ String with int will be later treated as int in helm.TemplateClient
+ (see helm/pkg/strvals/parser.go)
+ If unsure, convert type in helm chart like `{{ toString $value }}` or `{{ int $value }}`
+ (see http://masterminds.github.io/sprig/conversion.html)
+ */
+ expected: map[string]string{"name": "{a, b, c}", "servers[0].port": "80"},
+ input: brokerRequest{UserDirectives: directive{[]attribute{
+ {
+ Key: "name",
+ Value: "{a, b, c}",
+ },
+ {
+ Key: "servers[0].port",
+ Value: "80",
+ },
+ }}},
+ },
+ {
+ label: "Override variables",
+ expected: map[string]string{"empty": "", "sdnc": "sdnc", "user": "user", "oof": "oof"},
+ input: brokerRequest{
+ SDNCDirectives: directive{[]attribute{
+ {
+ Key: "empty",
+ Value: "sdnc",
+ },
+ {
+ Key: "sdnc",
+ Value: "sdnc",
+ },
+ {
+ Key: "oof",
+ Value: "sdnc",
+ },
+ }},
+ OOFDirectives: directive{[]attribute{
+ {
+ Key: "oof",
+ Value: "oof",
+ },
+ {
+ Key: "user",
+ Value: "oof",
+ },
+ }},
+ UserDirectives: directive{[]attribute{
+ {
+ Key: "user",
+ Value: "user",
+ },
+ {
+ Key: "empty",
+ Value: "",
+ },
+ }},
+ },
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ result := testCase.input.convertDirectives()
+ if !reflect.DeepEqual(result, testCase.expected) {
+ t.Fatalf("Unexpected result. Wanted '%v', retrieved '%v'",
+ testCase.expected, result)
+ }
+ })
+ }
+}
+
func TestBrokerCreateHandler(t *testing.T) {
testCases := []struct {
- label string
- input io.Reader
- expected brokerPOSTResponse
- expectedCode int
- instClient *mockInstanceClient
+ label string
+ input io.Reader
+ expected brokerPOSTResponse
+ expectedError string
+ expectedCode int
+ instClient *mockInstanceClient
}{
{
label: "Missing body failure",
expectedCode: http.StatusUnprocessableEntity,
},
{
- label: "Missing vf-module-*-id parameter",
+ label: "Missing vf-module-*-id parameter",
+ expectedError: "vf-module-model-customization-id is empty",
+ expectedCode: http.StatusBadRequest,
input: bytes.NewBuffer([]byte(`{
- "vf-module-model-customization-id": "84sdfkio938",
"vf-module-model-invariant-id": "123456qwerty",
+ "vf-module-model-version-id": "123qweasdzxc",
+ "generic-vnf-id": "dummy-vnf-id",
+ "vf-module-id": "dummy-vfm-id",
+ "template_data": {
+ "stack_name": "dummy-stack-name"
+ },
"sdnc_directives": {
"attributes": [
{
- "attribute_name": "vf_module_name",
- "attribute_value": "test-vf-module-name"
- },
+ "attribute_name": "k8s-rb-profile-name",
+ "attribute_value": "dummy-profile"
+ }
+ ]
+ }
+ }`)),
+ },
+ {
+ label: "Missing stack name parameter",
+ expectedError: "stack_name is missing from template_data",
+ expectedCode: http.StatusBadRequest,
+ input: bytes.NewBuffer([]byte(`{
+ "vf-module-model-customization-id": "84sdfkio938",
+ "vf-module-model-invariant-id": "123456qwerty",
+ "vf-module-model-version-id": "123qweasdzxc",
+ "generic-vnf-id": "dummy-vnf-id",
+ "vf-module-id": "dummy-vfm-id",
+ "template_data": {
+ },
+ "sdnc_directives": {
+ "attributes": [
{
"attribute_name": "k8s-rb-profile-name",
- "attribute_value": "profile1"
+ "attribute_value": "dummy-profile"
}
]
}
}`)),
- expectedCode: http.StatusBadRequest,
},
{
- label: "Missing parameter from sdnc_directives",
+ label: "Missing profile name directive",
+ expectedError: "k8s-rb-profile-name is missing from directives",
+ expectedCode: http.StatusBadRequest,
input: bytes.NewBuffer([]byte(`{
"vf-module-model-customization-id": "84sdfkio938",
"vf-module-model-invariant-id": "123456qwerty",
"vf-module-model-version-id": "123qweasdzxc",
+ "generic-vnf-id": "dummy-vnf-id",
+ "vf-module-id": "dummy-vfm-id",
+ "template_data": {
+ "stack_name": "dummy-stack-name"
+ },
+ "sdnc_directives": {
+ "attributes": [
+ ]
+ }
+ }`)),
+ },
+ {
+ label: "Missing vf-module-id parameter",
+ expectedError: "vf-module-id is empty",
+ expectedCode: http.StatusBadRequest,
+ input: bytes.NewBuffer([]byte(`{
+ "vf-module-model-customization-id": "84sdfkio938",
+ "vf-module-model-invariant-id": "123456qwerty",
+ "vf-module-model-version-id": "123qweasdzxc",
+ "generic-vnf-id": "dummy-vnf-id",
+ "template_data": {
+ "stack_name": "dummy-stack-name"
+ },
"sdnc_directives": {
"attributes": [
{
- "attribute_name": "vf_module_name",
- "attribute_value": "test-vf-module-name"
+ "attribute_name": "k8s-rb-profile-name",
+ "attribute_value": "dummy-profile"
}
]
}
}`)),
- expectedCode: http.StatusBadRequest,
},
{
label: "Succesfully create an Instance",
"vf-module-model-customization-id": "84sdfkio938",
"vf-module-model-invariant-id": "123456qwerty",
"vf-module-model-version-id": "123qweasdzxc",
+ "generic-vnf-id": "dummy-vnf-id",
+ "vf-module-id": "dummy-vfm-id",
+ "template_data": {
+ "stack_name": "dummy-stack-name"
+ },
"sdnc_directives": {
"attributes": [
- {
- "attribute_name": "vf_module_name",
- "attribute_value": "test-vf-module-name"
- },
{
"attribute_name": "k8s-rb-profile-name",
- "attribute_value": "profile1"
+ "attribute_value": "dummy-profile"
}
]
}
request := httptest.NewRequest("POST", "/cloudowner/cloudregion/infra_workload", testCase.input)
resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil, nil))
+ defer resp.Body.Close()
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)
+ t.Fatalf("Request method returned code '%v', but '%v' was expected",
+ resp.StatusCode, testCase.expectedCode)
}
if resp.StatusCode == http.StatusCreated {
t.Fatalf("TestGetHandler returned:\n result=%v\n expected=%v",
response, testCase.expected)
}
+ } else if testCase.expectedError != "" {
+ body, err := ioutil.ReadAll(resp.Body)
+ if err == nil {
+ if !strings.Contains(string(body), testCase.expectedError) {
+ t.Fatalf("Request method returned body '%s', but '%s' wasn't found",
+ body, testCase.expectedError)
+ }
+ } else {
+ t.Fatalf("Request method returned malformed body")
+ }
}
})
}