aboutsummaryrefslogtreecommitdiffstats
path: root/src/orchestrator
diff options
context:
space:
mode:
Diffstat (limited to 'src/orchestrator')
-rw-r--r--src/orchestrator/api/add_intents_handler.go136
-rw-r--r--src/orchestrator/api/api.go124
-rw-r--r--src/orchestrator/api/app_intent_handler.go138
-rw-r--r--src/orchestrator/api/app_profilehandler.go266
-rw-r--r--src/orchestrator/api/clusterhandler.go8
-rw-r--r--src/orchestrator/api/clusterhandler_test.go34
-rw-r--r--src/orchestrator/api/composite_profilehandler.go151
-rw-r--r--src/orchestrator/api/composite_profilehandler_test.go151
-rw-r--r--src/orchestrator/api/controllerhandler_test.go6
-rw-r--r--src/orchestrator/api/deployment_intent_groups_handler.go133
-rw-r--r--src/orchestrator/api/generic_placement_intent_handler.go130
-rw-r--r--src/orchestrator/api/projecthandler_test.go6
-rw-r--r--src/orchestrator/cmd/main.go2
-rw-r--r--src/orchestrator/go.mod3
-rw-r--r--src/orchestrator/go.sum1
-rw-r--r--src/orchestrator/pkg/infra/db/mock.go21
-rw-r--r--src/orchestrator/pkg/infra/db/mongo_test.go3
-rw-r--r--src/orchestrator/pkg/module/add_intents.go184
-rw-r--r--src/orchestrator/pkg/module/app_intent.go195
-rw-r--r--src/orchestrator/pkg/module/app_intent_test.go271
-rw-r--r--src/orchestrator/pkg/module/app_profile.go296
-rw-r--r--src/orchestrator/pkg/module/composite_profile.go192
-rw-r--r--src/orchestrator/pkg/module/composite_profile_test.go175
-rw-r--r--src/orchestrator/pkg/module/deployment_intent_groups.go177
-rw-r--r--src/orchestrator/pkg/module/deployment_intent_groups_test.go230
-rw-r--r--src/orchestrator/pkg/module/generic_placement_intent.go165
-rw-r--r--src/orchestrator/pkg/module/generic_placement_intent_test.go184
-rw-r--r--src/orchestrator/pkg/module/module.go20
-rw-r--r--src/orchestrator/utils/utils.go66
-rw-r--r--src/orchestrator/utils/utils_test.go74
30 files changed, 3491 insertions, 51 deletions
diff --git a/src/orchestrator/api/add_intents_handler.go b/src/orchestrator/api/add_intents_handler.go
new file mode 100644
index 00000000..dfe1a496
--- /dev/null
+++ b/src/orchestrator/api/add_intents_handler.go
@@ -0,0 +1,136 @@
+/*
+ * 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"
+)
+
+type intentHandler struct {
+ client moduleLib.IntentManager
+}
+
+func (h intentHandler) addIntentHandler(w http.ResponseWriter, r *http.Request) {
+ var i moduleLib.Intent
+
+ err := json.NewDecoder(r.Body).Decode(&i)
+ 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 i.MetaData.Name == "" {
+ http.Error(w, "Missing Intent in POST request", http.StatusBadRequest)
+ return
+ }
+
+ vars := mux.Vars(r)
+ p := vars["project-name"]
+ ca := vars["composite-app-name"]
+ v := vars["composite-app-version"]
+ d := vars["deployment-intent-group-name"]
+
+ intent, addError := h.client.AddIntent(i, p, ca, v, d)
+ if addError != nil {
+ http.Error(w, addError.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusCreated)
+ err = json.NewEncoder(w).Encode(intent)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+func (h intentHandler) getIntentHandler(w http.ResponseWriter, r *http.Request) {
+
+ vars := mux.Vars(r)
+
+ i := vars["intent-name"]
+ if i == "" {
+ http.Error(w, "Missing intentName in GET request", http.StatusBadRequest)
+ return
+ }
+
+ 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
+ }
+
+ di := vars["deployment-intent-group-name"]
+ if di == "" {
+ http.Error(w, "Missing name of DeploymentIntentGroup in GET request", http.StatusBadRequest)
+ return
+ }
+
+ intent, err := h.client.GetIntent(i, p, ca, v, di)
+ 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(intent)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+func (h intentHandler) deleteIntentHandler(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"]
+ di := vars["deployment-intent-group-name"]
+
+ err := h.client.DeleteIntent(i, p, ca, v, di)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ w.WriteHeader(http.StatusNoContent)
+}
diff --git a/src/orchestrator/api/api.go b/src/orchestrator/api/api.go
index 5e05b31b..9b33daf2 100644
--- a/src/orchestrator/api/api.go
+++ b/src/orchestrator/api/api.go
@@ -1,33 +1,47 @@
/*
-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,
+ deploymentIntentGrpClient moduleLib.DeploymentIntentGroupManager,
+ intentClient moduleLib.IntentManager,
+ compositeProfileClient moduleLib.CompositeProfileManager,
+ appProfileClient moduleLib.AppProfileManager) *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,17 +62,46 @@ 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")
+ if compositeProfileClient == nil {
+ compositeProfileClient = moduleClient.CompositeProfile
+ }
+ compProfilepHandler := compositeProfileHandler{
+ client: compositeProfileClient,
+ }
+ if appProfileClient == nil {
+ appProfileClient = moduleClient.AppProfile
+ }
+ appProfileHandler := appProfileHandler{
+ client: appProfileClient,
+ }
+
+ 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("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/composite-profiles/{composite-profile-name}/profiles", appProfileHandler.createAppProfileHandler).Methods("POST")
+ router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/composite-profiles/{composite-profile-name}/profiles", appProfileHandler.getAppProfileHandler).Methods("GET")
+ router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/composite-profiles/{composite-profile-name}/profiles", appProfileHandler.getAppProfileHandler).Queries("app-name", "{app-name}")
+ router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/composite-profiles/{composite-profile-name}/profiles/{app-profile}", appProfileHandler.getAppProfileHandler).Methods("GET")
+ router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/composite-profiles/{composite-profile-name}/profiles/{app-profile}", appProfileHandler.deleteAppProfileHandler).Methods("DELETE")
+
+ router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/composite-profiles/{composite-profile-name}/profiles", appProfileHandler.createAppProfileHandler).Methods("POST")
+ router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/composite-profiles/{composite-profile-name}/profiles", appProfileHandler.getAppProfileHandler).Methods("GET")
+ router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/composite-profiles/{composite-profile-name}/profiles", appProfileHandler.getAppProfileHandler).Queries("app-name", "{app-name}")
+ router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/composite-profiles/{composite-profile-name}/profiles/{app-profile}", appProfileHandler.getAppProfileHandler).Methods("GET")
+ router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/composite-profiles/{composite-profile-name}/profiles/{app-profile}", appProfileHandler.deleteAppProfileHandler).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")
@@ -79,6 +122,55 @@ 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")
+
+ //setting routes for deploymentIntentGroup
+ if deploymentIntentGrpClient == nil {
+ deploymentIntentGrpClient = moduleClient.DeploymentIntentGroup
+ }
+
+ deploymentIntentGrpHandler := deploymentIntentGroupHandler{
+ client: deploymentIntentGrpClient,
+ }
+ router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/deployment-intent-groups", deploymentIntentGrpHandler.createDeploymentIntentGroupHandler).Methods("POST")
+ router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/deployment-intent-groups/{deployment-intent-group-name}", deploymentIntentGrpHandler.getDeploymentIntentGroupHandler).Methods("GET")
+ router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/deployment-intent-groups/{deployment-intent-group-name}", deploymentIntentGrpHandler.deleteDeploymentIntentGroupHandler).Methods("DELETE")
+
+ // setting routes for AddingIntents
+ if intentClient == nil {
+ intentClient = moduleClient.Intent
+ }
+
+ intentHandler := intentHandler{
+ client: intentClient,
+ }
+
+ router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/deployment-intent-groups/{deployment-intent-group-name}/intents", intentHandler.addIntentHandler).Methods("POST")
+ router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/deployment-intent-groups/{deployment-intent-group-name}/intents/{intent-name}", intentHandler.getIntentHandler).Methods("GET")
+ router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/deployment-intent-groups/{deployment-intent-group-name}/intents/{intent-name}", intentHandler.deleteIntentHandler).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
index 00000000..ab510c8e
--- /dev/null
+++ b/src/orchestrator/api/app_intent_handler.go
@@ -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)
+}
diff --git a/src/orchestrator/api/app_profilehandler.go b/src/orchestrator/api/app_profilehandler.go
new file mode 100644
index 00000000..16423483
--- /dev/null
+++ b/src/orchestrator/api/app_profilehandler.go
@@ -0,0 +1,266 @@
+/*
+ * 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/base64"
+ "encoding/json"
+ "io"
+ "io/ioutil"
+ "mime"
+ "mime/multipart"
+ "net/http"
+ "net/textproto"
+
+ moduleLib "github.com/onap/multicloud-k8s/src/orchestrator/pkg/module"
+ "github.com/onap/multicloud-k8s/src/orchestrator/utils"
+
+ "github.com/gorilla/mux"
+ pkgerrors "github.com/pkg/errors"
+)
+
+/* Used to store backend implementation objects
+Also simplifies mocking for unit testing purposes
+*/
+type appProfileHandler struct {
+ client moduleLib.AppProfileManager
+}
+
+// createAppProfileHandler handles the create operation
+func (h appProfileHandler) createAppProfileHandler(w http.ResponseWriter, r *http.Request) {
+
+ vars := mux.Vars(r)
+ project := vars["project-name"]
+ compositeApp := vars["composite-app-name"]
+ compositeAppVersion := vars["composite-app-version"]
+ compositeProfile := vars["composite-profile-name"]
+
+ var ap moduleLib.AppProfile
+ var ac moduleLib.AppProfileContent
+
+ // Implemenation using multipart form
+ // Review and enable/remove at a later date
+ // Set Max size to 16mb here
+ err := r.ParseMultipartForm(16777216)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusUnprocessableEntity)
+ return
+ }
+
+ jsn := bytes.NewBuffer([]byte(r.FormValue("metadata")))
+ err = json.NewDecoder(jsn).Decode(&ap)
+ switch {
+ case err == io.EOF:
+ http.Error(w, "Empty body", http.StatusBadRequest)
+ return
+ case err != nil:
+ http.Error(w, err.Error(), http.StatusUnprocessableEntity)
+ return
+ }
+
+ //Read the file section and ignore the header
+ file, _, err := r.FormFile("file")
+ if err != nil {
+ http.Error(w, "Unable to process file", http.StatusUnprocessableEntity)
+ return
+ }
+
+ defer file.Close()
+
+ //Convert the file content to base64 for storage
+ content, err := ioutil.ReadAll(file)
+ if err != nil {
+ http.Error(w, "Unable to read file", http.StatusUnprocessableEntity)
+ return
+ }
+
+ err = utils.IsTarGz(bytes.NewBuffer(content))
+ if err != nil {
+ http.Error(w, "Error in file format", http.StatusUnprocessableEntity)
+ return
+ }
+
+ ac.Profile = base64.StdEncoding.EncodeToString(content)
+
+ // Name is required.
+ if ap.Metadata.Name == "" {
+ http.Error(w, "Missing name in POST request", http.StatusBadRequest)
+ return
+ }
+
+ ret, err := h.client.CreateAppProfile(project, compositeApp, compositeAppVersion, compositeProfile, ap, ac)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.WriteHeader(http.StatusCreated)
+ err = json.NewEncoder(w).Encode(ret)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+// getHandler handles the GET operations on AppProfile
+func (h appProfileHandler) getAppProfileHandler(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ project := vars["project-name"]
+ compositeApp := vars["composite-app-name"]
+ compositeAppVersion := vars["composite-app-version"]
+ compositeProfile := vars["composite-profile-name"]
+ name := vars["app-profile"]
+ appName := r.URL.Query().Get("app-name")
+
+ if len(name) != 0 && len(appName) != 0 {
+ http.Error(w, pkgerrors.New("Invalid query").Error(), http.StatusInternalServerError)
+ return
+ }
+
+ // handle the get all app profiles case - return a list of only the json parts
+ if len(name) == 0 && len(appName) == 0 {
+ var retList []moduleLib.AppProfile
+
+ ret, err := h.client.GetAppProfiles(project, compositeApp, compositeAppVersion, compositeProfile)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ for _, ap := range ret {
+ retList = append(retList, ap)
+ }
+
+ 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
+ }
+
+ accepted, _, err := mime.ParseMediaType(r.Header.Get("Accept"))
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusNotAcceptable)
+ return
+ }
+
+ var retAppProfile moduleLib.AppProfile
+ var retAppProfileContent moduleLib.AppProfileContent
+
+ if len(appName) != 0 {
+ retAppProfile, err = h.client.GetAppProfileByApp(project, compositeApp, compositeAppVersion, compositeProfile, appName)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ retAppProfileContent, err = h.client.GetAppProfileContentByApp(project, compositeApp, compositeAppVersion, compositeProfile, appName)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ } else {
+ retAppProfile, err = h.client.GetAppProfile(project, compositeApp, compositeAppVersion, compositeProfile, name)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ retAppProfileContent, err = h.client.GetAppProfileContent(project, compositeApp, compositeAppVersion, compositeProfile, name)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ }
+
+ switch accepted {
+ case "multipart/form-data":
+ mpw := multipart.NewWriter(w)
+ w.Header().Set("Content-Type", mpw.FormDataContentType())
+ w.WriteHeader(http.StatusOK)
+ pw, err := mpw.CreatePart(textproto.MIMEHeader{"Content-Type": {"application/json"}, "Content-Disposition": {"form-data; name=metadata"}})
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ if err := json.NewEncoder(pw).Encode(retAppProfile); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ pw, err = mpw.CreatePart(textproto.MIMEHeader{"Content-Type": {"application/octet-stream"}, "Content-Disposition": {"form-data; name=file"}})
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ kc_bytes, err := base64.StdEncoding.DecodeString(retAppProfileContent.Profile)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ _, err = pw.Write(kc_bytes)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ case "application/json":
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ err = json.NewEncoder(w).Encode(retAppProfile)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ case "application/octet-stream":
+ w.Header().Set("Content-Type", "application/octet-stream")
+ w.WriteHeader(http.StatusOK)
+ kc_bytes, err := base64.StdEncoding.DecodeString(retAppProfileContent.Profile)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ _, err = w.Write(kc_bytes)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ default:
+ http.Error(w, "set Accept: multipart/form-data, application/json or application/octet-stream", http.StatusMultipleChoices)
+ return
+ }
+}
+
+// deleteHandler handles the delete operations on AppProfile
+func (h appProfileHandler) deleteAppProfileHandler(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ project := vars["project-name"]
+ compositeApp := vars["composite-app-name"]
+ compositeAppVersion := vars["composite-app-version"]
+ compositeProfile := vars["composite-profile-name"]
+ name := vars["app-profile"]
+
+ err := h.client.DeleteAppProfile(project, compositeApp, compositeAppVersion, compositeProfile, name)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.WriteHeader(http.StatusNoContent)
+}
diff --git a/src/orchestrator/api/clusterhandler.go b/src/orchestrator/api/clusterhandler.go
index 7e769f31..ac4191e1 100644
--- a/src/orchestrator/api/clusterhandler.go
+++ b/src/orchestrator/api/clusterhandler.go
@@ -255,12 +255,12 @@ func (h clusterHandler) getClusterHandler(w http.ResponseWriter, r *http.Request
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
- kc_bytes, err := base64.StdEncoding.DecodeString(retKubeconfig.Kubeconfig)
+ kcBytes, err := base64.StdEncoding.DecodeString(retKubeconfig.Kubeconfig)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
- _, err = pw.Write(kc_bytes)
+ _, err = pw.Write(kcBytes)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -276,12 +276,12 @@ func (h clusterHandler) getClusterHandler(w http.ResponseWriter, r *http.Request
case "application/octet-stream":
w.Header().Set("Content-Type", "application/octet-stream")
w.WriteHeader(http.StatusOK)
- kc_bytes, err := base64.StdEncoding.DecodeString(retKubeconfig.Kubeconfig)
+ kcBytes, err := base64.StdEncoding.DecodeString(retKubeconfig.Kubeconfig)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
- _, err = w.Write(kc_bytes)
+ _, err = w.Write(kcBytes)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
diff --git a/src/orchestrator/api/clusterhandler_test.go b/src/orchestrator/api/clusterhandler_test.go
index db1b6f9f..71afdd1b 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))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, 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))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, 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))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, 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))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, 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))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, 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))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, 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))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, 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))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, 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))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, 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))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, 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))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, 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))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, 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))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, 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))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, 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))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, 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))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, 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))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.clusterClient, nil, 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..360653c7
--- /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, nil))
+
+ //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 c91943ad..ab0aeed8 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))
+ resp := executeRequest(request, NewRouter(nil, nil, testCase.controllerClient, nil, 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))
+ resp := executeRequest(request, NewRouter(nil, nil, testCase.controllerClient, nil, 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))
+ resp := executeRequest(request, NewRouter(nil, nil, testCase.controllerClient, nil, nil, nil, nil, nil, nil, nil))
//Check returned code
if resp.StatusCode != testCase.expectedCode {
diff --git a/src/orchestrator/api/deployment_intent_groups_handler.go b/src/orchestrator/api/deployment_intent_groups_handler.go
new file mode 100644
index 00000000..3f5b3969
--- /dev/null
+++ b/src/orchestrator/api/deployment_intent_groups_handler.go
@@ -0,0 +1,133 @@
+/*
+ * 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 deploymentIntentGroupHandler struct {
+ client moduleLib.DeploymentIntentGroupManager
+}
+
+// createDeploymentIntentGroupHandler handles the create operation of DeploymentIntentGroup
+func (h deploymentIntentGroupHandler) createDeploymentIntentGroupHandler(w http.ResponseWriter, r *http.Request) {
+
+ var d moduleLib.DeploymentIntentGroup
+
+ err := json.NewDecoder(r.Body).Decode(&d)
+ 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 d.MetaData.Name == "" {
+ http.Error(w, "Missing deploymentIntentGroupName in POST request", http.StatusBadRequest)
+ return
+ }
+
+ vars := mux.Vars(r)
+ projectName := vars["project-name"]
+ compositeAppName := vars["composite-app-name"]
+ version := vars["composite-app-version"]
+
+ dIntent, createErr := h.client.CreateDeploymentIntentGroup(d, 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(dIntent)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+func (h deploymentIntentGroupHandler) getDeploymentIntentGroupHandler(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
+ }
+
+ di := vars["deployment-intent-group-name"]
+ if v == "" {
+ http.Error(w, "Missing name of DeploymentIntentGroup in GET request", http.StatusBadRequest)
+ return
+ }
+
+ dIntentGrp, err := h.client.GetDeploymentIntentGroup(di, p, ca, v)
+ 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(dIntentGrp)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+}
+
+func (h deploymentIntentGroupHandler) deleteDeploymentIntentGroupHandler(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+
+ p := vars["project-name"]
+ ca := vars["composite-app-name"]
+ v := vars["composite-app-version"]
+ di := vars["deployment-intent-group-name"]
+
+ err := h.client.DeleteDeploymentIntentGroup(di, p, ca, v)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ w.WriteHeader(http.StatusNoContent)
+}
diff --git a/src/orchestrator/api/generic_placement_intent_handler.go b/src/orchestrator/api/generic_placement_intent_handler.go
new file mode 100644
index 00000000..e186735c
--- /dev/null
+++ b/src/orchestrator/api/generic_placement_intent_handler.go
@@ -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)
+}
diff --git a/src/orchestrator/api/projecthandler_test.go b/src/orchestrator/api/projecthandler_test.go
index c6da4e05..af40f961 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))
+ resp := executeRequest(request, NewRouter(testCase.projectClient, nil, 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))
+ resp := executeRequest(request, NewRouter(testCase.projectClient, nil, 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))
+ resp := executeRequest(request, NewRouter(testCase.projectClient, nil, 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 a4c46fe7..f95c057e 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)
+ httpRouter := api.NewRouter(nil, 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/go.mod b/src/orchestrator/go.mod
index d6fada43..547fa8ed 100644
--- a/src/orchestrator/go.mod
+++ b/src/orchestrator/go.mod
@@ -1,6 +1,7 @@
module github.com/onap/multicloud-k8s/src/orchestrator
require (
+ github.com/coreos/etcd v3.3.12+incompatible
github.com/docker/engine v0.0.0-20190620014054-c513a4c6c298
github.com/ghodss/yaml v1.0.0
github.com/gogo/protobuf v1.3.1 // indirect
@@ -32,3 +33,5 @@ replace (
k8s.io/client-go => k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible
k8s.io/cloud-provider => k8s.io/cloud-provider v0.0.0-20190409023720-1bc0c81fa51d
)
+
+go 1.13
diff --git a/src/orchestrator/go.sum b/src/orchestrator/go.sum
index d2015406..aeab3b50 100644
--- a/src/orchestrator/go.sum
+++ b/src/orchestrator/go.sum
@@ -172,6 +172,7 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/onap/multicloud-k8s v0.0.0-20191115005109-f168ebb73d8d h1:3uFucXVv6gqa3H1u85CjoLOvGraREfD8/NL7m/9W9tc=
github.com/onap/multicloud-k8s v0.0.0-20200131010833-90e13d101cf0 h1:2qDo6s4pdg/g7Vj6QGrCK02EP4jjwVehgEObnAfipSM=
+github.com/onap/multicloud-k8s v0.0.0-20200229013830-7b566f287523 h1:hVu6djUEav5nKQvVZZa3FT71ZD9QbCcTI3dM+1chvFU=
github.com/onap/multicloud-k8s/src/k8splugin v0.0.0-20191115005109-f168ebb73d8d h1:ucIEjqzNVeFPnQofeuBfUqro0OnilX//fajEFxuLsgA=
github.com/onap/multicloud-k8s/src/k8splugin v0.0.0-20191115005109-f168ebb73d8d/go.mod h1:EnQd/vQGZR1/55IihaHxiux4ZUig/zfXZux7bfmU0S8=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
diff --git a/src/orchestrator/pkg/infra/db/mock.go b/src/orchestrator/pkg/infra/db/mock.go
index 6b46a524..79366d10 100644
--- a/src/orchestrator/pkg/infra/db/mock.go
+++ b/src/orchestrator/pkg/infra/db/mock.go
@@ -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
}
-
diff --git a/src/orchestrator/pkg/infra/db/mongo_test.go b/src/orchestrator/pkg/infra/db/mongo_test.go
index d506dbda..9f813ca4 100644
--- a/src/orchestrator/pkg/infra/db/mongo_test.go
+++ b/src/orchestrator/pkg/infra/db/mongo_test.go
@@ -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/add_intents.go b/src/orchestrator/pkg/module/add_intents.go
new file mode 100644
index 00000000..a657cce7
--- /dev/null
+++ b/src/orchestrator/pkg/module/add_intents.go
@@ -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 Addlicable 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"
+)
+
+// Intent shall have 2 fields - MetaData and Spec
+type Intent 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 Intent
+type IntentSpecData struct {
+ Intent IntentObj `json:"intent"`
+}
+
+// IntentObj has name of the generic placement intent
+type IntentObj struct {
+ Generic string `json:"generic"`
+}
+
+// ListOfIntents is a list of intents
+type ListOfIntents struct {
+ ListOfIntents []map[string]string `json:"intent"`
+}
+
+// IntentManager is an interface which exposes the IntentManager functionality
+type IntentManager interface {
+ AddIntent(a Intent, p string, ca string, v string, di string) (Intent, error)
+ GetIntent(i string, p string, ca string, v string, di string) (Intent, error)
+ DeleteIntent(i string, p string, ca string, v string, di string) error
+}
+
+// IntentKey consists of Name if the intent, Project name, CompositeApp name,
+// CompositeApp version
+type IntentKey struct {
+ Name string `json:"name"`
+ Project string `json:"project"`
+ CompositeApp string `json:"compositeapp"`
+ Version string `json:"version"`
+ DeploymentIntentGroup string `json:"deployment-intent-group-name"`
+}
+
+// We will use json marshalling to convert to string to
+// preserve the underlying structure.
+func (ik IntentKey) String() string {
+ out, err := json.Marshal(ik)
+ if err != nil {
+ return ""
+ }
+ return string(out)
+}
+
+// IntentClient implements the AddIntentManager interface
+type IntentClient struct {
+ storeName string
+ tagMetaData string
+}
+
+// NewIntentClient returns an instance of AddIntentClient
+func NewIntentClient() *IntentClient {
+ return &IntentClient{
+ storeName: "orchestrator",
+ tagMetaData: "addintent",
+ }
+}
+
+// AddIntent adds a given intent to the deployment-intent-group and stores in the db. Other input parameters for it - projectName, compositeAppName, version, DeploymentIntentgroupName
+func (c *IntentClient) AddIntent(a Intent, p string, ca string, v string, di string) (Intent, error) {
+
+ //Check for the AddIntent already exists here.
+ res, err := c.GetIntent(a.MetaData.Name, p, ca, v, di)
+ if !reflect.DeepEqual(res, Intent{}) {
+ return Intent{}, pkgerrors.New("AppIntent already exists")
+ }
+
+ //Check if project exists
+ _, err = NewProjectClient().GetProject(p)
+ if err != nil {
+ return Intent{}, pkgerrors.New("Unable to find the project")
+ }
+
+ //check if compositeApp exists
+ _, err = NewCompositeAppClient().GetCompositeApp(ca, v, p)
+ if err != nil {
+ return Intent{}, pkgerrors.New("Unable to find the composite-app")
+ }
+
+ //check if DeploymentIntentGroup exists
+ _, err = NewDeploymentIntentGroupClient().GetDeploymentIntentGroup(di, p, ca, v)
+ if err != nil {
+ return Intent{}, pkgerrors.New("Unable to find the intent")
+ }
+
+ akey := IntentKey{
+ Name: a.MetaData.Name,
+ Project: p,
+ CompositeApp: ca,
+ Version: v,
+ DeploymentIntentGroup: di,
+ }
+
+ err = db.DBconn.Insert(c.storeName, akey, nil, c.tagMetaData, a)
+ if err != nil {
+ return Intent{}, pkgerrors.Wrap(err, "Create DB entry error")
+ }
+ return a, nil
+}
+
+// GetIntent returns an Intent
+func (c *IntentClient) GetIntent(i string, p string, ca string, v string, di string) (Intent, error) {
+
+ k := IntentKey{
+ Name: i,
+ Project: p,
+ CompositeApp: ca,
+ Version: v,
+ DeploymentIntentGroup: di,
+ }
+
+ result, err := db.DBconn.Find(c.storeName, k, c.tagMetaData)
+ if err != nil {
+ return Intent{}, pkgerrors.Wrap(err, "Get AppIntent error")
+ }
+
+ if result != nil {
+ a := Intent{}
+ err = db.DBconn.Unmarshal(result[0], &a)
+ if err != nil {
+ return Intent{}, pkgerrors.Wrap(err, "Unmarshalling AppIntent")
+ }
+ return a, nil
+
+ }
+ return Intent{}, pkgerrors.New("Error getting AppIntent")
+}
+
+// DeleteIntent deletes a given intent tied to project, composite app and deployment intent group
+func (c IntentClient) DeleteIntent(i string, p string, ca string, v string, di string) error {
+ k := IntentKey{
+ Name: i,
+ Project: p,
+ CompositeApp: ca,
+ Version: v,
+ DeploymentIntentGroup: di,
+ }
+
+ 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.go b/src/orchestrator/pkg/module/app_intent.go
new file mode 100644
index 00000000..70c80dac
--- /dev/null
+++ b/src/orchestrator/pkg/module/app_intent.go
@@ -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
index 00000000..5a4f7693
--- /dev/null
+++ b/src/orchestrator/pkg/module/app_intent_test.go
@@ -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/app_profile.go b/src/orchestrator/pkg/module/app_profile.go
new file mode 100644
index 00000000..77835fb4
--- /dev/null
+++ b/src/orchestrator/pkg/module/app_profile.go
@@ -0,0 +1,296 @@
+/*
+ * 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 (
+ "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/db"
+
+ pkgerrors "github.com/pkg/errors"
+)
+
+// AppProfile contains the parameters needed for AppProfiles
+// It implements the interface for managing the AppProfiles
+type AppProfile struct {
+ Metadata AppProfileMetadata `json:"metadata"`
+ Spec AppProfileSpec `json:"spec"`
+}
+
+type AppProfileContent struct {
+ Profile string `json:"profile"`
+}
+
+// AppProfileMetadata contains the metadata for AppProfiles
+type AppProfileMetadata struct {
+ Name string `json:"name"`
+ Description string `json:"description"`
+ UserData1 string `json:"userData1"`
+ UserData2 string `json:"userData2"`
+}
+
+// AppProfileSpec contains the Spec for AppProfiles
+type AppProfileSpec struct {
+ AppName string `json:"app-name"`
+}
+
+// AppProfileKey is the key structure that is used in the database
+type AppProfileKey struct {
+ Project string `json:"project"`
+ CompositeApp string `json:"compositeapp"`
+ CompositeAppVersion string `json:"compositeappversion"`
+ CompositeProfile string `json:"compositeprofile"`
+ Profile string `json:"profile"`
+}
+
+type AppProfileQueryKey struct {
+ AppName string `json:"app-name"`
+}
+
+type AppProfileFindByAppKey struct {
+ Project string `json:"project"`
+ CompositeApp string `json:"compositeapp"`
+ CompositeAppVersion string `json:"compositeappversion"`
+ CompositeProfile string `json:"compositeprofile"`
+ AppName string `json:"app-name"`
+}
+
+// AppProfileManager exposes the AppProfile functionality
+type AppProfileManager interface {
+ CreateAppProfile(provider, compositeApp, compositeAppVersion, compositeProfile string, ap AppProfile, ac AppProfileContent) (AppProfile, error)
+ GetAppProfile(project, compositeApp, compositeAppVersion, compositeProfile, profile string) (AppProfile, error)
+ GetAppProfiles(project, compositeApp, compositeAppVersion, compositeProfile string) ([]AppProfile, error)
+ GetAppProfileByApp(project, compositeApp, compositeAppVersion, compositeProfile, appName string) (AppProfile, error)
+ GetAppProfileContent(project, compositeApp, compositeAppVersion, compositeProfile, profile string) (AppProfileContent, error)
+ GetAppProfileContentByApp(project, compositeApp, compositeAppVersion, compositeProfile, appName string) (AppProfileContent, error)
+ DeleteAppProfile(project, compositeApp, compositeAppVersion, compositeProfile, profile string) error
+}
+
+// AppProfileClient implements the Manager
+// It will also be used to maintain some localized state
+type AppProfileClient struct {
+ storeName string
+ tagMeta string
+ tagContent string
+}
+
+// NewAppProfileClient returns an instance of the AppProfileClient
+// which implements the Manager
+func NewAppProfileClient() *AppProfileClient {
+ return &AppProfileClient{
+ storeName: "orchestrator",
+ tagMeta: "profilemetadata",
+ tagContent: "profilecontent",
+ }
+}
+
+// CreateAppProfile creates an entry for AppProfile in the database.
+func (c *AppProfileClient) CreateAppProfile(project, compositeApp, compositeAppVersion, compositeProfile string, ap AppProfile, ac AppProfileContent) (AppProfile, error) {
+ key := AppProfileKey{
+ Project: project,
+ CompositeApp: compositeApp,
+ CompositeAppVersion: compositeAppVersion,
+ CompositeProfile: compositeProfile,
+ Profile: ap.Metadata.Name,
+ }
+ qkey := AppProfileQueryKey{
+ AppName: ap.Spec.AppName,
+ }
+
+ res, err := c.GetAppProfile(project, compositeApp, compositeAppVersion, compositeProfile, ap.Metadata.Name)
+ if res != (AppProfile{}) {
+ return AppProfile{}, pkgerrors.New("AppProfile already exists")
+ }
+
+ res, err = c.GetAppProfileByApp(project, compositeApp, compositeAppVersion, compositeProfile, ap.Spec.AppName)
+ if res != (AppProfile{}) {
+ return AppProfile{}, pkgerrors.New("App already has an AppProfile")
+ }
+
+ //Check if composite profile exists (success assumes existance of all higher level 'parent' objects)
+ _, err = NewCompositeProfileClient().GetCompositeProfile(compositeProfile, project, compositeApp, compositeAppVersion)
+ if err != nil {
+ return AppProfile{}, pkgerrors.New("Unable to find the project")
+ }
+
+ // TODO: (after app api is ready) check that the app Spec.AppName exists as part of the composite app
+
+ err = db.DBconn.Insert(c.storeName, key, qkey, c.tagMeta, ap)
+ if err != nil {
+ return AppProfile{}, pkgerrors.Wrap(err, "Creating DB Entry")
+ }
+ err = db.DBconn.Insert(c.storeName, key, qkey, c.tagContent, ac)
+ if err != nil {
+ return AppProfile{}, pkgerrors.Wrap(err, "Creating DB Entry")
+ }
+
+ return ap, nil
+}
+
+// GetAppProfile - return specified App Profile
+func (c *AppProfileClient) GetAppProfile(project, compositeApp, compositeAppVersion, compositeProfile, profile string) (AppProfile, error) {
+ key := AppProfileKey{
+ Project: project,
+ CompositeApp: compositeApp,
+ CompositeAppVersion: compositeAppVersion,
+ CompositeProfile: compositeProfile,
+ Profile: profile,
+ }
+
+ value, err := db.DBconn.Find(c.storeName, key, c.tagMeta)
+ if err != nil {
+ return AppProfile{}, pkgerrors.Wrap(err, "Get App Profile error")
+ }
+
+ if value != nil {
+ ap := AppProfile{}
+ err = db.DBconn.Unmarshal(value[0], &ap)
+ if err != nil {
+ return AppProfile{}, pkgerrors.Wrap(err, "Unmarshalling AppProfile")
+ }
+ return ap, nil
+ }
+
+ return AppProfile{}, pkgerrors.New("Error getting AppProfile")
+
+}
+
+// GetAppProfile - return all App Profiles for given composite profile
+func (c *AppProfileClient) GetAppProfiles(project, compositeApp, compositeAppVersion, compositeProfile string) ([]AppProfile, error) {
+
+ key := AppProfileKey{
+ Project: project,
+ CompositeApp: compositeApp,
+ CompositeAppVersion: compositeAppVersion,
+ CompositeProfile: compositeProfile,
+ Profile: "",
+ }
+
+ var resp []AppProfile
+ values, err := db.DBconn.Find(c.storeName, key, c.tagMeta)
+ if err != nil {
+ return []AppProfile{}, pkgerrors.Wrap(err, "Get AppProfiles")
+ }
+
+ for _, value := range values {
+ ap := AppProfile{}
+ err = db.DBconn.Unmarshal(value, &ap)
+ if err != nil {
+ return []AppProfile{}, pkgerrors.Wrap(err, "Unmarshaling Value")
+ }
+ resp = append(resp, ap)
+ }
+
+ return resp, nil
+}
+
+// GetAppProfileByApp - return all App Profiles for given composite profile
+func (c *AppProfileClient) GetAppProfileByApp(project, compositeApp, compositeAppVersion, compositeProfile, appName string) (AppProfile, error) {
+
+ key := AppProfileFindByAppKey{
+ Project: project,
+ CompositeApp: compositeApp,
+ CompositeAppVersion: compositeAppVersion,
+ CompositeProfile: compositeProfile,
+ AppName: appName,
+ }
+
+ value, err := db.DBconn.Find(c.storeName, key, c.tagMeta)
+ if err != nil {
+ return AppProfile{}, pkgerrors.Wrap(err, "Get AppProfile by App")
+ }
+
+ if value != nil {
+ ap := AppProfile{}
+ err = db.DBconn.Unmarshal(value[0], &ap)
+ if err != nil {
+ return AppProfile{}, pkgerrors.Wrap(err, "Unmarshalling AppProfile")
+ }
+ return ap, nil
+ }
+
+ return AppProfile{}, pkgerrors.New("Error getting AppProfile by App")
+}
+
+func (c *AppProfileClient) GetAppProfileContent(project, compositeApp, compositeAppVersion, compositeProfile, profile string) (AppProfileContent, error) {
+ key := AppProfileKey{
+ Project: project,
+ CompositeApp: compositeApp,
+ CompositeAppVersion: compositeAppVersion,
+ CompositeProfile: compositeProfile,
+ Profile: profile,
+ }
+
+ value, err := db.DBconn.Find(c.storeName, key, c.tagContent)
+ if err != nil {
+ return AppProfileContent{}, pkgerrors.Wrap(err, "Get Cluster Content")
+ }
+
+ //value is a byte array
+ if value != nil {
+ ac := AppProfileContent{}
+ err = db.DBconn.Unmarshal(value[0], &ac)
+ if err != nil {
+ return AppProfileContent{}, pkgerrors.Wrap(err, "Unmarshaling Value")
+ }
+ return ac, nil
+ }
+
+ return AppProfileContent{}, pkgerrors.New("Error getting App Profile Content")
+}
+
+func (c *AppProfileClient) GetAppProfileContentByApp(project, compositeApp, compositeAppVersion, compositeProfile, appName string) (AppProfileContent, error) {
+ key := AppProfileFindByAppKey{
+ Project: project,
+ CompositeApp: compositeApp,
+ CompositeAppVersion: compositeAppVersion,
+ CompositeProfile: compositeProfile,
+ AppName: appName,
+ }
+
+ value, err := db.DBconn.Find(c.storeName, key, c.tagContent)
+ if err != nil {
+ return AppProfileContent{}, pkgerrors.Wrap(err, "Get Cluster Content")
+ }
+
+ //value is a byte array
+ if value != nil {
+ ac := AppProfileContent{}
+ err = db.DBconn.Unmarshal(value[0], &ac)
+ if err != nil {
+ return AppProfileContent{}, pkgerrors.Wrap(err, "Unmarshaling Value")
+ }
+ return ac, nil
+ }
+
+ return AppProfileContent{}, pkgerrors.New("Error getting App Profile Content")
+}
+
+// Delete AppProfile from the database
+func (c *AppProfileClient) DeleteAppProfile(project, compositeApp, compositeAppVersion, compositeProfile, profile string) error {
+ key := AppProfileKey{
+ Project: project,
+ CompositeApp: compositeApp,
+ CompositeAppVersion: compositeAppVersion,
+ CompositeProfile: compositeProfile,
+ Profile: profile,
+ }
+
+ err := db.DBconn.Remove(c.storeName, key)
+ if err != nil {
+ return pkgerrors.Wrap(err, "Delete AppProfile entry;")
+ }
+ return nil
+}
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/deployment_intent_groups.go b/src/orchestrator/pkg/module/deployment_intent_groups.go
new file mode 100644
index 00000000..fe622771
--- /dev/null
+++ b/src/orchestrator/pkg/module/deployment_intent_groups.go
@@ -0,0 +1,177 @@
+/*
+ * 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"
+ "reflect"
+
+ pkgerrors "github.com/pkg/errors"
+)
+
+// DeploymentIntentGroup shall have 2 fields - MetaData and Spec
+type DeploymentIntentGroup struct {
+ MetaData DepMetaData `json:"metadata"`
+ Spec DepSpecData `json:"spec"`
+}
+
+// DepMetaData has Name, description, userdata1, userdata2
+type DepMetaData struct {
+ Name string `json:"name"`
+ Description string `json:"description"`
+ UserData1 string `json:"userData1"`
+ UserData2 string `json:"userData2"`
+}
+
+// DepSpecData has profile, version, OverrideValuesObj
+type DepSpecData struct {
+ Profile string `json:"profile"`
+ Version string `json:"version"`
+ OverrideValuesObj []OverrideValues `json:"override-values"`
+}
+
+// OverrideValues has appName and ValuesObj
+type OverrideValues struct {
+ AppName string `json:"app-name"`
+ ValuesObj map[string]string `json:"values"`
+}
+
+// Values has ImageRepository
+// type Values struct {
+// ImageRepository string `json:"imageRepository"`
+// }
+
+// DeploymentIntentGroupManager is an interface which exposes the DeploymentIntentGroupManager functionality
+type DeploymentIntentGroupManager interface {
+ CreateDeploymentIntentGroup(d DeploymentIntentGroup, p string, ca string, v string) (DeploymentIntentGroup, error)
+ GetDeploymentIntentGroup(di string, p string, ca string, v string) (DeploymentIntentGroup, error)
+ DeleteDeploymentIntentGroup(di string, p string, ca string, v string) error
+}
+
+// DeploymentIntentGroupKey consists of Name of the deployment group, project name, CompositeApp name, CompositeApp version
+type DeploymentIntentGroupKey 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 (dk DeploymentIntentGroupKey) String() string {
+ out, err := json.Marshal(dk)
+ if err != nil {
+ return ""
+ }
+ return string(out)
+}
+
+// DeploymentIntentGroupClient implements the DeploymentIntentGroupManager interface
+type DeploymentIntentGroupClient struct {
+ storeName string
+ tagMetaData string
+}
+
+// NewDeploymentIntentGroupClient return an instance of DeploymentIntentGroupClient which implements DeploymentIntentGroupManager
+func NewDeploymentIntentGroupClient() *DeploymentIntentGroupClient {
+ return &DeploymentIntentGroupClient{
+ storeName: "orchestrator",
+ tagMetaData: "deploymentintentgroup",
+ }
+}
+
+// CreateDeploymentIntentGroup creates an entry for a given DeploymentIntentGroup in the database. Other Input parameters for it - projectName, compositeAppName, version
+func (c *DeploymentIntentGroupClient) CreateDeploymentIntentGroup(d DeploymentIntentGroup, p string, ca string,
+ v string) (DeploymentIntentGroup, error) {
+
+ res, err := c.GetDeploymentIntentGroup(d.MetaData.Name, p, ca, v)
+ if !reflect.DeepEqual(res, DeploymentIntentGroup{}) {
+ return DeploymentIntentGroup{}, pkgerrors.New("AppIntent already exists")
+ }
+
+ //Check if project exists
+ _, err = NewProjectClient().GetProject(p)
+ if err != nil {
+ return DeploymentIntentGroup{}, pkgerrors.New("Unable to find the project")
+ }
+
+ //check if compositeApp exists
+ _, err = NewCompositeAppClient().GetCompositeApp(ca, v, p)
+ if err != nil {
+ return DeploymentIntentGroup{}, pkgerrors.New("Unable to find the composite-app")
+ }
+
+ gkey := DeploymentIntentGroupKey{
+ Name: d.MetaData.Name,
+ Project: p,
+ CompositeApp: ca,
+ Version: v,
+ }
+
+ err = db.DBconn.Insert(c.storeName, gkey, nil, c.tagMetaData, d)
+ if err != nil {
+ return DeploymentIntentGroup{}, pkgerrors.Wrap(err, "Create DB entry error")
+ }
+
+ return d, nil
+}
+
+// GetDeploymentIntentGroup returns the DeploymentIntentGroup with a given name, project, compositeApp and version of compositeApp
+func (c *DeploymentIntentGroupClient) GetDeploymentIntentGroup(di string, p string, ca string, v string) (DeploymentIntentGroup, error) {
+
+ key := DeploymentIntentGroupKey{
+ Name: di,
+ Project: p,
+ CompositeApp: ca,
+ Version: v,
+ }
+
+ result, err := db.DBconn.Find(c.storeName, key, c.tagMetaData)
+ if err != nil {
+ return DeploymentIntentGroup{}, pkgerrors.Wrap(err, "Get DeploymentIntentGroup error")
+ }
+
+ if result != nil {
+ d := DeploymentIntentGroup{}
+ err = db.DBconn.Unmarshal(result[0], &d)
+ if err != nil {
+ return DeploymentIntentGroup{}, pkgerrors.Wrap(err, "Unmarshalling DeploymentIntentGroup")
+ }
+ return d, nil
+ }
+
+ return DeploymentIntentGroup{}, pkgerrors.New("Error getting DeploymentIntentGroup")
+
+}
+
+// DeleteDeploymentIntentGroup deletes a DeploymentIntentGroup
+func (c *DeploymentIntentGroupClient) DeleteDeploymentIntentGroup(di string, p string, ca string, v string) error {
+ k := DeploymentIntentGroupKey{
+ Name: di,
+ Project: p,
+ CompositeApp: ca,
+ Version: v,
+ }
+
+ err := db.DBconn.Remove(c.storeName, k)
+ if err != nil {
+ return pkgerrors.Wrap(err, "Delete DeploymentIntentGroup entry;")
+ }
+ return nil
+
+}
diff --git a/src/orchestrator/pkg/module/deployment_intent_groups_test.go b/src/orchestrator/pkg/module/deployment_intent_groups_test.go
new file mode 100644
index 00000000..c0876ceb
--- /dev/null
+++ b/src/orchestrator/pkg/module/deployment_intent_groups_test.go
@@ -0,0 +1,230 @@
+/*
+ * 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 TestCreateDeploymentIntentGroup(t *testing.T) {
+ testCases := []struct {
+ label string
+ inputDeploymentIntentGrp DeploymentIntentGroup
+ inputProject string
+ inputCompositeApp string
+ inputCompositeAppVersion string
+ expectedError string
+ mockdb *db.MockDB
+ expected DeploymentIntentGroup
+ }{
+ {
+ label: "Create DeploymentIntentGroup",
+ inputDeploymentIntentGrp: DeploymentIntentGroup{
+ MetaData: DepMetaData{
+ Name: "testDeploymentIntentGroup",
+ Description: "DescriptionTestDeploymentIntentGroup",
+ UserData1: "userData1",
+ UserData2: "userData2",
+ },
+ Spec: DepSpecData{
+ Profile: "Testprofile",
+ Version: "version of deployment",
+ OverrideValuesObj: []OverrideValues{
+ {AppName: "TestAppName",
+ ValuesObj: map[string]string{
+ "imageRepository": "registry.hub.docker.com",
+ }},
+ {AppName: "TestAppName",
+ ValuesObj: map[string]string{
+ "imageRepository": "registry.hub.docker.com",
+ }},
+ },
+ },
+ },
+ inputProject: "testProject",
+ inputCompositeApp: "testCompositeApp",
+ inputCompositeAppVersion: "testCompositeAppVersion",
+ expected: DeploymentIntentGroup{
+ MetaData: DepMetaData{
+ Name: "testDeploymentIntentGroup",
+ Description: "DescriptionTestDeploymentIntentGroup",
+ UserData1: "userData1",
+ UserData2: "userData2",
+ },
+ Spec: DepSpecData{
+ Profile: "Testprofile",
+ Version: "version of deployment",
+ OverrideValuesObj: []OverrideValues{
+ {AppName: "TestAppName",
+ ValuesObj: map[string]string{
+ "imageRepository": "registry.hub.docker.com",
+ }},
+ {AppName: "TestAppName",
+ ValuesObj: map[string]string{
+ "imageRepository": "registry.hub.docker.com",
+ }},
+ },
+ },
+ },
+ 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
+ depIntentCli := NewDeploymentIntentGroupClient()
+ got, err := depIntentCli.CreateDeploymentIntentGroup(testCase.inputDeploymentIntentGrp, testCase.inputProject, testCase.inputCompositeApp, testCase.inputCompositeAppVersion)
+ if err != nil {
+ if testCase.expectedError == "" {
+ t.Fatalf("CreateDeploymentIntentGroup returned an unexpected error %s, ", err)
+ }
+ if strings.Contains(err.Error(), testCase.expectedError) == false {
+ t.Fatalf("CreateDeploymentIntentGroup returned an unexpected error %s", err)
+ }
+ } else {
+ if reflect.DeepEqual(testCase.expected, got) == false {
+ t.Errorf("CreateDeploymentIntentGroup returned unexpected body: got %v; "+" expected %v", got, testCase.expected)
+ }
+ }
+ })
+ }
+}
+
+func TestGetDeploymentIntentGroup(t *testing.T) {
+ testCases := []struct {
+ label string
+ inputDeploymentIntentGrp string
+ inputProject string
+ inputCompositeApp string
+ inputCompositeAppVersion string
+ expected DeploymentIntentGroup
+ expectedError string
+ mockdb *db.MockDB
+ }{
+ {
+ label: "Get DeploymentIntentGroup",
+ inputDeploymentIntentGrp: "testDeploymentIntentGroup",
+ inputProject: "testProject",
+ inputCompositeApp: "testCompositeApp",
+ inputCompositeAppVersion: "testCompositeAppVersion",
+ expected: DeploymentIntentGroup{
+ MetaData: DepMetaData{
+ Name: "testDeploymentIntentGroup",
+ Description: "DescriptionTestDeploymentIntentGroup",
+ UserData1: "userData1",
+ UserData2: "userData2",
+ },
+ Spec: DepSpecData{
+ Profile: "Testprofile",
+ Version: "version of deployment",
+ OverrideValuesObj: []OverrideValues{
+ {AppName: "TestAppName",
+ ValuesObj: map[string]string{
+ "imageRepository": "registry.hub.docker.com",
+ }},
+ {AppName: "TestAppName",
+ ValuesObj: map[string]string{
+ "imageRepository": "registry.hub.docker.com",
+ }},
+ },
+ },
+ },
+ expectedError: "",
+ mockdb: &db.MockDB{
+ Items: map[string]map[string][]byte{
+ DeploymentIntentGroupKey{
+ Name: "testDeploymentIntentGroup",
+ Project: "testProject",
+ CompositeApp: "testCompositeApp",
+ Version: "testCompositeAppVersion",
+ }.String(): {
+ "deploymentintentgroup": []byte(
+ "{\"metadata\":{\"name\":\"testDeploymentIntentGroup\"," +
+ "\"description\":\"DescriptionTestDeploymentIntentGroup\"," +
+ "\"userData1\": \"userData1\"," +
+ "\"userData2\": \"userData2\"}," +
+ "\"spec\":{\"profile\": \"Testprofile\"," +
+ "\"version\": \"version of deployment\"," +
+ "\"override-values\":[" +
+ "{" +
+ "\"app-name\": \"TestAppName\"," +
+ "\"values\": " +
+ "{" +
+ "\"imageRepository\":\"registry.hub.docker.com\"" +
+ "}" +
+ "}," +
+ "{" +
+ "\"app-name\": \"TestAppName\"," +
+ "\"values\": " +
+ "{" +
+ "\"imageRepository\":\"registry.hub.docker.com\"" +
+ "}" +
+ "}" +
+ "]}}"),
+ },
+ },
+ },
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ db.DBconn = testCase.mockdb
+ depIntentCli := NewDeploymentIntentGroupClient()
+ got, err := depIntentCli.GetDeploymentIntentGroup(testCase.inputDeploymentIntentGrp, testCase.inputProject, testCase.inputCompositeApp, testCase.inputCompositeAppVersion)
+ if err != nil {
+ if testCase.expectedError == "" {
+ t.Fatalf("GetDeploymentIntentGroup returned an unexpected error: %s", err)
+ }
+ if strings.Contains(err.Error(), testCase.expectedError) == false {
+ t.Fatalf("GetDeploymentIntentGroup returned an unexpected error: %s", err)
+ }
+ } else {
+ if reflect.DeepEqual(testCase.expected, got) == false {
+ t.Errorf("GetDeploymentIntentGroup 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
index 00000000..8e739fc8
--- /dev/null
+++ b/src/orchestrator/pkg/module/generic_placement_intent.go
@@ -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 GenIntentMetaData `json:"metadata"`
+ Spec GenIntentSpecData `json:"spec"`
+}
+
+// GenIntentMetaData has name, description, userdata1, userdata2
+type GenIntentMetaData struct {
+ Name string `json:"name"`
+ Description string `json:"description"`
+ UserData1 string `json:"userData1"`
+ UserData2 string `json:"userData2"`
+}
+
+// GenIntentSpecData has logical-cloud-name
+type GenIntentSpecData 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
index 00000000..c87f9ddc
--- /dev/null
+++ b/src/orchestrator/pkg/module/generic_placement_intent_test.go
@@ -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: GenIntentMetaData{
+ Name: "testGenericPlacement",
+ Description: " A sample intent for testing",
+ UserData1: "userData1",
+ UserData2: "userData2",
+ },
+ Spec: GenIntentSpecData{
+ LogicalCloud: "logicalCloud1",
+ },
+ },
+ inputProject: "testProject",
+ inputCompositeApp: "testCompositeApp",
+ inputCompositeAppVersion: "testCompositeAppVersion",
+ expected: GenericPlacementIntent{
+ MetaData: GenIntentMetaData{
+ Name: "testGenericPlacement",
+ Description: " A sample intent for testing",
+ UserData1: "userData1",
+ UserData2: "userData2",
+ },
+ Spec: GenIntentSpecData{
+ 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: GenIntentMetaData{
+ Name: "testIntent",
+ Description: "A sample intent for testing",
+ UserData1: "userData1",
+ UserData2: "userData2",
+ },
+ Spec: GenIntentSpecData{
+ 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)
+ }
+ }
+
+ })
+ }
+
+}
diff --git a/src/orchestrator/pkg/module/module.go b/src/orchestrator/pkg/module/module.go
index f80ff55b..8f2948dd 100644
--- a/src/orchestrator/pkg/module/module.go
+++ b/src/orchestrator/pkg/module/module.go
@@ -18,10 +18,16 @@ package module
// Client for using the services in the orchestrator
type Client struct {
- Project *ProjectClient
- CompositeApp *CompositeAppClient
- Controller *ControllerClient
- Cluster *ClusterClient
+ Project *ProjectClient
+ CompositeApp *CompositeAppClient
+ Controller *ControllerClient
+ Cluster *ClusterClient
+ GenericPlacementIntent *GenericPlacementIntentClient
+ AppIntent *AppIntentClient
+ DeploymentIntentGroup *DeploymentIntentGroupClient
+ Intent *IntentClient
+ CompositeProfile *CompositeProfileClient
+ AppProfile *AppProfileClient
// Add Clients for API's here
}
@@ -32,6 +38,12 @@ func NewClient() *Client {
c.CompositeApp = NewCompositeAppClient()
c.Controller = NewControllerClient()
c.Cluster = NewClusterClient()
+ c.GenericPlacementIntent = NewGenericPlacementIntentClient()
+ c.AppIntent = NewAppIntentClient()
+ c.DeploymentIntentGroup = NewDeploymentIntentGroupClient()
+ c.Intent = NewIntentClient()
+ c.CompositeProfile = NewCompositeProfileClient()
+ c.AppProfile = NewAppProfileClient()
// Add Client API handlers here
return c
}
diff --git a/src/orchestrator/utils/utils.go b/src/orchestrator/utils/utils.go
new file mode 100644
index 00000000..44cf5120
--- /dev/null
+++ b/src/orchestrator/utils/utils.go
@@ -0,0 +1,66 @@
+/*
+ * 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 utils
+
+import (
+ "archive/tar"
+ "compress/gzip"
+ "io"
+
+ pkgerrors "github.com/pkg/errors"
+)
+
+func IsTarGz(r io.Reader) error {
+ //Check if it is a valid gz
+ gzf, err := gzip.NewReader(r)
+ if err != nil {
+ return pkgerrors.Wrap(err, "Invalid gzip format")
+ }
+
+ //Check if it is a valid tar file
+ //Unfortunately this can only be done by inspecting all the tar contents
+ tarR := tar.NewReader(gzf)
+ first := true
+
+ for true {
+ header, err := tarR.Next()
+
+ if err == io.EOF {
+ //Check if we have just a gzip file without a tar archive inside
+ if first {
+ return pkgerrors.New("Empty or non-existant Tar file found")
+ }
+ //End of archive
+ break
+ }
+
+ if err != nil {
+ return pkgerrors.Errorf("Error reading tar file %s", err.Error())
+ }
+
+ //Check if files are of type directory and regular file
+ if header.Typeflag != tar.TypeDir &&
+ header.Typeflag != tar.TypeReg {
+ return pkgerrors.Errorf("Unknown header in tar %s, %s",
+ header.Name, string(header.Typeflag))
+ }
+
+ first = false
+ }
+
+ return nil
+}
diff --git a/src/orchestrator/utils/utils_test.go b/src/orchestrator/utils/utils_test.go
new file mode 100644
index 00000000..63230e49
--- /dev/null
+++ b/src/orchestrator/utils/utils_test.go
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2018 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 utils
+
+import (
+ "bytes"
+ "testing"
+)
+
+func TestIsTarGz(t *testing.T) {
+
+ t.Run("Valid tar.gz", func(t *testing.T) {
+ content := []byte{
+ 0x1f, 0x8b, 0x08, 0x08, 0xb0, 0x6b, 0xf4, 0x5b,
+ 0x00, 0x03, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x74,
+ 0x61, 0x72, 0x00, 0xed, 0xce, 0x41, 0x0a, 0xc2,
+ 0x30, 0x10, 0x85, 0xe1, 0xac, 0x3d, 0x45, 0x4e,
+ 0x50, 0x12, 0xd2, 0xc4, 0xe3, 0x48, 0xa0, 0x01,
+ 0x4b, 0x52, 0x0b, 0xed, 0x88, 0x1e, 0xdf, 0x48,
+ 0x11, 0x5c, 0x08, 0xa5, 0x8b, 0x52, 0x84, 0xff,
+ 0xdb, 0xbc, 0x61, 0x66, 0x16, 0x4f, 0xd2, 0x2c,
+ 0x8d, 0x3c, 0x45, 0xed, 0xc8, 0x54, 0x21, 0xb4,
+ 0xef, 0xb4, 0x67, 0x6f, 0xbe, 0x73, 0x61, 0x9d,
+ 0xb2, 0xce, 0xd5, 0x55, 0xf0, 0xde, 0xd7, 0x3f,
+ 0xdb, 0xd6, 0x49, 0x69, 0xb3, 0x67, 0xa9, 0x8f,
+ 0xfb, 0x2c, 0x71, 0xd2, 0x5a, 0xc5, 0xee, 0x92,
+ 0x73, 0x8e, 0x43, 0x7f, 0x4b, 0x3f, 0xff, 0xd6,
+ 0xee, 0x7f, 0xea, 0x9a, 0x4a, 0x19, 0x1f, 0xe3,
+ 0x54, 0xba, 0xd3, 0xd1, 0x55, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x1b, 0xbc, 0x00, 0xb5, 0xe8,
+ 0x4a, 0xf9, 0x00, 0x28, 0x00, 0x00,
+ }
+
+ err := IsTarGz(bytes.NewBuffer(content))
+ if err != nil {
+ t.Errorf("Error reading valid tar.gz file %s", err.Error())
+ }
+ })
+
+ t.Run("Invalid tar.gz", func(t *testing.T) {
+ content := []byte{
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xff, 0xf2, 0x48, 0xcd,
+ }
+
+ err := IsTarGz(bytes.NewBuffer(content))
+ if err == nil {
+ t.Errorf("Error should NOT be nil")
+ }
+ })
+
+ t.Run("Empty tar.gz", func(t *testing.T) {
+ content := []byte{}
+ err := IsTarGz(bytes.NewBuffer(content))
+ if err == nil {
+ t.Errorf("Error should NOT be nil")
+ }
+ })
+}