Implemented AppIntents and GenPlaceIntents 66/102266/6
authorRajamohan Raj <rajamohan.raj@intel.com>
Mon, 24 Feb 2020 21:59:44 +0000 (21:59 +0000)
committerRajamohan Raj <rajamohan.raj@intel.com>
Tue, 3 Mar 2020 23:08:13 +0000 (23:08 +0000)
Implemented the routes for creating,
getting, and deleting generic placement intents
and App intents.

Issue-ID: MULTICLOUD-875
Signed-off-by: Rajamohan Raj <rajamohan.raj@intel.com>
Change-Id: Iecb11c442958a43a517772e066de45213e3d7030

14 files changed:
src/orchestrator/api/api.go
src/orchestrator/api/app_intent_handler.go [new file with mode: 0644]
src/orchestrator/api/clusterhandler_test.go
src/orchestrator/api/controllerhandler_test.go
src/orchestrator/api/generic_placement_intent_handler.go [new file with mode: 0644]
src/orchestrator/api/projecthandler_test.go
src/orchestrator/cmd/main.go
src/orchestrator/pkg/infra/db/mock.go
src/orchestrator/pkg/infra/db/mongo_test.go
src/orchestrator/pkg/module/app_intent.go [new file with mode: 0644]
src/orchestrator/pkg/module/app_intent_test.go [new file with mode: 0644]
src/orchestrator/pkg/module/generic_placement_intent.go [new file with mode: 0644]
src/orchestrator/pkg/module/generic_placement_intent_test.go [new file with mode: 0644]
src/orchestrator/pkg/module/module.go

index 5e05b31..f4381e6 100644 (file)
@@ -1,33 +1,43 @@
 /*
-Copyright 2020 Intel Corporation.
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-    http://www.apache.org/licenses/LICENSE-2.0
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
+ * 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 (
-       moduleLib "github.com/onap/multicloud-k8s/src/orchestrator/pkg/module"
-
        "github.com/gorilla/mux"
+       moduleLib "github.com/onap/multicloud-k8s/src/orchestrator/pkg/module"
 )
 
 var moduleClient *moduleLib.Client
 
 // NewRouter creates a router that registers the various urls that are supported
-func NewRouter(projectClient moduleLib.ProjectManager, compositeAppClient moduleLib.CompositeAppManager, ControllerClient moduleLib.ControllerManager, clusterClient moduleLib.ClusterManager) *mux.Router {
+func NewRouter(projectClient moduleLib.ProjectManager,
+       compositeAppClient moduleLib.CompositeAppManager,
+       ControllerClient moduleLib.ControllerManager,
+       clusterClient moduleLib.ClusterManager,
+       genericPlacementIntentClient moduleLib.GenericPlacementIntentManager,
+       appIntentClient moduleLib.AppIntentManager) *mux.Router {
 
        router := mux.NewRouter().PathPrefix("/v2").Subrouter()
+
        moduleClient = moduleLib.NewClient()
+
+       //setting routes for project
        if projectClient == nil {
                projectClient = moduleClient.Project
+
        }
        projHandler := projectHandler{
                client: projectClient,
@@ -48,13 +58,13 @@ func NewRouter(projectClient moduleLib.ProjectManager, compositeAppClient module
        router.HandleFunc("/projects/{project-name}", projHandler.getHandler).Methods("GET")
        router.HandleFunc("/projects/{project-name}", projHandler.deleteHandler).Methods("DELETE")
 
+       //setting routes for compositeApp
        if compositeAppClient == nil {
                compositeAppClient = moduleClient.CompositeApp
        }
        compAppHandler := compositeAppHandler{
                client: compositeAppClient,
        }
-
        router.HandleFunc("/projects/{project-name}/composite-apps", compAppHandler.createHandler).Methods("POST")
        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")
@@ -79,6 +89,30 @@ func NewRouter(projectClient moduleLib.ProjectManager, compositeAppClient module
        router.HandleFunc("/cluster-providers/{provider-name}/clusters/{cluster-name}/kv-pairs", clusterHandler.getClusterKvPairsHandler).Methods("GET")
        router.HandleFunc("/cluster-providers/{provider-name}/clusters/{cluster-name}/kv-pairs/{kvpair}", clusterHandler.getClusterKvPairsHandler).Methods("GET")
        router.HandleFunc("/cluster-providers/{provider-name}/clusters/{cluster-name}/kv-pairs/{kvpair}", clusterHandler.deleteClusterKvPairsHandler).Methods("DELETE")
+       //setting routes for genericPlacementIntent
+       if genericPlacementIntentClient == nil {
+               genericPlacementIntentClient = moduleClient.GenericPlacementIntent
+       }
+
+       genericPlacementIntentHandler := genericPlacementIntentHandler{
+               client: genericPlacementIntentClient,
+       }
+       router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/generic-placement-intents", genericPlacementIntentHandler.createGenericPlacementIntentHandler).Methods("POST")
+       router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/generic-placement-intents/{intent-name}", genericPlacementIntentHandler.getGenericPlacementHandler).Methods("GET")
+       router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/generic-placement-intents/{intent-name}", genericPlacementIntentHandler.deleteGenericPlacementHandler).Methods("DELETE")
+
+       //setting routes for AppIntent
+       if appIntentClient == nil {
+               appIntentClient = moduleClient.AppIntent
+       }
+
+       appIntentHandler := appIntentHandler{
+               client: appIntentClient,
+       }
+
+       router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/generic-placement-intents/{intent-name}/app-intents", appIntentHandler.createAppIntentHandler).Methods("POST")
+       router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/generic-placement-intents/{intent-name}/app-intents/{app-intent-name}", appIntentHandler.getAppIntentHandler).Methods("GET")
+       router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/generic-placement-intents/{intent-name}/app-intents/{app-intent-name}", appIntentHandler.deleteAppIntentHandler).Methods("DELETE")
 
        return router
 }
diff --git a/src/orchestrator/api/app_intent_handler.go b/src/orchestrator/api/app_intent_handler.go
new file mode 100644 (file)
index 0000000..ab510c8
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * 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"
+       "github.com/gorilla/mux"
+       moduleLib "github.com/onap/multicloud-k8s/src/orchestrator/pkg/module"
+       "io"
+       "net/http"
+)
+
+/* Used to store backend implementation objects
+Also simplifies mocking for unit testing purposes
+*/
+type appIntentHandler struct {
+       client moduleLib.AppIntentManager
+}
+
+// createAppIntentHandler handles the create operation of intent
+func (h appIntentHandler) createAppIntentHandler(w http.ResponseWriter, r *http.Request) {
+
+       var a moduleLib.AppIntent
+
+       err := json.NewDecoder(r.Body).Decode(&a)
+       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 a.MetaData.Name == "" {
+               http.Error(w, "Missing AppIntentName in POST request", http.StatusBadRequest)
+               return
+       }
+
+       vars := mux.Vars(r)
+       projectName := vars["project-name"]
+       compositeAppName := vars["composite-app-name"]
+       version := vars["composite-app-version"]
+       intent := vars["intent-name"]
+
+       appIntent, createErr := h.client.CreateAppIntent(a, projectName, compositeAppName, version, intent)
+       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(appIntent)
+       if err != nil {
+               http.Error(w, err.Error(), http.StatusInternalServerError)
+               return
+       }
+}
+
+func (h appIntentHandler) getAppIntentHandler(w http.ResponseWriter, r *http.Request) {
+       vars := mux.Vars(r)
+
+       p := vars["project-name"]
+       if p == "" {
+               http.Error(w, "Missing projectName in GET request", http.StatusBadRequest)
+               return
+       }
+       ca := vars["composite-app-name"]
+       if ca == "" {
+               http.Error(w, "Missing compositeAppName in GET request", http.StatusBadRequest)
+               return
+       }
+
+       v := vars["composite-app-version"]
+       if v == "" {
+               http.Error(w, "Missing version of compositeApp in GET request", http.StatusBadRequest)
+               return
+       }
+
+       i := vars["intent-name"]
+       if i == "" {
+               http.Error(w, "Missing genericPlacementIntentName in GET request", http.StatusBadRequest)
+               return
+       }
+
+       ai := vars["app-intent-name"]
+       if ai == "" {
+               http.Error(w, "Missing appIntentName in GET request", http.StatusBadRequest)
+               return
+       }
+
+       appIntent, err := h.client.GetAppIntent(ai, p, ca, v, i)
+       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(appIntent)
+       if err != nil {
+               http.Error(w, err.Error(), http.StatusInternalServerError)
+               return
+       }
+
+}
+
+func (h appIntentHandler) deleteAppIntentHandler(w http.ResponseWriter, r *http.Request) {
+       vars := mux.Vars(r)
+
+       p := vars["project-name"]
+       ca := vars["composite-app-name"]
+       v := vars["composite-app-version"]
+       i := vars["intent-name"]
+       ai := vars["app-intent-name"]
+
+       err := h.client.DeleteAppIntent(ai, p, ca, v, i)
+       if err != nil {
+               http.Error(w, err.Error(), http.StatusInternalServerError)
+               return
+       }
+       w.WriteHeader(http.StatusNoContent)
+}
index db1b6f9..ad7635e 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))
+                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, 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))
+                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, 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))
+                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, 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))
+                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, 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))
+                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, 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))
+                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, 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))
+                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, 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))
+                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, 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))
+                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, 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))
+                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, 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))
+                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, 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))
+                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, 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))
+                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, 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))
+                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, 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))
+                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, 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))
+                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, 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))
+                       resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, nil))
 
                        //Check returned code
                        if resp.StatusCode != testCase.expectedCode {
index c91943a..bcc06f9 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))
+                       resp := executeRequest(request, NewRouter(nil, nil, testCase.controllerClient, 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))
+                       resp := executeRequest(request, NewRouter(nil, nil, testCase.controllerClient, 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))
+                       resp := executeRequest(request, NewRouter(nil, nil, testCase.controllerClient, nil, nil, nil))
 
                        //Check returned code
                        if resp.StatusCode != testCase.expectedCode {
diff --git a/src/orchestrator/api/generic_placement_intent_handler.go b/src/orchestrator/api/generic_placement_intent_handler.go
new file mode 100644 (file)
index 0000000..e186735
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * 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 genericPlacementIntentHandler struct {
+       client moduleLib.GenericPlacementIntentManager
+}
+
+// createGenericPlacementIntentHandler handles the create operation of intent
+func (h genericPlacementIntentHandler) createGenericPlacementIntentHandler(w http.ResponseWriter, r *http.Request) {
+
+       var g moduleLib.GenericPlacementIntent
+
+       err := json.NewDecoder(r.Body).Decode(&g)
+       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 g.MetaData.Name == "" {
+               http.Error(w, "Missing genericPlacementIntentName in POST request", http.StatusBadRequest)
+               return
+       }
+
+       vars := mux.Vars(r)
+       projectName := vars["project-name"]
+       compositeAppName := vars["composite-app-name"]
+       version := vars["composite-app-version"]
+
+       gPIntent, createErr := h.client.CreateGenericPlacementIntent(g, 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(gPIntent)
+       if err != nil {
+               http.Error(w, err.Error(), http.StatusInternalServerError)
+               return
+       }
+}
+
+// getGenericPlacementHandler handles the GET operations on intent
+func (h genericPlacementIntentHandler) getGenericPlacementHandler(w http.ResponseWriter, r *http.Request) {
+       vars := mux.Vars(r)
+       intentName := vars["intent-name"]
+       if intentName == "" {
+               http.Error(w, "Missing genericPlacementIntentName in GET request", http.StatusBadRequest)
+               return
+       }
+       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
+       }
+
+       gPIntent, err := h.client.GetGenericPlacementIntent(intentName, 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(gPIntent)
+       if err != nil {
+               http.Error(w, err.Error(), http.StatusInternalServerError)
+               return
+       }
+}
+
+// deleteGenericPlacementHandler handles the delete operations on intent
+func (h genericPlacementIntentHandler) deleteGenericPlacementHandler(w http.ResponseWriter, r *http.Request) {
+       vars := mux.Vars(r)
+       i := vars["intent-name"]
+       p := vars["project-name"]
+       ca := vars["composite-app-name"]
+       v := vars["composite-app-version"]
+
+       err := h.client.DeleteGenericPlacementIntent(i, p, ca, v)
+       if err != nil {
+               http.Error(w, err.Error(), http.StatusInternalServerError)
+               return
+       }
+       w.WriteHeader(http.StatusNoContent)
+}
index c6da4e0..84e6752 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))
+                       resp := executeRequest(request, NewRouter(testCase.projectClient, 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))
+                       resp := executeRequest(request, NewRouter(testCase.projectClient, 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))
+                       resp := executeRequest(request, NewRouter(testCase.projectClient, nil, nil, nil, nil, nil))
 
                        //Check returned code
                        if resp.StatusCode != testCase.expectedCode {
index 179cf97..cf1faf4 100644 (file)
@@ -47,7 +47,7 @@ func main() {
                log.Fatalln("Exiting...")
        }
 
-       httpRouter := api.NewRouter(nil, nil, nil, nil)
+       httpRouter := api.NewRouter(nil, nil, nil, nil, nil, nil)
        loggedRouter := handlers.LoggingHandler(os.Stdout, httpRouter)
        log.Println("Starting Kubernetes Multicloud API")
 
index 6b46a52..79366d1 100644 (file)
@@ -45,6 +45,10 @@ func (m *MockDB) Create(table string, key Key, tag string, data interface{}) err
        return m.Err
 }
 
+func (m *MockDB) Insert(table string, key Key, query interface{}, tag string, data interface{}) error {
+       return m.Err
+}
+
 func (m *MockDB) Update(table string, key Key, tag string, data interface{}) error {
        return m.Err
 }
@@ -73,7 +77,22 @@ func (m *MockDB) Read(table string, key Key, tag string) ([]byte, error) {
        return nil, m.Err
 }
 
+func (m *MockDB) Find(table string, key Key, tag string) ([][]byte, error) {
+       if m.Err != nil {
+               return nil, m.Err
+       }
+
+       str := fmt.Sprintf("%v", key)
+       for k, v := range m.Items {
+               if k == str {
+
+                       return [][]byte{v[tag]}, nil
+               }
+       }
+
+       return nil, m.Err
+}
+
 func (m *MockDB) Delete(table string, key Key, tag string) error {
        return m.Err
 }
-
index d506dbd..9f813ca 100644 (file)
@@ -76,7 +76,7 @@ func (c *mockCollection) DeleteMany(ctx context.Context, filter interface{},
 
 func (c *mockCollection) UpdateOne(ctx context.Context, filter interface{}, update interface{},
        opts ...*options.UpdateOptions) (*mongo.UpdateResult, error) {
-               return nil, c.Err
+       return nil, c.Err
 }
 
 func TestCreate(t *testing.T) {
@@ -463,4 +463,3 @@ func TestDelete(t *testing.T) {
                })
        }
 }
-
diff --git a/src/orchestrator/pkg/module/app_intent.go b/src/orchestrator/pkg/module/app_intent.go
new file mode 100644 (file)
index 0000000..70c80da
--- /dev/null
@@ -0,0 +1,195 @@
+/*
+ * 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"
+       "reflect"
+
+       "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/db"
+
+       pkgerrors "github.com/pkg/errors"
+)
+
+// AppIntent has two components - metadata, spec
+type AppIntent struct {
+       MetaData MetaData `json:"metadata"`
+       Spec     SpecData `json:"spec"`
+}
+
+// MetaData has - name, description, userdata1, userdata2
+type MetaData struct {
+       Name        string `json:"name"`
+       Description string `json:"description"`
+       UserData1   string `json:"userData1"`
+       UserData2   string `json:"userData2"`
+}
+
+// AllOf consists of AnyOfArray and ClusterNames array
+type AllOf struct {
+       ClusterName      string  `json:"cluster-name,omitempty"`
+       ClusterLabelName string  `json:"cluster-label-name,omitempty"`
+       AnyOfArray       []AnyOf `json:"anyOf,omitempty"`
+}
+
+// AnyOf consists of Array of ClusterLabelNames
+type AnyOf struct {
+       ClusterName      string `json:"cluster-name,omitempty"`
+       ClusterLabelName string `json:"cluster-label-name,omitempty"`
+}
+
+// IntentStruc consists of AllOfArray and AnyOfArray
+type IntentStruc struct {
+       AllOfArray []AllOf `json:"allOf,omitempty"`
+       AnyOfArray []AnyOf `json:"anyOf,omitempty"`
+}
+
+// SpecData consists of appName and intent
+type SpecData struct {
+       AppName string      `json:"app-name"`
+       Intent  IntentStruc `json:"intent"`
+}
+
+// AppIntentManager is an interface which exposes the
+// AppIntentManager functionalities
+type AppIntentManager interface {
+       CreateAppIntent(a AppIntent, p string, ca string, v string, i string) (AppIntent, error)
+       GetAppIntent(ai string, p string, ca string, v string, i string) (AppIntent, error)
+       DeleteAppIntent(ai string, p string, ca string, v string, i string) error
+}
+
+// AppIntentKey is used as primary key
+type AppIntentKey struct {
+       Name         string `json:"name"`
+       Project      string `json:"project"`
+       CompositeApp string `json:"compositeapp"`
+       Version      string `json:"version"`
+       Intent       string `json:"intent-name"`
+}
+
+// We will use json marshalling to convert to string to
+// preserve the underlying structure.
+func (ak AppIntentKey) String() string {
+       out, err := json.Marshal(ak)
+       if err != nil {
+               return ""
+       }
+       return string(out)
+}
+
+// AppIntentClient implements the AppIntentManager interface
+type AppIntentClient struct {
+       storeName   string
+       tagMetaData string
+}
+
+// NewAppIntentClient returns an instance of AppIntentClient
+func NewAppIntentClient() *AppIntentClient {
+       return &AppIntentClient{
+               storeName:   "orchestrator",
+               tagMetaData: "appintent",
+       }
+}
+
+// CreateAppIntent creates an entry for AppIntent in the db. Other input parameters for it - projectName, compositeAppName, version, intentName.
+func (c *AppIntentClient) CreateAppIntent(a AppIntent, p string, ca string, v string, i string) (AppIntent, error) {
+
+       //Check for the AppIntent already exists here.
+       res, err := c.GetAppIntent(a.MetaData.Name, p, ca, v, i)
+       if !reflect.DeepEqual(res, AppIntent{}) {
+               return AppIntent{}, pkgerrors.New("AppIntent already exists")
+       }
+
+       //Check if project exists
+       _, err = NewProjectClient().GetProject(p)
+       if err != nil {
+               return AppIntent{}, pkgerrors.New("Unable to find the project")
+       }
+
+       // check if compositeApp exists
+       _, err = NewCompositeAppClient().GetCompositeApp(ca, v, p)
+       if err != nil {
+               return AppIntent{}, pkgerrors.New("Unable to find the composite-app")
+       }
+
+       // check if Intent exists
+       _, err = NewGenericPlacementIntentClient().GetGenericPlacementIntent(i, p, ca, v)
+       if err != nil {
+               return AppIntent{}, pkgerrors.New("Unable to find the intent")
+       }
+
+       akey := AppIntentKey{
+               Name:         a.MetaData.Name,
+               Project:      p,
+               CompositeApp: ca,
+               Version:      v,
+               Intent:       i,
+       }
+
+       err = db.DBconn.Insert(c.storeName, akey, nil, c.tagMetaData, a)
+       if err != nil {
+               return AppIntent{}, pkgerrors.Wrap(err, "Create DB entry error")
+       }
+
+       return a, nil
+}
+
+// GetAppIntent shall take arguments - name of the app intent, name of the project, name of the composite app, version of the composite app and intent name. It shall return the AppIntent
+func (c *AppIntentClient) GetAppIntent(ai string, p string, ca string, v string, i string) (AppIntent, error) {
+
+       k := AppIntentKey{
+               Name:         ai,
+               Project:      p,
+               CompositeApp: ca,
+               Version:      v,
+               Intent:       i,
+       }
+
+       result, err := db.DBconn.Find(c.storeName, k, c.tagMetaData)
+       if err != nil {
+               return AppIntent{}, pkgerrors.Wrap(err, "Get AppIntent error")
+       }
+
+       if result != nil {
+               a := AppIntent{}
+               err = db.DBconn.Unmarshal(result[0], &a)
+               if err != nil {
+                       return AppIntent{}, pkgerrors.Wrap(err, "Unmarshalling  AppIntent")
+               }
+               return a, nil
+
+       }
+       return AppIntent{}, pkgerrors.New("Error getting AppIntent")
+}
+
+// DeleteAppIntent delete an AppIntent
+func (c *AppIntentClient) DeleteAppIntent(ai string, p string, ca string, v string, i string) error {
+       k := AppIntentKey{
+               Name:         ai,
+               Project:      p,
+               CompositeApp: ca,
+               Version:      v,
+               Intent:       i,
+       }
+
+       err := db.DBconn.Remove(c.storeName, k)
+       if err != nil {
+               return pkgerrors.Wrap(err, "Delete Project entry;")
+       }
+       return nil
+
+}
diff --git a/src/orchestrator/pkg/module/app_intent_test.go b/src/orchestrator/pkg/module/app_intent_test.go
new file mode 100644 (file)
index 0000000..5a4f769
--- /dev/null
@@ -0,0 +1,271 @@
+/*
+ * 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 (
+       "reflect"
+       "strings"
+       "testing"
+
+       "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/db"
+)
+
+func TestCreateAppIntent(t *testing.T) {
+       testCases := []struct {
+               label                       string
+               inputAppIntent              AppIntent
+               inputProject                string
+               inputCompositeApp           string
+               inputCompositeAppVersion    string
+               inputGenericPlacementIntent string
+               expectedError               string
+               mockdb                      *db.MockDB
+               expected                    AppIntent
+       }{
+               {
+                       label: "Create AppIntent",
+                       inputAppIntent: AppIntent{
+                               MetaData: MetaData{
+                                       Name:        "testAppIntent",
+                                       Description: "A sample AppIntent",
+                                       UserData1:   "userData1",
+                                       UserData2:   "userData2",
+                               },
+                               Spec: SpecData{
+                                       AppName: "SampleApp",
+                                       Intent: IntentStruc{
+                                               AllOfArray: []AllOf{
+                                                       {
+                                                               ClusterName: "edge1",
+                                                               //ClusterLabelName: "edge1",
+                                                       },
+                                                       {
+                                                               ClusterName: "edge2",
+                                                               //ClusterLabelName: "edge2",
+                                                       },
+                                                       {
+                                                               AnyOfArray: []AnyOf{
+                                                                       {ClusterLabelName: "east-us1"},
+                                                                       {ClusterLabelName: "east-us2"},
+                                                                       //{ClusterName: "east-us1"},
+                                                                       //{ClusterName: "east-us2"},
+                                                               },
+                                                       },
+                                               },
+
+                                               AnyOfArray: []AnyOf{},
+                                       },
+                               },
+                       },
+
+                       inputProject:                "testProject",
+                       inputCompositeApp:           "testCompositeApp",
+                       inputCompositeAppVersion:    "testCompositeAppVersion",
+                       inputGenericPlacementIntent: "testIntent",
+                       expected: AppIntent{
+                               MetaData: MetaData{
+                                       Name:        "testAppIntent",
+                                       Description: "A sample AppIntent",
+                                       UserData1:   "userData1",
+                                       UserData2:   "userData2",
+                               },
+                               Spec: SpecData{
+                                       AppName: "SampleApp",
+                                       Intent: IntentStruc{
+                                               AllOfArray: []AllOf{
+                                                       {
+                                                               ClusterName: "edge1",
+                                                               //ClusterLabelName: "edge1",
+                                                       },
+                                                       {
+                                                               ClusterName: "edge2",
+                                                               //ClusterLabelName: "edge2",
+                                                       },
+                                                       {
+                                                               AnyOfArray: []AnyOf{
+                                                                       {ClusterLabelName: "east-us1"},
+                                                                       {ClusterLabelName: "east-us2"},
+                                                                       //{ClusterName: "east-us1"},
+                                                                       //{ClusterName: "east-us2"},
+                                                               },
+                                                       },
+                                               },
+                                               AnyOfArray: []AnyOf{},
+                                       },
+                               },
+                       },
+                       expectedError: "",
+                       mockdb: &db.MockDB{
+                               Items: map[string]map[string][]byte{
+                                       ProjectKey{ProjectName: "testProject"}.String(): {
+                                               "projectmetadata": []byte(
+                                                       "{\"project-name\":\"testProject\"," +
+                                                               "\"description\":\"Test project for unit testing\"}"),
+                                       },
+                                       CompositeAppKey{CompositeAppName: "testCompositeApp",
+                                               Version: "testCompositeAppVersion", Project: "testProject"}.String(): {
+                                               "compositeAppmetadata": []byte(
+                                                       "{\"metadata\":{" +
+                                                               "\"name\":\"testCompositeApp\"," +
+                                                               "\"description\":\"description\"," +
+                                                               "\"userData1\":\"user data\"," +
+                                                               "\"userData2\":\"user data\"" +
+                                                               "}," +
+                                                               "\"spec\":{" +
+                                                               "\"version\":\"version of the composite app\"}}"),
+                                       },
+                                       GenericPlacementIntentKey{
+                                               Name:         "testIntent",
+                                               Project:      "testProject",
+                                               CompositeApp: "testCompositeApp",
+                                               Version:      "testCompositeAppVersion",
+                                       }.String(): {
+                                               "genericplacementintent": []byte(
+                                                       "{\"metadata\":{\"Name\":\"testIntent\"," +
+                                                               "\"Description\":\"A sample intent for testing\"," +
+                                                               "\"UserData1\": \"userData1\"," +
+                                                               "\"UserData2\": \"userData2\"}," +
+                                                               "\"spec\":{\"Logical-Cloud\": \"logicalCloud1\"}}"),
+                                       },
+                               },
+                       },
+               },
+       }
+       for _, testCase := range testCases {
+               t.Run(testCase.label, func(t *testing.T) {
+                       db.DBconn = testCase.mockdb
+                       appIntentCli := NewAppIntentClient()
+                       got, err := appIntentCli.CreateAppIntent(testCase.inputAppIntent, testCase.inputProject, testCase.inputCompositeApp, testCase.inputCompositeAppVersion, testCase.inputGenericPlacementIntent)
+                       if err != nil {
+                               if testCase.expectedError == "" {
+                                       t.Fatalf("CreateAppIntent returned an unexpected error %s, ", err)
+                               }
+                               if strings.Contains(err.Error(), testCase.expectedError) == false {
+                                       t.Fatalf("CreateAppIntent returned an unexpected error %s", err)
+                               }
+                       } else {
+                               if reflect.DeepEqual(testCase.expected, got) == false {
+                                       t.Errorf("CreateAppIntent returned unexpected body: got %v; "+" expected %v", got, testCase.expected)
+                               }
+                       }
+               })
+       }
+}
+
+func TestGetAppIntent(t *testing.T) {
+       testCases := []struct {
+               label                  string
+               expectedError          string
+               expected               AppIntent
+               mockdb                 *db.MockDB
+               appIntentName          string
+               projectName            string
+               compositeAppName       string
+               compositeAppVersion    string
+               genericPlacementIntent string
+       }{
+               {
+                       label:                  "Get Intent",
+                       appIntentName:          "testAppIntent",
+                       projectName:            "testProject",
+                       compositeAppName:       "testCompositeApp",
+                       compositeAppVersion:    "testCompositeAppVersion",
+                       genericPlacementIntent: "testIntent",
+                       expected: AppIntent{
+                               MetaData: MetaData{
+                                       Name:        "testAppIntent",
+                                       Description: "testAppIntent",
+                                       UserData1:   "userData1",
+                                       UserData2:   "userData2",
+                               },
+                               Spec: SpecData{
+                                       AppName: "SampleApp",
+                                       Intent: IntentStruc{
+                                               AllOfArray: []AllOf{
+                                                       {
+                                                               ClusterName: "edge1",
+                                                       },
+                                                       {
+                                                               ClusterName: "edge2",
+                                                       },
+                                                       {
+                                                               AnyOfArray: []AnyOf{
+                                                                       {ClusterLabelName: "east-us1"},
+                                                                       {ClusterLabelName: "east-us2"},
+                                                               },
+                                                       },
+                                               },
+                                       },
+                               },
+                       },
+                       expectedError: "",
+                       mockdb: &db.MockDB{
+                               Items: map[string]map[string][]byte{
+                                       AppIntentKey{
+                                               Name:         "testAppIntent",
+                                               Project:      "testProject",
+                                               CompositeApp: "testCompositeApp",
+                                               Version:      "testCompositeAppVersion",
+                                               Intent:       "testIntent",
+                                       }.String(): {
+                                               "appintent": []byte(
+                                                       "{\"metadata\":{\"Name\":\"testAppIntent\"," +
+                                                               "\"Description\":\"testAppIntent\"," +
+                                                               "\"UserData1\": \"userData1\"," +
+                                                               "\"UserData2\": \"userData2\"}," +
+                                                               "\"spec\":{\"app-name\": \"SampleApp\"," +
+                                                               "\"intent\": {" +
+                                                               "\"allOf\":[" +
+                                                               "{\"cluster-name\":\"edge1\"}," +
+                                                               "{\"cluster-name\":\"edge2\"}," +
+                                                               "{" +
+                                                               "\"anyOf\":[" +
+                                                               "{" +
+                                                               "\"cluster-label-name\":\"east-us1\"}," +
+                                                               "{" +
+                                                               "\"cluster-label-name\":\"east-us2\"}" +
+                                                               "]}]" +
+                                                               "}}}"),
+                                       },
+                               },
+                       },
+               },
+       }
+
+       for _, testCase := range testCases {
+               t.Run(testCase.label, func(t *testing.T) {
+                       db.DBconn = testCase.mockdb
+                       appIntentCli := NewAppIntentClient()
+                       got, err := appIntentCli.GetAppIntent(testCase.appIntentName, testCase.projectName, testCase.compositeAppName, testCase.compositeAppVersion,
+                               testCase.genericPlacementIntent)
+                       if err != nil {
+                               if testCase.expectedError == "" {
+                                       t.Fatalf("GetAppIntent returned an unexpected error: %s", err)
+                               }
+                               if strings.Contains(err.Error(), testCase.expectedError) == false {
+                                       t.Fatalf("GetAppIntent returned an unexpected error: %s", err)
+                               }
+                       } else {
+                               if reflect.DeepEqual(testCase.expected, got) == false {
+                                       t.Errorf("GetAppIntent returned unexpected body: got %v;"+
+                                               " expected %v", got, testCase.expected)
+                               }
+                       }
+
+               })
+       }
+}
diff --git a/src/orchestrator/pkg/module/generic_placement_intent.go b/src/orchestrator/pkg/module/generic_placement_intent.go
new file mode 100644 (file)
index 0000000..4098194
--- /dev/null
@@ -0,0 +1,165 @@
+/*
+ * 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"
+)
+
+// GenericPlacementIntent shall have 2 fields - metadata and spec
+type GenericPlacementIntent struct {
+       MetaData IntentMetaData `json:"metadata"`
+       Spec     IntentSpecData `json:"spec"`
+}
+
+// IntentMetaData has name, description, userdata1, userdata2
+type IntentMetaData struct {
+       Name        string `json:"name"`
+       Description string `json:"description"`
+       UserData1   string `json:"userData1"`
+       UserData2   string `json:"userData2"`
+}
+
+// IntentSpecData has logical-cloud-name
+type IntentSpecData struct {
+       LogicalCloud string `json:"logical-cloud"`
+}
+
+// GenericPlacementIntentManager is an interface which exposes the GenericPlacementIntentManager functionality
+type GenericPlacementIntentManager interface {
+       CreateGenericPlacementIntent(g GenericPlacementIntent, p string, ca string,
+               v string) (GenericPlacementIntent, error)
+       GetGenericPlacementIntent(intentName string, projectName string,
+               compositeAppName string, version string) (GenericPlacementIntent, error)
+       DeleteGenericPlacementIntent(intentName string, projectName string,
+               compositeAppName string, version string) error
+}
+
+// GenericPlacementIntentKey is used as the primary key
+type GenericPlacementIntentKey struct {
+       Name         string `json:"name"`
+       Project      string `json:"project"`
+       CompositeApp string `json:"compositeapp"`
+       Version      string `json:"version"`
+}
+
+// We will use json marshalling to convert to string to
+// preserve the underlying structure.
+func (gk GenericPlacementIntentKey) String() string {
+       out, err := json.Marshal(gk)
+       if err != nil {
+               return ""
+       }
+       return string(out)
+}
+
+// GenericPlacementIntentClient implements the GenericPlacementIntentManager interface
+type GenericPlacementIntentClient struct {
+       storeName   string
+       tagMetaData string
+}
+
+// NewGenericPlacementIntentClient return an instance of GenericPlacementIntentClient which implements GenericPlacementIntentManager
+func NewGenericPlacementIntentClient() *GenericPlacementIntentClient {
+       return &GenericPlacementIntentClient{
+               storeName:   "orchestrator",
+               tagMetaData: "genericplacementintent",
+       }
+}
+
+// CreateGenericPlacementIntent creates an entry for GenericPlacementIntent in the database. Other Input parameters for it - projectName, compositeAppName, version
+func (c *GenericPlacementIntentClient) CreateGenericPlacementIntent(g GenericPlacementIntent, p string, ca string,
+       v string) (GenericPlacementIntent, error) {
+
+       // check if the genericPlacement already exists.
+       res, err := c.GetGenericPlacementIntent(g.MetaData.Name, p, ca, v)
+       if res != (GenericPlacementIntent{}) {
+               return GenericPlacementIntent{}, pkgerrors.New("Intent already exists")
+       }
+
+       //Check if project exists
+       _, err = NewProjectClient().GetProject(p)
+       if err != nil {
+               return GenericPlacementIntent{}, pkgerrors.New("Unable to find the project")
+       }
+
+       // check if compositeApp exists
+       _, err = NewCompositeAppClient().GetCompositeApp(ca, v, p)
+       if err != nil {
+               return GenericPlacementIntent{}, pkgerrors.New("Unable to find the composite-app")
+       }
+
+       gkey := GenericPlacementIntentKey{
+               Name:         g.MetaData.Name,
+               Project:      p,
+               CompositeApp: ca,
+               Version:      v,
+       }
+
+       err = db.DBconn.Insert(c.storeName, gkey, nil, c.tagMetaData, g)
+       if err != nil {
+               return GenericPlacementIntent{}, pkgerrors.Wrap(err, "Create DB entry error")
+       }
+
+       return g, nil
+}
+
+// GetGenericPlacementIntent shall take arguments - name of the intent, name of the project, name of the composite app and version of the composite app. It shall return the genericPlacementIntent if its present.
+func (c *GenericPlacementIntentClient) GetGenericPlacementIntent(i string, p string, ca string, v string) (GenericPlacementIntent, error) {
+       key := GenericPlacementIntentKey{
+               Name:         i,
+               Project:      p,
+               CompositeApp: ca,
+               Version:      v,
+       }
+
+       result, err := db.DBconn.Find(c.storeName, key, c.tagMetaData)
+       if err != nil {
+               return GenericPlacementIntent{}, pkgerrors.Wrap(err, "Get Intent error")
+       }
+
+       if result != nil {
+               g := GenericPlacementIntent{}
+               err = db.DBconn.Unmarshal(result[0], &g)
+               if err != nil {
+                       return GenericPlacementIntent{}, pkgerrors.Wrap(err, "Unmarshalling GenericPlacement Intent")
+               }
+               return g, nil
+       }
+
+       return GenericPlacementIntent{}, pkgerrors.New("Error getting GenericPlacementIntent")
+
+}
+
+// DeleteGenericPlacementIntent the intent from the database
+func (c *GenericPlacementIntentClient) DeleteGenericPlacementIntent(i string, p string, ca string, v string) error {
+       key := GenericPlacementIntentKey{
+               Name:         i,
+               Project:      p,
+               CompositeApp: ca,
+               Version:      v,
+       }
+
+       err := db.DBconn.Remove(c.storeName, key)
+       if err != nil {
+               return pkgerrors.Wrap(err, "Delete Project entry;")
+       }
+       return nil
+}
diff --git a/src/orchestrator/pkg/module/generic_placement_intent_test.go b/src/orchestrator/pkg/module/generic_placement_intent_test.go
new file mode 100644 (file)
index 0000000..3cb29e6
--- /dev/null
@@ -0,0 +1,184 @@
+/*
+ * 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 (
+       "reflect"
+       "strings"
+       "testing"
+
+       "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/db"
+)
+
+func TestCreateGenericPlacementIntent(t *testing.T) {
+       testCases := []struct {
+               label                    string
+               inputIntent              GenericPlacementIntent
+               inputProject             string
+               inputCompositeApp        string
+               inputCompositeAppVersion string
+               expectedError            string
+               mockdb                   *db.MockDB
+               expected                 GenericPlacementIntent
+       }{
+               {
+                       label: "Create GenericPlacementIntent",
+                       inputIntent: GenericPlacementIntent{
+                               MetaData: IntentMetaData{
+                                       Name:        "testGenericPlacement",
+                                       Description: " A sample intent for testing",
+                                       UserData1:   "userData1",
+                                       UserData2:   "userData2",
+                               },
+                               Spec: IntentSpecData{
+                                       LogicalCloud: "logicalCloud1",
+                               },
+                       },
+                       inputProject:             "testProject",
+                       inputCompositeApp:        "testCompositeApp",
+                       inputCompositeAppVersion: "testCompositeAppVersion",
+                       expected: GenericPlacementIntent{
+                               MetaData: IntentMetaData{
+                                       Name:        "testGenericPlacement",
+                                       Description: " A sample intent for testing",
+                                       UserData1:   "userData1",
+                                       UserData2:   "userData2",
+                               },
+                               Spec: IntentSpecData{
+                                       LogicalCloud: "logicalCloud1",
+                               },
+                       },
+                       expectedError: "",
+                       mockdb: &db.MockDB{
+                               Items: map[string]map[string][]byte{
+                                       ProjectKey{ProjectName: "testProject"}.String(): {
+                                               "projectmetadata": []byte(
+                                                       "{\"project-name\":\"testProject\"," +
+                                                               "\"description\":\"Test project for unit testing\"}"),
+                                       },
+                                       CompositeAppKey{CompositeAppName: "testCompositeApp",
+                                               Version: "testCompositeAppVersion", Project: "testProject"}.String(): {
+                                               "compositeAppmetadata": []byte(
+                                                       "{\"metadata\":{" +
+                                                               "\"name\":\"testCompositeApp\"," +
+                                                               "\"description\":\"description\"," +
+                                                               "\"userData1\":\"user data\"," +
+                                                               "\"userData2\":\"user data\"" +
+                                                               "}," +
+                                                               "\"spec\":{" +
+                                                               "\"version\":\"version of the composite app\"}}"),
+                                       },
+                               },
+                       },
+               },
+       }
+
+       for _, testCase := range testCases {
+               t.Run(testCase.label, func(t *testing.T) {
+                       db.DBconn = testCase.mockdb
+                       intentCli := NewGenericPlacementIntentClient()
+                       got, err := intentCli.CreateGenericPlacementIntent(testCase.inputIntent, testCase.inputProject, testCase.inputCompositeApp, testCase.inputCompositeAppVersion)
+                       if err != nil {
+                               if testCase.expectedError == "" {
+                                       t.Fatalf("CreateGenericPlacementIntent returned an unexpected error %s", err)
+                               }
+                               if strings.Contains(err.Error(), testCase.expectedError) == false {
+                                       t.Fatalf("CreateGenericPlacementIntent returned an unexpected error %s", err)
+                               }
+                       } else {
+                               if reflect.DeepEqual(testCase.expected, got) == false {
+                                       t.Errorf("CreateGenericPlacementIntent returned unexpected body: got %v; "+" expected %v", got, testCase.expected)
+                               }
+                       }
+               })
+
+       }
+}
+
+func TestGetGenericPlacementIntent(t *testing.T) {
+
+       testCases := []struct {
+               label               string
+               expectedError       string
+               expected            GenericPlacementIntent
+               mockdb              *db.MockDB
+               intentName          string
+               projectName         string
+               compositeAppName    string
+               compositeAppVersion string
+       }{
+               {
+                       label:               "Get Intent",
+                       intentName:          "testIntent",
+                       projectName:         "testProject",
+                       compositeAppName:    "testCompositeApp",
+                       compositeAppVersion: "testVersion",
+                       expected: GenericPlacementIntent{
+                               MetaData: IntentMetaData{
+                                       Name:        "testIntent",
+                                       Description: "A sample intent for testing",
+                                       UserData1:   "userData1",
+                                       UserData2:   "userData2",
+                               },
+                               Spec: IntentSpecData{
+                                       LogicalCloud: "logicalCloud1",
+                               },
+                       },
+                       expectedError: "",
+                       mockdb: &db.MockDB{
+                               Items: map[string]map[string][]byte{
+                                       GenericPlacementIntentKey{
+                                               Name:         "testIntent",
+                                               Project:      "testProject",
+                                               CompositeApp: "testCompositeApp",
+                                               Version:      "testVersion",
+                                       }.String(): {
+                                               "genericplacementintent": []byte(
+                                                       "{\"metadata\":{\"Name\":\"testIntent\"," +
+                                                               "\"Description\":\"A sample intent for testing\"," +
+                                                               "\"UserData1\": \"userData1\"," +
+                                                               "\"UserData2\": \"userData2\"}," +
+                                                               "\"spec\":{\"Logical-Cloud\": \"logicalCloud1\"}}"),
+                                       },
+                               },
+                       },
+               },
+       }
+
+       for _, testCase := range testCases {
+               t.Run(testCase.label, func(t *testing.T) {
+                       db.DBconn = testCase.mockdb
+                       intentCli := NewGenericPlacementIntentClient()
+                       got, err := intentCli.GetGenericPlacementIntent(testCase.intentName, testCase.projectName, testCase.compositeAppName, testCase.compositeAppVersion)
+                       if err != nil {
+                               if testCase.expectedError == "" {
+                                       t.Fatalf("GetGenericPlacementIntent returned an unexpected error: %s", err)
+                               }
+                               if strings.Contains(err.Error(), testCase.expectedError) == false {
+                                       t.Fatalf("GetGenericPlacementIntent returned an unexpected error: %s", err)
+                               }
+                       } else {
+                               if reflect.DeepEqual(testCase.expected, got) == false {
+                                       t.Errorf("GetGenericPlacementIntent returned unexpected body: got %v;"+
+                                               " expected %v", got, testCase.expected)
+                               }
+                       }
+
+               })
+       }
+
+}
index f80ff55..34a8462 100644 (file)
@@ -20,18 +20,24 @@ package module
 type Client struct {
        Project      *ProjectClient
        CompositeApp *CompositeAppClient
-       Controller *ControllerClient
-       Cluster *ClusterClient
+       Controller   *ControllerClient
+       Cluster      *ClusterClient
        // Add Clients for API's here
+       GenericPlacementIntent *GenericPlacementIntentClient
+       AppIntent              *AppIntentClient
 }
 
 // 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()
+
        return c
 }