summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/inventory/Makefile39
-rw-r--r--src/inventory/api/aaipullapi.go77
-rw-r--r--src/inventory/api/aaipullapi_test.go68
-rw-r--r--src/inventory/api/aaipushapi.go189
-rw-r--r--src/inventory/api/aaipushapi_test.go131
-rw-r--r--src/inventory/api/k8spluginapi.go120
-rw-r--r--src/inventory/api/k8spluginapi_test.go64
-rw-r--r--src/inventory/constants/const.go94
-rw-r--r--src/inventory/controller/main.go83
-rw-r--r--src/inventory/go.mod8
-rw-r--r--src/inventory/go.sum458
-rw-r--r--src/inventory/utils/util.go119
-rw-r--r--src/inventory/utils/util_test.go126
-rw-r--r--src/k8splugin/internal/app/instance.go2
-rw-r--r--src/k8splugin/internal/rb/archive.go4
-rw-r--r--src/k8splugin/internal/rb/archive_test.go44
-rw-r--r--src/ncm/Makefile36
-rw-r--r--src/ncm/api/api.go107
-rw-r--r--src/ncm/api/clusterhandler.go468
-rw-r--r--src/ncm/api/clusterhandler_test.go1412
-rw-r--r--src/ncm/api/networkhandler.go215
-rw-r--r--src/ncm/api/providernethandler.go268
-rw-r--r--src/ncm/api/testing.go31
-rw-r--r--src/ncm/cmd/main.go77
-rw-r--r--src/ncm/go.mod31
-rw-r--r--src/ncm/pkg/module/cluster.go532
-rw-r--r--src/ncm/pkg/module/module.go35
-rw-r--r--src/ncm/pkg/module/module_definitions.go101
-rw-r--r--src/ncm/pkg/module/network.go170
-rw-r--r--src/ncm/pkg/module/providernet.go172
-rw-r--r--src/ncm/scripts/Dockerfile30
-rw-r--r--src/ncm/tests/certs/auth_test_certificate.pem21
-rw-r--r--src/ncm/tests/certs/auth_test_key.pem28
-rw-r--r--src/ncm/tests/configs/mock_config.json5
-rw-r--r--src/orchestrator/api/add_intents_handler.go136
-rw-r--r--src/orchestrator/api/api.go137
-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/apphandler.go248
-rw-r--r--src/orchestrator/api/composite_app_handler.go1
-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.go4
-rw-r--r--src/orchestrator/go.mod3
-rw-r--r--src/orchestrator/go.sum1
-rw-r--r--src/orchestrator/pkg/infra/contextdb/contextdb.go2
-rw-r--r--src/orchestrator/pkg/infra/contextdb/etcd.go15
-rw-r--r--src/orchestrator/pkg/infra/db/README.md12
-rw-r--r--src/orchestrator/pkg/infra/db/mock.go27
-rw-r--r--src/orchestrator/pkg/infra/db/mongo.go32
-rw-r--r--src/orchestrator/pkg/infra/db/mongo_test.go8
-rw-r--r--src/orchestrator/pkg/infra/db/store.go14
-rw-r--r--src/orchestrator/pkg/infra/db/store_test.go4
-rw-r--r--src/orchestrator/pkg/infra/validation/validation.go264
-rw-r--r--src/orchestrator/pkg/infra/validation/validation_test.go466
-rw-r--r--src/orchestrator/pkg/module/add_intents.go184
-rw-r--r--src/orchestrator/pkg/module/app.go231
-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/app_test.go327
-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/compositeapp.go18
-rw-r--r--src/orchestrator/pkg/module/compositeapp_test.go236
-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/pkg/module/project.go8
-rw-r--r--src/orchestrator/pkg/rtcontext/rtcontext.go238
-rw-r--r--src/orchestrator/pkg/rtcontext/rtcontext_test.go596
77 files changed, 11404 insertions, 59 deletions
diff --git a/src/inventory/Makefile b/src/inventory/Makefile
new file mode 100644
index 00000000..3eff12dc
--- /dev/null
+++ b/src/inventory/Makefile
@@ -0,0 +1,39 @@
+# SPDX-license-identifier: Apache-2.0
+##############################################################################
+# Copyright (c) 2020 Tech Mahindra
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+PWD := $(shell pwd)
+PLATFORM := linux
+BINARY := statusquey
+
+export GO111MODULE=on
+
+all: test build
+deploy: test build
+
+build: clean test cover
+ CGO_ENABLED=0 GOOS=$(PLATFORM) GOARCH=amd64
+ go build -a -ldflags '-extldflags "-static"' \
+ -o $(PWD)/$(BINARY) controller/main.go
+
+deploy: build
+
+.PHONY: test
+test: clean
+ @go test -v ./...
+
+format:
+ @go fmt ./...
+
+clean:
+ @rm -f $(BINARY)
+
+.PHONY: cover
+cover:
+ @go test -p 2 ./... -coverprofile=coverage.out
+ @go tool cover -html=coverage.out -o coverage.html
diff --git a/src/inventory/api/aaipullapi.go b/src/inventory/api/aaipullapi.go
new file mode 100644
index 00000000..c8e4ca38
--- /dev/null
+++ b/src/inventory/api/aaipullapi.go
@@ -0,0 +1,77 @@
+/*
+Copyright 2020 Tech Mahindra.
+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 (
+ "crypto/tls"
+ "encoding/json"
+ con "github.com/onap/multicloud-k8s/src/inventory/constants"
+ log "github.com/onap/multicloud-k8s/src/inventory/logutils"
+ util "github.com/onap/multicloud-k8s/src/inventory/utils"
+ "io/ioutil"
+ "net/http"
+ "os"
+)
+
+func GetTenant(cloudOwner, cloudRegion string) string {
+
+ AAI_URI := os.Getenv("onap-aai")
+ AAI_Port := os.Getenv("aai-port")
+
+ http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
+
+ apiToCR := AAI_URI + ":" + AAI_Port + con.AAI_EP + con.AAI_CREP + "cloud-region/" + cloudOwner + "/" + cloudRegion + "?depth=all"
+ req, err := http.NewRequest(http.MethodGet, apiToCR, nil)
+ if err != nil {
+ log.Error("Error while constructing request for Tenant API")
+ return
+
+ }
+
+ util.SetRequestHeaders(req)
+
+ client := http.DefaultClient
+ res, err := client.Do(req)
+
+ if err != nil {
+ log.Error("Error while executing request for Tenant API")
+ return
+ }
+
+ defer res.Body.Close()
+
+ body, err := ioutil.ReadAll(res.Body)
+ if err != nil {
+
+ log.Error("Can't read Tenant response")
+ return
+
+ }
+
+ var tenant con.Tenant
+
+ json.Unmarshal([]byte(body), &tenant)
+
+ for k, v := range tenant.Tenants {
+ if k == "tenant" {
+ for _, val := range v {
+ return val.TenantId
+
+ }
+ }
+ }
+
+ return ""
+
+}
diff --git a/src/inventory/api/aaipullapi_test.go b/src/inventory/api/aaipullapi_test.go
new file mode 100644
index 00000000..4d838e38
--- /dev/null
+++ b/src/inventory/api/aaipullapi_test.go
@@ -0,0 +1,68 @@
+/*
+Copyright 2020 Tech Mahindra.
+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 (
+ "crypto/tls"
+ con "github.com/onap/multicloud-k8s/src/inventory/constants"
+ util "github.com/onap/multicloud-k8s/src/inventory/utils"
+ "io/ioutil"
+ "net/http"
+ "os"
+ "strings"
+ "testing"
+)
+
+func TestGetTenant(t *testing.T) {
+
+ cloudOwner := "CloudOwner"
+ cloudRegion := "RegionOne"
+
+ AAI_URI := os.Getenv("onap-aai")
+ AAI_Port := os.Getenv("aai-port")
+
+ http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
+
+ uri := AAI_URI + ":" + AAI_Port + con.AAI_EP + con.AAI_CREP + "cloud-region/" + cloudOwner + "/" + cloudRegion + "?depth=all"
+
+ req, err := http.NewRequest("GET", uri, nil)
+
+ util.SetRequestHeaders(req)
+
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ client := http.DefaultClient
+ res, err := client.Do(req)
+
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if res.StatusCode != http.StatusOK {
+
+ t.Fail()
+ }
+
+ if p, err := ioutil.ReadAll(res.Body); err != nil {
+ t.Fail()
+ } else {
+ if strings.Contains(string(p), "Error") {
+ t.Errorf("header response shouldn't return error: %s", p)
+ } else if !strings.Contains(string(p), `tenant`) {
+ t.Errorf("header response doen't match:\n%s", p)
+ }
+ }
+}
diff --git a/src/inventory/api/aaipushapi.go b/src/inventory/api/aaipushapi.go
new file mode 100644
index 00000000..7faf498e
--- /dev/null
+++ b/src/inventory/api/aaipushapi.go
@@ -0,0 +1,189 @@
+/*
+Copyright 2020 Tech Mahindra.
+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"
+ "crypto/tls"
+ "encoding/json"
+ con "github.com/onap/multicloud-k8s/src/inventory/constants"
+ log "github.com/onap/multicloud-k8s/src/inventory/logutils"
+ util "github.com/onap/multicloud-k8s/src/inventory/utils"
+ "io/ioutil"
+ "net/http"
+ "os"
+ "strconv"
+)
+
+/* Pushes each pod related details as vservers inot A&AI
+
+{
+ "vserver-id": "example20",
+ "vserver-name": "POD-NAME",
+ "vserver-name2": "Relese-name/Profile-name of the POD (Labels:release=profile-k8s)",
+ "prov-status": "NAMESPACEofthPOD",
+ "vserver-selflink": "example-vserver-selflink-val-57201",
+ "in-maint": true,
+ "is-closed-loop-disabled": true,
+ "l-interfaces": {
+ "l-interface": [{
+ "interface-name": "example-interface-name-val-20080",
+ "is-port-mirrored": true,
+ "in-maint": true,
+ "is-ip-unnumbered": true,
+ "l3-interface-ipv4-address-list": [{
+ "l3-interface-ipv4-address": "IP_Address",
+ "l3-interface-ipv4-prefix-length": "PORT"
+ }]
+ }]
+ }
+}
+
+*/
+func PushVservers(podInfo con.PodInfoToAAI, cloudOwner, cloudRegion, tenantId string) string {
+
+ AAI_URI := os.Getenv("onap-aai")
+ AAI_Port := os.Getenv("aai-port")
+
+ payload := "{\"vserver-name\":" + "\"" + podInfo.VserverName + "\"" + ", \"vserver-name2\":" + "\"" + podInfo.VserverName2 + "\"" + ", \"prov-status\":" + "\"" + podInfo.ProvStatus + "\"" + ",\"vserver-selflink\":" + "\"example-vserver-selflink-val-57201\", \"l-interfaces\": {\"l-interface\": [{\"interface-name\": \"example-interface-name-val-20080\",\"is-port-mirrored\": true,\"in-maint\": true,\"is-ip-unnumbered\": true,\"l3-interface-ipv4-address-list\": [{\"l3-interface-ipv4-address\":" + "\"" + podInfo.I3InterfaceIPv4Address + "\"" + ",\"l3-interface-ipv4-prefix-length\":" + "\"" + strconv.FormatInt(int64(podInfo.I3InterfaceIPvPrefixLength), 10) + "\"" + "}]}]}}"
+
+ http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
+
+ url := AAI_URI + ":" + AAI_Port + con.AAI_EP + "cloud-region/" + cloudOwner + "/" + cloudRegion + "/tenants/tenant/" + tenantId + "/vservers/vserver/" + podInfo.VserverName
+
+ var jsonStr = []byte(payload)
+
+ req, err := http.NewRequest("PUT", url, bytes.NewBuffer(jsonStr))
+
+ if err != nil {
+ log.Error("Error while constructing Vserver PUT request")
+ return
+ }
+
+ util.SetRequestHeaders(req)
+
+ client := &http.Client{}
+ resp, err := client.Do(req)
+ if err != nil {
+ log.Error("Error while executing Vserver PUT api")
+ return
+ }
+ defer resp.Body.Close()
+
+ return podInfo.VserverName
+}
+
+/* This links vservers to vf-module request payload */
+func LinkVserverVFM(vnfID, vfmID, cloudOwner, cloudRegion, tenantId string, relList []con.RelationList) {
+
+ AAI_URI := os.Getenv("onap-aai")
+ AAI_Port := os.Getenv("aai-port")
+
+ http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
+
+ apiToCR := AAI_URI + ":" + AAI_Port + con.AAI_EP + con.AAI_NEP + "/" + vnfID + "/vf-modules"
+ req, err := http.NewRequest(http.MethodGet, apiToCR, nil)
+ if err != nil {
+ log.Error("Error while constructing VFModules GET api request")
+ return
+
+ }
+
+ util.SetRequestHeaders(req)
+
+ client := http.DefaultClient
+ res, err := client.Do(req)
+
+ if err != nil {
+ log.Error("Error while executing VFModules GET api")
+ return
+ }
+
+ defer res.Body.Close()
+
+ body, err := ioutil.ReadAll(res.Body)
+ if err != nil {
+
+ log.Error("Error while reading vfmodules API response")
+ return
+
+ }
+
+ var vfmodules con.VFModules
+
+ json.Unmarshal([]byte(body), &vfmodules)
+
+ vfmList := vfmodules.VFModules
+
+ for key, vfmodule := range vfmList {
+
+ if vfmodule.VFModuleId == vfmID {
+
+ vfmodule.RelationshipList = map[string][]con.RelationList{"relationship": relList}
+
+ vfmList = append(vfmList, vfmodule)
+
+ vfmList[key] = vfmList[len(vfmList)-1] // Copy last element to index i.
+ vfmList = vfmList[:len(vfmList)-1]
+
+ //update vfmodule with vserver data
+
+ vfmPayload, err := json.Marshal(vfmodule)
+
+ if err != nil {
+
+ log.Error("Error while marshalling vfmodule linked vserver info response")
+ return
+
+ }
+
+ pushVFModuleToAAI(string(vfmPayload), vfmID, vnfID)
+ }
+
+ }
+
+}
+
+/* Pushes vf-module enriched with vserver information */
+func pushVFModuleToAAI(vfmPayload, vfmID, vnfID string) {
+
+ AAI_URI := os.Getenv("onap-aai")
+ AAI_Port := os.Getenv("aai-port")
+
+ http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
+ url := AAI_URI + ":" + AAI_Port + con.AAI_NEP + vnfID + "/vfmodules/vf-module/" + vfmID
+
+ var jsonStr = []byte(vfmPayload)
+
+ req, err := http.NewRequest("PUT", url, bytes.NewBuffer(jsonStr))
+
+ if err != nil {
+ log.Error("Error while constructing a VFModule request to AAI")
+ return
+ }
+
+ util.SetRequestHeaders(req)
+
+ client := &http.Client{}
+ resp, err := client.Do(req)
+ if err != nil {
+
+ log.Error("Error while executing PUT request of VFModule to AAI")
+ return
+
+ }
+
+ defer resp.Body.Close()
+
+}
diff --git a/src/inventory/api/aaipushapi_test.go b/src/inventory/api/aaipushapi_test.go
new file mode 100644
index 00000000..6c0dea3f
--- /dev/null
+++ b/src/inventory/api/aaipushapi_test.go
@@ -0,0 +1,131 @@
+/*
+Copyright 2020 Tech Mahindra.
+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"
+ "crypto/tls"
+ con "github.com/onap/multicloud-k8s/src/inventory/constants"
+ util "github.com/onap/multicloud-k8s/src/inventory/utils"
+ "net/http"
+ "os"
+ "testing"
+)
+
+func TestPushVservers(t *testing.T) {
+
+ cloudOwner := "CloudOwner"
+ cloudRegion := "RegionOne"
+ tenantId := "tenant123"
+
+ AAI_URI := os.Getenv("onap-aai")
+ AAI_Port := os.Getenv("aai-port")
+
+ payload := "{\"vserver-name\":" + "\"" + "pod123" + "\"" + ", \"vserver-name2\":" + "\"" + "profile123" + "\"" + ", \"prov-status\":" + "\"" + "default" + "\"" + ",\"vserver-selflink\":" + "\"example-vserver-selflink-val-57201\", \"l-interfaces\": {\"l-interface\": [{\"interface-name\": \"example-interface-name-val-20080\",\"is-port-mirrored\": true,\"in-maint\": true,\"is-ip-unnumbered\": true,\"l3-interface-ipv4-address-list\": [{\"l3-interface-ipv4-address\":" + "\"" + "10.214.22.220" + "\"" + ",\"l3-interface-ipv4-prefix-length\":" + "\"" + "667" + "\"" + "}]}]}}"
+
+ http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
+
+ url := AAI_URI + ":" + AAI_Port + con.AAI_EP + "cloud-region/" + cloudOwner + "/" + cloudRegion + "/tenants/tenant/" + tenantId + "/vservers/vserver/" + "pod123"
+
+ var jsonStr = []byte(payload)
+
+ req, err := http.NewRequest("PUT", url, bytes.NewBuffer(jsonStr))
+
+ if err != nil {
+
+ t.Error("Failed: Error consructing request")
+ }
+
+ util.SetRequestHeaders(req)
+
+ client := &http.Client{}
+ resp, err := client.Do(req)
+ if err != nil {
+
+ t.Error("Failed: Error while executing request ")
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ t.Fatalf("recieved unexpeted response ")
+ }
+
+}
+
+func TestLinkVserverVFM(t *testing.T) {
+
+ vnfID := "vnf123456"
+ AAI_URI := os.Getenv("onap-aai")
+ AAI_Port := os.Getenv("aai-port")
+
+ http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
+
+ apiToCR := AAI_URI + ":" + AAI_Port + con.AAI_EP + con.AAI_NEP + "/" + vnfID + "/vf-modules"
+ req, err := http.NewRequest(http.MethodGet, apiToCR, nil)
+ if err != nil {
+
+ t.Error("Failed: Error while constructing new request")
+
+ }
+
+ util.SetRequestHeaders(req)
+
+ client := http.DefaultClient
+ res, err := client.Do(req)
+
+ if err != nil {
+
+ t.Error("Failed: Error while executing request")
+ }
+
+ if res.StatusCode != http.StatusOK {
+ t.Fatalf("recieved unexpeted response ")
+ }
+
+}
+
+func TestPushVFModuleToAAI(t *testing.T) {
+
+ vnfID := "vnf123456"
+ vfmID := "vfm123456"
+ vfmPayload := ""
+
+ AAI_URI := os.Getenv("onap-aai")
+ AAI_Port := os.Getenv("aai-port")
+
+ http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
+ url := AAI_URI + ":" + AAI_Port + con.AAI_NEP + vnfID + "/vfmodules/vf-module/" + vfmID
+
+ var jsonStr = []byte(vfmPayload)
+
+ req, err := http.NewRequest("PUT", url, bytes.NewBuffer(jsonStr))
+
+ if err != nil {
+ t.Error("Failed: Error while executing request")
+
+ }
+
+ util.SetRequestHeaders(req)
+
+ client := &http.Client{}
+ resp, err := client.Do(req)
+ if err != nil {
+
+ t.Error("Failed: Error while executing request")
+ }
+
+ if resp.StatusCode != http.StatusOK {
+ t.Fatalf("recieved unexpeted response ")
+ }
+
+}
diff --git a/src/inventory/api/k8spluginapi.go b/src/inventory/api/k8spluginapi.go
new file mode 100644
index 00000000..4348d287
--- /dev/null
+++ b/src/inventory/api/k8spluginapi.go
@@ -0,0 +1,120 @@
+/*
+Copyright 2020 Tech Mahindra.
+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 (
+ con "github.com/onap/multicloud-k8s/src/inventory/constants"
+ log "github.com/onap/multicloud-k8s/src/inventory/logutils"
+ utils "github.com/onap/multicloud-k8s/src/inventory/utils"
+ k8sint "github.com/onap/multicloud-k8s/src/k8splugin/internal/app"
+ k8scon "github.com/onap/multicloud-k8s/src/k8splugin/internal/connection"
+
+ "encoding/json"
+ "net/http"
+ "os"
+)
+
+func ListInstances() []string {
+
+ MK8S_URI := os.Getenv("onap-multicloud-k8s")
+ MK8S_Port := os.Getenv("multicloud-k8s-port")
+
+ instancelist := MK8S_URI + ":" + MK8S_Port + con.MK8S_EP
+ req, err := http.NewRequest(http.MethodGet, instancelist, nil)
+ if err != nil {
+
+ log.Error("Something went wrong while listing resources - contructing request")
+ return
+ }
+
+ client := http.DefaultClient
+ res, err := client.Do(req)
+
+ if err != nil {
+ log.Error("Something went wrong while listing resources - executing request")
+ return
+ }
+
+ defer res.Body.Close()
+
+ decoder := json.NewDecoder(res.Body)
+ var rlist []k8sint.InstanceMiniResponse
+ err = decoder.Decode(&rlist)
+
+ resourceList := utils.ParseListInstanceResponse(rlist)
+
+ return resourceList
+
+}
+
+func GetConnection(cregion string) k8scon.Connection {
+
+ MK8S_URI := os.Getenv("onap-multicloud-k8s")
+ MK8S_Port := os.Getenv("multicloud-k8s-port")
+
+ instancelist := MK8S_URI + ":" + MK8S_Port + con.MK8S_CEP + cregion
+ req, err := http.NewRequest(http.MethodGet, instancelist, nil)
+ if err != nil {
+
+ log.Error("Something went wrong while getting Connection resource - contructing request")
+ return
+ }
+
+ client := http.DefaultClient
+ res, err := client.Do(req)
+
+ if err != nil {
+ log.Error("Something went wrong while getting Connection resource - executing request")
+ return
+ }
+
+ defer res.Body.Close()
+
+ decoder := json.NewDecoder(res.Body)
+ var connection k8scon.Connection
+ err = decoder.Decode(&connection)
+
+ return connection
+
+}
+
+func CheckStatusForEachInstance(instanceID string) k8sint.InstanceStatus {
+
+ MK8S_URI := os.Getenv("onap-multicloud-k8s")
+ MK8S_Port := os.Getenv("multicloud-k8s-port")
+
+ instancelist := MK8S_URI + ":" + MK8S_Port + con.MK8S_EP + instanceID + "/status"
+
+ req, err := http.NewRequest(http.MethodGet, instancelist, nil)
+ if err != nil {
+ log.Error("Error while checking instance status - building http request")
+ return
+ }
+
+ client := http.DefaultClient
+ resp, err := client.Do(req)
+ if err != nil {
+
+ log.Error("Error while checking instance status - making rest request")
+ return
+ }
+
+ defer resp.Body.Close()
+
+ decoder := json.NewDecoder(resp.Body)
+ var instStatus k8sint.InstanceStatus
+ err = decoder.Decode(&instStatus)
+
+ return instStatus
+}
diff --git a/src/inventory/api/k8spluginapi_test.go b/src/inventory/api/k8spluginapi_test.go
new file mode 100644
index 00000000..6eb16486
--- /dev/null
+++ b/src/inventory/api/k8spluginapi_test.go
@@ -0,0 +1,64 @@
+/*
+Copyright 2020 Tech Mahindra.
+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 (
+ "crypto/tls"
+ con "github.com/onap/multicloud-k8s/src/inventory/constants"
+ util "github.com/onap/multicloud-k8s/src/inventory/utils"
+ "io/ioutil"
+ "net/http"
+ "os"
+ "strings"
+ "testing"
+)
+
+func TestListInstances(t *testing.T) {
+
+ http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
+
+ MK8S_URI := os.Getenv("onap-multicloud-k8s")
+ MK8S_Port := os.Getenv("multicloud-k8s-port")
+
+ instancelist := MK8S_URI + ":" + MK8S_Port + con.MK8S_EP
+ req, err := http.NewRequest(http.MethodGet, instancelist, nil)
+
+ util.SetRequestHeaders(req)
+
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ client := http.DefaultClient
+ res, err := client.Do(req)
+
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if res.StatusCode != http.StatusOK {
+
+ t.Fail()
+ }
+
+ if p, err := ioutil.ReadAll(res.Body); err != nil {
+ t.Fail()
+ } else {
+ if strings.Contains(string(p), "Error") {
+ t.Errorf("header response shouldn't return error: %s", p)
+ } else if !strings.Contains(string(p), `cloud-region`) {
+ t.Errorf("header response doen't match:\n%s", p)
+ }
+ }
+}
diff --git a/src/inventory/constants/const.go b/src/inventory/constants/const.go
new file mode 100644
index 00000000..a1ba2334
--- /dev/null
+++ b/src/inventory/constants/const.go
@@ -0,0 +1,94 @@
+/*
+Copyright 2020 Tech Mahindra.
+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 constants
+
+import (
+
+//corev1 "k8s.io/api/core/v1"
+
+)
+
+const (
+ XFromAppId = "SO"
+ ContentType = "application/json"
+ Accept = "application/json"
+ XTransactionId = "get_aai_subscr"
+)
+
+const (
+ AAI_EP = "/aai/v14"
+ AAI_CREP = "/cloud-infrastructure/cloud-regions/"
+ AAI_NEP = "/network/generic-vnfs/generic-vnf/"
+)
+
+const (
+ MK8S_EP = "/api/multicloud-k8s/v1/v1/instance/"
+ MK8S_CEP = "/connectivity-info"
+)
+
+type PodInfoToAAI struct {
+ VserverName string
+ VserverName2 string
+ ProvStatus string
+ I3InterfaceIPv4Address string
+ I3InterfaceIPvPrefixLength int32
+ VnfId string
+ VfmId string
+ CloudRegion string
+}
+
+type RData struct {
+ RelationshipKey string `json:"relationship-key"`
+ RelationshipValue string `json:"relationship-value"`
+}
+
+type RelationList struct {
+ RelatedTo string `json:"related-to"`
+ RelatedLink string `json:"related-link"`
+ RelationshipData []RData `json:"relationship-data"`
+ RelatedToProperty []Property `json:"related-to-property"`
+}
+
+type TenantInfo struct {
+ TenantId string `json:"tenant-id"`
+ TenantName string `json:"tenant-name"`
+}
+
+type Tenant struct {
+ Tenants map[string][]TenantInfo `json:"tenants"`
+}
+
+type Property struct {
+ PropertyKey string `json:"property-key"`
+ PropertyValue string `json:"property-value"`
+}
+
+type VFModule struct {
+ VFModuleId string `json:"vf-module-id"`
+ VFModuleName string `json:"vf-module-name"`
+ HeatStackId string `json:"heat-stack-id"`
+ OrchestrationStatus string `json:"orchestration-status"`
+ ResourceVersion string `json:"resource-version"`
+ AutomatedAssignment string `json:"automated-assignment"`
+ IsBaseVfModule string `json:"is-base-vf-module"`
+ RelationshipList map[string][]RelationList `json:"relationship-list"`
+ ModelInvariantId string `json:"model-invariant-id"`
+ ModelVersionId string `json:"model-version-id"`
+ ModelCustomizationId string `json:"model-customization-id"`
+ ModuleIndex string `json:"module-index"`
+}
+
+type VFModules struct {
+ VFModules []VFModule `json:"vf-module"`
+}
diff --git a/src/inventory/controller/main.go b/src/inventory/controller/main.go
new file mode 100644
index 00000000..5e28282d
--- /dev/null
+++ b/src/inventory/controller/main.go
@@ -0,0 +1,83 @@
+/*
+Copyright 2020 Tech Mahindra.
+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 main
+
+import (
+ executor "github.com/onap/multicloud-k8s/src/inventory/api"
+ con "github.com/onap/multicloud-k8s/src/inventory/constants"
+ k8splugin "github.com/onap/multicloud-k8s/src/k8splugin/internal/app"
+ utils "github.com/onap/multicloud-k8s/src/inventory/utils"
+ "os"
+ "os/signal"
+ "time"
+)
+
+/* Root function which periodically polls status api for all the instances in the k8splugin and update the status information accordingly to AAI */
+func QueryAAI() {
+
+ for {
+ instanceList := executor.ListInstances()
+ statusList := CheckInstanceStatus(instanceList)
+ podList := utils.ParseStatusInstanceResponse(statusList)
+ PushPodInfoToAAI(podList)
+ time.Sleep(360000 * time.Second)
+ }
+
+}
+
+func CheckInstanceStatus(instanceList []string) []k8splugin.InstanceStatus {
+
+ var instStatusList []k8splugin.InstanceStatus
+
+ for _, instance := range instanceList {
+
+ instanceStatus := executor.CheckStatusForEachInstance(string(instance))
+
+ instStatusList = append(instStatusList, instanceStatus)
+
+ }
+
+ return instStatusList
+}
+
+func PushPodInfoToAAI(podList []con.PodInfoToAAI) {
+
+ var relList []con.RelationList
+
+ for _, pod := range podList {
+
+ connection := executor.GetConnection(pod.CloudRegion)
+
+ tenantId := executor.GetTenant(connection.CloudOwner, pod.CloudRegion)
+
+ vserverID := executor.PushVservers(pod, connection.CloudOwner, pod.CloudRegion, tenantId)
+
+ rl := utils.BuildRelationshipDataForVFModule(pod.VserverName, vserverID, connection.CloudOwner, pod.CloudRegion, tenantId)
+ relList = append(relList, rl)
+
+ executor.LinkVserverVFM(pod.VnfId, pod.VfmId, connection.CloudOwner, pod.CloudRegion, tenantId, relList)
+ }
+
+}
+
+func main() {
+
+ c := make(chan os.Signal, 1)
+ signal.Notify(c, os.Interrupt)
+
+ //go QueryAAI()
+
+ <-c
+
+}
diff --git a/src/inventory/go.mod b/src/inventory/go.mod
new file mode 100644
index 00000000..e7d94194
--- /dev/null
+++ b/src/inventory/go.mod
@@ -0,0 +1,8 @@
+module github.com/onap/multicloud-k8s/src/inventory
+
+go 1.12
+
+require (
+ github.com/onap/multicloud-k8s/src/k8splugin v0.0.0-20200303230813-37aed9b7a0db // indirect
+ k8s.io/api v0.17.2
+)
diff --git a/src/inventory/go.sum b/src/inventory/go.sum
new file mode 100644
index 00000000..3a648d49
--- /dev/null
+++ b/src/inventory/go.sum
@@ -0,0 +1,458 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
+github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
+github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
+github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
+github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
+github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
+github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
+github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
+github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
+github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
+github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e h1:eb0Pzkt15Bm7f2FFYv7sjY7NPFi3cPkS3tv1CcrFBWA=
+github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E=
+github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc=
+github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
+github.com/Masterminds/sprig v2.17.1+incompatible h1:PChbxFGKTWsg9IWh+pSZRCSj3zQkVpL6Hd9uWsFwxtc=
+github.com/Masterminds/sprig v2.17.1+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
+github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
+github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
+github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
+github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
+github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
+github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
+github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
+github.com/VamshiKrishnaNemalikonda/GO v0.0.0-20191112095043-d6b01bbf585f h1:I3PTea8BCbUFS3YCRYJwHN9ppi5CuN+pmH10NtNRt+A=
+github.com/aokoli/goutils v1.1.0 h1:jy4ghdcYvs5EIoGssZNslIASX5m+KNMfyyKvRQ0TEVE=
+github.com/aokoli/goutils v1.1.0/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ=
+github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
+github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
+github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/chai2010/gettext-go v0.0.0-20170215093142-bf70f2a70fb1 h1:HD4PLRzjuCVW79mQ0/pdsalOLHJ+FaEoqJLxfltpb2U=
+github.com/chai2010/gettext-go v0.0.0-20170215093142-bf70f2a70fb1/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw=
+github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
+github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
+github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
+github.com/coreos/etcd v3.3.12+incompatible h1:pAWNwdf7QiT1zfaWyqCtNZQWCLByQyA3JrSQyuYAqnQ=
+github.com/coreos/etcd v3.3.12+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
+github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
+github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
+github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
+github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg=
+github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
+github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/docker/distribution v2.7.0+incompatible h1:neUDAlf3wX6Ml4HdqTrbcOHXtfRN0TFIwt6YFL7N9RU=
+github.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
+github.com/docker/docker v0.7.3-0.20190912223608-ad718029b705 h1:up4REDeXtcm77SlkowEGUuakgjpdNR2N9TkGTZSL4rM=
+github.com/docker/docker v0.7.3-0.20190912223608-ad718029b705/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/engine v0.0.0-20190620014054-c513a4c6c298 h1:dDGt5n84DvY05kaJT26cw1TDxNW1NymRZ13j0KeEQaw=
+github.com/docker/engine v0.0.0-20190620014054-c513a4c6c298/go.mod h1:3CPr2caMgTHxxIAZgEMd3uLYPDlRvPqCpyeRf6ncPcY=
+github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
+github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c h1:ZfSZ3P3BedhKGUhzj7BQlPSU4OvT6tfOKe3DVHzOA7s=
+github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
+github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
+github.com/elazarl/goproxy v0.0.0-20190911111923-ecfe977594f1/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
+github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
+github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
+github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk=
+github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
+github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
+github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M=
+github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
+github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM=
+github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4=
+github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/ghodss/yaml v0.0.0-20180820084758-c7ce16629ff4/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
+github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
+github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
+github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w=
+github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
+github.com/go-openapi/jsonreference v0.19.2 h1:o20suLFB4Ri0tuzpWtyHlh7E7HnkqTNLq6aR6WVNS1w=
+github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
+github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
+github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=
+github.com/go-openapi/spec v0.19.3 h1:0XRyw8kguri6Yw4SxhsQA/atC88yqrk0+G4YhI2wabc=
+github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
+github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
+github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
+github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
+github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
+github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
+github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
+github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
+github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
+github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk=
+github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw=
+github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
+github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
+github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
+github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I=
+github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20181024230925-c65c006176ff h1:kOkM9whyQYodu09SJ6W3NCsHG7crFaJILQ22Gozp3lg=
+github.com/golang/groupcache v0.0.0-20181024230925-c65c006176ff/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
+github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
+github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
+github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
+github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g=
+github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
+github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
+github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
+github.com/gorilla/handlers v1.3.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
+github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
+github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
+github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f h1:ShTPMJQes6tubcjzGMODIVG5hlrCeImaBnZzKF2N8SM=
+github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
+github.com/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE=
+github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
+github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
+github.com/grpc-ecosystem/grpc-gateway v1.11.1/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/hashicorp/consul v1.4.0 h1:PQTW4xCuAExEiSbhrsFsikzbW5gVBoi74BjUvYFyKHw=
+github.com/hashicorp/consul v1.4.0/go.mod h1:mFrjN1mfidgJfYP1xrJCF+AfRhr6Eaqhb2+sfyn/OOI=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig=
+github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
+github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
+github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
+github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
+github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90 h1:VBj0QYQ0u2MCJzBfeYXGexnAl17GsH1yidnoxCqqD9E=
+github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90/go.mod h1:o4zcYY1e0GEZI6eSEr+43QDYmuGglw1qSO6qdHUHCgg=
+github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
+github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
+github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/hashicorp/memberlist v0.1.5/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
+github.com/hashicorp/serf v0.8.1 h1:mYs6SMzu72+90OcPa5wr3nfznA4Dw9UyR791ZFNOIf4=
+github.com/hashicorp/serf v0.8.1/go.mod h1:h/Ru6tmZazX7WO/GDmwdpS975F019L4t5ng5IgwbNrE=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/huandu/xstrings v1.2.0 h1:yPeWdRnmynF7p+lLYz0H2tthW9lqhMJrQV/U7yy4wX0=
+github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
+github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q=
+github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
+github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA=
+github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
+github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
+github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
+github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok=
+github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
+github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
+github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
+github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
+github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
+github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
+github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8=
+github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
+github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
+github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4=
+github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
+github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
+github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
+github.com/onap/multicloud-k8s v0.0.0-20200123181220-fc960539db54 h1:0iPySKR2wPGiyvi0KmtjNmWcBoM8oUUVc3Q4QkYbkp0=
+github.com/onap/multicloud-k8s v0.0.0-20200210192739-c06be6458e99 h1:ZVCLL4/4dEbq/G9XGWhl3y8b1jgTB03ByTI2AiVMgUk=
+github.com/onap/multicloud-k8s v0.0.0-20200303230813-37aed9b7a0db h1:H+IVbOqoE4rk2qcSwOrR335VejyBhkLK75V6UL2uoJ4=
+github.com/onap/multicloud-k8s/src/k8splugin v0.0.0-20200303230813-37aed9b7a0db h1:RevNsc1iFXT2DGBdYViLrJJ0HXIoAxdvj6QopQOKU5I=
+github.com/onap/multicloud-k8s/src/k8splugin v0.0.0-20200303230813-37aed9b7a0db/go.mod h1:EnQd/vQGZR1/55IihaHxiux4ZUig/zfXZux7bfmU0S8=
+github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
+github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
+github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
+github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
+github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
+github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
+github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
+github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740=
+github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 h1:PnBWHBf+6L0jOqq0gIVUe6Yk0/QMZ640k6NvkxcBf+8=
+github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
+github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nLJdBg+pBmGgkJlSaKC2KaQmTCk1XDtE=
+github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
+github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
+github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rubenv/sql-migrate v0.0.0-20190902133344-8926f37f0bc1 h1:G7j/gxkXAL80NMLOWi6EEctDET1Iuxl3sBMJXDnu2z0=
+github.com/rubenv/sql-migrate v0.0.0-20190902133344-8926f37f0bc1/go.mod h1:WS0rl9eEliYI8DPnr3TOwz4439pay+qNgzJoVya/DmY=
+github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
+github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
+github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
+github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
+github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
+github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
+github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
+github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
+github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
+github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/technosophos/moniker v0.0.0-20180509230615-a5dbd03a2245 h1:DNVk+NIkGS0RbLkjQOLCJb/759yfCysThkMbl7EXxyY=
+github.com/technosophos/moniker v0.0.0-20180509230615-a5dbd03a2245/go.mod h1:O1c8HleITsZqzNZDjSNzirUGsMT0oGu9LhHKoJrqO+A=
+github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
+github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
+github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
+github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
+github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
+github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
+github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk=
+github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
+github.com/xdg/stringprep v1.0.0 h1:d9X0esnoa3dFsV0FG35rAT0RIhYFlPq7MiP+DW89La0=
+github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
+github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
+github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8=
+github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
+github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
+go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
+go.etcd.io/etcd v3.3.12+incompatible h1:V6PRYRGpU4k5EajJaaj/GL3hqIdzyPnBU8aPUp+35yw=
+go.etcd.io/etcd v3.3.12+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI=
+go.mongodb.org/mongo-driver v1.0.0 h1:KxPRDyfB2xXnDE2My8acoOWBQkfv3tz0SaWTRZjJR0c=
+go.mongodb.org/mongo-driver v1.0.0/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
+go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
+go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
+go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4 h1:ydJNl0ENAG67pFbB+9tfhiL2pYqLhfoaZFw/cjLhY4A=
+golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI=
+golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
+golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456 h1:ng0gs1AKnRRuEMZoTLLlbOd+C17zUDepwGQBb/n+JVg=
+golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/grpc v1.19.0 h1:cfg4PD8YEdSFnm7qLV4++93WcmhH2nIUhMjhdCvl3j8=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/gorp.v1 v1.7.2 h1:j3DWlAyGVv8whO7AcIWznQ2Yj7yJkn34B8s63GViAAw=
+gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw=
+gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
+gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
+gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
+gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
+gopkg.in/square/go-jose.v2 v2.2.2 h1:orlkJ3myw8CN1nVQHBFfloD+L3egixIa4FvUP6RosSA=
+gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+k8s.io/api v0.0.0-20190831074750-7364b6bdad65/go.mod h1:u09ZxrpPFcoUNEQM2GsqT/KpglKAtXdEcK+tSMilQ3Q=
+k8s.io/api v0.17.2 h1:NF1UFXcKN7/OOv1uxdRz3qfra8AHsPav5M93hlV9+Dc=
+k8s.io/api v0.17.2/go.mod h1:BS9fjjLc4CMuqfSO8vgbHPKMt5+SF0ET6u/RVDihTo4=
+k8s.io/apiextensions-apiserver v0.0.0-20190409022649-727a075fdec8 h1:q1Qvjzs/iEdXF6A1a8H3AKVFDzJNcJn3nXMs6R6qFtA=
+k8s.io/apiextensions-apiserver v0.0.0-20190409022649-727a075fdec8/go.mod h1:IxkesAMoaCRoLrPJdZNZUQp9NfZnzqaVzLhb2VEQzXE=
+k8s.io/apimachinery v0.0.0-20190831074630-461753078381/go.mod h1:nL6pwRT8NgfF8TT68DBI8uEePRt89cSvoXUVqbkWHq4=
+k8s.io/apimachinery v0.17.2 h1:hwDQQFbdRlpnnsR64Asdi55GyCaIP/3WQpMmbNBeWr4=
+k8s.io/apimachinery v0.17.2/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
+k8s.io/apiserver v0.0.0-20190409021813-1ec86e4da56c h1:k7ALUVzrOEgz4hOF+pr4pePn7TqZ9lB/8Z8ndMSsWSU=
+k8s.io/apiserver v0.0.0-20190409021813-1ec86e4da56c/go.mod h1:6bqaTSOSJavUIXUtfaR9Os9JtTCm8ZqH2SUl2S60C4w=
+k8s.io/cli-runtime v0.0.0-20190913085402-777c64e2902f h1:gQH9KuiqXEhXWEHnov9bS/ysYPSIYNT1P3BWC9HGI7M=
+k8s.io/cli-runtime v0.0.0-20190913085402-777c64e2902f/go.mod h1:TtjkdmxYMLASzYbE8E7AUr/ZrXMcmXLnDLRY4sVWspw=
+k8s.io/client-go v0.0.0-20190831074946-3fe2abece89e/go.mod h1:hAiMqq+tCk9hxFvWr2DoRiVyCYEGpni4eOcGCQLOEfM=
+k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible h1:U5Bt+dab9K8qaUmXINrkXO135kA11/i5Kg1RUydgaMQ=
+k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
+k8s.io/cloud-provider v0.0.0-20190409023720-1bc0c81fa51d h1:ad7UpNUGRx6FbYoK4+xIYyeS2CUShjNKY45YN1ipjLI=
+k8s.io/cloud-provider v0.0.0-20190409023720-1bc0c81fa51d/go.mod h1:LlIffnLBu+GG7d4ppPzC8UnA1Ex8S+ntmSRVsnr7Xy4=
+k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
+k8s.io/helm v2.14.3+incompatible h1:uzotTcZXa/b2SWVoUzM1xiCXVjI38TuxMujS/1s+3Gw=
+k8s.io/helm v2.14.3+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI=
+k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
+k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
+k8s.io/klog v0.4.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
+k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
+k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
+k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
+k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a h1:UcxjrRMyNx/i/y8G7kPvLyy7rfbeuf1PYyBf973pgyU=
+k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
+k8s.io/kubernetes v1.14.1 h1:I9F52h5sqVxBmoSsBlNQ0YygNcukDilkpGxUbJRoBoY=
+k8s.io/kubernetes v1.14.1/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
+k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
+k8s.io/utils v0.0.0-20190907131718-3d4f5b7dea0b h1:eMM0sTvh3KBVGwJfuNcU86P38TJhlVMAICbFPDG3t0M=
+k8s.io/utils v0.0.0-20190907131718-3d4f5b7dea0b/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
+sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0=
+sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=
+sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
+sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
+sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
+vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787 h1:O69FD9pJA4WUZlEwYatBEEkRWKQ5cKodWpdKTrCS/iQ=
+vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI=
diff --git a/src/inventory/utils/util.go b/src/inventory/utils/util.go
new file mode 100644
index 00000000..8d204f92
--- /dev/null
+++ b/src/inventory/utils/util.go
@@ -0,0 +1,119 @@
+/*
+Copyright 2020 Tech Mahindra.
+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 (
+ con "github.com/onap/multicloud-k8s/src/inventory/constants"
+ k8splugin "github.com/onap/multicloud-k8s/src/k8splugin/internal/app"
+ "net/http"
+ "os"
+ "reflect"
+)
+
+/* Building relationship json to attach vserver details to vf-module*/
+func BuildRelationshipDataForVFModule(vserverName, vserverID, cloudOwner, cloudRegion, tenantId string) con.RelationList {
+
+ rl := con.RelationList{"vserver", "/aai/v14/cloud-infrastructure/cloud-regions/cloud-region/" + cloudOwner + "/" + cloudRegion + "/tenants/tenant/" + tenantId + "/vservers/vserver/" + vserverID, []con.RData{con.RData{"cloud-region.cloud-owner", cloudOwner},
+ con.RData{"cloud-region.cloud-region-id", cloudRegion},
+ con.RData{"tenant.tenant-id", tenantId},
+ con.RData{"vserver.vserver-id", vserverID}},
+ []con.Property{con.Property{"vserver.vserver-name", vserverName}}}
+
+ return rl
+
+}
+
+func ParseListInstanceResponse(rlist []k8splugin.InstanceMiniResponse) []string {
+
+ var resourceIdList []string
+
+ //assume there is only one resource created
+ for _, result := range rlist {
+
+ resourceIdList = append(resourceIdList, result.ID)
+ }
+
+ return resourceIdList
+}
+
+/* Parse status api response to pull required information like Pod name, Profile name, namespace, ip details, vnf-id and vf-module-id*/
+func ParseStatusInstanceResponse(instanceStatusses []k8splugin.InstanceStatus) []con.PodInfoToAAI {
+
+ var infoToAAI []con.PodInfoToAAI
+
+ for _, instanceStatus := range instanceStatusses {
+
+ var podInfo con.PodInfoToAAI
+
+ sa := reflect.ValueOf(&instanceStatus).Elem()
+ typeOf := sa.Type()
+ for i := 0; i < sa.NumField(); i++ {
+ f := sa.Field(i)
+ if typeOf.Field(i).Name == "Request" {
+ request := f.Interface()
+ if ireq, ok := request.(k8splugin.InstanceRequest); ok {
+ podInfo.VserverName2 = ireq.ProfileName
+ podInfo.CloudRegion = ireq.CloudRegion
+
+ for key, value := range ireq.Labels {
+ if key == "generic-vnf-id" {
+
+ podInfo.VnfId = value
+
+ }
+ if key == "vfmodule-id" {
+
+ podInfo.VfmId = value
+
+ }
+ }
+
+ } else {
+ //fmt.Printf("it's not a InstanceRequest \n")
+ }
+ }
+
+ if typeOf.Field(i).Name == "PodStatuses" {
+ ready := f.Interface()
+ if pss, ok := ready.([]con.PodStatus); ok {
+ for _, ps := range pss {
+ podInfo.VserverName = ps.Name
+ podInfo.ProvStatus = ps.Namespace
+ }
+
+ } else {
+ //fmt.Printf("it's not a InstanceRequest \n")
+ }
+ }
+ }
+
+ infoToAAI = append(infoToAAI, podInfo)
+
+ }
+
+ return infoToAAI
+
+}
+
+/* this sets http headers to request object*/
+func SetRequestHeaders(req *http.Request) {
+ authorization := os.Getenv("authorization")
+
+ req.Header.Set("X-FromAppId", con.XFromAppId)
+ req.Header.Set("Content-Type", con.ContentType)
+ req.Header.Set("Accept", con.Accept)
+ req.Header.Set("X-TransactionId", con.XTransactionId)
+ req.Header.Set("Authorization", authorization)
+
+}
diff --git a/src/inventory/utils/util_test.go b/src/inventory/utils/util_test.go
new file mode 100644
index 00000000..4dd0c133
--- /dev/null
+++ b/src/inventory/utils/util_test.go
@@ -0,0 +1,126 @@
+/*
+Copyright 2020 Tech Mahindra.
+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 (
+ con "github.com/onap/multicloud-k8s/src/inventory/constants"
+ k8splugin "github.com/onap/multicloud-k8s/src/k8splugin/internal/app"
+ "testing"
+)
+
+func TestBuildRelationshipDataForVFModule(t *testing.T) {
+
+ relList := BuildRelationshipDataForVFModule("vs_name", "vs1234", "CO", "CR", "tenant1234")
+
+ if relList.RelatedTo != "vserver" {
+ t.Error("Failed")
+ }
+
+ if (relList.RelatedLink) != "/aai/v14/cloud-infrastructure/cloud-regions/cloud-region/CO/CR/tenants/tenant/tenant1234/vservers/vserver/vs1234" {
+ t.Error("Failed")
+ }
+
+ rdadaList := relList.RelationshipData
+
+ for _, rdata := range rdadaList {
+
+ if rdata.RelationshipKey == "cloud-region.cloud-region-id" {
+
+ if rdata.RelationshipValue != "CR" {
+
+ t.Error("Failed")
+
+ }
+ }
+
+ if rdata.RelationshipKey == "tenant.tenant-id" {
+
+ if rdata.RelationshipValue != "tenant1234" {
+
+ t.Error("Failed")
+
+ }
+ }
+
+ if rdata.RelationshipKey == "vserver.vserver-id" {
+
+ if rdata.RelationshipValue != "vs1234" {
+
+ t.Error("Failed")
+
+ }
+ }
+
+ if rdata.RelationshipKey == "cloud-region.cloud-owner" {
+
+ if rdata.RelationshipValue != "CO" {
+
+ t.Error("Failed")
+
+ }
+ }
+
+ }
+
+ propertyList := relList.RelatedToProperty
+
+ for _, property := range propertyList {
+
+ if property.PropertyKey == "vserver.vserver-name" {
+
+ if property.PropertyValue != "vs_name" {
+
+ t.Error("Failed")
+
+ }
+ }
+
+ }
+
+}
+
+func TestParseStatusInstanceResponse(t *testing.T) {
+
+ var resourceIdList []k8splugin.InstanceStatus
+
+ instanceRequest := k8splugin.InstanceRequest{"rb_name", "rb_version", "profile123456", "c_region", map[string]string{"generic-vnf-id": "123456789", "vf-module-id": "987654321"}}
+ instanceStatus := k8splugin.InstanceStatus{instanceRequest, true, 12, []con.PodStatus{con.PodStatus{"pod123", "onap", true, []string{"10.211.1.100", "10.211.1.101"}}, con.PodStatus{"pod456", "default", true, []string{"10.211.1.200", "10.211.1.201"}}}}
+
+ resourceIdList = append(resourceIdList, instanceStatus)
+
+ podInfoToAAI := ParseStatusInstanceResponse(resourceIdList)
+
+ for _, podInfo := range podInfoToAAI {
+
+ if podInfo.VserverName == "pod123" {
+
+ t.Error("Failed")
+
+ }
+
+ if podInfo.VserverName2 == "default" {
+
+ t.Error("Failed")
+
+ }
+
+ if podInfo.ProvStatus == "profile123456" {
+
+ t.Error("Failed")
+
+ }
+
+ }
+
+}
diff --git a/src/k8splugin/internal/app/instance.go b/src/k8splugin/internal/app/instance.go
index 5d8b2100..d6eb91b4 100644
--- a/src/k8splugin/internal/app/instance.go
+++ b/src/k8splugin/internal/app/instance.go
@@ -120,7 +120,7 @@ func NewInstanceClient() *InstanceClient {
}
}
-// Create an entry for the resource bundle profile in the database
+// Create an instance of rb on the cluster in the database
func (v *InstanceClient) Create(i InstanceRequest) (InstanceResponse, error) {
// Name is required
diff --git a/src/k8splugin/internal/rb/archive.go b/src/k8splugin/internal/rb/archive.go
index c0753134..267c7cd2 100644
--- a/src/k8splugin/internal/rb/archive.go
+++ b/src/k8splugin/internal/rb/archive.go
@@ -115,6 +115,10 @@ func ExtractTarBall(r io.Reader) (string, error) {
}
}
case tar.TypeReg:
+ if target == outDir { // Handle '.' substituted to '' entry
+ continue
+ }
+
err = utils.EnsureDirectory(target)
if err != nil {
return "", pkgerrors.Wrap(err, "Creating Directory")
diff --git a/src/k8splugin/internal/rb/archive_test.go b/src/k8splugin/internal/rb/archive_test.go
index 5fa66e79..4c09069a 100644
--- a/src/k8splugin/internal/rb/archive_test.go
+++ b/src/k8splugin/internal/rb/archive_test.go
@@ -19,6 +19,7 @@ package rb
import (
"bytes"
"io/ioutil"
+ "os"
"path/filepath"
"strings"
"testing"
@@ -102,6 +103,7 @@ func TestExtractTarBall(t *testing.T) {
}
path, err := ExtractTarBall(bytes.NewBuffer(content))
+ defer os.RemoveAll(path)
if err != nil {
t.Errorf("Error reading valid tar.gz file %s", err.Error())
}
@@ -120,7 +122,8 @@ func TestExtractTarBall(t *testing.T) {
0x00, 0xff, 0xf2, 0x48, 0xcd,
}
- _, err := ExtractTarBall(bytes.NewBuffer(content))
+ path, err := ExtractTarBall(bytes.NewBuffer(content))
+ defer os.RemoveAll(path)
if err == nil {
t.Errorf("Error should NOT be nil")
}
@@ -128,9 +131,46 @@ func TestExtractTarBall(t *testing.T) {
t.Run("Empty tar.gz", func(t *testing.T) {
content := []byte{}
- err := isTarGz(bytes.NewBuffer(content))
+ path, err := ExtractTarBall(bytes.NewBuffer(content))
+ defer os.RemoveAll(path)
if err == nil {
t.Errorf("Error should NOT be nil")
}
})
+
+ t.Run("Specific tar.gz with '.' entry", func(t *testing.T) {
+ content := []byte{
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0xff, 0xed, 0x94, 0xcd, 0x0a, 0xc3, 0x20,
+ 0x10, 0x84, 0x3d, 0xfb, 0x14, 0x4b, 0xee, 0x29,
+ 0x6b, 0x4c, 0x14, 0x7c, 0x99, 0x22, 0xd4, 0x42,
+ 0x20, 0x3f, 0x25, 0x9a, 0x40, 0xde, 0x3e, 0x92,
+ 0x9c, 0x1a, 0x0a, 0x39, 0x34, 0xb1, 0x87, 0xee,
+ 0x77, 0x51, 0xd6, 0xc3, 0x8c, 0x8c, 0x23, 0x63,
+ 0xd7, 0x83, 0x02, 0x51, 0x95, 0x25, 0xe0, 0xc6,
+ 0x7e, 0x5d, 0xf7, 0x42, 0x2a, 0x89, 0x65, 0xa5,
+ 0xb1, 0x2a, 0xe2, 0x5c, 0x2b, 0x2c, 0x18, 0x60,
+ 0x02, 0x6f, 0x6c, 0xf4, 0xc1, 0x0e, 0x0c, 0xbf,
+ 0xd6, 0xda, 0x5f, 0xee, 0x04, 0x6b, 0x29, 0x68,
+ 0x6d, 0x57, 0x3f, 0x9d, 0x0f, 0xb7, 0xd9, 0xb6,
+ 0xcd, 0x45, 0x1a, 0x87, 0xf9, 0x2b, 0xfd, 0x9e,
+ 0xbf, 0x28, 0x24, 0x6a, 0xca, 0x3f, 0x05, 0x79,
+ 0x9e, 0xf3, 0xc9, 0x0d, 0xbe, 0xee, 0x3b, 0x03,
+ 0x93, 0xe0, 0x61, 0x7e, 0x39, 0xc3, 0x01, 0x26,
+ 0xdb, 0x8c, 0xce, 0x1b, 0xc8, 0xfa, 0x78, 0x3a,
+ 0xd4, 0x0f, 0x77, 0xdf, 0x26, 0xeb, 0x3b, 0xc9,
+ 0xf8, 0xaf, 0x6d, 0x13, 0x27, 0xf1, 0x29, 0xde,
+ 0xb3, 0x35, 0x0e, 0xfb, 0x2f, 0xd4, 0xae, 0xff,
+ 0x52, 0x69, 0x41, 0xfd, 0x4f, 0x41, 0x88, 0x7f,
+ 0xff, 0x96, 0xbd, 0x01, 0x4d, 0xb5, 0x26, 0x08,
+ 0x82, 0xf8, 0x1b, 0x16, 0xb6, 0x17, 0x45, 0x61,
+ 0x00, 0x0e, 0x00, 0x00,
+ }
+
+ path, err := ExtractTarBall(bytes.NewBuffer(content))
+ defer os.RemoveAll(path)
+ if err != nil {
+ t.Errorf("This tar.gz should be handled correctly, but '%s' occured", err)
+ }
+ })
}
diff --git a/src/ncm/Makefile b/src/ncm/Makefile
new file mode 100644
index 00000000..fbdd53f7
--- /dev/null
+++ b/src/ncm/Makefile
@@ -0,0 +1,36 @@
+# SPDX-license-identifier: Apache-2.0
+##############################################################################
+# Copyright (c) 2020 Intel Corporation
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+export GO111MODULE=on
+
+all: clean
+ CGO_ENABLED=1 GOOS=linux GOARCH=amd64
+ @go build -tags netgo -o ./ncm ./cmd/main.go
+
+# The following is done this way as each patch on CI runs build and each merge runs deploy. So for build we don't need to build binary and hence
+# no need to create a static binary with additional flags. However, for generating binary, additional build flags are necessary. This if used with
+# mock plugin errors out for unit tests. So the seperation avoids the error.
+
+build: clean test cover
+deploy: build
+
+.PHONY: test
+test: clean
+ @go test -race ./...
+
+format:
+ @go fmt ./...
+
+clean:
+ @rm -f ncm coverage.html coverage.out
+
+.PHONY: cover
+cover:
+ @go test -race ./... -coverprofile=coverage.out
+ @go tool cover -html=coverage.out -o coverage.html
diff --git a/src/ncm/api/api.go b/src/ncm/api/api.go
new file mode 100644
index 00000000..34b46c67
--- /dev/null
+++ b/src/ncm/api/api.go
@@ -0,0 +1,107 @@
+/*
+ * 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 (
+ "fmt"
+ "reflect"
+
+ "github.com/gorilla/mux"
+ moduleLib "github.com/onap/multicloud-k8s/src/ncm/pkg/module"
+)
+
+var moduleClient *moduleLib.Client
+
+// For the given client and testClient, if the testClient is not null and
+// implements the client manager interface corresponding to client, then
+// return the testClient, otherwise return the client.
+func setClient(client, testClient interface{}) interface{} {
+ switch cl := client.(type) {
+ case *moduleLib.ClusterClient:
+ if testClient != nil && reflect.TypeOf(testClient).Implements(reflect.TypeOf((*moduleLib.ClusterManager)(nil)).Elem()) {
+ c, ok := testClient.(moduleLib.ClusterManager)
+ if ok {
+ return c
+ }
+ }
+ case *moduleLib.NetworkClient:
+ if testClient != nil && reflect.TypeOf(testClient).Implements(reflect.TypeOf((*moduleLib.NetworkManager)(nil)).Elem()) {
+ c, ok := testClient.(moduleLib.NetworkManager)
+ if ok {
+ return c
+ }
+ }
+ case *moduleLib.ProviderNetClient:
+ if testClient != nil && reflect.TypeOf(testClient).Implements(reflect.TypeOf((*moduleLib.ProviderNetManager)(nil)).Elem()) {
+ c, ok := testClient.(moduleLib.ProviderNetManager)
+ if ok {
+ return c
+ }
+ }
+ default:
+ fmt.Printf("unknown type %T\n", cl)
+ }
+ return client
+}
+
+// NewRouter creates a router that registers the various urls that are supported
+// testClient parameter allows unit testing for a given client
+func NewRouter(testClient interface{}) *mux.Router {
+
+ moduleClient = moduleLib.NewClient()
+
+ router := mux.NewRouter().PathPrefix("/v2").Subrouter()
+
+ clusterHandler := clusterHandler{
+ client: setClient(moduleClient.Cluster, testClient).(moduleLib.ClusterManager),
+ }
+ router.HandleFunc("/cluster-providers", clusterHandler.createClusterProviderHandler).Methods("POST")
+ router.HandleFunc("/cluster-providers", clusterHandler.getClusterProviderHandler).Methods("GET")
+ router.HandleFunc("/cluster-providers/{name}", clusterHandler.getClusterProviderHandler).Methods("GET")
+ router.HandleFunc("/cluster-providers/{name}", clusterHandler.deleteClusterProviderHandler).Methods("DELETE")
+ router.HandleFunc("/cluster-providers/{provider-name}/clusters", clusterHandler.createClusterHandler).Methods("POST")
+ router.HandleFunc("/cluster-providers/{provider-name}/clusters", clusterHandler.getClusterHandler).Methods("GET")
+ router.HandleFunc("/cluster-providers/{provider-name}/clusters/{name}", clusterHandler.getClusterHandler).Methods("GET")
+ router.HandleFunc("/cluster-providers/{provider-name}/clusters/{name}", clusterHandler.deleteClusterHandler).Methods("DELETE")
+ router.HandleFunc("/cluster-providers/{provider-name}/clusters/{cluster-name}/labels", clusterHandler.createClusterLabelHandler).Methods("POST")
+ router.HandleFunc("/cluster-providers/{provider-name}/clusters/{cluster-name}/labels", clusterHandler.getClusterLabelHandler).Methods("GET")
+ router.HandleFunc("/cluster-providers/{provider-name}/clusters/{cluster-name}/labels/{label}", clusterHandler.getClusterLabelHandler).Methods("GET")
+ router.HandleFunc("/cluster-providers/{provider-name}/clusters/{cluster-name}/labels/{label}", clusterHandler.deleteClusterLabelHandler).Methods("DELETE")
+ router.HandleFunc("/cluster-providers/{provider-name}/clusters/{cluster-name}/kv-pairs", clusterHandler.createClusterKvPairsHandler).Methods("POST")
+ 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")
+
+ networkHandler := networkHandler{
+ client: setClient(moduleClient.Network, testClient).(moduleLib.NetworkManager),
+ }
+ router.HandleFunc("/cluster-providers/{provider-name}/clusters/{cluster-name}/networks", networkHandler.createNetworkHandler).Methods("POST")
+ router.HandleFunc("/cluster-providers/{provider-name}/clusters/{cluster-name}/networks", networkHandler.getNetworkHandler).Methods("GET")
+ router.HandleFunc("/cluster-providers/{provider-name}/clusters/{cluster-name}/networks/{name}", networkHandler.putNetworkHandler).Methods("PUT")
+ router.HandleFunc("/cluster-providers/{provider-name}/clusters/{cluster-name}/networks/{name}", networkHandler.getNetworkHandler).Methods("GET")
+ router.HandleFunc("/cluster-providers/{provider-name}/clusters/{cluster-name}/networks/{name}", networkHandler.deleteNetworkHandler).Methods("DELETE")
+
+ providernetHandler := providernetHandler{
+ client: setClient(moduleClient.ProviderNet, testClient).(moduleLib.ProviderNetManager),
+ }
+ router.HandleFunc("/cluster-providers/{provider-name}/clusters/{cluster-name}/provider-networks", providernetHandler.createProviderNetHandler).Methods("POST")
+ router.HandleFunc("/cluster-providers/{provider-name}/clusters/{cluster-name}/provider-networks", providernetHandler.getProviderNetHandler).Methods("GET")
+ router.HandleFunc("/cluster-providers/{provider-name}/clusters/{cluster-name}/provider-networks/{name}", providernetHandler.putProviderNetHandler).Methods("PUT")
+ router.HandleFunc("/cluster-providers/{provider-name}/clusters/{cluster-name}/provider-networks/{name}", providernetHandler.getProviderNetHandler).Methods("GET")
+ router.HandleFunc("/cluster-providers/{provider-name}/clusters/{cluster-name}/provider-networks/{name}", providernetHandler.deleteProviderNetHandler).Methods("DELETE")
+
+ return router
+}
diff --git a/src/ncm/api/clusterhandler.go b/src/ncm/api/clusterhandler.go
new file mode 100644
index 00000000..cb147a8a
--- /dev/null
+++ b/src/ncm/api/clusterhandler.go
@@ -0,0 +1,468 @@
+/*
+ * 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/ncm/pkg/module"
+
+ "github.com/gorilla/mux"
+)
+
+// Used to store backend implementations objects
+// Also simplifies mocking for unit testing purposes
+type clusterHandler struct {
+ // Interface that implements Cluster operations
+ // We will set this variable with a mock interface for testing
+ client moduleLib.ClusterManager
+}
+
+// Create handles creation of the ClusterProvider entry in the database
+func (h clusterHandler) createClusterProviderHandler(w http.ResponseWriter, r *http.Request) {
+ var p moduleLib.ClusterProvider
+
+ err := json.NewDecoder(r.Body).Decode(&p)
+
+ switch {
+ case err == io.EOF:
+ http.Error(w, "Empty body", http.StatusBadRequest)
+ return
+ case err != nil:
+ http.Error(w, err.Error(), http.StatusUnprocessableEntity)
+ return
+ }
+
+ // Name is required.
+ if p.Metadata.Name == "" {
+ http.Error(w, "Missing name in POST request", http.StatusBadRequest)
+ return
+ }
+
+ ret, err := h.client.CreateClusterProvider(p)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusCreated)
+ err = json.NewEncoder(w).Encode(ret)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+// Get handles GET operations on a particular ClusterProvider Name
+// Returns a ClusterProvider
+func (h clusterHandler) getClusterProviderHandler(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ name := vars["name"]
+ var ret interface{}
+ var err error
+
+ if len(name) == 0 {
+ ret, err = h.client.GetClusterProviders()
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ } else {
+ ret, err = h.client.GetClusterProvider(name)
+ 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(ret)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+// Delete handles DELETE operations on a particular ClusterProvider Name
+func (h clusterHandler) deleteClusterProviderHandler(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ name := vars["name"]
+
+ err := h.client.DeleteClusterProvider(name)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.WriteHeader(http.StatusNoContent)
+}
+
+// Create handles creation of the Cluster entry in the database
+func (h clusterHandler) createClusterHandler(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ provider := vars["provider-name"]
+ var p moduleLib.Cluster
+ var q moduleLib.ClusterContent
+
+ // 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(&p)
+ 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
+ }
+
+ q.Kubeconfig = base64.StdEncoding.EncodeToString(content)
+
+ // Name is required.
+ if p.Metadata.Name == "" {
+ http.Error(w, "Missing name in POST request", http.StatusBadRequest)
+ return
+ }
+
+ ret, err := h.client.CreateCluster(provider, p, q)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ // w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusCreated)
+ err = json.NewEncoder(w).Encode(ret)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+// Get handles GET operations on a particular Cluster Name
+// Returns a Cluster
+func (h clusterHandler) getClusterHandler(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ provider := vars["provider-name"]
+ name := vars["name"]
+
+ // handle the get all clusters case - return a list of only the json parts
+ if len(name) == 0 {
+ var retList []moduleLib.Cluster
+
+ ret, err := h.client.GetClusters(provider)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ for _, cl := range ret {
+ retList = append(retList, moduleLib.Cluster{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
+ }
+
+ accepted, _, err := mime.ParseMediaType(r.Header.Get("Accept"))
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusNotAcceptable)
+ return
+ }
+
+ retCluster, err := h.client.GetCluster(provider, name)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ retKubeconfig, err := h.client.GetClusterContent(provider, 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(retCluster); 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; filename=kubeconfig"}})
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ kcBytes, err := base64.StdEncoding.DecodeString(retKubeconfig.Kubeconfig)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ _, err = pw.Write(kcBytes)
+ 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(retCluster)
+ 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)
+ kcBytes, err := base64.StdEncoding.DecodeString(retKubeconfig.Kubeconfig)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ _, err = w.Write(kcBytes)
+ 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
+ }
+}
+
+// Delete handles DELETE operations on a particular Cluster Name
+func (h clusterHandler) deleteClusterHandler(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ provider := vars["provider-name"]
+ name := vars["name"]
+
+ err := h.client.DeleteCluster(provider, name)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.WriteHeader(http.StatusNoContent)
+}
+
+// Create handles creation of the ClusterLabel entry in the database
+func (h clusterHandler) createClusterLabelHandler(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ provider := vars["provider-name"]
+ cluster := vars["cluster-name"]
+ var p moduleLib.ClusterLabel
+
+ err := json.NewDecoder(r.Body).Decode(&p)
+
+ // LabelName is required.
+ if p.LabelName == "" {
+ http.Error(w, "Missing label name in POST request", http.StatusBadRequest)
+ return
+ }
+
+ ret, err := h.client.CreateClusterLabel(provider, cluster, p)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusCreated)
+ err = json.NewEncoder(w).Encode(ret)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+// Get handles GET operations on a particular Cluster Label
+// Returns a ClusterLabel
+func (h clusterHandler) getClusterLabelHandler(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ provider := vars["provider-name"]
+ cluster := vars["cluster-name"]
+ label := vars["label"]
+
+ var ret interface{}
+ var err error
+
+ if len(label) == 0 {
+ ret, err = h.client.GetClusterLabels(provider, cluster)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ } else {
+ ret, err = h.client.GetClusterLabel(provider, cluster, label)
+ 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(ret)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+// Delete handles DELETE operations on a particular ClusterLabel Name
+func (h clusterHandler) deleteClusterLabelHandler(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ provider := vars["provider-name"]
+ cluster := vars["cluster-name"]
+ label := vars["label"]
+
+ err := h.client.DeleteClusterLabel(provider, cluster, label)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.WriteHeader(http.StatusNoContent)
+}
+
+// Create handles creation of the ClusterKvPairs entry in the database
+func (h clusterHandler) createClusterKvPairsHandler(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ provider := vars["provider-name"]
+ cluster := vars["cluster-name"]
+ var p moduleLib.ClusterKvPairs
+
+ err := json.NewDecoder(r.Body).Decode(&p)
+
+ // KvPairsName is required.
+ if p.Metadata.Name == "" {
+ http.Error(w, "Missing Key Value pair name in POST request", http.StatusBadRequest)
+ return
+ }
+
+ ret, err := h.client.CreateClusterKvPairs(provider, cluster, p)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusCreated)
+ err = json.NewEncoder(w).Encode(ret)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+// Get handles GET operations on a particular Cluster Key Value Pair
+// Returns a ClusterKvPairs
+func (h clusterHandler) getClusterKvPairsHandler(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ provider := vars["provider-name"]
+ cluster := vars["cluster-name"]
+ kvpair := vars["kvpair"]
+
+ var ret interface{}
+ var err error
+
+ if len(kvpair) == 0 {
+ ret, err = h.client.GetAllClusterKvPairs(provider, cluster)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ } else {
+ ret, err = h.client.GetClusterKvPairs(provider, cluster, kvpair)
+ 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(ret)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+// Delete handles DELETE operations on a particular Cluster Name
+func (h clusterHandler) deleteClusterKvPairsHandler(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ provider := vars["provider-name"]
+ cluster := vars["cluster-name"]
+ kvpair := vars["kvpair"]
+
+ err := h.client.DeleteClusterKvPairs(provider, cluster, kvpair)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.WriteHeader(http.StatusNoContent)
+}
diff --git a/src/ncm/api/clusterhandler_test.go b/src/ncm/api/clusterhandler_test.go
new file mode 100644
index 00000000..af5bd160
--- /dev/null
+++ b/src/ncm/api/clusterhandler_test.go
@@ -0,0 +1,1412 @@
+/*
+ * 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"
+ "mime/multipart"
+ "net/http"
+ "net/http/httptest"
+ "net/textproto"
+ "reflect"
+ "testing"
+
+ moduleLib "github.com/onap/multicloud-k8s/src/ncm/pkg/module"
+
+ pkgerrors "github.com/pkg/errors"
+)
+
+//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 mockClusterManager struct {
+ // Items and err will be used to customize each test
+ // via a localized instantiation of mockClusterManager
+ ClusterProviderItems []moduleLib.ClusterProvider
+ ClusterItems []moduleLib.Cluster
+ ClusterContentItems []moduleLib.ClusterContent
+ ClusterLabelItems []moduleLib.ClusterLabel
+ ClusterKvPairsItems []moduleLib.ClusterKvPairs
+ Err error
+}
+
+func (m *mockClusterManager) CreateClusterProvider(inp moduleLib.ClusterProvider) (moduleLib.ClusterProvider, error) {
+ if m.Err != nil {
+ return moduleLib.ClusterProvider{}, m.Err
+ }
+
+ return m.ClusterProviderItems[0], nil
+}
+
+func (m *mockClusterManager) GetClusterProvider(name string) (moduleLib.ClusterProvider, error) {
+ if m.Err != nil {
+ return moduleLib.ClusterProvider{}, m.Err
+ }
+
+ return m.ClusterProviderItems[0], nil
+}
+
+func (m *mockClusterManager) GetClusterProviders() ([]moduleLib.ClusterProvider, error) {
+ if m.Err != nil {
+ return []moduleLib.ClusterProvider{}, m.Err
+ }
+
+ return m.ClusterProviderItems, nil
+}
+
+func (m *mockClusterManager) DeleteClusterProvider(name string) error {
+ return m.Err
+}
+
+func (m *mockClusterManager) CreateCluster(provider string, inp moduleLib.Cluster, inq moduleLib.ClusterContent) (moduleLib.Cluster, error) {
+ if m.Err != nil {
+ return moduleLib.Cluster{}, m.Err
+ }
+
+ return m.ClusterItems[0], nil
+}
+
+func (m *mockClusterManager) GetCluster(provider, name string) (moduleLib.Cluster, error) {
+ if m.Err != nil {
+ return moduleLib.Cluster{}, m.Err
+ }
+
+ return m.ClusterItems[0], nil
+}
+
+func (m *mockClusterManager) GetClusterContent(provider, name string) (moduleLib.ClusterContent, error) {
+ if m.Err != nil {
+ return moduleLib.ClusterContent{}, m.Err
+ }
+
+ return m.ClusterContentItems[0], nil
+}
+
+func (m *mockClusterManager) GetClusters(provider string) ([]moduleLib.Cluster, error) {
+ if m.Err != nil {
+ return []moduleLib.Cluster{}, m.Err
+ }
+
+ return m.ClusterItems, nil
+}
+
+func (m *mockClusterManager) DeleteCluster(provider, name string) error {
+ return m.Err
+}
+
+func (m *mockClusterManager) CreateClusterLabel(provider, cluster string, inp moduleLib.ClusterLabel) (moduleLib.ClusterLabel, error) {
+ if m.Err != nil {
+ return moduleLib.ClusterLabel{}, m.Err
+ }
+
+ return m.ClusterLabelItems[0], nil
+}
+
+func (m *mockClusterManager) GetClusterLabel(provider, cluster, label string) (moduleLib.ClusterLabel, error) {
+ if m.Err != nil {
+ return moduleLib.ClusterLabel{}, m.Err
+ }
+
+ return m.ClusterLabelItems[0], nil
+}
+
+func (m *mockClusterManager) GetClusterLabels(provider, cluster string) ([]moduleLib.ClusterLabel, error) {
+ if m.Err != nil {
+ return []moduleLib.ClusterLabel{}, m.Err
+ }
+
+ return m.ClusterLabelItems, nil
+}
+
+func (m *mockClusterManager) DeleteClusterLabel(provider, cluster, label string) error {
+ return m.Err
+}
+
+func (m *mockClusterManager) CreateClusterKvPairs(provider, cluster string, inp moduleLib.ClusterKvPairs) (moduleLib.ClusterKvPairs, error) {
+ if m.Err != nil {
+ return moduleLib.ClusterKvPairs{}, m.Err
+ }
+
+ return m.ClusterKvPairsItems[0], nil
+}
+
+func (m *mockClusterManager) GetClusterKvPairs(provider, cluster, kvpair string) (moduleLib.ClusterKvPairs, error) {
+ if m.Err != nil {
+ return moduleLib.ClusterKvPairs{}, m.Err
+ }
+
+ return m.ClusterKvPairsItems[0], nil
+}
+
+func (m *mockClusterManager) GetAllClusterKvPairs(provider, cluster string) ([]moduleLib.ClusterKvPairs, error) {
+ if m.Err != nil {
+ return []moduleLib.ClusterKvPairs{}, m.Err
+ }
+
+ return m.ClusterKvPairsItems, nil
+}
+
+func (m *mockClusterManager) DeleteClusterKvPairs(provider, cluster, kvpair string) error {
+ return m.Err
+}
+
+func TestClusterProviderCreateHandler(t *testing.T) {
+ testCases := []struct {
+ label string
+ reader io.Reader
+ expected moduleLib.ClusterProvider
+ expectedCode int
+ clusterClient *mockClusterManager
+ }{
+ {
+ label: "Missing Cluster Provider Body Failure",
+ expectedCode: http.StatusBadRequest,
+ clusterClient: &mockClusterManager{},
+ },
+ {
+ label: "Create Cluster Provider",
+ expectedCode: http.StatusCreated,
+ reader: bytes.NewBuffer([]byte(`{
+ "metadata": {
+ "name": "clusterProviderTest",
+ "description": "testClusterProvider",
+ "userData1": "some user data 1",
+ "userData2": "some user data 2"
+ }
+ }`)),
+ expected: moduleLib.ClusterProvider{
+ Metadata: moduleLib.Metadata{
+ Name: "clusterProviderTest",
+ Description: "testClusterProvider",
+ UserData1: "some user data 1",
+ UserData2: "some user data 2",
+ },
+ },
+ clusterClient: &mockClusterManager{
+ //Items that will be returned by the mocked Client
+ ClusterProviderItems: []moduleLib.ClusterProvider{
+ {
+ Metadata: moduleLib.Metadata{
+ Name: "clusterProviderTest",
+ Description: "testClusterProvider",
+ UserData1: "some user data 1",
+ UserData2: "some user data 2",
+ },
+ },
+ },
+ },
+ },
+ {
+ label: "Missing ClusterProvider Name in Request Body",
+ reader: bytes.NewBuffer([]byte(`{
+ "metadata": {
+ "description": "this is a test cluster provider",
+ "userData1": "some user data 1",
+ "userData2": "some user data 2"
+ }
+ }`)),
+ expectedCode: http.StatusBadRequest,
+ clusterClient: &mockClusterManager{},
+ },
+ }
+
+ 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(testCase.clusterClient))
+
+ //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.ClusterProvider{}
+ 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)
+ }
+ }
+ })
+ }
+}
+
+func TestClusterProviderGetAllHandler(t *testing.T) {
+
+ testCases := []struct {
+ label string
+ expected []moduleLib.ClusterProvider
+ name, version string
+ expectedCode int
+ clusterClient *mockClusterManager
+ }{
+ {
+ label: "Get Cluster Provider",
+ expectedCode: http.StatusOK,
+ expected: []moduleLib.ClusterProvider{
+ {
+ Metadata: moduleLib.Metadata{
+ Name: "testClusterProvider1",
+ Description: "testClusterProvider 1 description",
+ UserData1: "some user data 1",
+ UserData2: "some user data 2",
+ },
+ },
+ {
+ Metadata: moduleLib.Metadata{
+ Name: "testClusterProvider2",
+ Description: "testClusterProvider 2 description",
+ UserData1: "some user data A",
+ UserData2: "some user data B",
+ },
+ },
+ },
+ clusterClient: &mockClusterManager{
+ //Items that will be returned by the mocked Client
+ ClusterProviderItems: []moduleLib.ClusterProvider{
+ {
+ Metadata: moduleLib.Metadata{
+ Name: "testClusterProvider1",
+ Description: "testClusterProvider 1 description",
+ UserData1: "some user data 1",
+ UserData2: "some user data 2",
+ },
+ },
+ {
+ Metadata: moduleLib.Metadata{
+ Name: "testClusterProvider2",
+ Description: "testClusterProvider 2 description",
+ UserData1: "some user data A",
+ UserData2: "some user data B",
+ },
+ },
+ },
+ },
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ request := httptest.NewRequest("GET", "/v2/cluster-providers", nil)
+ resp := executeRequest(request, NewRouter(testCase.clusterClient))
+
+ //Check returned code
+ if resp.StatusCode != testCase.expectedCode {
+ t.Fatalf("Expected %d; Got: %d", testCase.expectedCode, resp.StatusCode)
+ }
+
+ //Check returned body only if statusOK
+ if resp.StatusCode == http.StatusOK {
+ got := []moduleLib.ClusterProvider{}
+ json.NewDecoder(resp.Body).Decode(&got)
+
+ if reflect.DeepEqual(testCase.expected, got) == false {
+ t.Errorf("listHandler returned unexpected body: got %v;"+
+ " expected %v", got, testCase.expected)
+ }
+ }
+ })
+ }
+}
+
+func TestClusterProviderGetHandler(t *testing.T) {
+
+ testCases := []struct {
+ label string
+ expected moduleLib.ClusterProvider
+ name, version string
+ expectedCode int
+ clusterClient *mockClusterManager
+ }{
+ {
+ label: "Get Cluster Provider",
+ expectedCode: http.StatusOK,
+ expected: moduleLib.ClusterProvider{
+ Metadata: moduleLib.Metadata{
+ Name: "testClusterProvider",
+ Description: "testClusterProvider description",
+ UserData1: "some user data 1",
+ UserData2: "some user data 2",
+ },
+ },
+ name: "testClusterProvider",
+ clusterClient: &mockClusterManager{
+ //Items that will be returned by the mocked Client
+ ClusterProviderItems: []moduleLib.ClusterProvider{
+ {
+ Metadata: moduleLib.Metadata{
+ Name: "testClusterProvider",
+ Description: "testClusterProvider description",
+ UserData1: "some user data 1",
+ UserData2: "some user data 2",
+ },
+ },
+ },
+ },
+ },
+ {
+ label: "Get Non-Existing Cluster Provider",
+ expectedCode: http.StatusInternalServerError,
+ name: "nonexistingclusterprovider",
+ clusterClient: &mockClusterManager{
+ ClusterProviderItems: []moduleLib.ClusterProvider{},
+ Err: pkgerrors.New("Internal Error"),
+ },
+ },
+ }
+
+ 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(testCase.clusterClient))
+
+ //Check returned code
+ if resp.StatusCode != testCase.expectedCode {
+ t.Fatalf("Expected %d; Got: %d", testCase.expectedCode, resp.StatusCode)
+ }
+
+ //Check returned body only if statusOK
+ if resp.StatusCode == http.StatusOK {
+ got := moduleLib.ClusterProvider{}
+ json.NewDecoder(resp.Body).Decode(&got)
+
+ if reflect.DeepEqual(testCase.expected, got) == false {
+ t.Errorf("listHandler returned unexpected body: got %v;"+
+ " expected %v", got, testCase.expected)
+ }
+ }
+ })
+ }
+}
+
+func TestClusterProviderDeleteHandler(t *testing.T) {
+
+ testCases := []struct {
+ label string
+ name string
+ version string
+ expectedCode int
+ clusterClient *mockClusterManager
+ }{
+ {
+ label: "Delete Cluster Provider",
+ expectedCode: http.StatusNoContent,
+ name: "testClusterProvider",
+ clusterClient: &mockClusterManager{},
+ },
+ {
+ label: "Delete Non-Existing Cluster Provider",
+ expectedCode: http.StatusInternalServerError,
+ name: "testClusterProvider",
+ clusterClient: &mockClusterManager{
+ Err: pkgerrors.New("Internal Error"),
+ },
+ },
+ }
+
+ 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(testCase.clusterClient))
+
+ //Check returned code
+ if resp.StatusCode != testCase.expectedCode {
+ t.Fatalf("Expected %d; Got: %d", testCase.expectedCode, resp.StatusCode)
+ }
+ })
+ }
+}
+
+func TestClusterCreateHandler(t *testing.T) {
+ testCases := []struct {
+ label string
+ metadata string
+ kubeconfig string
+ expected moduleLib.Cluster
+ expectedCode int
+ clusterClient *mockClusterManager
+ }{
+ {
+ label: "Missing Cluster Body Failure",
+ expectedCode: http.StatusBadRequest,
+ clusterClient: &mockClusterManager{},
+ },
+ {
+ label: "Create Cluster",
+ expectedCode: http.StatusCreated,
+ metadata: `
+{
+ "metadata": {
+ "name": "clusterTest",
+ "description": "this is test cluster",
+ "userData1": "some cluster data abc",
+ "userData2": "some cluster data def"
+ }
+}`,
+ kubeconfig: `test contents
+of a file attached
+to the creation
+of clusterTest
+`,
+ expected: moduleLib.Cluster{
+ Metadata: moduleLib.Metadata{
+ Name: "clusterTest",
+ Description: "testCluster",
+ UserData1: "some user data 1",
+ UserData2: "some user data 2",
+ },
+ },
+ clusterClient: &mockClusterManager{
+ //Items that will be returned by the mocked Client
+ ClusterProviderItems: []moduleLib.ClusterProvider{
+ {
+ Metadata: moduleLib.Metadata{
+ Name: "clusterProvider1",
+ Description: "ClusterProvider 1 description",
+ UserData1: "some user data 1",
+ UserData2: "some user data 2",
+ },
+ },
+ },
+ ClusterItems: []moduleLib.Cluster{
+ {
+ Metadata: moduleLib.Metadata{
+ Name: "clusterTest",
+ Description: "testCluster",
+ UserData1: "some user data 1",
+ UserData2: "some user data 2",
+ },
+ },
+ },
+ ClusterContentItems: []moduleLib.ClusterContent{
+ {
+ Kubeconfig: "dGVzdCBjb250ZW50cwpvZiBhIGZpbGUgYXR0YWNoZWQKdG8gdGhlIGNyZWF0aW9uCm9mIGNsdXN0ZXJUZXN0Cg==",
+ },
+ },
+ },
+ },
+ {
+ label: "Missing Cluster Name in Request Body",
+ expectedCode: http.StatusBadRequest,
+ metadata: `
+{
+ "metadata": {
+ "description": "this is test cluster",
+ "userData1": "some cluster data abc",
+ "userData2": "some cluster data def"
+ }
+}`,
+ kubeconfig: `test contents
+of a file attached
+to the creation
+of clusterTest
+`,
+ clusterClient: &mockClusterManager{},
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ // Create the multipart test Request body
+ body := new(bytes.Buffer)
+ multiwr := multipart.NewWriter(body)
+ multiwr.SetBoundary("------------------------f77f80a7092eb312")
+ pw, _ := multiwr.CreatePart(textproto.MIMEHeader{"Content-Type": {"application/json"}, "Content-Disposition": {"form-data; name=metadata"}})
+ pw.Write([]byte(testCase.metadata))
+ pw, _ = multiwr.CreateFormFile("file", "kubeconfig")
+ pw.Write([]byte(testCase.kubeconfig))
+ multiwr.Close()
+
+ request := httptest.NewRequest("POST", "/v2/cluster-providers/clusterProvider1/clusters", bytes.NewBuffer(body.Bytes()))
+ request.Header.Set("Content-Type", multiwr.FormDataContentType())
+ resp := executeRequest(request, NewRouter(testCase.clusterClient))
+
+ //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.Cluster{}
+ 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)
+ }
+ }
+ })
+ }
+}
+
+func TestClusterGetAllHandler(t *testing.T) {
+
+ testCases := []struct {
+ label string
+ expected []moduleLib.Cluster
+ name, version string
+ expectedCode int
+ clusterClient *mockClusterManager
+ }{
+ {
+ label: "Get Clusters",
+ expectedCode: http.StatusOK,
+ expected: []moduleLib.Cluster{
+ {
+ Metadata: moduleLib.Metadata{
+ Name: "testCluster1",
+ Description: "testCluster 1 description",
+ UserData1: "some user data 1",
+ UserData2: "some user data 2",
+ },
+ },
+ {
+ Metadata: moduleLib.Metadata{
+ Name: "testCluster2",
+ Description: "testCluster 2 description",
+ UserData1: "some user data A",
+ UserData2: "some user data B",
+ },
+ },
+ },
+ clusterClient: &mockClusterManager{
+ //Items that will be returned by the mocked Client
+ ClusterItems: []moduleLib.Cluster{
+ {
+ Metadata: moduleLib.Metadata{
+ Name: "testCluster1",
+ Description: "testCluster 1 description",
+ UserData1: "some user data 1",
+ UserData2: "some user data 2",
+ },
+ },
+ {
+ Metadata: moduleLib.Metadata{
+ Name: "testCluster2",
+ Description: "testCluster 2 description",
+ UserData1: "some user data A",
+ UserData2: "some user data B",
+ },
+ },
+ },
+ ClusterContentItems: []moduleLib.ClusterContent{
+ // content here doesn't matter - just needs to be present
+ {
+ Kubeconfig: "dGVzdCBjb250ZW50cwpvZiBhIGZpbGUgYXR0YWNoZWQKdG8gdGhlIGNyZWF0aW9uCm9mIGNsdXN0ZXJUZXN0Cg==",
+ },
+ {
+ Kubeconfig: "dGVzdCBjb250ZW50cwpvZiBhIGZpbGUgYXR0YWNoZWQKdG8gdGhlIGNyZWF0aW9uCm9mIGNsdXN0ZXJUZXN0Cg==",
+ },
+ },
+ },
+ },
+ }
+
+ 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(testCase.clusterClient))
+
+ //Check returned code
+ if resp.StatusCode != testCase.expectedCode {
+ t.Fatalf("Expected %d; Got: %d", testCase.expectedCode, resp.StatusCode)
+ }
+
+ //Check returned body only if statusOK
+ if resp.StatusCode == http.StatusOK {
+ got := []moduleLib.Cluster{}
+ json.NewDecoder(resp.Body).Decode(&got)
+
+ if reflect.DeepEqual(testCase.expected, got) == false {
+ t.Errorf("listHandler returned unexpected body: got %v;"+
+ " expected %v", got, testCase.expected)
+ }
+ }
+ })
+ }
+}
+
+func TestClusterGetHandler(t *testing.T) {
+
+ testCases := []struct {
+ label string
+ expected moduleLib.Cluster
+ name, version string
+ accept string
+ expectedCode int
+ clusterClient *mockClusterManager
+ }{
+ {
+ label: "Get Cluster with Accept: application/json",
+ accept: "application/json",
+ expectedCode: http.StatusOK,
+ expected: moduleLib.Cluster{
+ Metadata: moduleLib.Metadata{
+ Name: "testCluster",
+ Description: "testCluster description",
+ UserData1: "some user data 1",
+ UserData2: "some user data 2",
+ },
+ },
+ name: "testCluster",
+ clusterClient: &mockClusterManager{
+ //Items that will be returned by the mocked Client
+ ClusterItems: []moduleLib.Cluster{
+ {
+ Metadata: moduleLib.Metadata{
+ Name: "testCluster",
+ Description: "testCluster description",
+ UserData1: "some user data 1",
+ UserData2: "some user data 2",
+ },
+ },
+ },
+ ClusterContentItems: []moduleLib.ClusterContent{
+ {
+ Kubeconfig: "dGVzdCBjb250ZW50cwpvZiBhIGZpbGUgYXR0YWNoZWQKdG8gdGhlIGNyZWF0aW9uCm9mIGNsdXN0ZXJUZXN0Cg==",
+ },
+ },
+ },
+ },
+ {
+ label: "Get Non-Existing Cluster",
+ accept: "application/json",
+ expectedCode: http.StatusInternalServerError,
+ name: "nonexistingcluster",
+ clusterClient: &mockClusterManager{
+ ClusterItems: []moduleLib.Cluster{},
+ Err: pkgerrors.New("Internal Error"),
+ },
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ request := httptest.NewRequest("GET", "/v2/cluster-providers/clusterProvider1/clusters/"+testCase.name, nil)
+ if len(testCase.accept) > 0 {
+ request.Header.Set("Accept", testCase.accept)
+ }
+ resp := executeRequest(request, NewRouter(testCase.clusterClient))
+
+ //Check returned code
+ if resp.StatusCode != testCase.expectedCode {
+ t.Fatalf("Expected %d; Got: %d", testCase.expectedCode, resp.StatusCode)
+ }
+
+ //Check returned body only if statusOK
+ if resp.StatusCode == http.StatusOK {
+ got := moduleLib.Cluster{}
+ json.NewDecoder(resp.Body).Decode(&got)
+
+ if reflect.DeepEqual(testCase.expected, got) == false {
+ t.Errorf("listHandler returned unexpected body: got %v;"+
+ " expected %v", got, testCase.expected)
+ }
+ }
+ })
+ }
+}
+
+func TestClusterGetContentHandler(t *testing.T) {
+
+ testCases := []struct {
+ label string
+ expected string
+ name, version string
+ accept string
+ expectedCode int
+ clusterClient *mockClusterManager
+ }{
+ {
+ label: "Get Cluster Content with Accept: application/octet-stream",
+ accept: "application/octet-stream",
+ expectedCode: http.StatusOK,
+ expected: `test contents
+of a file attached
+to the creation
+of clusterTest
+`,
+ name: "testCluster",
+ clusterClient: &mockClusterManager{
+ //Items that will be returned by the mocked Client
+ ClusterItems: []moduleLib.Cluster{
+ {
+ Metadata: moduleLib.Metadata{
+ Name: "testCluster",
+ Description: "testCluster description",
+ UserData1: "some user data 1",
+ UserData2: "some user data 2",
+ },
+ },
+ },
+ ClusterContentItems: []moduleLib.ClusterContent{
+ {
+ Kubeconfig: "dGVzdCBjb250ZW50cwpvZiBhIGZpbGUgYXR0YWNoZWQKdG8gdGhlIGNyZWF0aW9uCm9mIGNsdXN0ZXJUZXN0Cg==",
+ },
+ },
+ },
+ },
+ {
+ label: "Get Non-Existing Cluster",
+ accept: "application/octet-stream",
+ expectedCode: http.StatusInternalServerError,
+ name: "nonexistingcluster",
+ clusterClient: &mockClusterManager{
+ ClusterItems: []moduleLib.Cluster{},
+ Err: pkgerrors.New("Internal Error"),
+ },
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ request := httptest.NewRequest("GET", "/v2/cluster-providers/clusterProvider1/clusters/"+testCase.name, nil)
+ if len(testCase.accept) > 0 {
+ request.Header.Set("Accept", testCase.accept)
+ }
+ resp := executeRequest(request, NewRouter(testCase.clusterClient))
+
+ //Check returned code
+ if resp.StatusCode != testCase.expectedCode {
+ t.Fatalf("Expected %d; Got: %d", testCase.expectedCode, resp.StatusCode)
+ }
+
+ //Check returned body only if statusOK
+ if resp.StatusCode == http.StatusOK {
+ body := new(bytes.Buffer)
+ body.ReadFrom(resp.Body)
+ got := body.String()
+
+ if reflect.DeepEqual(testCase.expected, got) == false {
+ t.Errorf("listHandler returned unexpected body: got %v;"+
+ " expected %v", got, testCase.expected)
+ }
+ }
+ })
+ }
+}
+
+func TestClusterDeleteHandler(t *testing.T) {
+
+ testCases := []struct {
+ label string
+ name string
+ version string
+ expectedCode int
+ clusterClient *mockClusterManager
+ }{
+ {
+ label: "Delete Cluster",
+ expectedCode: http.StatusNoContent,
+ name: "testCluster",
+ clusterClient: &mockClusterManager{},
+ },
+ {
+ label: "Delete Non-Existing Cluster",
+ expectedCode: http.StatusInternalServerError,
+ name: "testCluster",
+ clusterClient: &mockClusterManager{
+ Err: pkgerrors.New("Internal Error"),
+ },
+ },
+ }
+
+ 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(testCase.clusterClient))
+
+ //Check returned code
+ if resp.StatusCode != testCase.expectedCode {
+ t.Fatalf("Expected %d; Got: %d", testCase.expectedCode, resp.StatusCode)
+ }
+ })
+ }
+}
+
+func TestClusterLabelCreateHandler(t *testing.T) {
+ testCases := []struct {
+ label string
+ reader io.Reader
+ expected moduleLib.ClusterLabel
+ expectedCode int
+ clusterClient *mockClusterManager
+ }{
+ {
+ label: "Missing Cluster Label Body Failure",
+ expectedCode: http.StatusBadRequest,
+ clusterClient: &mockClusterManager{},
+ },
+ {
+ label: "Create Cluster Label",
+ expectedCode: http.StatusCreated,
+ reader: bytes.NewBuffer([]byte(`{
+ "label-name": "test-label"
+ }`)),
+ expected: moduleLib.ClusterLabel{
+ LabelName: "test-label",
+ },
+ clusterClient: &mockClusterManager{
+ //Items that will be returned by the mocked Client
+ ClusterLabelItems: []moduleLib.ClusterLabel{
+ {
+ LabelName: "test-label",
+ },
+ },
+ },
+ },
+ }
+
+ 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(testCase.clusterClient))
+
+ //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.ClusterLabel{}
+ 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)
+ }
+ }
+ })
+ }
+}
+
+func TestClusterLabelsGetHandler(t *testing.T) {
+
+ testCases := []struct {
+ label string
+ expected []moduleLib.ClusterLabel
+ name, version string
+ expectedCode int
+ clusterClient *mockClusterManager
+ }{
+ {
+ label: "Get Cluster Labels",
+ expectedCode: http.StatusOK,
+ expected: []moduleLib.ClusterLabel{
+ {
+ LabelName: "test-label1",
+ },
+ {
+ LabelName: "test-label-two",
+ },
+ {
+ LabelName: "test-label-3",
+ },
+ },
+ clusterClient: &mockClusterManager{
+ //Items that will be returned by the mocked Client
+ ClusterLabelItems: []moduleLib.ClusterLabel{
+ {
+ LabelName: "test-label1",
+ },
+ {
+ LabelName: "test-label-two",
+ },
+ {
+ LabelName: "test-label-3",
+ },
+ },
+ },
+ },
+ }
+
+ 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(testCase.clusterClient))
+
+ //Check returned code
+ if resp.StatusCode != testCase.expectedCode {
+ t.Fatalf("Expected %d; Got: %d", testCase.expectedCode, resp.StatusCode)
+ }
+
+ //Check returned body only if statusOK
+ if resp.StatusCode == http.StatusOK {
+ got := []moduleLib.ClusterLabel{}
+ json.NewDecoder(resp.Body).Decode(&got)
+
+ if reflect.DeepEqual(testCase.expected, got) == false {
+ t.Errorf("listHandler returned unexpected body: got %v;"+
+ " expected %v", got, testCase.expected)
+ }
+ }
+ })
+ }
+}
+
+func TestClusterLabelGetHandler(t *testing.T) {
+
+ testCases := []struct {
+ label string
+ expected moduleLib.ClusterLabel
+ name, version string
+ expectedCode int
+ clusterClient *mockClusterManager
+ }{
+ {
+ label: "Get Cluster Label",
+ expectedCode: http.StatusOK,
+ expected: moduleLib.ClusterLabel{
+ LabelName: "testlabel",
+ },
+ name: "testlabel",
+ clusterClient: &mockClusterManager{
+ //Items that will be returned by the mocked Client
+ ClusterLabelItems: []moduleLib.ClusterLabel{
+ {
+ LabelName: "testlabel",
+ },
+ },
+ },
+ },
+ {
+ label: "Get Non-Existing Cluster Label",
+ expectedCode: http.StatusInternalServerError,
+ name: "nonexistingclusterlabel",
+ clusterClient: &mockClusterManager{
+ ClusterLabelItems: []moduleLib.ClusterLabel{},
+ Err: pkgerrors.New("Internal Error"),
+ },
+ },
+ }
+
+ 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(testCase.clusterClient))
+
+ //Check returned code
+ if resp.StatusCode != testCase.expectedCode {
+ t.Fatalf("Expected %d; Got: %d", testCase.expectedCode, resp.StatusCode)
+ }
+
+ //Check returned body only if statusOK
+ if resp.StatusCode == http.StatusOK {
+ got := moduleLib.ClusterLabel{}
+ json.NewDecoder(resp.Body).Decode(&got)
+
+ if reflect.DeepEqual(testCase.expected, got) == false {
+ t.Errorf("listHandler returned unexpected body: got %v;"+
+ " expected %v", got, testCase.expected)
+ }
+ }
+ })
+ }
+}
+
+func TestClusterLabelDeleteHandler(t *testing.T) {
+
+ testCases := []struct {
+ label string
+ name string
+ version string
+ expectedCode int
+ clusterClient *mockClusterManager
+ }{
+ {
+ label: "Delete Cluster Label",
+ expectedCode: http.StatusNoContent,
+ name: "testClusterLabel",
+ clusterClient: &mockClusterManager{},
+ },
+ {
+ label: "Delete Non-Existing Cluster Label",
+ expectedCode: http.StatusInternalServerError,
+ name: "testClusterLabel",
+ clusterClient: &mockClusterManager{
+ Err: pkgerrors.New("Internal Error"),
+ },
+ },
+ }
+
+ 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(testCase.clusterClient))
+
+ //Check returned code
+ if resp.StatusCode != testCase.expectedCode {
+ t.Fatalf("Expected %d; Got: %d", testCase.expectedCode, resp.StatusCode)
+ }
+ })
+ }
+}
+
+func TestClusterKvPairsCreateHandler(t *testing.T) {
+ testCases := []struct {
+ label string
+ reader io.Reader
+ expected moduleLib.ClusterKvPairs
+ expectedCode int
+ clusterClient *mockClusterManager
+ }{
+ {
+ label: "Missing Cluster KvPairs Body Failure",
+ expectedCode: http.StatusBadRequest,
+ clusterClient: &mockClusterManager{},
+ },
+ {
+ label: "Create Cluster KvPairs",
+ expectedCode: http.StatusCreated,
+ reader: bytes.NewBuffer([]byte(`{
+ "metadata": {
+ "name": "ClusterKvPair1",
+ "description": "test cluster kv pairs",
+ "userData1": "some user data 1",
+ "userData2": "some user data 2"
+ },
+ "spec": {
+ "kv": [
+ {
+ "key1": "value1"
+ },
+ {
+ "key2": "value2"
+ }
+ ]
+ }
+ }`)),
+ expected: moduleLib.ClusterKvPairs{
+ Metadata: moduleLib.Metadata{
+ Name: "ClusterKvPair1",
+ Description: "test cluster kv pairs",
+ UserData1: "some user data 1",
+ UserData2: "some user data 2",
+ },
+ Spec: moduleLib.ClusterKvSpec{
+ Kv: []map[string]interface{}{
+ {
+ "key1": "value1",
+ },
+ {
+ "key2": "value2",
+ },
+ },
+ },
+ },
+ clusterClient: &mockClusterManager{
+ //Items that will be returned by the mocked Client
+ ClusterKvPairsItems: []moduleLib.ClusterKvPairs{
+ {
+ Metadata: moduleLib.Metadata{
+ Name: "ClusterKvPair1",
+ Description: "test cluster kv pairs",
+ UserData1: "some user data 1",
+ UserData2: "some user data 2",
+ },
+ Spec: moduleLib.ClusterKvSpec{
+ Kv: []map[string]interface{}{
+ {
+ "key1": "value1",
+ },
+ {
+ "key2": "value2",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+
+ 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(testCase.clusterClient))
+
+ //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.ClusterKvPairs{}
+ 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)
+ }
+ }
+ })
+ }
+}
+
+func TestClusterKvPairsGetAllHandler(t *testing.T) {
+
+ testCases := []struct {
+ label string
+ expected []moduleLib.ClusterKvPairs
+ name, version string
+ expectedCode int
+ clusterClient *mockClusterManager
+ }{
+ {
+ label: "Get Cluster KvPairs",
+ expectedCode: http.StatusOK,
+ expected: []moduleLib.ClusterKvPairs{
+ {
+ Metadata: moduleLib.Metadata{
+ Name: "ClusterKvPair1",
+ Description: "test cluster kv pairs",
+ UserData1: "some user data 1",
+ UserData2: "some user data 2",
+ },
+ Spec: moduleLib.ClusterKvSpec{
+ Kv: []map[string]interface{}{
+ {
+ "key1": "value1",
+ },
+ {
+ "key2": "value2",
+ },
+ },
+ },
+ },
+ {
+ Metadata: moduleLib.Metadata{
+ Name: "ClusterKvPair2",
+ Description: "test cluster kv pairs",
+ UserData1: "some user data A",
+ UserData2: "some user data B",
+ },
+ Spec: moduleLib.ClusterKvSpec{
+ Kv: []map[string]interface{}{
+ {
+ "keyA": "valueA",
+ },
+ {
+ "keyB": "valueB",
+ },
+ },
+ },
+ },
+ },
+ clusterClient: &mockClusterManager{
+ //Items that will be returned by the mocked Client
+ ClusterKvPairsItems: []moduleLib.ClusterKvPairs{
+ {
+ Metadata: moduleLib.Metadata{
+ Name: "ClusterKvPair1",
+ Description: "test cluster kv pairs",
+ UserData1: "some user data 1",
+ UserData2: "some user data 2",
+ },
+ Spec: moduleLib.ClusterKvSpec{
+ Kv: []map[string]interface{}{
+ {
+ "key1": "value1",
+ },
+ {
+ "key2": "value2",
+ },
+ },
+ },
+ },
+ {
+ Metadata: moduleLib.Metadata{
+ Name: "ClusterKvPair2",
+ Description: "test cluster kv pairs",
+ UserData1: "some user data A",
+ UserData2: "some user data B",
+ },
+ Spec: moduleLib.ClusterKvSpec{
+ Kv: []map[string]interface{}{
+ {
+ "keyA": "valueA",
+ },
+ {
+ "keyB": "valueB",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+
+ 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(testCase.clusterClient))
+
+ //Check returned code
+ if resp.StatusCode != testCase.expectedCode {
+ t.Fatalf("Expected %d; Got: %d", testCase.expectedCode, resp.StatusCode)
+ }
+
+ //Check returned body only if statusOK
+ if resp.StatusCode == http.StatusOK {
+ got := []moduleLib.ClusterKvPairs{}
+ json.NewDecoder(resp.Body).Decode(&got)
+
+ if reflect.DeepEqual(testCase.expected, got) == false {
+ t.Errorf("listHandler returned unexpected body: got %v;"+
+ " expected %v", got, testCase.expected)
+ }
+ }
+ })
+ }
+}
+
+func TestClusterKvPairsGetHandler(t *testing.T) {
+
+ testCases := []struct {
+ label string
+ expected moduleLib.ClusterKvPairs
+ name, version string
+ expectedCode int
+ clusterClient *mockClusterManager
+ }{
+ {
+ label: "Get Cluster KV Pairs",
+ expectedCode: http.StatusOK,
+ expected: moduleLib.ClusterKvPairs{
+ Metadata: moduleLib.Metadata{
+ Name: "ClusterKvPair2",
+ Description: "test cluster kv pairs",
+ UserData1: "some user data A",
+ UserData2: "some user data B",
+ },
+ Spec: moduleLib.ClusterKvSpec{
+ Kv: []map[string]interface{}{
+ {
+ "keyA": "valueA",
+ },
+ {
+ "keyB": "valueB",
+ },
+ },
+ },
+ },
+ name: "ClusterKvPair2",
+ clusterClient: &mockClusterManager{
+ //Items that will be returned by the mocked Client
+ ClusterKvPairsItems: []moduleLib.ClusterKvPairs{
+ {
+ Metadata: moduleLib.Metadata{
+ Name: "ClusterKvPair2",
+ Description: "test cluster kv pairs",
+ UserData1: "some user data A",
+ UserData2: "some user data B",
+ },
+ Spec: moduleLib.ClusterKvSpec{
+ Kv: []map[string]interface{}{
+ {
+ "keyA": "valueA",
+ },
+ {
+ "keyB": "valueB",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ label: "Get Non-Existing Cluster KV Pairs",
+ expectedCode: http.StatusInternalServerError,
+ name: "nonexistingclusterkvpairs",
+ clusterClient: &mockClusterManager{
+ ClusterKvPairsItems: []moduleLib.ClusterKvPairs{},
+ Err: pkgerrors.New("Internal Error"),
+ },
+ },
+ }
+
+ 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(testCase.clusterClient))
+
+ //Check returned code
+ if resp.StatusCode != testCase.expectedCode {
+ t.Fatalf("Expected %d; Got: %d", testCase.expectedCode, resp.StatusCode)
+ }
+
+ //Check returned body only if statusOK
+ if resp.StatusCode == http.StatusOK {
+ got := moduleLib.ClusterKvPairs{}
+ json.NewDecoder(resp.Body).Decode(&got)
+
+ if reflect.DeepEqual(testCase.expected, got) == false {
+ t.Errorf("listHandler returned unexpected body: got %v;"+
+ " expected %v", got, testCase.expected)
+ }
+ }
+ })
+ }
+}
+
+func TestClusterKvPairsDeleteHandler(t *testing.T) {
+
+ testCases := []struct {
+ label string
+ name string
+ version string
+ expectedCode int
+ clusterClient *mockClusterManager
+ }{
+ {
+ label: "Delete Cluster KV Pairs",
+ expectedCode: http.StatusNoContent,
+ name: "testClusterKvPairs",
+ clusterClient: &mockClusterManager{},
+ },
+ {
+ label: "Delete Non-Existing Cluster KV Pairs",
+ expectedCode: http.StatusInternalServerError,
+ name: "testClusterKvPairs",
+ clusterClient: &mockClusterManager{
+ Err: pkgerrors.New("Internal Error"),
+ },
+ },
+ }
+
+ 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(testCase.clusterClient))
+
+ //Check returned code
+ if resp.StatusCode != testCase.expectedCode {
+ t.Fatalf("Expected %d; Got: %d", testCase.expectedCode, resp.StatusCode)
+ }
+ })
+ }
+}
diff --git a/src/ncm/api/networkhandler.go b/src/ncm/api/networkhandler.go
new file mode 100644
index 00000000..01d077a7
--- /dev/null
+++ b/src/ncm/api/networkhandler.go
@@ -0,0 +1,215 @@
+/*
+ * 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"
+ "fmt"
+ "io"
+ "net/http"
+
+ moduleLib "github.com/onap/multicloud-k8s/src/ncm/pkg/module"
+ "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/validation"
+ pkgerrors "github.com/pkg/errors"
+
+ "github.com/gorilla/mux"
+)
+
+// Used to store backend implementations objects
+// Also simplifies mocking for unit testing purposes
+type networkHandler struct {
+ // Interface that implements Cluster operations
+ // We will set this variable with a mock interface for testing
+ client moduleLib.NetworkManager
+}
+
+// Check for valid format of input parameters
+func validateNetworkInputs(p moduleLib.Network) error {
+ // validate name
+ errs := validation.IsValidName(p.Metadata.Name)
+ if len(errs) > 0 {
+ return pkgerrors.Errorf("Invalid network name - name=[%v], errors: %v", p.Metadata.Name, errs)
+ }
+
+ // validate cni type
+ found := false
+ for _, val := range moduleLib.CNI_TYPES {
+ if p.Spec.CniType == val {
+ found = true
+ break
+ }
+ }
+ if !found {
+ return pkgerrors.Errorf("Invalid cni type: %v", p.Spec.CniType)
+ }
+
+ subnets := p.Spec.Ipv4Subnets
+ for _, subnet := range subnets {
+ err := moduleLib.ValidateSubnet(subnet)
+ if err != nil {
+ return pkgerrors.Wrap(err, "invalid subnet")
+ }
+ }
+ return nil
+}
+
+// Create handles creation of the Network entry in the database
+func (h networkHandler) createNetworkHandler(w http.ResponseWriter, r *http.Request) {
+ var p moduleLib.Network
+ vars := mux.Vars(r)
+ clusterProvider := vars["provider-name"]
+ cluster := vars["cluster-name"]
+
+ err := json.NewDecoder(r.Body).Decode(&p)
+
+ switch {
+ case err == io.EOF:
+ http.Error(w, "Empty body", http.StatusBadRequest)
+ return
+ case err != nil:
+ http.Error(w, err.Error(), http.StatusUnprocessableEntity)
+ return
+ }
+
+ // Name is required.
+ if p.Metadata.Name == "" {
+ http.Error(w, "Missing name in POST request", http.StatusBadRequest)
+ return
+ }
+
+ err = validateNetworkInputs(p)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+
+ ret, err := h.client.CreateNetwork(p, clusterProvider, cluster, false)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusCreated)
+ err = json.NewEncoder(w).Encode(ret)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+// Put handles creation/update of the Network entry in the database
+func (h networkHandler) putNetworkHandler(w http.ResponseWriter, r *http.Request) {
+ var p moduleLib.Network
+ vars := mux.Vars(r)
+ clusterProvider := vars["provider-name"]
+ cluster := vars["cluster-name"]
+ name := vars["name"]
+
+ err := json.NewDecoder(r.Body).Decode(&p)
+
+ switch {
+ case err == io.EOF:
+ http.Error(w, "Empty body", http.StatusBadRequest)
+ return
+ case err != nil:
+ http.Error(w, err.Error(), http.StatusUnprocessableEntity)
+ return
+ }
+
+ // Name is required.
+ if p.Metadata.Name == "" {
+ http.Error(w, "Missing name in PUT request", http.StatusBadRequest)
+ return
+ }
+
+ // Name in URL should match name in body
+ if p.Metadata.Name != name {
+ fmt.Printf("bodyname = %v, name= %v\n", p.Metadata.Name, name)
+ http.Error(w, "Mismatched name in PUT request", http.StatusBadRequest)
+ return
+ }
+
+ err = validateNetworkInputs(p)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+
+ ret, err := h.client.CreateNetwork(p, clusterProvider, cluster, true)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusCreated)
+ err = json.NewEncoder(w).Encode(ret)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+// Get handles GET operations on a particular Network Name
+// Returns a Network
+func (h networkHandler) getNetworkHandler(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ clusterProvider := vars["provider-name"]
+ cluster := vars["cluster-name"]
+ name := vars["name"]
+ var ret interface{}
+ var err error
+
+ if len(name) == 0 {
+ ret, err = h.client.GetNetworks(clusterProvider, cluster)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ } else {
+ ret, err = h.client.GetNetwork(name, clusterProvider, cluster)
+ 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(ret)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+// Delete handles DELETE operations on a particular Network Name
+func (h networkHandler) deleteNetworkHandler(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ clusterProvider := vars["provider-name"]
+ cluster := vars["cluster-name"]
+ name := vars["name"]
+
+ err := h.client.DeleteNetwork(name, clusterProvider, cluster)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.WriteHeader(http.StatusNoContent)
+}
diff --git a/src/ncm/api/providernethandler.go b/src/ncm/api/providernethandler.go
new file mode 100644
index 00000000..b38a16c5
--- /dev/null
+++ b/src/ncm/api/providernethandler.go
@@ -0,0 +1,268 @@
+/*
+ * 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"
+ "fmt"
+ "io"
+ "net/http"
+ "strings"
+
+ moduleLib "github.com/onap/multicloud-k8s/src/ncm/pkg/module"
+ "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/validation"
+ pkgerrors "github.com/pkg/errors"
+
+ "github.com/gorilla/mux"
+)
+
+// Used to store backend implementations objects
+// Also simplifies mocking for unit testing purposes
+type providernetHandler struct {
+ // Interface that implements Cluster operations
+ // We will set this variable with a mock interface for testing
+ client moduleLib.ProviderNetManager
+}
+
+// Check for valid format of input parameters
+func validateProviderNetInputs(p moduleLib.ProviderNet) error {
+ // validate name
+ errs := validation.IsValidName(p.Metadata.Name)
+ if len(errs) > 0 {
+ return pkgerrors.Errorf("Invalid provider network name=[%v], errors: %v", p.Metadata.Name, errs)
+ }
+
+ // validate cni type
+ found := false
+ for _, val := range moduleLib.CNI_TYPES {
+ if p.Spec.CniType == val {
+ found = true
+ break
+ }
+ }
+ if !found {
+ return pkgerrors.Errorf("Invalid cni type: %v", p.Spec.CniType)
+ }
+
+ // validate the provider network type
+ found = false
+ for _, val := range moduleLib.PROVIDER_NET_TYPES {
+ if strings.ToUpper(p.Spec.ProviderNetType) == val {
+ found = true
+ break
+ }
+ }
+ if !found {
+ return pkgerrors.Errorf("Invalid provider network type: %v", p.Spec.ProviderNetType)
+ }
+
+ // validate the subnets
+ subnets := p.Spec.Ipv4Subnets
+ for _, subnet := range subnets {
+ err := moduleLib.ValidateSubnet(subnet)
+ if err != nil {
+ return pkgerrors.Wrap(err, "invalid subnet")
+ }
+ }
+
+ // validate the VLAN ID
+ errs = validation.IsValidNumber(p.Spec.Vlan.VlanId, 0, 4095)
+ if len(errs) > 0 {
+ return pkgerrors.Errorf("Invalid VlAN ID %v - error: %v", p.Spec.Vlan.VlanId, errs)
+ }
+
+ // validate the VLAN Node Selector value
+ expectLabels := false
+ found = false
+ for _, val := range moduleLib.VLAN_NODE_SELECTORS {
+ if strings.ToLower(p.Spec.Vlan.VlanNodeSelector) == val {
+ found = true
+ if val == moduleLib.VLAN_NODE_SPECIFIC {
+ expectLabels = true
+ }
+ break
+ }
+ }
+ if !found {
+ return pkgerrors.Errorf("Invalid VlAN Node Selector %v", p.Spec.Vlan.VlanNodeSelector)
+ }
+
+ // validate the node label list
+ gotLabels := false
+ for _, label := range p.Spec.Vlan.NodeLabelList {
+ errs = validation.IsValidLabel(label)
+ if len(errs) > 0 {
+ return pkgerrors.Errorf("Invalid Label=%v - errors: %v", label, errs)
+ }
+ gotLabels = true
+ }
+
+ // Need at least one label if node selector value was "specific"
+ // (if selector is "any" - don't care if labels were supplied or not
+ if expectLabels && !gotLabels {
+ return pkgerrors.Errorf("Node Labels required for VlAN node selector \"%v\"", moduleLib.VLAN_NODE_SPECIFIC)
+ }
+
+ return nil
+}
+
+// Create handles creation of the ProviderNet entry in the database
+func (h providernetHandler) createProviderNetHandler(w http.ResponseWriter, r *http.Request) {
+ var p moduleLib.ProviderNet
+ vars := mux.Vars(r)
+ clusterProvider := vars["provider-name"]
+ cluster := vars["cluster-name"]
+
+ err := json.NewDecoder(r.Body).Decode(&p)
+
+ switch {
+ case err == io.EOF:
+ http.Error(w, "Empty body", http.StatusBadRequest)
+ return
+ case err != nil:
+ http.Error(w, err.Error(), http.StatusUnprocessableEntity)
+ return
+ }
+
+ // Name is required.
+ if p.Metadata.Name == "" {
+ http.Error(w, "Missing name in POST request", http.StatusBadRequest)
+ return
+ }
+
+ err = validateProviderNetInputs(p)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+
+ ret, err := h.client.CreateProviderNet(p, clusterProvider, cluster, false)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusCreated)
+ err = json.NewEncoder(w).Encode(ret)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+// Put handles creation/update of the ProviderNet entry in the database
+func (h providernetHandler) putProviderNetHandler(w http.ResponseWriter, r *http.Request) {
+ var p moduleLib.ProviderNet
+ vars := mux.Vars(r)
+ clusterProvider := vars["provider-name"]
+ cluster := vars["cluster-name"]
+ name := vars["name"]
+
+ err := json.NewDecoder(r.Body).Decode(&p)
+
+ switch {
+ case err == io.EOF:
+ http.Error(w, "Empty body", http.StatusBadRequest)
+ return
+ case err != nil:
+ http.Error(w, err.Error(), http.StatusUnprocessableEntity)
+ return
+ }
+
+ // Name is required.
+ if p.Metadata.Name == "" {
+ http.Error(w, "Missing name in PUT request", http.StatusBadRequest)
+ return
+ }
+
+ // Name in URL should match name in body
+ if p.Metadata.Name != name {
+ fmt.Printf("bodyname = %v, name= %v\n", p.Metadata.Name, name)
+ http.Error(w, "Mismatched name in PUT request", http.StatusBadRequest)
+ return
+ }
+
+ err = validateProviderNetInputs(p)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+
+ ret, err := h.client.CreateProviderNet(p, clusterProvider, cluster, true)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusCreated)
+ err = json.NewEncoder(w).Encode(ret)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+// Get handles GET operations on a particular ProviderNet Name
+// Returns a ProviderNet
+func (h providernetHandler) getProviderNetHandler(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ clusterProvider := vars["provider-name"]
+ cluster := vars["cluster-name"]
+ name := vars["name"]
+ var ret interface{}
+ var err error
+
+ if len(name) == 0 {
+ ret, err = h.client.GetProviderNets(clusterProvider, cluster)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ } else {
+ ret, err = h.client.GetProviderNet(name, clusterProvider, cluster)
+ 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(ret)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+// Delete handles DELETE operations on a particular ProviderNet Name
+func (h providernetHandler) deleteProviderNetHandler(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ clusterProvider := vars["provider-name"]
+ cluster := vars["cluster-name"]
+ name := vars["name"]
+
+ err := h.client.DeleteProviderNet(name, clusterProvider, cluster)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.WriteHeader(http.StatusNoContent)
+}
diff --git a/src/ncm/api/testing.go b/src/ncm/api/testing.go
new file mode 100644
index 00000000..fed5c080
--- /dev/null
+++ b/src/ncm/api/testing.go
@@ -0,0 +1,31 @@
+/*
+ * 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 (
+ "net/http"
+ "net/http/httptest"
+
+ "github.com/gorilla/mux"
+)
+
+func executeRequest(request *http.Request, router *mux.Router) *http.Response {
+ recorder := httptest.NewRecorder()
+ router.ServeHTTP(recorder, request)
+ resp := recorder.Result()
+ return resp
+}
diff --git a/src/ncm/cmd/main.go b/src/ncm/cmd/main.go
new file mode 100644
index 00000000..c4ae423f
--- /dev/null
+++ b/src/ncm/cmd/main.go
@@ -0,0 +1,77 @@
+/*
+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.
+*/
+
+package main
+
+import (
+ "context"
+ "log"
+ "math/rand"
+ "net/http"
+ "os"
+ "os/signal"
+ "time"
+
+ "github.com/gorilla/handlers"
+ "github.com/onap/multicloud-k8s/src/ncm/api"
+ "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/auth"
+ "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/config"
+ contextDb "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/contextdb"
+ "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/db"
+)
+
+func main() {
+
+ rand.Seed(time.Now().UnixNano())
+
+ err := db.InitializeDatabaseConnection("mco")
+ if err != nil {
+ log.Println("Unable to initialize database connection...")
+ log.Println(err)
+ log.Fatalln("Exiting...")
+ }
+ err = contextDb.InitializeContextDatabase()
+ if err != nil {
+ log.Println("Unable to initialize database connection...")
+ log.Println(err)
+ log.Fatalln("Exiting...")
+ }
+
+ httpRouter := api.NewRouter(nil)
+ loggedRouter := handlers.LoggingHandler(os.Stdout, httpRouter)
+ log.Println("Starting Network Customization Manager")
+
+ httpServer := &http.Server{
+ Handler: loggedRouter,
+ Addr: ":" + config.GetConfiguration().ServicePort,
+ }
+
+ connectionsClose := make(chan struct{})
+ go func() {
+ c := make(chan os.Signal, 1)
+ signal.Notify(c, os.Interrupt)
+ <-c
+ httpServer.Shutdown(context.Background())
+ close(connectionsClose)
+ }()
+
+ tlsConfig, err := auth.GetTLSConfig("ca.cert", "server.cert", "server.key")
+ if err != nil {
+ log.Println("Error Getting TLS Configuration. Starting without TLS...")
+ log.Fatal(httpServer.ListenAndServe())
+ } else {
+ httpServer.TLSConfig = tlsConfig
+ // empty strings because tlsconfig already has this information
+ err = httpServer.ListenAndServeTLS("", "")
+ }
+}
diff --git a/src/ncm/go.mod b/src/ncm/go.mod
new file mode 100644
index 00000000..32ff481a
--- /dev/null
+++ b/src/ncm/go.mod
@@ -0,0 +1,31 @@
+module github.com/onap/multicloud-k8s/src/ncm
+
+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/gorilla/handlers v1.3.0
+ github.com/gorilla/mux v1.6.2
+ github.com/hashicorp/consul v1.4.0
+ github.com/pkg/errors v0.8.1
+ github.com/sirupsen/logrus v1.4.2
+ go.etcd.io/etcd v3.3.12+incompatible
+ go.mongodb.org/mongo-driver v1.0.0
+ golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297
+ k8s.io/api v0.0.0-20190831074750-7364b6bdad65
+ k8s.io/apimachinery v0.0.0-20190831074630-461753078381
+ k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible
+ k8s.io/helm v2.14.3+incompatible
+)
+
+replace (
+ k8s.io/api => k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b
+ k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.0.0-20190409022649-727a075fdec8
+ k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d
+ k8s.io/apiserver => k8s.io/apiserver v0.0.0-20190409021813-1ec86e4da56c
+ k8s.io/cli-runtime => k8s.io/cli-runtime v0.0.0-20190409023024-d644b00f3b79
+ 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/ncm/pkg/module/cluster.go b/src/ncm/pkg/module/cluster.go
new file mode 100644
index 00000000..e3260b7e
--- /dev/null
+++ b/src/ncm/pkg/module/cluster.go
@@ -0,0 +1,532 @@
+/*
+ * 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"
+)
+
+// ClusterProvider contains the parameters needed for ClusterProviders
+type ClusterProvider struct {
+ Metadata Metadata `json:"metadata"`
+}
+
+type Cluster struct {
+ Metadata Metadata `json:"metadata"`
+}
+
+type ClusterContent struct {
+ Kubeconfig string `json:"kubeconfig"`
+}
+
+type ClusterLabel struct {
+ LabelName string `json:"label-name"`
+}
+
+type ClusterKvPairs struct {
+ Metadata Metadata `json:"metadata"`
+ Spec ClusterKvSpec `json:"spec"`
+}
+
+type ClusterKvSpec struct {
+ Kv []map[string]interface{} `json:"kv"`
+}
+
+// ClusterProviderKey is the key structure that is used in the database
+type ClusterProviderKey struct {
+ ClusterProviderName string `json:"provider"`
+}
+
+// ClusterKey is the key structure that is used in the database
+type ClusterKey struct {
+ ClusterProviderName string `json:"provider"`
+ ClusterName string `json:"cluster"`
+}
+
+// ClusterLabelKey is the key structure that is used in the database
+type ClusterLabelKey struct {
+ ClusterProviderName string `json:"provider"`
+ ClusterName string `json:"cluster"`
+ ClusterLabelName string `json:"label"`
+}
+
+// ClusterKvPairsKey is the key structure that is used in the database
+type ClusterKvPairsKey struct {
+ ClusterProviderName string `json:"provider"`
+ ClusterName string `json:"cluster"`
+ ClusterKvPairsName string `json:"kvname"`
+}
+
+// Manager is an interface exposes the Cluster functionality
+type ClusterManager interface {
+ CreateClusterProvider(pr ClusterProvider) (ClusterProvider, error)
+ GetClusterProvider(name string) (ClusterProvider, error)
+ GetClusterProviders() ([]ClusterProvider, error)
+ DeleteClusterProvider(name string) error
+ CreateCluster(provider string, pr Cluster, qr ClusterContent) (Cluster, error)
+ GetCluster(provider, name string) (Cluster, error)
+ GetClusterContent(provider, name string) (ClusterContent, error)
+ GetClusters(provider string) ([]Cluster, error)
+ DeleteCluster(provider, name string) error
+ CreateClusterLabel(provider, cluster string, pr ClusterLabel) (ClusterLabel, error)
+ GetClusterLabel(provider, cluster, label string) (ClusterLabel, error)
+ GetClusterLabels(provider, cluster string) ([]ClusterLabel, error)
+ DeleteClusterLabel(provider, cluster, label string) error
+ CreateClusterKvPairs(provider, cluster string, pr ClusterKvPairs) (ClusterKvPairs, error)
+ GetClusterKvPairs(provider, cluster, kvpair string) (ClusterKvPairs, error)
+ GetAllClusterKvPairs(provider, cluster string) ([]ClusterKvPairs, error)
+ DeleteClusterKvPairs(provider, cluster, kvpair string) error
+}
+
+// ClusterClient implements the Manager
+// It will also be used to maintain some localized state
+type ClusterClient struct {
+ db ClientDbInfo
+}
+
+// NewClusterClient returns an instance of the ClusterClient
+// which implements the Manager
+func NewClusterClient() *ClusterClient {
+ return &ClusterClient{
+ db: ClientDbInfo{
+ storeName: "cluster",
+ tagMeta: "clustermetadata",
+ tagContent: "clustercontent",
+ },
+ }
+}
+
+// CreateClusterProvider - create a new Cluster Provider
+func (v *ClusterClient) CreateClusterProvider(p ClusterProvider) (ClusterProvider, error) {
+
+ //Construct key and tag to select the entry
+ key := ClusterProviderKey{
+ ClusterProviderName: p.Metadata.Name,
+ }
+
+ //Check if this ClusterProvider already exists
+ _, err := v.GetClusterProvider(p.Metadata.Name)
+ if err == nil {
+ return ClusterProvider{}, pkgerrors.New("ClusterProvider already exists")
+ }
+
+ err = db.DBconn.Insert(v.db.storeName, key, nil, v.db.tagMeta, p)
+ if err != nil {
+ return ClusterProvider{}, pkgerrors.Wrap(err, "Creating DB Entry")
+ }
+
+ return p, nil
+}
+
+// GetClusterProvider returns the ClusterProvider for corresponding name
+func (v *ClusterClient) GetClusterProvider(name string) (ClusterProvider, error) {
+
+ //Construct key and tag to select the entry
+ key := ClusterProviderKey{
+ ClusterProviderName: name,
+ }
+
+ value, err := db.DBconn.Find(v.db.storeName, key, v.db.tagMeta)
+ if err != nil {
+ return ClusterProvider{}, pkgerrors.Wrap(err, "Get ClusterProvider")
+ }
+
+ //value is a byte array
+ if value != nil {
+ cp := ClusterProvider{}
+ err = db.DBconn.Unmarshal(value[0], &cp)
+ if err != nil {
+ return ClusterProvider{}, pkgerrors.Wrap(err, "Unmarshalling Value")
+ }
+ return cp, nil
+ }
+
+ return ClusterProvider{}, pkgerrors.New("Error getting ClusterProvider")
+}
+
+// GetClusterProviderList returns all of the ClusterProvider for corresponding name
+func (v *ClusterClient) GetClusterProviders() ([]ClusterProvider, error) {
+
+ //Construct key and tag to select the entry
+ key := ClusterProviderKey{
+ ClusterProviderName: "",
+ }
+
+ var resp []ClusterProvider
+ values, err := db.DBconn.Find(v.db.storeName, key, v.db.tagMeta)
+ if err != nil {
+ return []ClusterProvider{}, pkgerrors.Wrap(err, "Get ClusterProviders")
+ }
+
+ for _, value := range values {
+ cp := ClusterProvider{}
+ err = db.DBconn.Unmarshal(value, &cp)
+ if err != nil {
+ return []ClusterProvider{}, pkgerrors.Wrap(err, "Unmarshalling Value")
+ }
+ resp = append(resp, cp)
+ }
+
+ return resp, nil
+}
+
+// DeleteClusterProvider the ClusterProvider from database
+func (v *ClusterClient) DeleteClusterProvider(name string) error {
+
+ //Construct key and tag to select the entry
+ key := ClusterProviderKey{
+ ClusterProviderName: name,
+ }
+
+ err := db.DBconn.Remove(v.db.storeName, key)
+ if err != nil {
+ return pkgerrors.Wrap(err, "Delete ClusterProvider Entry;")
+ }
+
+ return nil
+}
+
+// CreateCluster - create a new Cluster for a cluster-provider
+func (v *ClusterClient) CreateCluster(provider string, p Cluster, q ClusterContent) (Cluster, error) {
+
+ //Construct key and tag to select the entry
+ key := ClusterKey{
+ ClusterProviderName: provider,
+ ClusterName: p.Metadata.Name,
+ }
+
+ //Verify ClusterProvider already exists
+ _, err := v.GetClusterProvider(provider)
+ if err != nil {
+ return Cluster{}, pkgerrors.New("ClusterProvider does not exist")
+ }
+
+ //Check if this Cluster already exists
+ _, err = v.GetCluster(provider, p.Metadata.Name)
+ if err == nil {
+ return Cluster{}, pkgerrors.New("Cluster already exists")
+ }
+
+ err = db.DBconn.Insert(v.db.storeName, key, nil, v.db.tagMeta, p)
+ if err != nil {
+ return Cluster{}, pkgerrors.Wrap(err, "Creating DB Entry")
+ }
+ err = db.DBconn.Insert(v.db.storeName, key, nil, v.db.tagContent, q)
+ if err != nil {
+ return Cluster{}, pkgerrors.Wrap(err, "Creating DB Entry")
+ }
+
+ return p, nil
+}
+
+// GetCluster returns the Cluster for corresponding provider and name
+func (v *ClusterClient) GetCluster(provider, name string) (Cluster, error) {
+ //Construct key and tag to select the entry
+ key := ClusterKey{
+ ClusterProviderName: provider,
+ ClusterName: name,
+ }
+
+ value, err := db.DBconn.Find(v.db.storeName, key, v.db.tagMeta)
+ if err != nil {
+ return Cluster{}, pkgerrors.Wrap(err, "Get Cluster")
+ }
+
+ //value is a byte array
+ if value != nil {
+ cl := Cluster{}
+ err = db.DBconn.Unmarshal(value[0], &cl)
+ if err != nil {
+ return Cluster{}, pkgerrors.Wrap(err, "Unmarshalling Value")
+ }
+ return cl, nil
+ }
+
+ return Cluster{}, pkgerrors.New("Error getting Cluster")
+}
+
+// GetClusterContent returns the ClusterContent for corresponding provider and name
+func (v *ClusterClient) GetClusterContent(provider, name string) (ClusterContent, error) {
+ //Construct key and tag to select the entry
+ key := ClusterKey{
+ ClusterProviderName: provider,
+ ClusterName: name,
+ }
+
+ value, err := db.DBconn.Find(v.db.storeName, key, v.db.tagContent)
+ if err != nil {
+ return ClusterContent{}, pkgerrors.Wrap(err, "Get Cluster Content")
+ }
+
+ //value is a byte array
+ if value != nil {
+ cc := ClusterContent{}
+ err = db.DBconn.Unmarshal(value[0], &cc)
+ if err != nil {
+ return ClusterContent{}, pkgerrors.Wrap(err, "Unmarshalling Value")
+ }
+ return cc, nil
+ }
+
+ return ClusterContent{}, pkgerrors.New("Error getting Cluster Content")
+}
+
+// GetClusters returns all the Clusters for corresponding provider
+func (v *ClusterClient) GetClusters(provider string) ([]Cluster, error) {
+ //Construct key and tag to select the entry
+ key := ClusterKey{
+ ClusterProviderName: provider,
+ ClusterName: "",
+ }
+
+ values, err := db.DBconn.Find(v.db.storeName, key, v.db.tagMeta)
+ if err != nil {
+ return []Cluster{}, pkgerrors.Wrap(err, "Get Clusters")
+ }
+
+ var resp []Cluster
+
+ for _, value := range values {
+ cp := Cluster{}
+ err = db.DBconn.Unmarshal(value, &cp)
+ if err != nil {
+ return []Cluster{}, pkgerrors.Wrap(err, "Unmarshalling Value")
+ }
+ resp = append(resp, cp)
+ }
+
+ return resp, nil
+}
+
+// DeleteCluster the Cluster from database
+func (v *ClusterClient) DeleteCluster(provider, name string) error {
+ //Construct key and tag to select the entry
+ key := ClusterKey{
+ ClusterProviderName: provider,
+ ClusterName: name,
+ }
+
+ err := db.DBconn.Remove(v.db.storeName, key)
+ if err != nil {
+ return pkgerrors.Wrap(err, "Delete Cluster Entry;")
+ }
+
+ return nil
+}
+
+// CreateClusterLabel - create a new Cluster Label mongo document for a cluster-provider/cluster
+func (v *ClusterClient) CreateClusterLabel(provider string, cluster string, p ClusterLabel) (ClusterLabel, error) {
+ //Construct key and tag to select the entry
+ key := ClusterLabelKey{
+ ClusterProviderName: provider,
+ ClusterName: cluster,
+ ClusterLabelName: p.LabelName,
+ }
+
+ //Verify Cluster already exists
+ _, err := v.GetCluster(provider, cluster)
+ if err != nil {
+ return ClusterLabel{}, pkgerrors.New("Cluster does not exist")
+ }
+
+ //Check if this ClusterLabel already exists
+ _, err = v.GetClusterLabel(provider, cluster, p.LabelName)
+ if err == nil {
+ return ClusterLabel{}, pkgerrors.New("Cluster Label already exists")
+ }
+
+ err = db.DBconn.Insert(v.db.storeName, key, nil, v.db.tagMeta, p)
+ if err != nil {
+ return ClusterLabel{}, pkgerrors.Wrap(err, "Creating DB Entry")
+ }
+
+ return p, nil
+}
+
+// GetClusterLabel returns the Cluster for corresponding provider, cluster and label
+func (v *ClusterClient) GetClusterLabel(provider, cluster, label string) (ClusterLabel, error) {
+ //Construct key and tag to select the entry
+ key := ClusterLabelKey{
+ ClusterProviderName: provider,
+ ClusterName: cluster,
+ ClusterLabelName: label,
+ }
+
+ value, err := db.DBconn.Find(v.db.storeName, key, v.db.tagMeta)
+ if err != nil {
+ return ClusterLabel{}, pkgerrors.Wrap(err, "Get Cluster")
+ }
+
+ //value is a byte array
+ if value != nil {
+ cl := ClusterLabel{}
+ err = db.DBconn.Unmarshal(value[0], &cl)
+ if err != nil {
+ return ClusterLabel{}, pkgerrors.Wrap(err, "Unmarshalling Value")
+ }
+ return cl, nil
+ }
+
+ return ClusterLabel{}, pkgerrors.New("Error getting Cluster")
+}
+
+// GetClusterLabels returns the Cluster Labels for corresponding provider and cluster
+func (v *ClusterClient) GetClusterLabels(provider, cluster string) ([]ClusterLabel, error) {
+ //Construct key and tag to select the entry
+ key := ClusterLabelKey{
+ ClusterProviderName: provider,
+ ClusterName: cluster,
+ ClusterLabelName: "",
+ }
+
+ values, err := db.DBconn.Find(v.db.storeName, key, v.db.tagMeta)
+ if err != nil {
+ return []ClusterLabel{}, pkgerrors.Wrap(err, "Get Cluster Labels")
+ }
+
+ var resp []ClusterLabel
+
+ for _, value := range values {
+ cp := ClusterLabel{}
+ err = db.DBconn.Unmarshal(value, &cp)
+ if err != nil {
+ return []ClusterLabel{}, pkgerrors.Wrap(err, "Unmarshalling Value")
+ }
+ resp = append(resp, cp)
+ }
+
+ return resp, nil
+}
+
+// Delete the Cluster Label from database
+func (v *ClusterClient) DeleteClusterLabel(provider, cluster, label string) error {
+ //Construct key and tag to select the entry
+ key := ClusterLabelKey{
+ ClusterProviderName: provider,
+ ClusterName: cluster,
+ ClusterLabelName: label,
+ }
+
+ err := db.DBconn.Remove(v.db.storeName, key)
+ if err != nil {
+ return pkgerrors.Wrap(err, "Delete ClusterLabel Entry;")
+ }
+
+ return nil
+}
+
+// CreateClusterKvPairs - Create a New Cluster KV pairs document
+func (v *ClusterClient) CreateClusterKvPairs(provider string, cluster string, p ClusterKvPairs) (ClusterKvPairs, error) {
+ key := ClusterKvPairsKey{
+ ClusterProviderName: provider,
+ ClusterName: cluster,
+ ClusterKvPairsName: p.Metadata.Name,
+ }
+
+ //Verify Cluster already exists
+ _, err := v.GetCluster(provider, cluster)
+ if err != nil {
+ return ClusterKvPairs{}, pkgerrors.New("Cluster does not exist")
+ }
+
+ //Check if this ClusterKvPairs already exists
+ _, err = v.GetClusterKvPairs(provider, cluster, p.Metadata.Name)
+ if err == nil {
+ return ClusterKvPairs{}, pkgerrors.New("Cluster KV Pair already exists")
+ }
+
+ err = db.DBconn.Insert(v.db.storeName, key, nil, v.db.tagMeta, p)
+ if err != nil {
+ return ClusterKvPairs{}, pkgerrors.Wrap(err, "Creating DB Entry")
+ }
+
+ return p, nil
+}
+
+// GetClusterKvPairs returns the Cluster KeyValue pair for corresponding provider, cluster and KV pair name
+func (v *ClusterClient) GetClusterKvPairs(provider, cluster, kvpair string) (ClusterKvPairs, error) {
+ //Construct key and tag to select entry
+ key := ClusterKvPairsKey{
+ ClusterProviderName: provider,
+ ClusterName: cluster,
+ ClusterKvPairsName: kvpair,
+ }
+
+ value, err := db.DBconn.Find(v.db.storeName, key, v.db.tagMeta)
+ if err != nil {
+ return ClusterKvPairs{}, pkgerrors.Wrap(err, "Get Cluster")
+ }
+
+ //value is a byte array
+ if value != nil {
+ ckvp := ClusterKvPairs{}
+ err = db.DBconn.Unmarshal(value[0], &ckvp)
+ if err != nil {
+ return ClusterKvPairs{}, pkgerrors.Wrap(err, "Unmarshalling Value")
+ }
+ return ckvp, nil
+ }
+
+ return ClusterKvPairs{}, pkgerrors.New("Error getting Cluster")
+}
+
+// GetAllClusterKvPairs returns the Cluster Kv Pairs for corresponding provider and cluster
+func (v *ClusterClient) GetAllClusterKvPairs(provider, cluster string) ([]ClusterKvPairs, error) {
+ //Construct key and tag to select the entry
+ key := ClusterKvPairsKey{
+ ClusterProviderName: provider,
+ ClusterName: cluster,
+ ClusterKvPairsName: "",
+ }
+
+ values, err := db.DBconn.Find(v.db.storeName, key, v.db.tagMeta)
+ if err != nil {
+ return []ClusterKvPairs{}, pkgerrors.Wrap(err, "Get Cluster KV Pairs")
+ }
+
+ var resp []ClusterKvPairs
+
+ for _, value := range values {
+ cp := ClusterKvPairs{}
+ err = db.DBconn.Unmarshal(value, &cp)
+ if err != nil {
+ return []ClusterKvPairs{}, pkgerrors.Wrap(err, "Unmarshalling Value")
+ }
+ resp = append(resp, cp)
+ }
+
+ return resp, nil
+}
+
+// DeleteClusterKvPairs the ClusterKvPairs from database
+func (v *ClusterClient) DeleteClusterKvPairs(provider, cluster, kvpair string) error {
+ //Construct key and tag to select entry
+ key := ClusterKvPairsKey{
+ ClusterProviderName: provider,
+ ClusterName: cluster,
+ ClusterKvPairsName: kvpair,
+ }
+
+ err := db.DBconn.Remove(v.db.storeName, key)
+ if err != nil {
+ return pkgerrors.Wrap(err, "Delete ClusterKvPairs Entry;")
+ }
+
+ return nil
+}
diff --git a/src/ncm/pkg/module/module.go b/src/ncm/pkg/module/module.go
new file mode 100644
index 00000000..a9950901
--- /dev/null
+++ b/src/ncm/pkg/module/module.go
@@ -0,0 +1,35 @@
+/*
+ * 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
+
+// Client for using the services in the orchestrator
+type Client struct {
+ Cluster *ClusterClient
+ Network *NetworkClient
+ ProviderNet *ProviderNetClient
+ // Add Clients for API's here
+}
+
+// NewClient creates a new client for using the services
+func NewClient() *Client {
+ c := &Client{}
+ c.Cluster = NewClusterClient()
+ c.Network = NewNetworkClient()
+ c.ProviderNet = NewProviderNetClient()
+ // Add Client API handlers here
+ return c
+}
diff --git a/src/ncm/pkg/module/module_definitions.go b/src/ncm/pkg/module/module_definitions.go
new file mode 100644
index 00000000..1e839014
--- /dev/null
+++ b/src/ncm/pkg/module/module_definitions.go
@@ -0,0 +1,101 @@
+/*
+ * 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 (
+ "strings"
+
+ "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/validation"
+ pkgerrors "github.com/pkg/errors"
+)
+
+const VLAN_PROVIDER_NET_TYPE_VLAN string = "VLAN"
+const VLAN_PROVIDER_NET_TYPE_DIRECT string = "DIRECT"
+
+var PROVIDER_NET_TYPES = [...]string{VLAN_PROVIDER_NET_TYPE_VLAN, VLAN_PROVIDER_NET_TYPE_DIRECT}
+
+const CNI_TYPE_OVN4NFV string = "ovn4nfv"
+
+var CNI_TYPES = [...]string{CNI_TYPE_OVN4NFV}
+
+// It implements the interface for managing the ClusterProviders
+type Metadata struct {
+ Name string `json:"name"`
+ Description string `json:"description"`
+ UserData1 string `json:"userData1"`
+ UserData2 string `json:"userData2"`
+}
+
+type ClientDbInfo struct {
+ storeName string // name of the mongodb collection to use for client documents
+ tagMeta string // attribute key name for the json data of a client document
+ tagContent string // attribute key name for the file data of a client document
+}
+
+type Ipv4Subnet struct {
+ Subnet string `json:"subnet"` // CIDR notation, e.g. 172.16.33.0/24
+ Name string `json:"name"`
+ Gateway string `json:"gateway"` // IPv4 addre, e.g. 172.16.33.1/24
+ Exclude string `json:"excludeIps"` // space separated list of single IPs or ranges e.g. "172.16.33.2 172.16.33.5..172.16.33.10"
+}
+
+const VLAN_NODE_ANY = "any"
+const VLAN_NODE_SPECIFIC = "specific"
+
+var VLAN_NODE_SELECTORS = [...]string{VLAN_NODE_ANY, VLAN_NODE_SPECIFIC}
+
+type Vlan struct {
+ VlanId int `json:"vlanID"`
+ ProviderInterfaceName string `json:"providerInterfaceName"`
+ LogicalInterfaceName string `json:"logicalInterfaceName"`
+ VlanNodeSelector string `json:"vlanNodeSelector"`
+ NodeLabelList []string `json:"nodeLabelList"`
+}
+
+// Check for valid format of an Ipv4Subnet
+func ValidateSubnet(sub Ipv4Subnet) error {
+ // verify subnet is in valid cidr format
+ err := validation.IsIpv4Cidr(sub.Subnet)
+ if err != nil {
+ return pkgerrors.Wrap(err, "invalid subnet")
+ }
+
+ // just a size check on interface name - system dependent
+ errs := validation.IsValidName(sub.Name)
+ if len(errs) > 0 {
+ return pkgerrors.Errorf("Invalid subnet name=[%v], errors: %v", sub.Name, errs)
+ }
+
+ // verify gateway is in valid cidr format
+ if len(sub.Gateway) > 0 {
+ err = validation.IsIpv4Cidr(sub.Gateway)
+ if err != nil {
+ return pkgerrors.Wrap(err, "invalid gateway")
+ }
+ }
+
+ // verify excludeIps is composed of space separated ipv4 addresses and
+ // ipv4 address ranges separated by '..'
+ for _, value := range strings.Fields(sub.Exclude) {
+ for _, ip := range strings.SplitN(value, "..", 2) {
+ err = validation.IsIpv4(ip)
+ if err != nil {
+ return pkgerrors.Errorf("invalid ipv4 exclude list %v", sub.Exclude)
+ }
+ }
+ }
+ return nil
+}
diff --git a/src/ncm/pkg/module/network.go b/src/ncm/pkg/module/network.go
new file mode 100644
index 00000000..2d4121e9
--- /dev/null
+++ b/src/ncm/pkg/module/network.go
@@ -0,0 +1,170 @@
+/*
+ * 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"
+)
+
+// Network contains the parameters needed for dynamic networks
+type Network struct {
+ Metadata Metadata `json:"metadata"`
+ Spec NetworkSpec `json:"spec"`
+}
+
+type NetworkSpec struct {
+ CniType string `json:"cniType"`
+ Ipv4Subnets []Ipv4Subnet `json:"ipv4Subnets"`
+}
+
+// NetworkKey is the key structure that is used in the database
+type NetworkKey struct {
+ ClusterProviderName string `json:"provider"`
+ ClusterName string `json:"cluster"`
+ NetworkName string `json:"network"`
+}
+
+// Manager is an interface exposing the Network functionality
+type NetworkManager interface {
+ CreateNetwork(pr Network, clusterProvider, cluster string, exists bool) (Network, error)
+ GetNetwork(name, clusterProvider, cluster string) (Network, error)
+ GetNetworks(clusterProvider, cluster string) ([]Network, error)
+ DeleteNetwork(name, clusterProvider, cluster string) error
+}
+
+// NetworkClient implements the Manager
+// It will also be used to maintain some localized state
+type NetworkClient struct {
+ db ClientDbInfo
+}
+
+// NewNetworkClient returns an instance of the NetworkClient
+// which implements the Manager
+func NewNetworkClient() *NetworkClient {
+ return &NetworkClient{
+ db: ClientDbInfo{
+ storeName: "cluster",
+ tagMeta: "networkmetadata",
+ },
+ }
+}
+
+// CreateNetwork - create a new Network
+func (v *NetworkClient) CreateNetwork(p Network, clusterProvider, cluster string, exists bool) (Network, error) {
+
+ //Construct key and tag to select the entry
+ key := NetworkKey{
+ ClusterProviderName: clusterProvider,
+ ClusterName: cluster,
+ NetworkName: p.Metadata.Name,
+ }
+
+ //Check if cluster exists
+ _, err := NewClusterClient().GetCluster(clusterProvider, cluster)
+ if err != nil {
+ return Network{}, pkgerrors.New("Unable to find the cluster")
+ }
+
+ //Check if this Network already exists
+ _, err = v.GetNetwork(p.Metadata.Name, clusterProvider, cluster)
+ if err == nil && !exists {
+ return Network{}, pkgerrors.New("Network already exists")
+ }
+
+ err = db.DBconn.Insert(v.db.storeName, key, nil, v.db.tagMeta, p)
+ if err != nil {
+ return Network{}, pkgerrors.Wrap(err, "Creating DB Entry")
+ }
+
+ return p, nil
+}
+
+// GetNetwork returns the Network for corresponding name
+func (v *NetworkClient) GetNetwork(name, clusterProvider, cluster string) (Network, error) {
+
+ //Construct key and tag to select the entry
+ key := NetworkKey{
+ ClusterProviderName: clusterProvider,
+ ClusterName: cluster,
+ NetworkName: name,
+ }
+
+ value, err := db.DBconn.Find(v.db.storeName, key, v.db.tagMeta)
+ if err != nil {
+ return Network{}, pkgerrors.Wrap(err, "Get Network")
+ }
+
+ //value is a byte array
+ if value != nil {
+ cp := Network{}
+ err = db.DBconn.Unmarshal(value[0], &cp)
+ if err != nil {
+ return Network{}, pkgerrors.Wrap(err, "Unmarshalling Value")
+ }
+ return cp, nil
+ }
+
+ return Network{}, pkgerrors.New("Error getting Network")
+}
+
+// GetNetworkList returns all of the Network for corresponding name
+func (v *NetworkClient) GetNetworks(clusterProvider, cluster string) ([]Network, error) {
+
+ //Construct key and tag to select the entry
+ key := NetworkKey{
+ ClusterProviderName: clusterProvider,
+ ClusterName: cluster,
+ NetworkName: "",
+ }
+
+ var resp []Network
+ values, err := db.DBconn.Find(v.db.storeName, key, v.db.tagMeta)
+ if err != nil {
+ return []Network{}, pkgerrors.Wrap(err, "Get Networks")
+ }
+
+ for _, value := range values {
+ cp := Network{}
+ err = db.DBconn.Unmarshal(value, &cp)
+ if err != nil {
+ return []Network{}, pkgerrors.Wrap(err, "Unmarshalling Value")
+ }
+ resp = append(resp, cp)
+ }
+
+ return resp, nil
+}
+
+// Delete the Network from database
+func (v *NetworkClient) DeleteNetwork(name, clusterProvider, cluster string) error {
+
+ //Construct key and tag to select the entry
+ key := NetworkKey{
+ ClusterProviderName: clusterProvider,
+ ClusterName: cluster,
+ NetworkName: name,
+ }
+
+ err := db.DBconn.Remove(v.db.storeName, key)
+ if err != nil {
+ return pkgerrors.Wrap(err, "Delete Network Entry;")
+ }
+
+ return nil
+}
diff --git a/src/ncm/pkg/module/providernet.go b/src/ncm/pkg/module/providernet.go
new file mode 100644
index 00000000..5e2c0343
--- /dev/null
+++ b/src/ncm/pkg/module/providernet.go
@@ -0,0 +1,172 @@
+/*
+ * 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"
+)
+
+// ProviderNet contains the parameters needed for dynamic networks
+type ProviderNet struct {
+ Metadata Metadata `json:"metadata"`
+ Spec ProviderNetSpec `json:"spec"`
+}
+
+type ProviderNetSpec struct {
+ CniType string `json:"cniType"`
+ Ipv4Subnets []Ipv4Subnet `json:"ipv4Subnets"`
+ ProviderNetType string `json:"providerNetType"`
+ Vlan Vlan `json:"vlan"`
+}
+
+// ProviderNetKey is the key structure that is used in the database
+type ProviderNetKey struct {
+ ClusterProviderName string `json:"provider"`
+ ClusterName string `json:"cluster"`
+ ProviderNetName string `json:"providernet"`
+}
+
+// Manager is an interface exposing the ProviderNet functionality
+type ProviderNetManager interface {
+ CreateProviderNet(pr ProviderNet, clusterProvider, cluster string, exists bool) (ProviderNet, error)
+ GetProviderNet(name, clusterProvider, cluster string) (ProviderNet, error)
+ GetProviderNets(clusterProvider, cluster string) ([]ProviderNet, error)
+ DeleteProviderNet(name, clusterProvider, cluster string) error
+}
+
+// ProviderNetClient implements the Manager
+// It will also be used to maintain some localized state
+type ProviderNetClient struct {
+ db ClientDbInfo
+}
+
+// NewProviderNetClient returns an instance of the ProviderNetClient
+// which implements the Manager
+func NewProviderNetClient() *ProviderNetClient {
+ return &ProviderNetClient{
+ db: ClientDbInfo{
+ storeName: "cluster",
+ tagMeta: "networkmetadata",
+ },
+ }
+}
+
+// CreateProviderNet - create a new ProviderNet
+func (v *ProviderNetClient) CreateProviderNet(p ProviderNet, clusterProvider, cluster string, exists bool) (ProviderNet, error) {
+
+ //Construct key and tag to select the entry
+ key := ProviderNetKey{
+ ClusterProviderName: clusterProvider,
+ ClusterName: cluster,
+ ProviderNetName: p.Metadata.Name,
+ }
+
+ //Check if cluster exists
+ _, err := NewClusterClient().GetCluster(clusterProvider, cluster)
+ if err != nil {
+ return ProviderNet{}, pkgerrors.New("Unable to find the cluster")
+ }
+
+ //Check if this ProviderNet already exists
+ _, err = v.GetProviderNet(p.Metadata.Name, clusterProvider, cluster)
+ if err == nil && !exists {
+ return ProviderNet{}, pkgerrors.New("ProviderNet already exists")
+ }
+
+ err = db.DBconn.Insert(v.db.storeName, key, nil, v.db.tagMeta, p)
+ if err != nil {
+ return ProviderNet{}, pkgerrors.Wrap(err, "Creating DB Entry")
+ }
+
+ return p, nil
+}
+
+// GetProviderNet returns the ProviderNet for corresponding name
+func (v *ProviderNetClient) GetProviderNet(name, clusterProvider, cluster string) (ProviderNet, error) {
+
+ //Construct key and tag to select the entry
+ key := ProviderNetKey{
+ ClusterProviderName: clusterProvider,
+ ClusterName: cluster,
+ ProviderNetName: name,
+ }
+
+ value, err := db.DBconn.Find(v.db.storeName, key, v.db.tagMeta)
+ if err != nil {
+ return ProviderNet{}, pkgerrors.Wrap(err, "Get ProviderNet")
+ }
+
+ //value is a byte array
+ if value != nil {
+ cp := ProviderNet{}
+ err = db.DBconn.Unmarshal(value[0], &cp)
+ if err != nil {
+ return ProviderNet{}, pkgerrors.Wrap(err, "Unmarshalling Value")
+ }
+ return cp, nil
+ }
+
+ return ProviderNet{}, pkgerrors.New("Error getting ProviderNet")
+}
+
+// GetProviderNetList returns all of the ProviderNet for corresponding name
+func (v *ProviderNetClient) GetProviderNets(clusterProvider, cluster string) ([]ProviderNet, error) {
+
+ //Construct key and tag to select the entry
+ key := ProviderNetKey{
+ ClusterProviderName: clusterProvider,
+ ClusterName: cluster,
+ ProviderNetName: "",
+ }
+
+ var resp []ProviderNet
+ values, err := db.DBconn.Find(v.db.storeName, key, v.db.tagMeta)
+ if err != nil {
+ return []ProviderNet{}, pkgerrors.Wrap(err, "Get ProviderNets")
+ }
+
+ for _, value := range values {
+ cp := ProviderNet{}
+ err = db.DBconn.Unmarshal(value, &cp)
+ if err != nil {
+ return []ProviderNet{}, pkgerrors.Wrap(err, "Unmarshalling Value")
+ }
+ resp = append(resp, cp)
+ }
+
+ return resp, nil
+}
+
+// Delete the ProviderNet from database
+func (v *ProviderNetClient) DeleteProviderNet(name, clusterProvider, cluster string) error {
+
+ //Construct key and tag to select the entry
+ key := ProviderNetKey{
+ ClusterProviderName: clusterProvider,
+ ClusterName: cluster,
+ ProviderNetName: name,
+ }
+
+ err := db.DBconn.Remove(v.db.storeName, key)
+ if err != nil {
+ return pkgerrors.Wrap(err, "Delete ProviderNet Entry;")
+ }
+
+ return nil
+}
diff --git a/src/ncm/scripts/Dockerfile b/src/ncm/scripts/Dockerfile
new file mode 100644
index 00000000..d1b58677
--- /dev/null
+++ b/src/ncm/scripts/Dockerfile
@@ -0,0 +1,30 @@
+# SPDX-license-identifier: Apache-2.0
+##############################################################################
+# Copyright (c) 2020
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+FROM ubuntu:18.04
+
+ARG HTTP_PROXY=${HTTP_PROXY}
+ARG HTTPS_PROXY=${HTTPS_PROXY}
+
+ENV http_proxy $HTTP_PROXY
+ENV https_proxy $HTTPS_PROXY
+ENV no_proxy $NO_PROXY
+
+EXPOSE 9016
+
+RUN groupadd -r onap && useradd -r -g onap onap
+
+WORKDIR /opt/multicloud/k8s/ncm
+RUN chown onap:onap /opt/multicloud/k8s/ncm -R
+
+ADD --chown=onap ./ncm ./
+
+USER onap
+
+CMD ["./ncm"]
diff --git a/src/ncm/tests/certs/auth_test_certificate.pem b/src/ncm/tests/certs/auth_test_certificate.pem
new file mode 100644
index 00000000..42e77491
--- /dev/null
+++ b/src/ncm/tests/certs/auth_test_certificate.pem
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDXTCCAkWgAwIBAgIJAKAHJi8eUs73MA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQwHhcNMTgwNTE1MDQ0MDQwWhcNMTkwNTE1MDQ0MDQwWjBF
+MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
+ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEA5PHDk+RRFh5o3Xe2nZuLn0Vo+5BjnHp/ul2NNYSG00Slc8F86gW4xcNA
+wm6xC8tYCSangV2lFG3E8H2L7SCEVaM5VDV2GCOpOoMihc+2Qenk/YbHwuYenwjo
+OgTK4aCItqjcAJ2DB1KC7AxARxHBDu9Kif+M/pc49so+G9ObQuS8k2vmTTaRYkMK
+ZvbTJcWsc0vbNnPhxcG5PVj4unlaREM+yQDm18/glUkkBNnYKMHABRvPnBrDktTT
+BQWsqkbQTw7ZuLOrl9rCzVTstZX9wEXarrstIdQJj3KZjbFOp2opND8bjNIjcdVt
+iRFnP1nHQYr7EgRqcx/YMJZ+gmCy3wIDAQABo1AwTjAdBgNVHQ4EFgQU9qPNwwIu
+kZh41sJqFtnMC2blSYMwHwYDVR0jBBgwFoAU9qPNwwIukZh41sJqFtnMC2blSYMw
+DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEA4+daLY1wE10IMPaOKurG
+QBvfYeO/rgNXGeg0TisTIKAfx/We9Hmwo/37Bd2Nk5gxfy/DIJ4lMbrzXjgWITlm
+XOrS5QfluwvaEcREtHFtPFa3NZqn2VzKNDFTR+rJj7I5o600NKdcPrGeQ1i/vny2
+K0g68ogw2jfufcuePvZTYGst8RclomPr7ZXxI24kIjcE1MbiViy68sQueTXBEr5s
+Th6RsvPfVnLxjR/m/V6VJl31nn4T6hbmKzXCHo/X7aC3I8Isui4bQGKgfAxyBkhE
+0T7tP+GgymiEKQ6qJ/1c4HFFSuFRUQjLnK7uJu9jM/HMKoLCPayx6birHZRIMF94
+pg==
+-----END CERTIFICATE-----
diff --git a/src/ncm/tests/certs/auth_test_key.pem b/src/ncm/tests/certs/auth_test_key.pem
new file mode 100644
index 00000000..5f01f572
--- /dev/null
+++ b/src/ncm/tests/certs/auth_test_key.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDk8cOT5FEWHmjd
+d7adm4ufRWj7kGOcen+6XY01hIbTRKVzwXzqBbjFw0DCbrELy1gJJqeBXaUUbcTw
+fYvtIIRVozlUNXYYI6k6gyKFz7ZB6eT9hsfC5h6fCOg6BMrhoIi2qNwAnYMHUoLs
+DEBHEcEO70qJ/4z+lzj2yj4b05tC5LyTa+ZNNpFiQwpm9tMlxaxzS9s2c+HFwbk9
+WPi6eVpEQz7JAObXz+CVSSQE2dgowcAFG8+cGsOS1NMFBayqRtBPDtm4s6uX2sLN
+VOy1lf3ARdquuy0h1AmPcpmNsU6naik0PxuM0iNx1W2JEWc/WcdBivsSBGpzH9gw
+ln6CYLLfAgMBAAECggEAYB3H2D0QddLKf8AUoNJ+qZ1AV+zkhPtAyIMiF4fN+sBl
+HdXrlWxViGFSvM4v8h2qlhzuUfd4qLz042ox5pmyNSnTlbDkJXpDP9dyFO+BOubx
+Ribhksd9r5LTvBfq/RKikt0NkAyQx/AyGtuB2NRxUs3PY2QwU2o1dhauQIx0MH5/
+6D8PgQf6+5njKQaKa4e8Kp4kB+KjnALvt6JgYuNJUHWap+nnDbuuVy5dl1bKkAZ+
+qa7CITKWO4kE2EqaCb2asFc2w3538+w72UJZtwQCmOaxtKpRSl9fQXu54N8jIGoZ
+1FvEj5x3X6QkglE+iVJYaX3RmiJ3uzZ2LICDr89vEQKBgQD7fquIw4p1idSxz3Cm
+5o3Y5kD0CKm61ZaRJWKd+tNlSsxubmV9HROYW6vj2xEPSDvyp1na00pDXxMJQLLc
+O5Awd1SaU+d45jnoi70fsEY8X0WH1rDTYfnU+zQBmpbGqX5qTIfpy4yoADiUD1CQ
+EBdaSBWiKPx2jFSct58TwDP9YwKBgQDpC64TScZYz7uQq4gAbDso/7TjNwgt/Bw8
+JgLSdx1UdUclh81smTujsouyCFwJSvRjKik8e/Qt0f5patukFbFRINxUGUDhOKbA
+7zqeNQbeYaP7Rvw+3z01CU2BTBmB/EWa2xWDam8B9xQvjiHSOrubqkt3sIQJb045
+hzuigdV7VQKBgQD7Gnd0nyCwyMSIIMGuswYv6X4y6i9lr3qdQ4GakOTe/vbsz+cf
+K5f0CJuwbnszEgFg/zzVIx/D8rqUA3hSMlp+ObdMO7gi22Q4TsWvTRZjkxBeV7rH
+48xJneNIMqyWgIcK5YzSn3y6BTZ4hm3+2UInz09iUJ/6UZTtwNzhIIgIVwKBgQCT
+LxRHDE4gIzrT+PHRSonmr/DfnA8nc9WlS2B26lH02IkRs/5Su0iGb6p4y3zNRbCp
+vKQElki2c60ZiSqlLCosEfP1jWmDlRMEQVMlPlpTMxmtBr0jPDzc9T4lDhoCFYEk
+d3/T2vG3LQRrsHm92+hHPTuioTIS/2BJRxar4RIibQKBgQC8zoayoQ7zfEYxy3Ax
+OSao8g85hj0EAJk/VKQP2POgz6KoPay3JE9D7P7OvkebTyv/pijAuTPby4XipCNI
+K0JbFC2Kn7RW/ZV23UdnoO9crh2omOh+/52prStWXKoc+/pJe70Af+4rU7FUiI7F
+y1mIE9krIoVis6iYsyFEmkP7iw==
+-----END PRIVATE KEY-----
diff --git a/src/ncm/tests/configs/mock_config.json b/src/ncm/tests/configs/mock_config.json
new file mode 100644
index 00000000..47a6b627
--- /dev/null
+++ b/src/ncm/tests/configs/mock_config.json
@@ -0,0 +1,5 @@
+{
+ "database-type": "mock_db_test",
+ "database-ip": "127.0.0.1",
+ "plugin-dir": "."
+} \ No newline at end of file
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 f0342433..1d38f106 100644
--- a/src/orchestrator/api/api.go
+++ b/src/orchestrator/api/api.go
@@ -1,34 +1,48 @@
/*
-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) *mux.Router {
+func NewRouter(projectClient moduleLib.ProjectManager,
+ compositeAppClient moduleLib.CompositeAppManager,
+ appClient moduleLib.AppManager,
+ ControllerClient moduleLib.ControllerManager,
+ 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,
@@ -43,20 +57,111 @@ 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 appClient == nil {
+ appClient = moduleClient.App
+ }
+ appHandler := appHandler{
+ client: appClient,
+ }
+
+ router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{version}/apps", appHandler.createAppHandler).Methods("POST")
+ router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{version}/apps/{app-name}", appHandler.getAppHandler).Methods("GET")
+ router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{version}/apps", appHandler.getAppHandler).Methods("GET")
+ router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{version}/apps/{app-name}", appHandler.deleteAppHandler).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")
router.HandleFunc("/controllers/{controller-name}", controlHandler.deleteHandler).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..ef7833de
--- /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"
+
+ "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/validation"
+ moduleLib "github.com/onap/multicloud-k8s/src/orchestrator/pkg/module"
+
+ "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 = validation.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/apphandler.go b/src/orchestrator/api/apphandler.go
new file mode 100644
index 00000000..2c81431c
--- /dev/null
+++ b/src/orchestrator/api/apphandler.go
@@ -0,0 +1,248 @@
+/*
+ * 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"
+
+ "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/validation"
+ moduleLib "github.com/onap/multicloud-k8s/src/orchestrator/pkg/module"
+
+ "github.com/gorilla/mux"
+)
+
+// appHandler to store backend implementations objects
+// Also simplifies mocking for unit testing purposes
+type appHandler struct {
+ // Interface that implements App operations
+ // We will set this variable with a mock interface for testing
+ client moduleLib.AppManager
+}
+
+// createAppHandler handles creation of the App entry in the database
+// This is a multipart handler. See following example curl request
+// curl -X POST http://localhost:9015/v2/projects/sampleProject/composite-apps/sampleCompositeApp/v1/apps \
+// -F "metadata={\"metadata\":{\"name\":\"app\",\"description\":\"sample app\",\"UserData1\":\"data1\",\"UserData2\":\"data2\"}};type=application/json" \
+// -F file=@/pathToFile
+
+func (h appHandler) createAppHandler(w http.ResponseWriter, r *http.Request) {
+ var a moduleLib.App
+ var ac moduleLib.AppContent
+
+ // Implemenation using multipart form
+ // 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(&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
+ }
+
+ // Name is required.
+ if a.Metadata.Name == "" {
+ http.Error(w, "Missing name in POST request", http.StatusBadRequest)
+ 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 = validation.IsTarGz(bytes.NewBuffer(content))
+ if err != nil {
+ http.Error(w, "Error in file format", http.StatusUnprocessableEntity)
+ return
+ }
+
+ ac.FileContent = base64.StdEncoding.EncodeToString(content)
+
+ vars := mux.Vars(r)
+ projectName := vars["project-name"]
+ compositeAppName := vars["composite-app-name"]
+ compositeAppVersion := vars["version"]
+
+ ret, err := h.client.CreateApp(a, ac, projectName, compositeAppName, compositeAppVersion)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusCreated)
+ err = json.NewEncoder(w).Encode(ret)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+// getAppHandler handles GET operations on a particular App Name
+// Returns an app
+func (h appHandler) getAppHandler(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ projectName := vars["project-name"]
+ compositeAppName := vars["composite-app-name"]
+ compositeAppVersion := vars["version"]
+ name := vars["app-name"]
+
+ // handle the get all apps case - return a list of only the json parts
+ if len(name) == 0 {
+ var retList []moduleLib.App
+
+ ret, err := h.client.GetApps(projectName, compositeAppName, compositeAppVersion)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ for _, app := range ret {
+ retList = append(retList, moduleLib.App{Metadata: app.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
+ }
+
+ accepted, _, err := mime.ParseMediaType(r.Header.Get("Accept"))
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusNotAcceptable)
+ return
+ }
+
+ var retApp moduleLib.App
+ var retAppContent moduleLib.AppContent
+
+ retApp, err = h.client.GetApp(name, projectName, compositeAppName, compositeAppVersion)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ retAppContent, err = h.client.GetAppContent(name, projectName, compositeAppName, compositeAppVersion)
+ 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(retApp); 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; filename=fileContent"}})
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ fcBytes, err := base64.StdEncoding.DecodeString(retAppContent.FileContent)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ _, err = pw.Write(fcBytes)
+ 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(retApp)
+ 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)
+ fcBytes, err := base64.StdEncoding.DecodeString(retAppContent.FileContent)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ _, err = w.Write(fcBytes)
+ 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
+ }
+}
+
+// deleteAppHandler handles DELETE operations on a particular App Name
+func (h appHandler) deleteAppHandler(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ projectName := vars["project-name"]
+ compositeAppName := vars["composite-app-name"]
+ compositeAppVersion := vars["version"]
+ name := vars["app-name"]
+
+ err := h.client.DeleteApp(name, projectName, compositeAppName, compositeAppVersion)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.WriteHeader(http.StatusNoContent)
+}
diff --git a/src/orchestrator/api/composite_app_handler.go b/src/orchestrator/api/composite_app_handler.go
index 42c72cdb..b54c488e 100644
--- a/src/orchestrator/api/composite_app_handler.go
+++ b/src/orchestrator/api/composite_app_handler.go
@@ -35,7 +35,6 @@ type compositeAppHandler struct {
}
// createHandler handles creation of the CompositeApp entry in the database
-// This is a multipart handler
func (h compositeAppHandler) createHandler(w http.ResponseWriter, r *http.Request) {
var c moduleLib.CompositeApp
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 f0804107..1844fb32 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))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.controllerClient, nil, nil, nil, nil, nil, nil))
//Check returned code
if resp.StatusCode != testCase.expectedCode {
@@ -173,7 +173,7 @@ func TestControllerGetHandler(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("GET", "/v2/controllers/"+testCase.name, nil)
- resp := executeRequest(request, NewRouter(nil, nil, testCase.controllerClient))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.controllerClient, nil, nil, nil, nil, nil, nil))
//Check returned code
if resp.StatusCode != testCase.expectedCode {
@@ -222,7 +222,7 @@ func TestControllerDeleteHandler(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.label, func(t *testing.T) {
request := httptest.NewRequest("DELETE", "/v2/controllers/"+testCase.name, nil)
- resp := executeRequest(request, NewRouter(nil, nil, testCase.controllerClient))
+ resp := executeRequest(request, NewRouter(nil, nil, nil, testCase.controllerClient, 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 1e273349..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))
+ 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))
+ 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))
+ 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 a6c72ae3..f95c057e 100644
--- a/src/orchestrator/cmd/main.go
+++ b/src/orchestrator/cmd/main.go
@@ -34,7 +34,7 @@ func main() {
rand.Seed(time.Now().UnixNano())
- err := db.InitializeDatabaseConnection()
+ err := db.InitializeDatabaseConnection("mco")
if err != nil {
log.Println("Unable to initialize database connection...")
log.Println(err)
@@ -47,7 +47,7 @@ func main() {
log.Fatalln("Exiting...")
}
- httpRouter := api.NewRouter(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/contextdb/contextdb.go b/src/orchestrator/pkg/infra/contextdb/contextdb.go
index d18af227..58832a19 100644
--- a/src/orchestrator/pkg/infra/contextdb/contextdb.go
+++ b/src/orchestrator/pkg/infra/contextdb/contextdb.go
@@ -29,6 +29,8 @@ type ContextDb interface {
Put(key string, value interface{}) error
// Delete k,v
Delete(key string) error
+ // Delete all keys in heirarchy
+ DeleteAll(key string) error
// Gets Json Struct from db
Get(key string, value interface{}) error
// Returns all keys with a prefix
diff --git a/src/orchestrator/pkg/infra/contextdb/etcd.go b/src/orchestrator/pkg/infra/contextdb/etcd.go
index a1922d3b..44f8ab48 100644
--- a/src/orchestrator/pkg/infra/contextdb/etcd.go
+++ b/src/orchestrator/pkg/infra/contextdb/etcd.go
@@ -156,13 +156,26 @@ func (e *EtcdClient) GetAllKeys(key string) ([]string, error) {
return keys, nil
}
+// DeleteAll keys from Etcd DB
+func (e *EtcdClient) DeleteAll(key string) error {
+ cli := getEtcd(e)
+ if cli == nil {
+ return pkgerrors.Errorf("Etcd Client not initialized")
+ }
+ _, err := cli.Delete(context.Background(), key, clientv3.WithPrefix())
+ if err != nil {
+ return pkgerrors.Errorf("Delete failed etcd entry: %s", err.Error())
+ }
+ return nil
+}
+
// Delete values from Etcd DB
func (e *EtcdClient) Delete(key string) error {
cli := getEtcd(e)
if cli == nil {
return pkgerrors.Errorf("Etcd Client not initialized")
}
- _, err := cli.Delete(context.Background(), key, clientv3.WithPrefix())
+ _, err := cli.Delete(context.Background(), key)
if err != nil {
return pkgerrors.Errorf("Delete failed etcd entry: %s", err.Error())
}
diff --git a/src/orchestrator/pkg/infra/db/README.md b/src/orchestrator/pkg/infra/db/README.md
index ff482b98..71da1e0a 100644
--- a/src/orchestrator/pkg/infra/db/README.md
+++ b/src/orchestrator/pkg/infra/db/README.md
@@ -130,7 +130,7 @@ key := CompositeAppKey{
NOTE: Key structure can be different from the original key and can include Query fields also. ANY operation is not supported for Query fields.
-### Remove
+### RemoveAll
Arguments:
```go
@@ -139,6 +139,15 @@ key interface
```
Similar to find. This will remove one or more documents based on the key structure.
+### Remove
+
+Arguments:
+```go
+collection string
+key interface
+```
+This will remove one document based on the key structure. If child refrences exist for the key then the document will not be removed.
+
### Unmarshal
Data in mongo is stored as `bson` which is a compressed form of `json`. We need mongo to convert the stored `bson` data to regular `json`
@@ -147,3 +156,4 @@ that we can use in our code when returned.
`bson.Unmarshal` API is used to achieve this.
+
diff --git a/src/orchestrator/pkg/infra/db/mock.go b/src/orchestrator/pkg/infra/db/mock.go
index afa4b024..e43be8fb 100644
--- a/src/orchestrator/pkg/infra/db/mock.go
+++ b/src/orchestrator/pkg/infra/db/mock.go
@@ -15,6 +15,7 @@ package db
import (
"encoding/json"
+ "fmt"
pkgerrors "github.com/pkg/errors"
)
@@ -44,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
}
@@ -62,8 +67,9 @@ func (m *MockDB) Read(table string, key Key, tag string) ([]byte, error) {
return nil, m.Err
}
+ str := fmt.Sprintf("%v", key)
for k, v := range m.Items {
- if k == key.String() {
+ if k == str {
return v[tag], nil
}
}
@@ -71,7 +77,26 @@ 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
}
+func (m *MockDB) Remove(table string, key Key) error {
+ return m.Err
+}
diff --git a/src/orchestrator/pkg/infra/db/mongo.go b/src/orchestrator/pkg/infra/db/mongo.go
index 30eb899f..a344aa1c 100644
--- a/src/orchestrator/pkg/infra/db/mongo.go
+++ b/src/orchestrator/pkg/infra/db/mongo.go
@@ -49,6 +49,8 @@ type MongoCollection interface {
opts ...*options.FindOptions) (*mongo.Cursor, error)
UpdateOne(ctx context.Context, filter interface{}, update interface{},
opts ...*options.UpdateOptions) (*mongo.UpdateResult, error)
+ CountDocuments(ctx context.Context, filter interface{},
+ opts ...*options.CountOptions) (int64, error)
}
// MongoStore is an implementation of the db.Store interface
@@ -543,8 +545,8 @@ func (m *MongoStore) Find(coll string, key Key, tag string) ([][]byte, error) {
return result, nil
}
-// Remove method to remove the documet by key
-func (m *MongoStore) Remove(coll string, key Key) error {
+// RemoveAll method to removes all the documet matching key
+func (m *MongoStore) RemoveAll(coll string, key Key) error {
if !m.validateParams(coll, key) {
return pkgerrors.New("Mandatory fields are missing")
}
@@ -560,3 +562,29 @@ func (m *MongoStore) Remove(coll string, key Key) error {
}
return nil
}
+
+// Remove method to remove the documet by key if no child references
+func (m *MongoStore) Remove(coll string, key Key) error {
+ if !m.validateParams(coll, key) {
+ return pkgerrors.New("Mandatory fields are missing")
+ }
+ c := getCollection(coll, m)
+ ctx := context.Background()
+ filter, err := m.findFilter(key)
+ if err != nil {
+ return err
+ }
+ count, err := c.CountDocuments(context.Background(), filter)
+ if err != nil {
+ return pkgerrors.Errorf("Error finding: %s", err.Error())
+ }
+ if count > 1 {
+ return pkgerrors.Errorf("Can't delete parent without deleting child references first")
+ }
+ _, err = c.DeleteOne(ctx, filter)
+ if err != nil {
+ return pkgerrors.Errorf("Error Deleting from database: %s", err.Error())
+ }
+ return nil
+}
+
diff --git a/src/orchestrator/pkg/infra/db/mongo_test.go b/src/orchestrator/pkg/infra/db/mongo_test.go
index d506dbda..d57c19dd 100644
--- a/src/orchestrator/pkg/infra/db/mongo_test.go
+++ b/src/orchestrator/pkg/infra/db/mongo_test.go
@@ -76,7 +76,12 @@ 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 (c *mockCollection) CountDocuments(ctx context.Context, filter interface{},
+ opts ...*options.CountOptions) (int64, error) {
+ return 1, c.Err
}
func TestCreate(t *testing.T) {
@@ -463,4 +468,3 @@ func TestDelete(t *testing.T) {
})
}
}
-
diff --git a/src/orchestrator/pkg/infra/db/store.go b/src/orchestrator/pkg/infra/db/store.go
index c8a8f744..e87722cd 100644
--- a/src/orchestrator/pkg/infra/db/store.go
+++ b/src/orchestrator/pkg/infra/db/store.go
@@ -29,7 +29,6 @@ var DBconn Store
// that wants to use the Store interface. This allows various
// db backends and key types.
type Key interface {
- String() string
}
// Store is an interface for accessing the database
@@ -60,17 +59,20 @@ type Store interface {
// Find the document(s) with key and get the tag values from the document(s)
Find(coll string, key Key, tag string) ([][]byte, error)
- // Removes the document(s) matching the key
+ // Removes the document(s) matching the key if no child reference in collection
Remove(coll string, key Key) error
+
+ // Remove all the document(s) matching the key
+ RemoveAll(coll string, key Key) error
}
// CreateDBClient creates the DB client
-func createDBClient(dbType string) error {
+func createDBClient(dbType string, dbName string) error {
var err error
switch dbType {
case "mongo":
// create a mongodb database with orchestrator as the name
- DBconn, err = NewMongoStore("orchestrator", nil)
+ DBconn, err = NewMongoStore(dbName, nil)
default:
return pkgerrors.New(dbType + "DB not supported")
}
@@ -97,8 +99,8 @@ func DeSerialize(str string, v interface{}) error {
// InitializeDatabaseConnection sets up the connection to the
// configured database to allow the application to talk to it.
-func InitializeDatabaseConnection() error {
- err := createDBClient(config.GetConfiguration().DatabaseType)
+func InitializeDatabaseConnection(dbName string) error {
+ err := createDBClient(config.GetConfiguration().DatabaseType, dbName)
if err != nil {
return pkgerrors.Cause(err)
}
diff --git a/src/orchestrator/pkg/infra/db/store_test.go b/src/orchestrator/pkg/infra/db/store_test.go
index 42a41787..fb23e232 100644
--- a/src/orchestrator/pkg/infra/db/store_test.go
+++ b/src/orchestrator/pkg/infra/db/store_test.go
@@ -23,7 +23,7 @@ func TestCreateDBClient(t *testing.T) {
t.Run("Successfully create DB client", func(t *testing.T) {
expected := &MongoStore{}
- err := createDBClient("mongo")
+ err := createDBClient("mongo", "testdb")
if err != nil {
t.Fatalf("CreateDBClient returned an error (%s)", err)
}
@@ -32,7 +32,7 @@ func TestCreateDBClient(t *testing.T) {
}
})
t.Run("Fail to create client for unsupported DB", func(t *testing.T) {
- err := createDBClient("fakeDB")
+ err := createDBClient("fakeDB", "testdb2")
if err == nil {
t.Fatal("CreateDBClient didn't return an error")
}
diff --git a/src/orchestrator/pkg/infra/validation/validation.go b/src/orchestrator/pkg/infra/validation/validation.go
new file mode 100644
index 00000000..d744dc3d
--- /dev/null
+++ b/src/orchestrator/pkg/infra/validation/validation.go
@@ -0,0 +1,264 @@
+/*
+ * 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 validation
+
+import (
+ "archive/tar"
+ "compress/gzip"
+ "io"
+ "net"
+ "regexp"
+ "strings"
+
+ pkgerrors "github.com/pkg/errors"
+ "k8s.io/apimachinery/pkg/util/validation"
+)
+
+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
+}
+
+func IsIpv4Cidr(cidr string) error {
+ _, _, err := net.ParseCIDR(cidr)
+ if err != nil {
+ return pkgerrors.Wrapf(err, "could not parse subnet %v", cidr)
+ }
+ return nil
+}
+
+func IsIpv4(ip string) error {
+ addr := net.ParseIP(ip)
+ if addr == nil {
+ return pkgerrors.Errorf("invalid ipv4 address %v", ip)
+ }
+ return nil
+}
+
+// default name check - matches valid label value with addtion that length > 0
+func IsValidName(name string) []string {
+ var errs []string
+
+ errs = validation.IsValidLabelValue(name)
+ if len(name) == 0 {
+ errs = append(errs, "name must have non-zero length")
+ }
+ return errs
+}
+
+const VALID_NAME_STR string = "NAME"
+
+var validNameRegEx = regexp.MustCompile("^([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]$")
+
+const VALID_ALPHA_STR string = "ALPHA"
+
+var validAlphaStrRegEx = regexp.MustCompile("^[A-Za-z]*$")
+
+const VALID_ALPHANUM_STR string = "ALPHANUM"
+
+var validAlphaNumStrRegEx = regexp.MustCompile("^[A-Za-z0-9]*$")
+
+// doesn't verify valid base64 length - just checks for proper base64 characters
+const VALID_BASE64_STR string = "BASE64"
+
+var validBase64StrRegEx = regexp.MustCompile("^[A-Za-z0-9+/]+={0,2}$")
+
+const VALID_ANY_STR string = "ANY"
+
+var validAnyStrRegEx = regexp.MustCompile("(?s)^.*$")
+
+// string check - validates for conformance to provided lengths and specified content
+// min and max - the string
+// if format string provided - check against matching predefined
+func IsValidString(str string, min, max int, format string) []string {
+ var errs []string
+
+ if min > max {
+ errs = append(errs, "Invalid string length constraints - min is greater than max")
+ return errs
+ }
+
+ if len(str) < min {
+ errs = append(errs, "string length is less than the minimum constraint")
+ return errs
+ }
+ if len(str) > max {
+ errs = append(errs, "string length is greater than the maximum constraint")
+ return errs
+ }
+
+ switch format {
+ case VALID_ALPHA_STR:
+ if !validAlphaStrRegEx.MatchString(str) {
+ errs = append(errs, "string does not match the alpha only constraint")
+ }
+ case VALID_ALPHANUM_STR:
+ if !validAlphaNumStrRegEx.MatchString(str) {
+ errs = append(errs, "string does not match the alphanumeric only constraint")
+ }
+ case VALID_NAME_STR:
+ if !validNameRegEx.MatchString(str) {
+ errs = append(errs, "string does not match the valid k8s name constraint")
+ }
+ case VALID_BASE64_STR:
+ if !validBase64StrRegEx.MatchString(str) {
+ errs = append(errs, "string does not match the valid base64 characters constraint")
+ }
+ if len(str)%4 != 0 {
+ errs = append(errs, "base64 string length should be a multiple of 4")
+ }
+ case VALID_ANY_STR:
+ if !validAnyStrRegEx.MatchString(str) {
+ errs = append(errs, "string does not match the any characters constraint")
+ }
+ default:
+ // invalid string format supplied
+ errs = append(errs, "an invalid string constraint was supplied")
+ }
+
+ return errs
+}
+
+// validate that label conforms to kubernetes label conventions
+// general label format expected is:
+// "<labelprefix>/<labelname>=<Labelvalue>"
+// where labelprefix matches DNS1123Subdomain format
+// labelname matches DNS1123Label format
+//
+// Input labels are allowed to match following formats:
+// "<DNS1123Subdomain>/<DNS1123Label>=<Labelvalue>"
+// "<DNS1123Label>=<LabelValue>"
+// "<LabelValue>"
+func IsValidLabel(label string) []string {
+ var labelerrs []string
+
+ expectLabelName := false
+ expectLabelPrefix := false
+
+ // split label up into prefix, name and value
+ // format: prefix/name=value
+ var labelprefix, labelname, labelvalue string
+
+ kv := strings.SplitN(label, "=", 2)
+ if len(kv) == 1 {
+ labelprefix = ""
+ labelname = ""
+ labelvalue = kv[0]
+ } else {
+ pn := strings.SplitN(kv[0], "/", 2)
+ if len(pn) == 1 {
+ labelprefix = ""
+ labelname = pn[0]
+ } else {
+ labelprefix = pn[0]
+ labelname = pn[1]
+ expectLabelPrefix = true
+ }
+ labelvalue = kv[1]
+ // if "=" was in the label input, then expect a non-zero length name
+ expectLabelName = true
+ }
+
+ // check label prefix validity - prefix is optional
+ if len(labelprefix) > 0 {
+ errs := validation.IsDNS1123Subdomain(labelprefix)
+ if len(errs) > 0 {
+ labelerrs = append(labelerrs, "Invalid label prefix - label=["+label+"%], labelprefix=["+labelprefix+"], errors: ")
+ for _, err := range errs {
+ labelerrs = append(labelerrs, err)
+ }
+ }
+ } else if expectLabelPrefix {
+ labelerrs = append(labelerrs, "Invalid label prefix - label=["+label+"%], labelprefix=["+labelprefix+"]")
+ }
+ if expectLabelName {
+ errs := validation.IsDNS1123Label(labelname)
+ if len(errs) > 0 {
+ labelerrs = append(labelerrs, "Invalid label name - label=["+label+"%], labelname=["+labelname+"], errors: ")
+ for _, err := range errs {
+ labelerrs = append(labelerrs, err)
+ }
+ }
+ }
+ if len(labelvalue) > 0 {
+ errs := validation.IsValidLabelValue(labelvalue)
+ if len(errs) > 0 {
+ labelerrs = append(labelerrs, "Invalid label value - label=["+label+"%], labelvalue=["+labelvalue+"], errors: ")
+ for _, err := range errs {
+ labelerrs = append(labelerrs, err)
+ }
+ }
+ } else {
+ // expect a non-zero value
+ labelerrs = append(labelerrs, "Invalid label value - label=["+label+"%], labelvalue=["+labelvalue+"]")
+ }
+
+ return labelerrs
+}
+
+func IsValidNumber(value, min, max int) []string {
+ var errs []string
+
+ if min > max {
+ errs = append(errs, "invalid constraints")
+ return errs
+ }
+
+ if value < min {
+ errs = append(errs, "value less than minimum")
+ }
+ if value > max {
+ errs = append(errs, "value greater than maximum")
+ }
+ return errs
+}
diff --git a/src/orchestrator/pkg/infra/validation/validation_test.go b/src/orchestrator/pkg/infra/validation/validation_test.go
new file mode 100644
index 00000000..5109b6c7
--- /dev/null
+++ b/src/orchestrator/pkg/infra/validation/validation_test.go
@@ -0,0 +1,466 @@
+/*
+ * 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 validation
+
+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")
+ }
+ })
+}
+
+func TestIsValidName(t *testing.T) {
+ t.Run("Valid Names", func(t *testing.T) {
+ validnames := []string{
+ "abc123",
+ "1_abc123.ONE",
+ "0abcABC_-.5",
+ "123456789012345678901234567890123456789012345678901234567890123", // max of 63 characters
+ }
+ for _, name := range validnames {
+ errs := IsValidName(name)
+ if len(errs) > 0 {
+ t.Errorf("Valid name failed to pass: %v", name)
+ }
+ }
+ })
+
+ t.Run("Invalid Names", func(t *testing.T) {
+ invalidnames := []string{
+ "", // empty
+ "_abc123", // starts with non-alphanum
+ "-abc123", // starts with non-alphanum
+ ".abc123", // starts with non-alphanum
+ "abc123-", // ends with non-alphanum
+ "abc123_", // ends with non-alphanum
+ "abc123.", // ends with non-alphanum
+ "1_abc-123.O=NE", // contains not allowed character
+ "1_a/bc-123.ONE", // contains not allowed character
+ "1234567890123456789012345678901234567890123456789012345678901234", // longer than 63 characters
+ }
+ for _, name := range invalidnames {
+ errs := IsValidName(name)
+ if len(errs) == 0 {
+ t.Errorf("Invalid name passed: %v", name)
+ }
+ }
+ })
+}
+
+func TestIsIpv4Cidr(t *testing.T) {
+ t.Run("Valid IPv4 Cidr", func(t *testing.T) {
+ validipv4cidr := []string{
+ "1.2.3.4/32",
+ "10.11.12.0/24",
+ "192.168.1.2/8",
+ "255.0.0.0/16",
+ }
+ for _, ip := range validipv4cidr {
+ err := IsIpv4Cidr(ip)
+ if err != nil {
+ t.Errorf("Valid IPv4 CIDR string failed to pass: %v", ip)
+ }
+ }
+ })
+
+ t.Run("Invalid IPv4 Cidr", func(t *testing.T) {
+ invalidipv4cidr := []string{
+ "",
+ "1.2.3.4.5/32",
+ "1.2.3.415/16",
+ "1.2.3.4/33",
+ "2.3.4/24",
+ "1.2.3.4",
+ "1.2.3.4/",
+ }
+ for _, ip := range invalidipv4cidr {
+ err := IsIpv4Cidr(ip)
+ if err == nil {
+ t.Errorf("Invalid IPv4 Cidr passed: %v", ip)
+ }
+ }
+ })
+}
+
+func TestIsIpv4(t *testing.T) {
+ t.Run("Valid IPv4", func(t *testing.T) {
+ validipv4 := []string{
+ "1.2.3.42",
+ "10.11.12.0",
+ "192.168.1.2",
+ "255.0.0.0",
+ "255.255.255.255",
+ "0.0.0.0",
+ }
+ for _, ip := range validipv4 {
+ err := IsIpv4(ip)
+ if err != nil {
+ t.Errorf("Valid IPv4 string failed to pass: %v", ip)
+ }
+ }
+ })
+
+ t.Run("Invalid IPv4", func(t *testing.T) {
+ invalidipv4 := []string{
+ "",
+ "1.2.3.4.5",
+ "1.2.3.45/32",
+ "1.2.3.4a",
+ "2.3.4",
+ "1.2.3.400",
+ "256.255.255.255",
+ "10,11,12,13",
+ "1.2.3.4/",
+ }
+ for _, ip := range invalidipv4 {
+ err := IsIpv4(ip)
+ if err == nil {
+ t.Errorf("Invalid IPv4 passed: %v", ip)
+ }
+ }
+ })
+}
+
+func TestIsValidString(t *testing.T) {
+ t.Run("Valid Strings", func(t *testing.T) {
+ validStrings := []struct {
+ str string
+ min int
+ max int
+ format string
+ }{
+ {
+ str: "abc123",
+ min: 0,
+ max: 16,
+ format: VALID_NAME_STR,
+ },
+ {
+ str: "ab-c1_2.3",
+ min: 0,
+ max: 16,
+ format: VALID_NAME_STR,
+ },
+ {
+ str: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
+ min: 0,
+ max: 62,
+ format: VALID_ALPHANUM_STR,
+ },
+ {
+ str: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
+ min: 0,
+ max: 52,
+ format: VALID_ALPHA_STR,
+ },
+ {
+ str: "",
+ min: 0,
+ max: 52,
+ format: VALID_ALPHA_STR,
+ },
+ {
+ str: "",
+ min: 0,
+ max: 52,
+ format: VALID_ALPHANUM_STR,
+ },
+ {
+ str: "dGhpcyBpcyBhCnRlc3Qgc3RyaW5nCg==",
+ min: 0,
+ max: 52,
+ format: VALID_BASE64_STR,
+ },
+ {
+ str: "\t\n \n0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+-=,.<>/?'\"\\[]{}\n",
+ min: 0,
+ max: 256,
+ format: VALID_ANY_STR,
+ },
+ }
+ for _, test := range validStrings {
+ errs := IsValidString(test.str, test.min, test.max, test.format)
+ if len(errs) > 0 {
+ t.Errorf("Valid string failed to pass: str:%v, min:%v, max:%v, format:%v", test.str, test.min, test.max, test.format)
+ }
+ }
+ })
+
+ t.Run("Invalid Strings", func(t *testing.T) {
+ inValidStrings := []struct {
+ str string
+ min int
+ max int
+ format string
+ }{
+ {
+ str: "abc123",
+ min: 0,
+ max: 5,
+ format: VALID_NAME_STR,
+ },
+ {
+ str: "",
+ min: 0,
+ max: 5,
+ format: VALID_NAME_STR,
+ },
+ {
+ str: "-ab-c1_2.3",
+ min: 0,
+ max: 16,
+ format: VALID_NAME_STR,
+ },
+ {
+ str: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ=",
+ min: 0,
+ max: 100,
+ format: VALID_ALPHANUM_STR,
+ },
+ {
+ str: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456",
+ min: 0,
+ max: 62,
+ format: VALID_ALPHA_STR,
+ },
+ {
+ str: "",
+ min: 1,
+ max: 52,
+ format: VALID_ALPHA_STR,
+ },
+ {
+ str: "abc123",
+ min: 1,
+ max: 3,
+ format: VALID_ALPHA_STR,
+ },
+ {
+ str: "",
+ min: 1,
+ max: 52,
+ format: VALID_ALPHANUM_STR,
+ },
+ {
+ str: "dGhpcyBpcyBhCnRlc3Qgc3RyaW5nCg===",
+ min: 0,
+ max: 52,
+ format: VALID_BASE64_STR,
+ },
+ {
+ str: "dGhpcyBpcyBhCnRlc3=Qgc3RyaW5nCg==",
+ min: 0,
+ max: 52,
+ format: VALID_BASE64_STR,
+ },
+ {
+ str: "dGhpcyBpcyBhCnRlc3#Qgc3RyaW5nCg==",
+ min: 0,
+ max: 52,
+ format: VALID_BASE64_STR,
+ },
+ {
+ str: "\t\n \n0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+-=,.<>/?'\"\\[]{}\n",
+ min: 0,
+ max: 10,
+ format: VALID_ANY_STR,
+ },
+ {
+ str: "abc123",
+ min: 0,
+ max: 10,
+ format: "unknownformat",
+ },
+ }
+ for _, test := range inValidStrings {
+ errs := IsValidString(test.str, test.min, test.max, test.format)
+ if len(errs) == 0 {
+ t.Errorf("Invalid string passed: str:%v, min:%v, max:%v, format:%v", test.str, test.min, test.max, test.format)
+ }
+ }
+ })
+}
+
+func TestIsValidLabel(t *testing.T) {
+ t.Run("Valid Labels", func(t *testing.T) {
+ validlabels := []string{
+ "kubernetes.io/hostname=localhost",
+ "hostname=localhost",
+ "localhost",
+ }
+ for _, label := range validlabels {
+ errs := IsValidLabel(label)
+ if len(errs) > 0 {
+ t.Errorf("Valid label failed to pass: %v %v", label, errs)
+ }
+ }
+ })
+
+ t.Run("Invalid Labels", func(t *testing.T) {
+ invalidlabels := []string{
+ "",
+ "kubernetes$.io/hostname=localhost",
+ "hostname==localhost",
+ "=localhost",
+ "/hostname=localhost",
+ ".a.b/hostname=localhost",
+ "kubernetes.io/hostname",
+ "kubernetes.io/hostname=",
+ "kubernetes.io/1234567890123456789012345678901234567890123456789012345678901234=localhost", // too long name
+ "kubernetes.io/hostname=localhost1234567890123456789012345678901234567890123456789012345678901234", // too long value
+ "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234/hostname=localhost", // too long prefix
+ }
+ for _, label := range invalidlabels {
+ errs := IsValidLabel(label)
+ if len(errs) == 0 {
+ t.Errorf("Invalid label passed: %v", label)
+ }
+ }
+ })
+}
+
+func TestIsValidNumber(t *testing.T) {
+ t.Run("Valid Number", func(t *testing.T) {
+ validNumbers := []struct {
+ value int
+ min int
+ max int
+ }{
+ {
+ value: 0,
+ min: 0,
+ max: 5,
+ },
+ {
+ value: 1000,
+ min: 0,
+ max: 4095,
+ },
+ {
+ value: 0,
+ min: 0,
+ max: 0,
+ },
+ {
+ value: -100,
+ min: -200,
+ max: -99,
+ },
+ {
+ value: 123,
+ min: 123,
+ max: 123,
+ },
+ }
+ for _, test := range validNumbers {
+ err := IsValidNumber(test.value, test.min, test.max)
+ if len(err) > 0 {
+ t.Errorf("Valid number failed to pass - value:%v, min:%v, max:%v", test.value, test.min, test.max)
+ }
+ }
+ })
+
+ t.Run("Invalid Number", func(t *testing.T) {
+ inValidNumbers := []struct {
+ value int
+ min int
+ max int
+ }{
+ {
+ value: 6,
+ min: 0,
+ max: 5,
+ },
+ {
+ value: 4096,
+ min: 0,
+ max: 4095,
+ },
+ {
+ value: 11,
+ min: 10,
+ max: 10,
+ },
+ {
+ value: -100,
+ min: -99,
+ max: -200,
+ },
+ {
+ value: 123,
+ min: 223,
+ max: 123,
+ },
+ }
+ for _, test := range inValidNumbers {
+ err := IsValidNumber(test.value, test.min, test.max)
+ if len(err) == 0 {
+ t.Errorf("Invalid number passed - value:%v, min:%v, max:%v", test.value, test.min, test.max)
+ }
+ }
+ })
+}
diff --git a/src/orchestrator/pkg/module/add_intents.go b/src/orchestrator/pkg/module/add_intents.go
new file mode 100644
index 00000000..20fba189
--- /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:"intentname"`
+ Project string `json:"project"`
+ CompositeApp string `json:"compositeapp"`
+ Version string `json:"compositeappversion"`
+ DeploymentIntentGroup string `json:"deploymentintentgroup"`
+}
+
+// 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.go b/src/orchestrator/pkg/module/app.go
new file mode 100644
index 00000000..1e1a5974
--- /dev/null
+++ b/src/orchestrator/pkg/module/app.go
@@ -0,0 +1,231 @@
+/*
+ * 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 governinog 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"
+)
+
+// App contains metadata for Apps
+type App struct {
+ Metadata AppMetaData `json:"metadata"`
+}
+
+//AppMetaData contains the parameters needed for Apps
+type AppMetaData struct {
+ Name string `json:"name"`
+ Description string `json:"description"`
+ UserData1 string `json:"userData1"`
+ UserData2 string `json:"userData2"`
+}
+
+//AppContent contains fileContent
+type AppContent struct {
+ FileContent string
+}
+
+// AppKey is the key structure that is used in the database
+type AppKey struct {
+ App string `json:"app"`
+ Project string `json:"project"`
+ CompositeApp string `json:"compositeapp"`
+ CompositeAppVersion string `json:"compositeappversion"`
+}
+
+// We will use json marshalling to convert to string to
+// preserve the underlying structure.
+func (aK AppKey) String() string {
+ out, err := json.Marshal(aK)
+ if err != nil {
+ return ""
+ }
+ return string(out)
+}
+
+// AppManager is an interface exposes the App functionality
+type AppManager interface {
+ CreateApp(a App, ac AppContent, p string, cN string, cV string) (App, error)
+ GetApp(name string, p string, cN string, cV string) (App, error)
+ GetAppContent(name string, p string, cN string, cV string) (AppContent, error)
+ GetApps(p string, cN string, cV string) ([]App, error)
+ DeleteApp(name string, p string, cN string, cV string) error
+}
+
+// AppClient implements the AppManager
+// It will also be used to maintain some localized state
+type AppClient struct {
+ storeName string
+ tagMeta, tagContent string
+}
+
+// NewAppClient returns an instance of the AppClient
+// which implements the AppManager
+func NewAppClient() *AppClient {
+ return &AppClient{
+ storeName: "orchestrator",
+ tagMeta: "appmetadata",
+ tagContent: "appcontent",
+ }
+}
+
+// CreateApp creates a new collection based on the App
+func (v *AppClient) CreateApp(a App, ac AppContent, p string, cN string, cV string) (App, error) {
+
+ //Construct the composite key to select the entry
+ key := AppKey{
+ App: a.Metadata.Name,
+ Project: p,
+ CompositeApp: cN,
+ CompositeAppVersion: cV,
+ }
+
+ //Check if this App already exists
+ _, err := v.GetApp(a.Metadata.Name, p, cN, cV)
+ if err == nil {
+ return App{}, pkgerrors.New("App already exists")
+ }
+
+ //Check if Project exists
+ _, err = NewProjectClient().GetProject(p)
+ if err != nil {
+ return App{}, pkgerrors.New("Unable to find the project")
+ }
+
+ //check if CompositeApp with version exists
+ _, err = NewCompositeAppClient().GetCompositeApp(cN, cV, p)
+ if err != nil {
+ return App{}, pkgerrors.New("Unable to find the composite app with version")
+ }
+
+ err = db.DBconn.Insert(v.storeName, key, nil, v.tagMeta, a)
+ if err != nil {
+ return App{}, pkgerrors.Wrap(err, "Creating DB Entry")
+ }
+
+ err = db.DBconn.Insert(v.storeName, key, nil, v.tagContent, ac)
+ if err != nil {
+ return App{}, pkgerrors.Wrap(err, "Creating DB Entry")
+ }
+
+ return a, nil
+}
+
+// GetApp returns the App for corresponding name
+func (v *AppClient) GetApp(name string, p string, cN string, cV string) (App, error) {
+
+ //Construct the composite key to select the entry
+ key := AppKey{
+ App: name,
+ Project: p,
+ CompositeApp: cN,
+ CompositeAppVersion: cV,
+ }
+ value, err := db.DBconn.Find(v.storeName, key, v.tagMeta)
+ if err != nil {
+ return App{}, pkgerrors.Wrap(err, "Get app")
+ }
+
+ //value is a byte array
+ if value != nil {
+ app := App{}
+ err = db.DBconn.Unmarshal(value[0], &app)
+ if err != nil {
+ return App{}, pkgerrors.Wrap(err, "Unmarshaling Value")
+ }
+ return app, nil
+ }
+
+ return App{}, pkgerrors.New("Error getting app")
+}
+
+// GetAppContent returns content for corresponding app
+func (v *AppClient) GetAppContent(name string, p string, cN string, cV string) (AppContent, error) {
+
+ //Construct the composite key to select the entry
+ key := AppKey{
+ App: name,
+ Project: p,
+ CompositeApp: cN,
+ CompositeAppVersion: cV,
+ }
+ value, err := db.DBconn.Find(v.storeName, key, v.tagContent)
+ if err != nil {
+ return AppContent{}, pkgerrors.Wrap(err, "Get app content")
+ }
+
+ //value is a byte array
+ if value != nil {
+ ac := AppContent{}
+ err = db.DBconn.Unmarshal(value[0], &ac)
+ if err != nil {
+ return AppContent{}, pkgerrors.Wrap(err, "Unmarshaling Value")
+ }
+ return ac, nil
+ }
+
+ return AppContent{}, pkgerrors.New("Error getting app content")
+}
+
+// GetApps returns all Apps for given composite App
+func (v *AppClient) GetApps(project, compositeApp, compositeAppVersion string) ([]App, error) {
+
+ key := AppKey{
+ App: "",
+ Project: project,
+ CompositeApp: compositeApp,
+ CompositeAppVersion: compositeAppVersion,
+ }
+
+ var resp []App
+ values, err := db.DBconn.Find(v.storeName, key, v.tagMeta)
+ if err != nil {
+ return []App{}, pkgerrors.Wrap(err, "Get Apps")
+ }
+
+ for _, value := range values {
+ a := App{}
+ err = db.DBconn.Unmarshal(value, &a)
+ if err != nil {
+ return []App{}, pkgerrors.Wrap(err, "Unmarshaling Value")
+ }
+ resp = append(resp, a)
+ }
+
+ return resp, nil
+}
+
+// DeleteApp deletes the App from database
+func (v *AppClient) DeleteApp(name string, p string, cN string, cV string) error {
+
+ //Construct the composite key to select the entry
+ key := AppKey{
+ App: name,
+ Project: p,
+ CompositeApp: cN,
+ CompositeAppVersion: cV,
+ }
+ err := db.DBconn.Remove(v.storeName, key)
+ if err != nil {
+ return pkgerrors.Wrap(err, "Delete App 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..a3f4b832
--- /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:"appintent"`
+ Project string `json:"project"`
+ CompositeApp string `json:"compositeapp"`
+ Version string `json:"compositeappversion"`
+ Intent string `json:"genericplacement"`
+}
+
+// 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: "appintentmetadata",
+ }
+}
+
+// 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..6cbdf15f
--- /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(): {
+ "genericplacementintentmetadata": []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(): {
+ "appintentmetadata": []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/app_test.go b/src/orchestrator/pkg/module/app_test.go
new file mode 100644
index 00000000..3bbbaf2f
--- /dev/null
+++ b/src/orchestrator/pkg/module/app_test.go
@@ -0,0 +1,327 @@
+/*
+ * 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"
+ pkgerrors "github.com/pkg/errors"
+ // pkgerrors "github.com/pkg/errors"
+)
+
+func TestCreateApp(t *testing.T) {
+ testCases := []struct {
+ label string
+ inpApp App
+ inpAppContent AppContent
+ inpProject string
+ inpCompositeAppName string
+ inpCompositeAppVersion string
+ expectedError string
+ mockdb *db.MockDB
+ expected App
+ }{
+ {
+ label: "Create App",
+ inpApp: App{
+ Metadata: AppMetaData{
+ Name: "testApp",
+ Description: "A sample app used for unit testing",
+ UserData1: "userData1",
+ UserData2: "userData2",
+ },
+ },
+
+ inpAppContent: AppContent{
+ FileContent: "Sample file content",
+ },
+ inpProject: "testProject",
+ inpCompositeAppName: "testCompositeApp",
+ inpCompositeAppVersion: "v1",
+ expected: App{
+ Metadata: AppMetaData{
+ Name: "testApp",
+ Description: "A sample app used for unit 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", Version: "v1", Project: "testProject"}.String(): {
+ "compositeappmetadata": []byte(
+ "{" +
+ "\"metadata\":{" +
+ "\"Name\":\"testCompositeApp\"," +
+ "\"Description\":\"Test CompositeApp 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
+ impl := NewAppClient()
+ got, err := impl.CreateApp(testCase.inpApp, testCase.inpAppContent, testCase.inpProject, testCase.inpCompositeAppName, testCase.inpCompositeAppVersion)
+ if err != nil {
+ if testCase.expectedError == "" {
+ t.Fatalf("Create returned an unexpected error %s", err)
+ }
+ if strings.Contains(err.Error(), testCase.expectedError) == false {
+ t.Fatalf("Create returned an unexpected error %s", err)
+ }
+ } else {
+ if reflect.DeepEqual(testCase.expected, got) == false {
+ t.Errorf("Create returned unexpected body: got %v;"+
+ " expected %v", got, testCase.expected)
+ }
+ }
+ })
+ }
+}
+
+func TestGetApp(t *testing.T) {
+
+ testCases := []struct {
+ label string
+ inpApp string
+ inpProject string
+ inpCompositeAppName string
+ inpCompositeAppVersion string
+ expectedError string
+ mockdb *db.MockDB
+ expected App
+ }{
+ {
+ label: "Get Composite App",
+ inpApp: "testApp",
+ inpProject: "testProject",
+ inpCompositeAppName: "testCompositeApp",
+ inpCompositeAppVersion: "v1",
+ expected: App{
+ Metadata: AppMetaData{
+ Name: "testApp",
+ Description: "Test App for unit testing",
+ UserData1: "userData1",
+ UserData2: "userData2",
+ },
+ },
+ expectedError: "",
+ mockdb: &db.MockDB{
+ Items: map[string]map[string][]byte{
+ AppKey{App: "testApp", Project: "testProject", CompositeApp: "testCompositeApp", CompositeAppVersion: "v1"}.String(): {
+ "appmetadata": []byte(
+ "{" +
+ "\"metadata\": {" +
+ "\"Name\": \"testApp\"," +
+ "\"Description\": \"Test App for unit testing\"," +
+ "\"UserData1\": \"userData1\"," +
+ "\"UserData2\": \"userData2\"}" +
+ "}"),
+ "appcontent": []byte(
+ "{" +
+ "\"FileContent\": \"sample file content\"" +
+ "}"),
+ },
+ },
+ },
+ },
+ {
+ label: "Get Error",
+ expectedError: "DB Error",
+ mockdb: &db.MockDB{
+ Err: pkgerrors.New("DB Error"),
+ },
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ db.DBconn = testCase.mockdb
+ impl := NewAppClient()
+ got, err := impl.GetApp(testCase.inpApp, testCase.inpProject, testCase.inpCompositeAppName, testCase.inpCompositeAppVersion)
+ if err != nil {
+ if testCase.expectedError == "" {
+ t.Fatalf("Get returned an unexpected error: %s", err)
+ }
+ if strings.Contains(err.Error(), testCase.expectedError) == false {
+ t.Fatalf("Get returned an unexpected error: %s", err)
+ }
+ } else {
+ if reflect.DeepEqual(testCase.expected, got) == false {
+ t.Errorf("Get returned unexpected body: got %v;"+
+ " expected %v", got, testCase.expected)
+ }
+ }
+ })
+ }
+}
+
+func TestGetAppContent(t *testing.T) {
+
+ testCases := []struct {
+ label string
+ inpApp string
+ inpProject string
+ inpCompositeAppName string
+ inpCompositeAppVersion string
+ expectedError string
+ mockdb *db.MockDB
+ expected AppContent
+ }{
+ {
+ label: "Get App content",
+ inpApp: "testApp",
+ inpProject: "testProject",
+ inpCompositeAppName: "testCompositeApp",
+ inpCompositeAppVersion: "v1",
+ expected: AppContent{
+ FileContent: "Samplefilecontent",
+ },
+ expectedError: "",
+ mockdb: &db.MockDB{
+ Items: map[string]map[string][]byte{
+ AppKey{App: "testApp", Project: "testProject", CompositeApp: "testCompositeApp", CompositeAppVersion: "v1"}.String(): {
+ "appmetadata": []byte(
+ "{" +
+ "\"metadata\": {" +
+ "\"Name\": \"testApp\"," +
+ "\"Description\": \"Test App for unit testing\"," +
+ "\"UserData1\": \"userData1\"," +
+ "\"UserData2\": \"userData2\"}" +
+ "}"),
+ "appcontent": []byte(
+ "{" +
+ "\"FileContent\": \"Samplefilecontent\"" +
+ "}"),
+ },
+ },
+ },
+ },
+ {
+ label: "Get Error",
+ expectedError: "DB Error",
+ mockdb: &db.MockDB{
+ Err: pkgerrors.New("DB Error"),
+ },
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ db.DBconn = testCase.mockdb
+ impl := NewAppClient()
+ got, err := impl.GetAppContent(testCase.inpApp, testCase.inpProject, testCase.inpCompositeAppName, testCase.inpCompositeAppVersion)
+ if err != nil {
+ if testCase.expectedError == "" {
+ t.Fatalf("Get returned an unexpected error: %s", err)
+ }
+ if strings.Contains(err.Error(), testCase.expectedError) == false {
+ t.Fatalf("Get returned an unexpected error: %s", err)
+ }
+ } else {
+ if reflect.DeepEqual(testCase.expected, got) == false {
+ t.Errorf("Get returned unexpected body: got %v;"+
+ " expected %v", got, testCase.expected)
+ }
+ }
+ })
+ }
+}
+
+func TestDeleteApp(t *testing.T) {
+
+ testCases := []struct {
+ label string
+ inpApp string
+ inpProject string
+ inpCompositeAppName string
+ inpCompositeAppVersion string
+ expectedError string
+ mockdb *db.MockDB
+ }{
+ {
+ label: "Delete App",
+ inpApp: "testApp",
+ inpProject: "testProject",
+ inpCompositeAppName: "testCompositeApp",
+ inpCompositeAppVersion: "v1",
+ mockdb: &db.MockDB{
+ Items: map[string]map[string][]byte{
+ AppKey{App: "testApp", Project: "testProject", CompositeApp: "testCompositeApp", CompositeAppVersion: "v1"}.String(): {
+ "appmetadata": []byte(
+ "{" +
+ "\"metadata\": {" +
+ "\"Name\": \"testApp\"," +
+ "\"Description\": \"Test App for unit testing\"," +
+ "\"UserData1\": \"userData1\"," +
+ "\"UserData2\": \"userData2\"}" +
+ "}"),
+ "appcontent": []byte(
+ "{" +
+ "\"FileContent\": \"Samplefilecontent\"" +
+ "}"),
+ },
+ },
+ },
+ },
+ {
+ label: "Delete Error",
+ expectedError: "DB Error",
+ mockdb: &db.MockDB{
+ Err: pkgerrors.New("DB Error"),
+ },
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ db.DBconn = testCase.mockdb
+ impl := NewAppClient()
+ err := impl.DeleteApp(testCase.inpApp, testCase.inpProject, testCase.inpCompositeAppName, testCase.inpCompositeAppVersion)
+ if err != nil {
+ if testCase.expectedError == "" {
+ t.Fatalf("Delete returned an unexpected error %s", err)
+ }
+ if strings.Contains(err.Error(), testCase.expectedError) == false {
+ t.Fatalf("Delete returned an unexpected error %s", err)
+ }
+ }
+ })
+ }
+}
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/compositeapp.go b/src/orchestrator/pkg/module/compositeapp.go
index 0a4e158c..70502367 100644
--- a/src/orchestrator/pkg/module/compositeapp.go
+++ b/src/orchestrator/pkg/module/compositeapp.go
@@ -45,8 +45,8 @@ type CompositeAppSpec struct {
// CompositeAppKey is the key structure that is used in the database
type CompositeAppKey struct {
- CompositeAppName string `json:"compositeappname"`
- Version string `json:"version"`
+ CompositeAppName string `json:"compositeapp"`
+ Version string `json:"compositeappversion"`
Project string `json:"project"`
}
@@ -70,8 +70,8 @@ type CompositeAppManager interface {
// CompositeAppClient implements the CompositeAppManager
// It will also be used to maintain some localized state
type CompositeAppClient struct {
- storeName string
- tagMeta, tagContent string
+ storeName string
+ tagMeta string
}
// NewCompositeAppClient returns an instance of the CompositeAppClient
@@ -79,7 +79,7 @@ type CompositeAppClient struct {
func NewCompositeAppClient() *CompositeAppClient {
return &CompositeAppClient{
storeName: "orchestrator",
- tagMeta: "compositeAppmetadata",
+ tagMeta: "compositeappmetadata",
}
}
@@ -105,7 +105,7 @@ func (v *CompositeAppClient) CreateCompositeApp(c CompositeApp, p string) (Compo
return CompositeApp{}, pkgerrors.New("Unable to find the project")
}
- err = db.DBconn.Create(v.storeName, key, v.tagMeta, c)
+ err = db.DBconn.Insert(v.storeName, key, nil, v.tagMeta, c)
if err != nil {
return CompositeApp{}, pkgerrors.Wrap(err, "Creating DB Entry")
}
@@ -122,7 +122,7 @@ func (v *CompositeAppClient) GetCompositeApp(name string, version string, p stri
Version: version,
Project: p,
}
- value, err := db.DBconn.Read(v.storeName, key, v.tagMeta)
+ value, err := db.DBconn.Find(v.storeName, key, v.tagMeta)
if err != nil {
return CompositeApp{}, pkgerrors.Wrap(err, "Get composite application")
}
@@ -130,7 +130,7 @@ func (v *CompositeAppClient) GetCompositeApp(name string, version string, p stri
//value is a byte array
if value != nil {
compApp := CompositeApp{}
- err = db.DBconn.Unmarshal(value, &compApp)
+ err = db.DBconn.Unmarshal(value[0], &compApp)
if err != nil {
return CompositeApp{}, pkgerrors.Wrap(err, "Unmarshaling Value")
}
@@ -149,7 +149,7 @@ func (v *CompositeAppClient) DeleteCompositeApp(name string, version string, p s
Version: version,
Project: p,
}
- err := db.DBconn.Delete(v.storeName, key, v.tagMeta)
+ err := db.DBconn.Remove(v.storeName, key)
if err != nil {
return pkgerrors.Wrap(err, "Delete CompositeApp Entry;")
}
diff --git a/src/orchestrator/pkg/module/compositeapp_test.go b/src/orchestrator/pkg/module/compositeapp_test.go
new file mode 100644
index 00000000..1fc5f5d2
--- /dev/null
+++ b/src/orchestrator/pkg/module/compositeapp_test.go
@@ -0,0 +1,236 @@
+/*
+ * 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"
+
+ pkgerrors "github.com/pkg/errors"
+)
+
+func TestCreateCompositeApp(t *testing.T) {
+ testCases := []struct {
+ label string
+ inpCompApp CompositeApp
+ inpProject string
+ expectedError string
+ mockdb *db.MockDB
+ expected CompositeApp
+ }{
+ {
+ label: "Create Composite App",
+ inpCompApp: CompositeApp{
+ Metadata: CompositeAppMetaData{
+ Name: "testCompositeApp",
+ Description: "A sample composite app used for unit testing",
+ UserData1: "userData1",
+ UserData2: "userData2",
+ },
+ Spec: CompositeAppSpec{
+ Version: "v1",
+ },
+ },
+
+ inpProject: "testProject",
+ expected: CompositeApp{
+ Metadata: CompositeAppMetaData{
+ Name: "testCompositeApp",
+ Description: "A sample composite app used for unit testing",
+ UserData1: "userData1",
+ UserData2: "userData2",
+ },
+ Spec: CompositeAppSpec{
+ Version: "v1",
+ },
+ },
+ 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\"}" +
+ "}"),
+ },
+ },
+ },
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ db.DBconn = testCase.mockdb
+ impl := NewCompositeAppClient()
+ got, err := impl.CreateCompositeApp(testCase.inpCompApp, testCase.inpProject)
+ if err != nil {
+ if testCase.expectedError == "" {
+ t.Fatalf("Create returned an unexpected error %s", err)
+ }
+ if strings.Contains(err.Error(), testCase.expectedError) == false {
+ t.Fatalf("Create returned an unexpected error %s", err)
+ }
+ } else {
+ if reflect.DeepEqual(testCase.expected, got) == false {
+ t.Errorf("Create returned unexpected body: got %v;"+
+ " expected %v", got, testCase.expected)
+ }
+ }
+ })
+ }
+}
+
+func TestGetCompositeApp(t *testing.T) {
+
+ testCases := []struct {
+ label string
+ inpName string
+ inpVersion string
+ inpProject string
+ expectedError string
+ mockdb *db.MockDB
+ expected CompositeApp
+ }{
+ {
+ label: "Get Composite App",
+ inpName: "testCompositeApp",
+ inpVersion: "v1",
+ inpProject: "testProject",
+ expected: CompositeApp{
+ Metadata: CompositeAppMetaData{
+ Name: "testCompositeApp",
+ Description: "Test CompositeApp for unit testing",
+ UserData1: "userData1",
+ UserData2: "userData2",
+ },
+ Spec: CompositeAppSpec{
+ Version: "v1",
+ },
+ },
+ expectedError: "",
+ mockdb: &db.MockDB{
+ Items: map[string]map[string][]byte{
+ CompositeAppKey{CompositeAppName: "testCompositeApp", Version: "v1", Project: "testProject"}.String(): {
+ "compositeappmetadata": []byte(
+ "{" +
+ "\"metadata\":{" +
+ "\"Name\":\"testCompositeApp\"," +
+ "\"Description\":\"Test CompositeApp for unit testing\"," +
+ "\"UserData1\":\"userData1\"," +
+ "\"UserData2\":\"userData2\"}," +
+ "\"spec\":{" +
+ "\"Version\":\"v1\"}" +
+ "}"),
+ },
+ },
+ },
+ },
+ {
+ label: "Get Error",
+ expectedError: "DB Error",
+ mockdb: &db.MockDB{
+ Err: pkgerrors.New("DB Error"),
+ },
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ db.DBconn = testCase.mockdb
+ impl := NewCompositeAppClient()
+ got, err := impl.GetCompositeApp(testCase.inpName, testCase.inpVersion, testCase.inpProject)
+ if err != nil {
+ if testCase.expectedError == "" {
+ t.Fatalf("Get returned an unexpected error: %s", err)
+ }
+ if strings.Contains(err.Error(), testCase.expectedError) == false {
+ t.Fatalf("Get returned an unexpected error: %s", err)
+ }
+ } else {
+ if reflect.DeepEqual(testCase.expected, got) == false {
+ t.Errorf("Get returned unexpected body: got %v;"+
+ " expected %v", got, testCase.expected)
+ }
+ }
+ })
+ }
+}
+
+func TestDeleteCompositeApp(t *testing.T) {
+
+ testCases := []struct {
+ label string
+ inpName string
+ inpVersion string
+ inpProject string
+ expectedError string
+ mockdb *db.MockDB
+ }{
+ {
+ label: "Delete Composite app",
+ inpName: "testCompositeApp",
+ inpVersion: "v1",
+ inpProject: "testProject",
+ mockdb: &db.MockDB{
+ Items: map[string]map[string][]byte{
+ CompositeAppKey{CompositeAppName: "testCompositeApp", Version: "v1", Project: "testProject"}.String(): {
+ "compositeappmetadata": []byte(
+ "{" +
+ "\"metadata\":{" +
+ "\"Name\":\"testCompositeApp\"," +
+ "\"Description\":\"Test CompositeApp for unit testing\"," +
+ "\"UserData1\":\"userData1\"," +
+ "\"UserData2\":\"userData2\"}," +
+ "\"spec\":{" +
+ "\"Version\":\"v1\"}" +
+ "}"),
+ },
+ },
+ },
+ },
+ {
+ label: "Delete Error",
+ expectedError: "DB Error",
+ mockdb: &db.MockDB{
+ Err: pkgerrors.New("DB Error"),
+ },
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ db.DBconn = testCase.mockdb
+ impl := NewCompositeAppClient()
+ err := impl.DeleteCompositeApp(testCase.inpName, testCase.inpVersion, testCase.inpProject)
+ if err != nil {
+ if testCase.expectedError == "" {
+ t.Fatalf("Delete returned an unexpected error %s", err)
+ }
+ if strings.Contains(err.Error(), testCase.expectedError) == false {
+ t.Fatalf("Delete returned an unexpected error %s", err)
+ }
+ }
+ })
+ }
+}
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..cfbf53e2
--- /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:"deploymentintentgroup"`
+ 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 (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: "deploymentintentgroupmetadata",
+ }
+}
+
+// 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..0fdeb4a1
--- /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(): {
+ "deploymentintentgroupmetadata": []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..73849474
--- /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:"genericplacement"`
+ 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 (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: "genericplacementintentmetadata",
+ }
+}
+
+// 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..d779e81f
--- /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(): {
+ "genericplacementintentmetadata": []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 a94a4207..c697bbff 100644
--- a/src/orchestrator/pkg/module/module.go
+++ b/src/orchestrator/pkg/module/module.go
@@ -18,9 +18,16 @@ package module
// Client for using the services in the orchestrator
type Client struct {
- Project *ProjectClient
- CompositeApp *CompositeAppClient
- Controller *ControllerClient
+ Project *ProjectClient
+ CompositeApp *CompositeAppClient
+ App *AppClient
+ Controller *ControllerClient
+ GenericPlacementIntent *GenericPlacementIntentClient
+ AppIntent *AppIntentClient
+ DeploymentIntentGroup *DeploymentIntentGroupClient
+ Intent *IntentClient
+ CompositeProfile *CompositeProfileClient
+ AppProfile *AppProfileClient
// Add Clients for API's here
}
@@ -29,7 +36,14 @@ func NewClient() *Client {
c := &Client{}
c.Project = NewProjectClient()
c.CompositeApp = NewCompositeAppClient()
+ c.App = NewAppClient()
c.Controller = NewControllerClient()
+ 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/pkg/module/project.go b/src/orchestrator/pkg/module/project.go
index a95251b5..a6f59254 100644
--- a/src/orchestrator/pkg/module/project.go
+++ b/src/orchestrator/pkg/module/project.go
@@ -90,7 +90,7 @@ func (v *ProjectClient) CreateProject(p Project) (Project, error) {
return Project{}, pkgerrors.New("Project already exists")
}
- err = db.DBconn.Create(v.storeName, key, v.tagMeta, p)
+ err = db.DBconn.Insert(v.storeName, key, nil, v.tagMeta, p)
if err != nil {
return Project{}, pkgerrors.Wrap(err, "Creating DB Entry")
}
@@ -105,7 +105,7 @@ func (v *ProjectClient) GetProject(name string) (Project, error) {
key := ProjectKey{
ProjectName: name,
}
- value, err := db.DBconn.Read(v.storeName, key, v.tagMeta)
+ value, err := db.DBconn.Find(v.storeName, key, v.tagMeta)
if err != nil {
return Project{}, pkgerrors.Wrap(err, "Get Project")
}
@@ -113,7 +113,7 @@ func (v *ProjectClient) GetProject(name string) (Project, error) {
//value is a byte array
if value != nil {
proj := Project{}
- err = db.DBconn.Unmarshal(value, &proj)
+ err = db.DBconn.Unmarshal(value[0], &proj)
if err != nil {
return Project{}, pkgerrors.Wrap(err, "Unmarshaling Value")
}
@@ -130,7 +130,7 @@ func (v *ProjectClient) DeleteProject(name string) error {
key := ProjectKey{
ProjectName: name,
}
- err := db.DBconn.Delete(v.storeName, key, v.tagMeta)
+ err := db.DBconn.Remove(v.storeName, key)
if err != nil {
return pkgerrors.Wrap(err, "Delete Project Entry;")
}
diff --git a/src/orchestrator/pkg/rtcontext/rtcontext.go b/src/orchestrator/pkg/rtcontext/rtcontext.go
new file mode 100644
index 00000000..e1f1c03b
--- /dev/null
+++ b/src/orchestrator/pkg/rtcontext/rtcontext.go
@@ -0,0 +1,238 @@
+/*
+ * 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 rtcontext
+
+import (
+ "fmt"
+ "math/rand"
+ "time"
+ "strings"
+ "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/contextdb"
+ pkgerrors "github.com/pkg/errors"
+)
+
+const maxrand = 0x7fffffffffffffff
+const prefix string = "/context/"
+
+type RunTimeContext struct {
+ cid interface{}
+}
+
+type Rtcontext interface {
+ RtcCreate() (interface{}, error)
+ RtcGet() (interface{}, error)
+ RtcAddLevel(handle interface{}, level string, value string) (interface{}, error)
+ RtcAddResource(handle interface{}, resname string, value interface{}) (interface{}, error)
+ RtcAddInstruction(handle interface{}, level string, insttype string, value interface{}) (interface{}, error)
+ RtcDeletePair(handle interface{}) (error)
+ RtcDeletePrefix(handle interface{}) (error)
+ RtcGetHandles(handle interface{}) ([]interface{}, error)
+ RtcGetValue(handle interface{}, value interface{}) (error)
+ RtcUpdateValue(handle interface{}, value interface{}) (error)
+}
+
+//Create context by assiging a new id
+func (rtc *RunTimeContext) RtcCreate() (interface{}, error) {
+
+ ra := rand.New(rand.NewSource(time.Now().UnixNano()))
+ rn := ra.Int63n(maxrand)
+ id := fmt.Sprintf("%v", rn)
+ cid := (prefix + id + "/")
+ rtc.cid = interface{}(cid)
+
+ err := contextdb.Db.Put(cid, id)
+ if err != nil {
+ return nil, pkgerrors.Errorf("Error creating run time context: %s", err.Error())
+ }
+
+ return rtc.cid, nil
+}
+
+//Get the root handle
+func (rtc *RunTimeContext) RtcGet() (interface{}, error) {
+ str := fmt.Sprintf("%v", rtc.cid)
+ if !strings.HasPrefix(str, prefix) {
+ return nil, pkgerrors.Errorf("Not a valid run time context")
+ }
+
+ var value string
+ err := contextdb.Db.Get(str, &value)
+ if err != nil {
+ return nil, pkgerrors.Errorf("Error getting run time context metadata: %s", err.Error())
+ }
+ if !strings.Contains(str, value) {
+ return nil, pkgerrors.Errorf("Error matching run time context metadata")
+ }
+
+ return rtc.cid, nil
+}
+
+//Add a new level at a given handle and return the new handle
+func (rtc *RunTimeContext) RtcAddLevel(handle interface{}, level string, value string) (interface{}, error) {
+ str := fmt.Sprintf("%v", handle)
+ sid := fmt.Sprintf("%v", rtc.cid)
+ if !strings.HasPrefix(str, sid) {
+ return nil, pkgerrors.Errorf("Not a valid run time context handle")
+ }
+
+ if level == "" {
+ return nil, pkgerrors.Errorf("Not a valid run time context level")
+ }
+ if value == "" {
+ return nil, pkgerrors.Errorf("Not a valid run time context level value")
+ }
+
+ key := str + level + "/" + value + "/"
+ err := contextdb.Db.Put(key, value)
+ if err != nil {
+ return nil, pkgerrors.Errorf("Error adding run time context level: %s", err.Error())
+ }
+
+ return (interface{})(key), nil
+}
+
+// Add a resource under the given level and return new handle
+func (rtc *RunTimeContext) RtcAddResource(handle interface{}, resname string, value interface{}) (interface{}, error) {
+
+ str := fmt.Sprintf("%v", handle)
+ sid := fmt.Sprintf("%v", rtc.cid)
+ if !strings.HasPrefix(str, sid) {
+ return nil, pkgerrors.Errorf("Not a valid run time context handle")
+ }
+ if resname == "" {
+ return nil, pkgerrors.Errorf("Not a valid run time context resource name")
+ }
+ if value == nil {
+ return nil, pkgerrors.Errorf("Not a valid run time context resource value")
+ }
+
+ k := str + "resource" + "/" + resname + "/"
+ err := contextdb.Db.Put(k, value)
+ if err != nil {
+ return nil, pkgerrors.Errorf("Error adding run time context resource: %s", err.Error())
+ }
+ return (interface{})(k), nil
+}
+
+// Add instruction at a given level and type, return the new handle
+func (rtc *RunTimeContext) RtcAddInstruction(handle interface{}, level string, insttype string, value interface{}) (interface{}, error) {
+ str := fmt.Sprintf("%v", handle)
+ sid := fmt.Sprintf("%v", rtc.cid)
+ if !strings.HasPrefix(str, sid) {
+ return nil, pkgerrors.Errorf("Not a valid run time context handle")
+ }
+
+ if level == "" {
+ return nil, pkgerrors.Errorf("Not a valid run time context level")
+ }
+ if insttype == "" {
+ return nil, pkgerrors.Errorf("Not a valid run time context instruction type")
+ }
+ if value == nil {
+ return nil, pkgerrors.Errorf("Not a valid run time context instruction value")
+ }
+
+ k := str + level + "/" + "instruction" + "/" + insttype +"/"
+ err := contextdb.Db.Put(k, fmt.Sprintf("%v", value))
+ if err != nil {
+ return nil, pkgerrors.Errorf("Error adding run time context instruction: %s", err.Error())
+ }
+
+ return (interface{})(k), nil
+}
+
+//Delete the key value pair using given handle
+func (rtc *RunTimeContext) RtcDeletePair(handle interface{}) (error) {
+ str := fmt.Sprintf("%v", handle)
+ sid := fmt.Sprintf("%v", rtc.cid)
+ if !strings.HasPrefix(str, sid) {
+ return pkgerrors.Errorf("Not a valid run time context handle")
+ }
+
+ err := contextdb.Db.Delete(str)
+ if err != nil {
+ return pkgerrors.Errorf("Error deleting run time context pair: %s", err.Error())
+ }
+
+ return nil
+}
+
+// Delete all handles underneath the given handle
+func (rtc *RunTimeContext) RtcDeletePrefix(handle interface{}) (error) {
+ str := fmt.Sprintf("%v", handle)
+ sid := fmt.Sprintf("%v", rtc.cid)
+ if !strings.HasPrefix(str, sid) {
+ return pkgerrors.Errorf("Not a valid run time context handle")
+ }
+
+ err := contextdb.Db.DeleteAll(str)
+ if err != nil {
+ return pkgerrors.Errorf("Error deleting run time context with prefix: %s", err.Error())
+ }
+
+ return nil
+}
+
+// Return the list of handles under the given handle
+func (rtc *RunTimeContext) RtcGetHandles(handle interface{}) ([]interface{}, error) {
+ str := fmt.Sprintf("%v", handle)
+ sid := fmt.Sprintf("%v", rtc.cid)
+ if !strings.HasPrefix(str, sid) {
+ return nil, pkgerrors.Errorf("Not a valid run time context handle")
+ }
+
+ s, err := contextdb.Db.GetAllKeys(str)
+ if err != nil {
+ return nil, pkgerrors.Errorf("Error getting run time context handles: %s", err.Error())
+ }
+ r := make([]interface{}, len(s))
+ for i, v := range s {
+ r[i] = v
+ }
+ return r, nil
+}
+
+// Get the value for a given handle
+func (rtc *RunTimeContext) RtcGetValue(handle interface{}, value interface{}) (error) {
+ str := fmt.Sprintf("%v", handle)
+ sid := fmt.Sprintf("%v", rtc.cid)
+ if !strings.HasPrefix(str, sid) {
+ return pkgerrors.Errorf("Not a valid run time context handle")
+ }
+
+ err := contextdb.Db.Get(str, value)
+ if err != nil {
+ return pkgerrors.Errorf("Error getting run time context value: %s", err.Error())
+ }
+
+ return nil
+}
+
+// Update the value of a given handle
+func (rtc *RunTimeContext) RtcUpdateValue(handle interface{}, value interface{}) (error) {
+ str := fmt.Sprintf("%v", handle)
+ sid := fmt.Sprintf("%v", rtc.cid)
+ if !strings.HasPrefix(str, sid) {
+ return pkgerrors.Errorf("Not a valid run time context handle")
+ }
+ err := contextdb.Db.Put(str, value)
+ if err != nil {
+ return pkgerrors.Errorf("Error updating run time context value: %s", err.Error())
+ }
+ return nil
+
+}
diff --git a/src/orchestrator/pkg/rtcontext/rtcontext_test.go b/src/orchestrator/pkg/rtcontext/rtcontext_test.go
new file mode 100644
index 00000000..29df9a25
--- /dev/null
+++ b/src/orchestrator/pkg/rtcontext/rtcontext_test.go
@@ -0,0 +1,596 @@
+/*
+ * 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 rtcontext
+
+import (
+ "testing"
+ "strings"
+ "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/contextdb"
+ pkgerrors "github.com/pkg/errors"
+)
+
+// MockContextDb for mocking contextdb
+type MockContextDb struct {
+
+ Items map[string]interface{}
+ Err error
+}
+
+// Put function
+func (c *MockContextDb) Put(key string, val interface{}) (error) {
+ if c.Items == nil {
+ c.Items = make(map[string]interface{})
+ }
+ c.Items[key] = val
+ return c.Err
+}
+
+// Get function
+func (c *MockContextDb) Get(key string, val interface{}) (error) {
+ var s *string
+ s = val.(*string)
+ for kvKey, kvValue := range c.Items {
+ if kvKey == key {
+ *s = kvValue.(string)
+ return c.Err
+ }
+ }
+ return c.Err
+}
+
+// Delete function
+func (c *MockContextDb) Delete(key string) (error) {
+ delete(c.Items, key)
+ return c.Err
+}
+
+// Delete all function
+func (c *MockContextDb) DeleteAll(key string) (error) {
+ for kvKey, _ := range c.Items {
+ delete(c.Items, kvKey)
+ }
+ return c.Err
+}
+
+// GetAllKeys function
+func (c *MockContextDb) GetAllKeys(path string) ([]string, error) {
+ var keys []string
+
+ for k, _ := range c.Items {
+ keys = append(keys, string(k))
+ }
+ return keys, c.Err
+}
+
+func (c *MockContextDb) HealthCheck() error {
+ return nil
+}
+
+func TestRtcCreate(t *testing.T) {
+ var rtc = RunTimeContext{}
+ testCases := []struct {
+ label string
+ mockContextDb *MockContextDb
+ expectedError string
+ }{
+ {
+ label: "Success case",
+ mockContextDb: &MockContextDb{},
+ },
+ {
+ label: "Create returns error case",
+ mockContextDb: &MockContextDb{Err: pkgerrors.Errorf("Client not intialized")},
+ expectedError: "Error creating run time context:",
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ contextdb.Db = testCase.mockContextDb
+ _, err := rtc.RtcCreate()
+ if err != nil {
+ if !strings.Contains(string(err.Error()), testCase.expectedError) {
+ t.Fatalf("Method returned an error (%s)", err)
+ }
+ }
+
+ })
+ }
+}
+
+func TestRtcGet(t *testing.T) {
+ var rtc = RunTimeContext{}
+ var rtc1 = RunTimeContext{"/context/5345674458787728/"}
+ testCases := []struct {
+ label string
+ mockContextDb *MockContextDb
+ expectedError string
+ }{
+ {
+ label: "Success case",
+ mockContextDb: &MockContextDb{},
+ },
+ {
+ label: "Get returns error case",
+ mockContextDb: &MockContextDb{Err: pkgerrors.Errorf("Client not intialized")},
+ expectedError: "Error getting run time context metadata:",
+ },
+ {
+ label: "Context handle does not match",
+ mockContextDb: &MockContextDb{Err: nil},
+ expectedError: "Error matching run time context metadata",
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ switch testCase.label {
+ case "Success case":
+ contextdb.Db = testCase.mockContextDb
+ chandle, err := rtc.RtcCreate()
+ if err != nil {
+ t.Fatalf("Create returned an error (%s)", err)
+ }
+ ghandle, err := rtc.RtcGet()
+ if err != nil {
+ t.Fatalf("Get returned an error (%s)", err)
+ }
+ if ( chandle != ghandle ) {
+ t.Fatalf("Create and Get does not match")
+ }
+ case "Get returns error case":
+ contextdb.Db = testCase.mockContextDb
+ _, err := rtc.RtcGet()
+ if err != nil {
+ if !strings.Contains(string(err.Error()), testCase.expectedError) {
+ t.Fatalf("Method returned an error (%s)", err)
+ }
+ }
+ case "Context handle does not match":
+ contextdb.Db = testCase.mockContextDb
+ contextdb.Db.Put("/context/5345674458787728/", "6345674458787728")
+ _, err := rtc1.RtcGet()
+ if err != nil {
+ if !strings.Contains(string(err.Error()), testCase.expectedError) {
+ t.Fatalf("Method returned an error (%s)", err)
+ }
+ }
+ }
+ })
+ }
+}
+
+func TestRtcAddLevel(t *testing.T) {
+ var rtc = RunTimeContext{"/context/3528435435454354/"}
+ testCases := []struct {
+ label string
+ mockContextDb *MockContextDb
+ handle interface{}
+ level string
+ value string
+ expectedError string
+ }{
+ {
+ label: "Success case",
+ mockContextDb: &MockContextDb{},
+ handle: "/context/3528435435454354/",
+ level: "app",
+ value: "testapp1",
+ },
+ {
+ label: "Not a valid rtc handle",
+ mockContextDb: &MockContextDb{},
+ handle: "/context/9528435435454354/",
+ level: "app",
+ value: "testapp1",
+ expectedError: "Not a valid run time context handle",
+ },
+ {
+ label: "Not a valid rtc level",
+ mockContextDb: &MockContextDb{},
+ handle: "/context/3528435435454354/",
+ level: "",
+ value: "testapp1",
+ expectedError: "Not a valid run time context level",
+ },
+ {
+ label: "Not a valid rtc value",
+ mockContextDb: &MockContextDb{},
+ handle: "/context/3528435435454354/",
+ level: "app",
+ value: "",
+ expectedError: "Not a valid run time context level value",
+ },
+ {
+ label: "Put returns error",
+ mockContextDb: &MockContextDb{Err: pkgerrors.Errorf("Client not intialized")},
+ handle: "/context/3528435435454354/",
+ level: "app",
+ value: "testapp1",
+ expectedError: "Error adding run time context level:",
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ contextdb.Db = testCase.mockContextDb
+ _, err := rtc.RtcAddLevel(testCase.handle, testCase.level, testCase.value)
+ if err != nil {
+ if !strings.Contains(string(err.Error()), testCase.expectedError) {
+ t.Fatalf("Method returned an error (%s)", err)
+ }
+ }
+ })
+ }
+}
+
+func TestRtcAddResource(t *testing.T) {
+ var rtc = RunTimeContext{"/context/3528435435454354/"}
+ testCases := []struct {
+ label string
+ mockContextDb *MockContextDb
+ handle interface{}
+ resname string
+ value interface{}
+ expectedError string
+ }{
+ {
+ label: "Success case",
+ mockContextDb: &MockContextDb{},
+ handle: "/context/3528435435454354/app/apptest1/cluster/cluster1/",
+ resname: "R1",
+ value: "res1",
+ },
+ {
+ label: "Not a valid rtc handle",
+ mockContextDb: &MockContextDb{},
+ handle: "/context/9528435435454354/app/apptest1/cluster/cluster1/",
+ resname: "R1",
+ value: "res1",
+ expectedError: "Not a valid run time context handle",
+ },
+ {
+ label: "Not a valid rtc resource name",
+ mockContextDb: &MockContextDb{},
+ handle: "/context/3528435435454354/app/apptest1/cluster/cluster1/",
+ resname: "",
+ value: "res1",
+ expectedError: "Not a valid run time context resource name",
+ },
+ {
+ label: "Not a valid rtc value",
+ mockContextDb: &MockContextDb{},
+ handle: "/context/3528435435454354/app/apptest1/cluster/cluster1/",
+ resname: "R1",
+ value: nil,
+ expectedError: "Not a valid run time context resource value",
+ },
+ {
+ label: "Put returns error",
+ mockContextDb: &MockContextDb{Err: pkgerrors.Errorf("Client not intialized")},
+ handle: "/context/3528435435454354/app/apptest1/cluster/cluster1/",
+ resname: "R1",
+ value: "res1",
+ expectedError: "Error adding run time context resource:",
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ contextdb.Db = testCase.mockContextDb
+ _, err := rtc.RtcAddResource(testCase.handle, testCase.resname, testCase.value)
+ if err != nil {
+ if !strings.Contains(string(err.Error()), testCase.expectedError) {
+ t.Fatalf("Method returned an error (%s)", err)
+ }
+ }
+ })
+ }
+}
+
+func TestRtcAddInstruction(t *testing.T) {
+ var rtc = RunTimeContext{"/context/3528435435454354/"}
+ testCases := []struct {
+ label string
+ mockContextDb *MockContextDb
+ handle interface{}
+ level string
+ insttype string
+ value interface{}
+ expectedError string
+ }{
+ {
+ label: "Success case",
+ mockContextDb: &MockContextDb{},
+ handle: "/context/3528435435454354/app/apptest1/cluster/cluster1/",
+ level: "resource",
+ insttype: "order",
+ value: "{resorder: [R3, R1, R2]}",
+ },
+ {
+ label: "Not a valid rtc handle",
+ mockContextDb: &MockContextDb{},
+ handle: "/context/9528435435454354/app/apptest1/cluster/cluster1/",
+ level: "resource",
+ insttype: "order",
+ value: "{resorder: [R3, R1, R2]}",
+ expectedError: "Not a valid run time context handle",
+ },
+ {
+ label: "Not a valid rtc level name",
+ mockContextDb: &MockContextDb{},
+ handle: "/context/3528435435454354/app/apptest1/cluster/cluster1/",
+ level: "",
+ insttype: "order",
+ value: "{resorder: [R3, R1, R2]}",
+ expectedError: "Not a valid run time context level",
+ },
+ {
+ label: "Not a valid rtc instruction type",
+ mockContextDb: &MockContextDb{},
+ handle: "/context/3528435435454354/app/apptest1/cluster/cluster1/",
+ level: "resource",
+ insttype: "",
+ value: "{resorder: [R3, R1, R2]}",
+ expectedError: "Not a valid run time context instruction type",
+ },
+ {
+ label: "Not a valid rtc value",
+ mockContextDb: &MockContextDb{},
+ handle: "/context/3528435435454354/app/apptest1/cluster/cluster1/",
+ level: "resource",
+ insttype: "order",
+ value: nil,
+ expectedError: "Not a valid run time context instruction value",
+ },
+ {
+ label: "Put returns error",
+ mockContextDb: &MockContextDb{Err: pkgerrors.Errorf("Client not intialized")},
+ handle: "/context/3528435435454354/app/apptest1/cluster/cluster1/",
+ level: "resource",
+ insttype: "order",
+ value: "{resorder: [R3, R1, R2]}",
+ expectedError: "Error adding run time context instruction:",
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ contextdb.Db = testCase.mockContextDb
+ _, err := rtc.RtcAddInstruction(testCase.handle, testCase.level, testCase.insttype, testCase.value)
+ if err != nil {
+ if !strings.Contains(string(err.Error()), testCase.expectedError) {
+ t.Fatalf("Method returned an error (%s)", err)
+ }
+ }
+ })
+ }
+}
+
+func TestRtcGetHandles(t *testing.T) {
+ var rtc = RunTimeContext{"/context/5345674458787728/"}
+ testCases := []struct {
+ label string
+ mockContextDb *MockContextDb
+ key interface{}
+ expectedError string
+ }{
+ {
+ label: "Not valid input handle case",
+ mockContextDb: &MockContextDb{},
+ key: "/context/3528435435454354/",
+ expectedError: "Not a valid run time context handle",
+ },
+ {
+ label: "Contextdb call returns error case",
+ mockContextDb: &MockContextDb{Err: pkgerrors.Errorf("Key does not exist")},
+ key: "/context/5345674458787728/",
+ expectedError: "Error getting run time context handles:",
+ },
+ {
+ label: "Success case",
+ mockContextDb: &MockContextDb{},
+ key: "/context/5345674458787728/",
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ contextdb.Db = testCase.mockContextDb
+ if testCase.label == "Success case" {
+ contextdb.Db.Put("/context/5345674458787728/", 5345674458787728)
+ }
+ _, err := rtc.RtcGetHandles(testCase.key)
+ if err != nil {
+ if !strings.Contains(string(err.Error()), testCase.expectedError) {
+ t.Fatalf("Method returned an error (%s)", err)
+ }
+ }
+ })
+ }
+}
+
+func TestRtcGetValue(t *testing.T) {
+ var rtc = RunTimeContext{"/context/5345674458787728/"}
+ testCases := []struct {
+ label string
+ mockContextDb *MockContextDb
+ key interface{}
+ expectedError string
+ }{
+ {
+ label: "Not valid input handle case",
+ mockContextDb: &MockContextDb{},
+ key: "/context/3528435435454354/",
+ expectedError: "Not a valid run time context handle",
+ },
+ {
+ label: "Contextdb call returns error case",
+ mockContextDb: &MockContextDb{Err: pkgerrors.Errorf("Key does not exist")},
+ key: "/context/5345674458787728/",
+ expectedError: "Error getting run time context value:",
+ },
+ {
+ label: "Success case",
+ mockContextDb: &MockContextDb{},
+ key: "/context/5345674458787728/",
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ contextdb.Db = testCase.mockContextDb
+ if testCase.label == "Success case" {
+ contextdb.Db.Put("/context/5345674458787728/", "5345674458787728")
+ }
+ var val string
+ err := rtc.RtcGetValue(testCase.key, &val)
+ if err != nil {
+ if !strings.Contains(string(err.Error()), testCase.expectedError) {
+ t.Fatalf("Method returned an error (%s)", err)
+ }
+ }
+ })
+ }
+}
+
+func TestRtcUpdateValue(t *testing.T) {
+ var rtc = RunTimeContext{"/context/5345674458787728/"}
+ testCases := []struct {
+ label string
+ mockContextDb *MockContextDb
+ key interface{}
+ value interface{}
+ expectedError string
+ }{
+ {
+ label: "Not valid input handle case",
+ mockContextDb: &MockContextDb{},
+ key: "/context/3528435435454354/",
+ value: "{apporder: [app1, app2, app3]}",
+ expectedError: "Not a valid run time context handle",
+ },
+ {
+ label: "Contextdb call returns error case",
+ mockContextDb: &MockContextDb{Err: pkgerrors.Errorf("Key does not exist")},
+ key: "/context/5345674458787728/",
+ value: "{apporder: [app1, app2, app3]}",
+ expectedError: "Error updating run time context value:",
+ },
+ {
+ label: "Success case",
+ mockContextDb: &MockContextDb{},
+ key: "/context/5345674458787728/",
+ value: "{apporder: [app2, app3, app1]}",
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ contextdb.Db = testCase.mockContextDb
+ if testCase.label == "Success case" {
+ contextdb.Db.Put("/context/5345674458787728/", "5345674458787728")
+ }
+ err := rtc.RtcUpdateValue(testCase.key, testCase.value)
+ if err != nil {
+ if !strings.Contains(string(err.Error()), testCase.expectedError) {
+ t.Fatalf("Method returned an error (%s)", err)
+ }
+ }
+ })
+ }
+}
+
+func TestRtcDeletePair(t *testing.T) {
+ var rtc = RunTimeContext{"/context/5345674458787728/"}
+ testCases := []struct {
+ label string
+ mockContextDb *MockContextDb
+ key interface{}
+ expectedError string
+ }{
+ {
+ label: "Not valid input handle case",
+ mockContextDb: &MockContextDb{},
+ key: "/context/3528435435454354/",
+ expectedError: "Not a valid run time context handle",
+ },
+ {
+ label: "Contextdb call returns error case",
+ mockContextDb: &MockContextDb{Err: pkgerrors.Errorf("Key does not exist")},
+ key: "/context/5345674458787728/",
+ expectedError: "Error deleting run time context pair:",
+ },
+ {
+ label: "Success case",
+ mockContextDb: &MockContextDb{},
+ key: "/context/5345674458787728/",
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ contextdb.Db = testCase.mockContextDb
+ err := rtc.RtcDeletePair(testCase.key)
+ if err != nil {
+ if !strings.Contains(string(err.Error()), testCase.expectedError) {
+ t.Fatalf("Method returned an error (%s)", err)
+ }
+ }
+ })
+ }
+}
+
+func TestRtcDeletePrefix(t *testing.T) {
+ var rtc = RunTimeContext{"/context/5345674458787728/"}
+ testCases := []struct {
+ label string
+ mockContextDb *MockContextDb
+ key interface{}
+ expectedError string
+ }{
+ {
+ label: "Not valid input handle case",
+ mockContextDb: &MockContextDb{},
+ key: "/context/3528435435454354/",
+ expectedError: "Not a valid run time context handle",
+ },
+ {
+ label: "Contextdb call returns error case",
+ mockContextDb: &MockContextDb{Err: pkgerrors.Errorf("Key does not exist")},
+ key: "/context/5345674458787728/",
+ expectedError: "Error deleting run time context with prefix:",
+ },
+ {
+ label: "Success case",
+ mockContextDb: &MockContextDb{},
+ key: "/context/5345674458787728/",
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ contextdb.Db = testCase.mockContextDb
+ err := rtc.RtcDeletePrefix(testCase.key)
+ if err != nil {
+ if !strings.Contains(string(err.Error()), testCase.expectedError) {
+ t.Fatalf("Method returned an error (%s)", err)
+ }
+ }
+ })
+ }
+}