From 8e0c4c63e5a014c95805f16d30a64cd53ddc7385 Mon Sep 17 00:00:00 2001 From: Dileep Ranganathan Date: Wed, 19 Feb 2020 16:19:18 -0800 Subject: Composite Profile API and Handler 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 Signed-off-by: Dileep Ranganathan Change-Id: Iab105cf88ef4635038cd36e90f3251bc900b2faf --- src/orchestrator/api/api.go | 15 +- src/orchestrator/api/clusterhandler_test.go | 34 ++-- src/orchestrator/api/composite_profilehandler.go | 151 ++++++++++++++++ .../api/composite_profilehandler_test.go | 151 ++++++++++++++++ src/orchestrator/api/controllerhandler_test.go | 6 +- src/orchestrator/api/projecthandler_test.go | 6 +- src/orchestrator/cmd/main.go | 2 +- src/orchestrator/pkg/module/composite_profile.go | 192 +++++++++++++++++++++ .../pkg/module/composite_profile_test.go | 175 +++++++++++++++++++ src/orchestrator/pkg/module/module.go | 9 +- 10 files changed, 712 insertions(+), 29 deletions(-) create mode 100644 src/orchestrator/api/composite_profilehandler.go create mode 100644 src/orchestrator/api/composite_profilehandler_test.go create mode 100644 src/orchestrator/pkg/module/composite_profile.go create mode 100644 src/orchestrator/pkg/module/composite_profile_test.go (limited to 'src/orchestrator') diff --git a/src/orchestrator/api/api.go b/src/orchestrator/api/api.go index f3e3b177..7c540dd3 100644 --- a/src/orchestrator/api/api.go +++ b/src/orchestrator/api/api.go @@ -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") diff --git a/src/orchestrator/api/clusterhandler_test.go b/src/orchestrator/api/clusterhandler_test.go index a32bf021..58db6360 100644 --- a/src/orchestrator/api/clusterhandler_test.go +++ b/src/orchestrator/api/clusterhandler_test.go @@ -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 index 00000000..66c64dda --- /dev/null +++ b/src/orchestrator/api/composite_profilehandler.go @@ -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 index 00000000..e98ed961 --- /dev/null +++ b/src/orchestrator/api/composite_profilehandler_test.go @@ -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) + } + } + }) + } + +} diff --git a/src/orchestrator/api/controllerhandler_test.go b/src/orchestrator/api/controllerhandler_test.go index dd542de7..a195cb83 100644 --- a/src/orchestrator/api/controllerhandler_test.go +++ b/src/orchestrator/api/controllerhandler_test.go @@ -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 { diff --git a/src/orchestrator/api/projecthandler_test.go b/src/orchestrator/api/projecthandler_test.go index eccccb90..547420b3 100644 --- a/src/orchestrator/api/projecthandler_test.go +++ b/src/orchestrator/api/projecthandler_test.go @@ -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 { diff --git a/src/orchestrator/cmd/main.go b/src/orchestrator/cmd/main.go index 9296a54c..0385416e 100644 --- a/src/orchestrator/cmd/main.go +++ b/src/orchestrator/cmd/main.go @@ -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 index 00000000..dca2116a --- /dev/null +++ b/src/orchestrator/pkg/module/composite_profile.go @@ -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 index 00000000..af0dd7b7 --- /dev/null +++ b/src/orchestrator/pkg/module/composite_profile_test.go @@ -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) + } + } + + }) + } + +} +*/ diff --git a/src/orchestrator/pkg/module/module.go b/src/orchestrator/pkg/module/module.go index b46c6068..fb46b895 100644 --- a/src/orchestrator/pkg/module/module.go +++ b/src/orchestrator/pkg/module/module.go @@ -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 } -- cgit 1.2.3-korg