Composite Profile API and Handler 01/102101/8
authorDileep Ranganathan <dileep.ranganathan@intel.com>
Thu, 20 Feb 2020 00:19:18 +0000 (16:19 -0800)
committerEric Multanen <eric.w.multanen@intel.com>
Thu, 5 Mar 2020 17:54:19 +0000 (09:54 -0800)
Implemented Composite Profile API and handler,
adding create, get and delete handlers for the
composite profile.

Issue-ID: MULTICLOUD-997
Signed-off-by: Eric Multanen <eric.w.multanen@intel.com>
Signed-off-by: Dileep Ranganathan <dileep.ranganathan@intel.com>
Change-Id: Iab105cf88ef4635038cd36e90f3251bc900b2faf

src/orchestrator/api/api.go
src/orchestrator/api/clusterhandler_test.go
src/orchestrator/api/composite_profilehandler.go [new file with mode: 0644]
src/orchestrator/api/composite_profilehandler_test.go [new file with mode: 0644]
src/orchestrator/api/controllerhandler_test.go
src/orchestrator/api/projecthandler_test.go
src/orchestrator/cmd/main.go
src/orchestrator/pkg/module/composite_profile.go [new file with mode: 0644]
src/orchestrator/pkg/module/composite_profile_test.go [new file with mode: 0644]
src/orchestrator/pkg/module/module.go

index f3e3b17..7c540dd 100644 (file)
@@ -30,7 +30,8 @@ func NewRouter(projectClient moduleLib.ProjectManager,
        genericPlacementIntentClient moduleLib.GenericPlacementIntentManager,
        appIntentClient moduleLib.AppIntentManager,
        deploymentIntentGrpClient moduleLib.DeploymentIntentGroupManager,
-       intentClient moduleLib.IntentManager) *mux.Router {
+       intentClient moduleLib.IntentManager,
+       compositeProfileClient moduleLib.CompositeProfileManager) *mux.Router {
 
        router := mux.NewRouter().PathPrefix("/v2").Subrouter()
 
@@ -71,6 +72,18 @@ func NewRouter(projectClient moduleLib.ProjectManager,
        router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{version}", compAppHandler.getHandler).Methods("GET")
        router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{version}", compAppHandler.deleteHandler).Methods("DELETE")
 
+       if compositeProfileClient == nil {
+               compositeProfileClient = moduleClient.CompositeProfile
+       }
+       compProfilepHandler := compositeProfileHandler{
+               client: compositeProfileClient,
+       }
+
+       router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/composite-profiles", compProfilepHandler.createHandler).Methods("POST")
+       router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/composite-profiles", compProfilepHandler.getHandler).Methods("GET")
+       router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/composite-profiles/{composite-profile-name}", compProfilepHandler.getHandler).Methods("GET")
+       router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/composite-profiles/{composite-profile-name}", compProfilepHandler.deleteHandler).Methods("DELETE")
+
        router.HandleFunc("/controllers", controlHandler.createHandler).Methods("POST")
        router.HandleFunc("/controllers", controlHandler.createHandler).Methods("PUT")
        router.HandleFunc("/controllers/{controller-name}", controlHandler.getHandler).Methods("GET")
index a32bf02..58db636 100644 (file)
@@ -229,7 +229,7 @@ func TestClusterProviderCreateHandler(t *testing.T) {
        for _, testCase := range testCases {
                t.Run(testCase.label, func(t *testing.T) {
                        request := httptest.NewRequest("POST", "/v2/cluster-providers", testCase.reader)
-                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil))
+                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil))
 
                        //Check returned code
                        if resp.StatusCode != testCase.expectedCode {
@@ -307,7 +307,7 @@ func TestClusterProviderGetAllHandler(t *testing.T) {
        for _, testCase := range testCases {
                t.Run(testCase.label, func(t *testing.T) {
                        request := httptest.NewRequest("GET", "/v2/cluster-providers", nil)
-                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil))
+                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil))
 
                        //Check returned code
                        if resp.StatusCode != testCase.expectedCode {
@@ -377,7 +377,7 @@ func TestClusterProviderGetHandler(t *testing.T) {
        for _, testCase := range testCases {
                t.Run(testCase.label, func(t *testing.T) {
                        request := httptest.NewRequest("GET", "/v2/cluster-providers/"+testCase.name, nil)
-                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil))
+                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil))
 
                        //Check returned code
                        if resp.StatusCode != testCase.expectedCode {
@@ -426,7 +426,7 @@ func TestClusterProviderDeleteHandler(t *testing.T) {
        for _, testCase := range testCases {
                t.Run(testCase.label, func(t *testing.T) {
                        request := httptest.NewRequest("DELETE", "/v2/cluster-providers/"+testCase.name, nil)
-                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil))
+                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil))
 
                        //Check returned code
                        if resp.StatusCode != testCase.expectedCode {
@@ -538,7 +538,7 @@ of clusterTest
 
                        request := httptest.NewRequest("POST", "/v2/cluster-providers/clusterProvider1/clusters", bytes.NewBuffer(body.Bytes()))
                        request.Header.Set("Content-Type", multiwr.FormDataContentType())
-                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil))
+                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil))
 
                        //Check returned code
                        if resp.StatusCode != testCase.expectedCode {
@@ -625,7 +625,7 @@ func TestClusterGetAllHandler(t *testing.T) {
        for _, testCase := range testCases {
                t.Run(testCase.label, func(t *testing.T) {
                        request := httptest.NewRequest("GET", "/v2/cluster-providers/clusterProvder1/clusters", nil)
-                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil))
+                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil))
 
                        //Check returned code
                        if resp.StatusCode != testCase.expectedCode {
@@ -706,7 +706,7 @@ func TestClusterGetHandler(t *testing.T) {
                        if len(testCase.accept) > 0 {
                                request.Header.Set("Accept", testCase.accept)
                        }
-                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil))
+                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil))
 
                        //Check returned code
                        if resp.StatusCode != testCase.expectedCode {
@@ -784,7 +784,7 @@ of clusterTest
                        if len(testCase.accept) > 0 {
                                request.Header.Set("Accept", testCase.accept)
                        }
-                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil))
+                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil))
 
                        //Check returned code
                        if resp.StatusCode != testCase.expectedCode {
@@ -834,7 +834,7 @@ func TestClusterDeleteHandler(t *testing.T) {
        for _, testCase := range testCases {
                t.Run(testCase.label, func(t *testing.T) {
                        request := httptest.NewRequest("DELETE", "/v2/cluster-providers/clusterProvider1/clusters/"+testCase.name, nil)
-                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil))
+                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil))
 
                        //Check returned code
                        if resp.StatusCode != testCase.expectedCode {
@@ -880,7 +880,7 @@ func TestClusterLabelCreateHandler(t *testing.T) {
        for _, testCase := range testCases {
                t.Run(testCase.label, func(t *testing.T) {
                        request := httptest.NewRequest("POST", "/v2/cluster-providers/cp1/clusters/cl1/labels", testCase.reader)
-                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil))
+                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil))
 
                        //Check returned code
                        if resp.StatusCode != testCase.expectedCode {
@@ -944,7 +944,7 @@ func TestClusterLabelsGetHandler(t *testing.T) {
        for _, testCase := range testCases {
                t.Run(testCase.label, func(t *testing.T) {
                        request := httptest.NewRequest("GET", "/v2/cluster-providers/cp1/clusters/cl1/labels", nil)
-                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil))
+                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil))
 
                        //Check returned code
                        if resp.StatusCode != testCase.expectedCode {
@@ -1004,7 +1004,7 @@ func TestClusterLabelGetHandler(t *testing.T) {
        for _, testCase := range testCases {
                t.Run(testCase.label, func(t *testing.T) {
                        request := httptest.NewRequest("GET", "/v2/cluster-providers/clusterProvider1/clusters/cl1/labels/"+testCase.name, nil)
-                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil))
+                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil))
 
                        //Check returned code
                        if resp.StatusCode != testCase.expectedCode {
@@ -1053,7 +1053,7 @@ func TestClusterLabelDeleteHandler(t *testing.T) {
        for _, testCase := range testCases {
                t.Run(testCase.label, func(t *testing.T) {
                        request := httptest.NewRequest("DELETE", "/v2/cluster-providers/cp1/clusters/cl1/labels/"+testCase.name, nil)
-                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil))
+                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil))
 
                        //Check returned code
                        if resp.StatusCode != testCase.expectedCode {
@@ -1144,7 +1144,7 @@ func TestClusterKvPairsCreateHandler(t *testing.T) {
        for _, testCase := range testCases {
                t.Run(testCase.label, func(t *testing.T) {
                        request := httptest.NewRequest("POST", "/v2/cluster-providers/cp1/clusters/cl1/kv-pairs", testCase.reader)
-                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil))
+                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil))
 
                        //Check returned code
                        if resp.StatusCode != testCase.expectedCode {
@@ -1262,7 +1262,7 @@ func TestClusterKvPairsGetAllHandler(t *testing.T) {
        for _, testCase := range testCases {
                t.Run(testCase.label, func(t *testing.T) {
                        request := httptest.NewRequest("GET", "/v2/cluster-providers/cp1/clusters/cl1/kv-pairs", nil)
-                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil))
+                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil))
 
                        //Check returned code
                        if resp.StatusCode != testCase.expectedCode {
@@ -1352,7 +1352,7 @@ func TestClusterKvPairsGetHandler(t *testing.T) {
        for _, testCase := range testCases {
                t.Run(testCase.label, func(t *testing.T) {
                        request := httptest.NewRequest("GET", "/v2/cluster-providers/clusterProvider1/clusters/cl1/kv-pairs/"+testCase.name, nil)
-                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil))
+                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil))
 
                        //Check returned code
                        if resp.StatusCode != testCase.expectedCode {
@@ -1401,7 +1401,7 @@ func TestClusterKvPairsDeleteHandler(t *testing.T) {
        for _, testCase := range testCases {
                t.Run(testCase.label, func(t *testing.T) {
                        request := httptest.NewRequest("DELETE", "/v2/cluster-providers/cp1/clusters/cl1/kv-pairs/"+testCase.name, nil)
-                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil))
+                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil, nil, nil, nil))
 
                        //Check returned code
                        if resp.StatusCode != testCase.expectedCode {
diff --git a/src/orchestrator/api/composite_profilehandler.go b/src/orchestrator/api/composite_profilehandler.go
new file mode 100644 (file)
index 0000000..66c64dd
--- /dev/null
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2020 Intel Corporation, Inc
+ *
+ * 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"
+
+       moduleLib "github.com/onap/multicloud-k8s/src/orchestrator/pkg/module"
+
+       "github.com/gorilla/mux"
+)
+
+/* Used to store backend implementation objects
+Also simplifies mocking for unit testing purposes
+*/
+type compositeProfileHandler struct {
+       client moduleLib.CompositeProfileManager
+}
+
+// createCompositeProfileHandler handles the create operation of intent
+func (h compositeProfileHandler) createHandler(w http.ResponseWriter, r *http.Request) {
+
+       var cpf moduleLib.CompositeProfile
+
+       err := json.NewDecoder(r.Body).Decode(&cpf)
+       switch {
+       case err == io.EOF:
+               http.Error(w, "Empty body", http.StatusBadRequest)
+               return
+       case err != nil:
+               http.Error(w, err.Error(), http.StatusUnprocessableEntity)
+               return
+       }
+
+       if cpf.Metadata.Name == "" {
+               http.Error(w, "Missing compositeProfileName in POST request", http.StatusBadRequest)
+               return
+       }
+
+       vars := mux.Vars(r)
+       projectName := vars["project-name"]
+       compositeAppName := vars["composite-app-name"]
+       version := vars["composite-app-version"]
+
+       cProf, createErr := h.client.CreateCompositeProfile(cpf, projectName, compositeAppName, version)
+       if createErr != nil {
+               http.Error(w, createErr.Error(), http.StatusInternalServerError)
+               return
+       }
+
+       w.Header().Set("Content-Type", "application/json")
+       w.WriteHeader(http.StatusCreated)
+       err = json.NewEncoder(w).Encode(cProf)
+       if err != nil {
+               http.Error(w, err.Error(), http.StatusInternalServerError)
+               return
+       }
+}
+
+// getHandler handles the GET operations on CompositeProfile
+func (h compositeProfileHandler) getHandler(w http.ResponseWriter, r *http.Request) {
+       vars := mux.Vars(r)
+       cProfName := vars["composite-profile-name"]
+
+       projectName := vars["project-name"]
+       if projectName == "" {
+               http.Error(w, "Missing projectName in GET request", http.StatusBadRequest)
+               return
+       }
+       compositeAppName := vars["composite-app-name"]
+       if compositeAppName == "" {
+               http.Error(w, "Missing compositeAppName in GET request", http.StatusBadRequest)
+               return
+       }
+
+       version := vars["composite-app-version"]
+       if version == "" {
+               http.Error(w, "Missing version in GET request", http.StatusBadRequest)
+               return
+       }
+
+       // handle the get all composite profile case
+       if len(cProfName) == 0 {
+               var retList []moduleLib.CompositeProfile
+
+               ret, err := h.client.GetCompositeProfiles(projectName, compositeAppName, version)
+               if err != nil {
+                       http.Error(w, err.Error(), http.StatusInternalServerError)
+                       return
+               }
+
+               for _, cl := range ret {
+                       retList = append(retList, moduleLib.CompositeProfile{Metadata: cl.Metadata})
+               }
+
+               w.Header().Set("Content-Type", "application/json")
+               w.WriteHeader(http.StatusOK)
+               err = json.NewEncoder(w).Encode(retList)
+               if err != nil {
+                       http.Error(w, err.Error(), http.StatusInternalServerError)
+                       return
+               }
+               return
+       }
+
+       cProf, err := h.client.GetCompositeProfile(cProfName, projectName, compositeAppName, version)
+       if err != nil {
+               http.Error(w, err.Error(), http.StatusInternalServerError)
+               return
+       }
+
+       w.Header().Set("Content-Type", "application/json")
+       w.WriteHeader(http.StatusOK)
+       err = json.NewEncoder(w).Encode(cProf)
+       if err != nil {
+               http.Error(w, err.Error(), http.StatusInternalServerError)
+               return
+       }
+}
+
+// deleteHandler handles the delete operations on CompostiteProfile
+func (h compositeProfileHandler) deleteHandler(w http.ResponseWriter, r *http.Request) {
+       vars := mux.Vars(r)
+       c := vars["composite-profile-name"]
+       p := vars["project-name"]
+       ca := vars["composite-app-name"]
+       v := vars["composite-app-version"]
+
+       err := h.client.DeleteCompositeProfile(c, p, ca, v)
+       if err != nil {
+               http.Error(w, err.Error(), http.StatusInternalServerError)
+               return
+       }
+       w.WriteHeader(http.StatusNoContent)
+}
diff --git a/src/orchestrator/api/composite_profilehandler_test.go b/src/orchestrator/api/composite_profilehandler_test.go
new file mode 100644 (file)
index 0000000..e98ed96
--- /dev/null
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2020 Intel Corporation, Inc
+ *
+ * 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"
+       "net/http"
+       "net/http/httptest"
+       "reflect"
+       "testing"
+
+       moduleLib "github.com/onap/multicloud-k8s/src/orchestrator/pkg/module"
+)
+
+//Creating an embedded interface via anonymous variable
+//This allows us to make mockDB satisfy the DatabaseConnection
+//interface even if we are not implementing all the methods in it
+type mockCompositeProfileManager struct {
+       // Items and err will be used to customize each test
+       // via a localized instantiation of mockCompositeProfileManager
+       Items []moduleLib.CompositeProfile
+       Err   error
+}
+
+func (m *mockCompositeProfileManager) CreateCompositeProfile(inp moduleLib.CompositeProfile, p string, ca string,
+       v string) (moduleLib.CompositeProfile, error) {
+       if m.Err != nil {
+               return moduleLib.CompositeProfile{}, m.Err
+       }
+
+       return m.Items[0], nil
+}
+
+func (m *mockCompositeProfileManager) GetCompositeProfile(name string, projectName string,
+       compositeAppName string, version string) (moduleLib.CompositeProfile, error) {
+       if m.Err != nil {
+               return moduleLib.CompositeProfile{}, m.Err
+       }
+
+       return m.Items[0], nil
+}
+
+func (m *mockCompositeProfileManager) GetCompositeProfiles(projectName string,
+       compositeAppName string, version string) ([]moduleLib.CompositeProfile, error) {
+       if m.Err != nil {
+               return []moduleLib.CompositeProfile{}, m.Err
+       }
+
+       return m.Items, nil
+}
+
+func (m *mockCompositeProfileManager) DeleteCompositeProfile(name string, projectName string,
+       compositeAppName string, version string) error {
+       return m.Err
+}
+
+func Test_compositeProfileHandler_createHandler(t *testing.T) {
+       testCases := []struct {
+               label        string
+               reader       io.Reader
+               expected     moduleLib.CompositeProfile
+               expectedCode int
+               cProfClient  *mockCompositeProfileManager
+       }{
+               {
+                       label:        "Missing Body Failure",
+                       expectedCode: http.StatusBadRequest,
+                       cProfClient:  &mockCompositeProfileManager{},
+               },
+               {
+                       label:        "Create Composite Profile",
+                       expectedCode: http.StatusCreated,
+                       reader: bytes.NewBuffer([]byte(`{
+                               "metadata" : {
+                                       "name": "testCompositeProfile",
+                               "description": "Test CompositeProfile used for unit testing",
+                               "userData1": "data1",
+                               "userData2": "data2"
+                               }
+                       }`)),
+                       expected: moduleLib.CompositeProfile{
+                               Metadata: moduleLib.CompositeProfileMetadata{
+                                       Name:        "testCompositeProfile",
+                                       Description: "Test CompositeProfile used for unit testing",
+                                       UserData1:   "data1",
+                                       UserData2:   "data2",
+                               },
+                       },
+                       cProfClient: &mockCompositeProfileManager{
+                               //Items that will be returned by the mocked Client
+                               Items: []moduleLib.CompositeProfile{
+                                       moduleLib.CompositeProfile{
+                                               Metadata: moduleLib.CompositeProfileMetadata{
+                                                       Name:        "testCompositeProfile",
+                                                       Description: "Test CompositeProfile used for unit testing",
+                                                       UserData1:   "data1",
+                                                       UserData2:   "data2",
+                                               },
+                                       },
+                               },
+                       },
+               },
+               {
+                       label: "Missing Composite Profile Name in Request Body",
+                       reader: bytes.NewBuffer([]byte(`{
+                               "description":"test description"
+                               }`)),
+                       expectedCode: http.StatusBadRequest,
+                       cProfClient:  &mockCompositeProfileManager{},
+               },
+       }
+       for _, testCase := range testCases {
+               t.Run(testCase.label, func(t *testing.T) {
+                       request := httptest.NewRequest("POST", "/v2/projects/{project-name}/composite-apps/{composite-app-name}/{version}/composite-profiles", testCase.reader)
+                       resp := executeRequest(request, NewRouter(nil, nil, nil, nil, nil, nil, nil, nil, testCase.cProfClient))
+
+                       //Check returned code
+                       if resp.StatusCode != testCase.expectedCode {
+                               t.Fatalf("Expected %d; Got: %d", testCase.expectedCode, resp.StatusCode)
+                       }
+
+                       //Check returned body only if statusCreated
+                       if resp.StatusCode == http.StatusCreated {
+                               got := moduleLib.CompositeProfile{}
+                               json.NewDecoder(resp.Body).Decode(&got)
+
+                               if reflect.DeepEqual(testCase.expected, got) == false {
+                                       t.Errorf("createHandler returned unexpected body: got %v;"+
+                                               " expected %v", got, testCase.expected)
+                               }
+                       }
+               })
+       }
+
+}
index dd542de..a195cb8 100644 (file)
@@ -110,7 +110,7 @@ func TestControllerCreateHandler(t *testing.T) {
        for _, testCase := range testCases {
                t.Run(testCase.label, func(t *testing.T) {
                        request := httptest.NewRequest("POST", "/v2/controllers", testCase.reader)
-                       resp := executeRequest(request, NewRouter(nil, nil, testCase.controllerClient, nil, nil, nil, nil, nil))
+                       resp := executeRequest(request, NewRouter(nil, nil, testCase.controllerClient, nil, nil, nil, nil, nil, nil))
 
                        //Check returned code
                        if resp.StatusCode != testCase.expectedCode {
@@ -173,7 +173,7 @@ func TestControllerGetHandler(t *testing.T) {
        for _, testCase := range testCases {
                t.Run(testCase.label, func(t *testing.T) {
                        request := httptest.NewRequest("GET", "/v2/controllers/"+testCase.name, nil)
-                       resp := executeRequest(request, NewRouter(nil, nil, testCase.controllerClient, nil, nil, nil, nil, nil))
+                       resp := executeRequest(request, NewRouter(nil, nil, testCase.controllerClient, nil, nil, nil, nil, nil, nil))
 
                        //Check returned code
                        if resp.StatusCode != testCase.expectedCode {
@@ -222,7 +222,7 @@ func TestControllerDeleteHandler(t *testing.T) {
        for _, testCase := range testCases {
                t.Run(testCase.label, func(t *testing.T) {
                        request := httptest.NewRequest("DELETE", "/v2/controllers/"+testCase.name, nil)
-                       resp := executeRequest(request, NewRouter(nil, nil, testCase.controllerClient, nil, nil, nil, nil, nil))
+                       resp := executeRequest(request, NewRouter(nil, nil, testCase.controllerClient, nil, nil, nil, nil, nil, nil))
 
                        //Check returned code
                        if resp.StatusCode != testCase.expectedCode {
index eccccb9..547420b 100644 (file)
@@ -119,7 +119,7 @@ func TestProjectCreateHandler(t *testing.T) {
        for _, testCase := range testCases {
                t.Run(testCase.label, func(t *testing.T) {
                        request := httptest.NewRequest("POST", "/v2/projects", testCase.reader)
-                       resp := executeRequest(request, NewRouter(testCase.projectClient, nil, nil, nil, nil, nil, nil, nil))
+                       resp := executeRequest(request, NewRouter(testCase.projectClient, nil, nil, nil, nil, nil, nil, nil, nil))
 
                        //Check returned code
                        if resp.StatusCode != testCase.expectedCode {
@@ -188,7 +188,7 @@ func TestProjectGetHandler(t *testing.T) {
        for _, testCase := range testCases {
                t.Run(testCase.label, func(t *testing.T) {
                        request := httptest.NewRequest("GET", "/v2/projects/"+testCase.name, nil)
-                       resp := executeRequest(request, NewRouter(testCase.projectClient, nil, nil, nil, nil, nil, nil, nil))
+                       resp := executeRequest(request, NewRouter(testCase.projectClient, nil, nil, nil, nil, nil, nil, nil, nil))
 
                        //Check returned code
                        if resp.StatusCode != testCase.expectedCode {
@@ -237,7 +237,7 @@ func TestProjectDeleteHandler(t *testing.T) {
        for _, testCase := range testCases {
                t.Run(testCase.label, func(t *testing.T) {
                        request := httptest.NewRequest("DELETE", "/v2/projects/"+testCase.name, nil)
-                       resp := executeRequest(request, NewRouter(testCase.projectClient, nil, nil, nil, nil, nil, nil, nil))
+                       resp := executeRequest(request, NewRouter(testCase.projectClient, nil, nil, nil, nil, nil, nil, nil, nil))
 
                        //Check returned code
                        if resp.StatusCode != testCase.expectedCode {
index 9296a54..0385416 100644 (file)
@@ -47,7 +47,7 @@ func main() {
                log.Fatalln("Exiting...")
        }
 
-       httpRouter := api.NewRouter(nil, nil, nil, nil, nil, nil, nil, nil)
+       httpRouter := api.NewRouter(nil, nil, nil, nil, nil, nil, nil, nil, nil)
        loggedRouter := handlers.LoggingHandler(os.Stdout, httpRouter)
        log.Println("Starting Kubernetes Multicloud API")
 
diff --git a/src/orchestrator/pkg/module/composite_profile.go b/src/orchestrator/pkg/module/composite_profile.go
new file mode 100644 (file)
index 0000000..dca2116
--- /dev/null
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2020 Intel Corporation, Inc
+ *
+ * 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 module
+
+import (
+       "encoding/json"
+
+       "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/db"
+
+       pkgerrors "github.com/pkg/errors"
+)
+
+// CompositeProfile contains the parameters needed for CompositeProfiles
+// It implements the interface for managing the CompositeProfiles
+type CompositeProfile struct {
+       Metadata CompositeProfileMetadata `json:"metadata"`
+}
+
+// CompositeProfileMetadata contains the metadata for CompositeProfiles
+type CompositeProfileMetadata struct {
+       Name        string `json:"name"`
+       Description string `json:"description"`
+       UserData1   string `json:"userData1"`
+       UserData2   string `json:"userData2"`
+}
+
+// CompositeProfileKey is the key structure that is used in the database
+type CompositeProfileKey struct {
+       Name         string `json:"compositeprofile"`
+       Project      string `json:"project"`
+       CompositeApp string `json:"compositeapp"`
+       Version      string `json:"compositeappversion"`
+}
+
+// We will use json marshalling to convert to string to
+// preserve the underlying structure.
+func (cpk CompositeProfileKey) String() string {
+       out, err := json.Marshal(cpk)
+       if err != nil {
+               return ""
+       }
+
+       return string(out)
+}
+
+// CompositeProfileManager exposes the CompositeProfile functionality
+type CompositeProfileManager interface {
+       CreateCompositeProfile(cpf CompositeProfile, p string, ca string,
+               v string) (CompositeProfile, error)
+       GetCompositeProfile(compositeProfileName string, projectName string,
+               compositeAppName string, version string) (CompositeProfile, error)
+       GetCompositeProfiles(projectName string, compositeAppName string,
+               version string) ([]CompositeProfile, error)
+       DeleteCompositeProfile(compositeProfileName string, projectName string,
+               compositeAppName string, version string) error
+}
+
+// CompositeProfileClient implements the Manager
+// It will also be used to maintain some localized state
+type CompositeProfileClient struct {
+       storeName string
+       tagMeta   string
+}
+
+// NewCompositeProfileClient returns an instance of the CompositeProfileClient
+// which implements the Manager
+func NewCompositeProfileClient() *CompositeProfileClient {
+       return &CompositeProfileClient{
+               storeName: "orchestrator",
+               tagMeta:   "compositeprofilemetadata",
+       }
+}
+
+// CreateCompositeProfile creates an entry for CompositeProfile in the database. Other Input parameters for it - projectName, compositeAppName, version
+func (c *CompositeProfileClient) CreateCompositeProfile(cpf CompositeProfile, p string, ca string,
+       v string) (CompositeProfile, error) {
+
+       res, err := c.GetCompositeProfile(cpf.Metadata.Name, p, ca, v)
+       if res != (CompositeProfile{}) {
+               return CompositeProfile{}, pkgerrors.New("CompositeProfile already exists")
+       }
+
+       //Check if project exists
+       _, err = NewProjectClient().GetProject(p)
+       if err != nil {
+               return CompositeProfile{}, pkgerrors.New("Unable to find the project")
+       }
+
+       // check if compositeApp exists
+       _, err = NewCompositeAppClient().GetCompositeApp(ca, v, p)
+       if err != nil {
+               return CompositeProfile{}, pkgerrors.New("Unable to find the composite-app")
+       }
+
+       cProfkey := CompositeProfileKey{
+               Name:         cpf.Metadata.Name,
+               Project:      p,
+               CompositeApp: ca,
+               Version:      v,
+       }
+
+       err = db.DBconn.Insert(c.storeName, cProfkey, nil, c.tagMeta, cpf)
+       if err != nil {
+               return CompositeProfile{}, pkgerrors.Wrap(err, "Create DB entry error")
+       }
+
+       return cpf, nil
+}
+
+// GetCompositeProfile shall take arguments - name of the composite profile, name of //// the project, name of the composite app and version of the composite app. It shall return the CompositeProfile if its present.
+func (c *CompositeProfileClient) GetCompositeProfile(cpf string, p string, ca string, v string) (CompositeProfile, error) {
+       key := CompositeProfileKey{
+               Name:         cpf,
+               Project:      p,
+               CompositeApp: ca,
+               Version:      v,
+       }
+
+       result, err := db.DBconn.Find(c.storeName, key, c.tagMeta)
+       if err != nil {
+               return CompositeProfile{}, pkgerrors.Wrap(err, "Get Composite Profile error")
+       }
+
+       if result != nil {
+               cProf := CompositeProfile{}
+               err = db.DBconn.Unmarshal(result[0], &cProf)
+               if err != nil {
+                       return CompositeProfile{}, pkgerrors.Wrap(err, "Unmarshalling CompositeProfile")
+               }
+               return cProf, nil
+       }
+
+       return CompositeProfile{}, pkgerrors.New("Error getting CompositeProfile")
+}
+
+// GetCompositeProfile shall take arguments - name of the composite profile, name of //// the project, name of the composite app and version of the composite app. It shall return the CompositeProfile if its present.
+func (c *CompositeProfileClient) GetCompositeProfiles(p string, ca string, v string) ([]CompositeProfile, error) {
+       key := CompositeProfileKey{
+               Name:         "",
+               Project:      p,
+               CompositeApp: ca,
+               Version:      v,
+       }
+
+       values, err := db.DBconn.Find(c.storeName, key, c.tagMeta)
+       if err != nil {
+               return []CompositeProfile{}, pkgerrors.Wrap(err, "Get Composite Profiles error")
+       }
+
+       var resp []CompositeProfile
+
+       for _, value := range values {
+               cp := CompositeProfile{}
+               err = db.DBconn.Unmarshal(value, &cp)
+               if err != nil {
+                       return []CompositeProfile{}, pkgerrors.Wrap(err, "Get Composite Profiles unmarshalling error")
+               }
+               resp = append(resp, cp)
+       }
+
+       return resp, nil
+}
+
+// DeleteCompositeProfile the intent from the database
+func (c *CompositeProfileClient) DeleteCompositeProfile(cpf string, p string, ca string, v string) error {
+       key := CompositeProfileKey{
+               Name:         cpf,
+               Project:      p,
+               CompositeApp: ca,
+               Version:      v,
+       }
+
+       err := db.DBconn.Remove(c.storeName, key)
+       if err != nil {
+               return pkgerrors.Wrap(err, "Delete CompositeProfile entry;")
+       }
+       return nil
+}
diff --git a/src/orchestrator/pkg/module/composite_profile_test.go b/src/orchestrator/pkg/module/composite_profile_test.go
new file mode 100644 (file)
index 0000000..af0dd7b
--- /dev/null
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2020 Intel Corporation, Inc
+ *
+ * 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 module
+
+//pkgerrors "github.com/pkg/errors"
+
+/* TODO - db.MockDB needs to be enhanced and then these can be fixed up
+func TestCreateCompositeProfile(t *testing.T) {
+       testCases := []struct {
+               label               string
+               compositeProfile    CompositeProfile
+               projectName         string
+               compositeApp        string
+               compositeAppVersion string
+               expectedError       string
+               mockdb              *db.MockDB
+               expected            CompositeProfile
+       }{
+               {
+                       label: "Create CompositeProfile",
+                       compositeProfile: CompositeProfile{
+                               Metadata: CompositeProfileMetadata{
+                                       Name:        "testCompositeProfile",
+                                       Description: "A sample Composite Profile for testing",
+                                       UserData1:   "userData1",
+                                       UserData2:   "userData2",
+                               },
+                       },
+                       projectName:         "testProject",
+                       compositeApp:        "testCompositeApp",
+                       compositeAppVersion: "v1",
+                       expected: CompositeProfile{
+                               Metadata: CompositeProfileMetadata{
+                                       Name:        "testCompositeProfile",
+                                       Description: "A sample Composite Profile for testing",
+                                       UserData1:   "userData1",
+                                       UserData2:   "userData2",
+                               },
+                       },
+                       expectedError: "",
+                       mockdb: &db.MockDB{
+                               Items: map[string]map[string][]byte{
+                                       ProjectKey{ProjectName: "testProject"}.String(): {
+                                               "projectmetadata": []byte(
+                                                       "{" +
+                                                               "\"metadata\" : {" +
+                                                               "\"Name\":\"testProject\"," +
+                                                               "\"Description\":\"Test project for unit testing\"," +
+                                                               "\"UserData1\": \"userData1\"," +
+                                                               "\"UserData2\":\"userData2\"}" +
+                                                               "}"),
+                                       },
+                                       CompositeAppKey{CompositeAppName: "testCompositeApp", Project: "testProject", Version: "v1"}.String(): {
+                                               "compositeAppmetadata": []byte(
+                                                       "{" +
+                                                               "\"metadata\" : {" +
+                                                               "\"Name\":\"testCompositeApp\"," +
+                                                               "\"Description\":\"Test Composite App for unit testing\"," +
+                                                               "\"UserData1\": \"userData1\"," +
+                                                               "\"UserData2\":\"userData2\"}," +
+                                                               "\"spec\": {" +
+                                                               "\"Version\": \"v1\"}" +
+                                                               "}"),
+                                       },
+                               },
+                       },
+               },
+       }
+
+       for _, testCase := range testCases {
+               t.Run(testCase.label, func(t *testing.T) {
+                       db.DBconn = testCase.mockdb
+                       cprofCli := NewCompositeProfileClient()
+                       got, err := cprofCli.CreateCompositeProfile(testCase.compositeProfile, testCase.projectName, testCase.compositeApp, testCase.compositeAppVersion)
+                       if err != nil {
+                               if testCase.expectedError == "" {
+                                       t.Fatalf("CreateCompositeProfile returned an unexpected error %s", err)
+                               }
+                               if strings.Contains(err.Error(), testCase.expectedError) == false {
+                                       t.Fatalf("CreateCompositeProfile returned an unexpected error %s", err)
+                               }
+                       } else {
+                               if reflect.DeepEqual(testCase.expected, got) == false {
+                                       t.Errorf("CreateCompositeProfile returned unexpected body: got %v; "+" expected %v", got, testCase.expected)
+                               }
+                       }
+               })
+
+       }
+}
+
+func TestGetCompositeProfile(t *testing.T) {
+
+       testCases := []struct {
+               label                string
+               expectedError        string
+               expected             CompositeProfile
+               mockdb               *db.MockDB
+               compositeProfileName string
+               projectName          string
+               compositeAppName     string
+               compositeAppVersion  string
+       }{
+               {
+                       label:                "Get CompositeProfile",
+                       compositeProfileName: "testCompositeProfile",
+                       projectName:          "testProject",
+                       compositeAppName:     "testCompositeApp",
+                       compositeAppVersion:  "v1",
+                       expected: CompositeProfile{
+                               Metadata: CompositeProfileMetadata{
+                                       Name:        "testCompositeProfile",
+                                       Description: "A sample CompositeProfile for testing",
+                                       UserData1:   "userData1",
+                                       UserData2:   "userData2",
+                               },
+                       },
+                       expectedError: "",
+                       mockdb: &db.MockDB{
+                               Items: map[string]map[string][]byte{
+                                       CompositeProfileKey{
+                                               Name:         "testCompositeProfile",
+                                               Project:      "testProject",
+                                               CompositeApp: "testCompositeApp",
+                                               Version:      "v1",
+                                       }.String(): {
+                                               "compositeprofile": []byte(
+                                                       "{\"metadata\":{\"Name\":\"testCompositeProfile\"," +
+                                                               "\"Description\":\"A sample CompositeProfile for testing\"," +
+                                                               "\"UserData1\": \"userData1\"," +
+                                                               "\"UserData2\": \"userData2\"}}"),
+                                       },
+                               },
+                       },
+               },
+       }
+
+       for _, testCase := range testCases {
+               t.Run(testCase.label, func(t *testing.T) {
+                       db.DBconn = testCase.mockdb
+                       cprofCli := NewCompositeProfileClient()
+                       got, err := cprofCli.GetCompositeProfile(testCase.compositeProfileName, testCase.projectName, testCase.compositeAppName, testCase.compositeAppVersion)
+                       if err != nil {
+                               if testCase.expectedError == "" {
+                                       t.Fatalf("GetCompositeProfile returned an unexpected error: %s", err)
+                               }
+                               if strings.Contains(err.Error(), testCase.expectedError) == false {
+                                       t.Fatalf("GetCompositeProfile returned an unexpected error: %s", err)
+                               }
+                       } else {
+                               if reflect.DeepEqual(testCase.expected, got) == false {
+                                       t.Errorf("GetCompositeProfile returned unexpected body: got %v;"+
+                                               " expected %v", got, testCase.expected)
+                               }
+                       }
+
+               })
+       }
+
+}
+*/
index b46c606..fb46b89 100644 (file)
@@ -24,23 +24,24 @@ type Client struct {
        Cluster                *ClusterClient
        GenericPlacementIntent *GenericPlacementIntentClient
        AppIntent              *AppIntentClient
+       DeploymentIntentGroup  *DeploymentIntentGroupClient
+       Intent                 *IntentClient
+       CompositeProfile       *CompositeProfileClient
        // Add Clients for API's here
-       DeploymentIntentGroup *DeploymentIntentGroupClient
-       Intent                *IntentClient
 }
 
 // NewClient creates a new client for using the services
 func NewClient() *Client {
        c := &Client{}
-       // Add Client API handlers here
        c.Project = NewProjectClient()
        c.CompositeApp = NewCompositeAppClient()
        c.Controller = NewControllerClient()
        c.Cluster = NewClusterClient()
-       // Add Client API handlers here
        c.GenericPlacementIntent = NewGenericPlacementIntentClient()
        c.AppIntent = NewAppIntentClient()
        c.DeploymentIntentGroup = NewDeploymentIntentGroupClient()
        c.Intent = NewIntentClient()
+       c.CompositeProfile = NewCompositeProfileClient()
+       // Add Client API handlers here
        return c
 }