aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorKiran Kamineni <kiran.k.kamineni@intel.com>2019-03-28 12:53:13 -0700
committerKiran Kamineni <kiran.k.kamineni@intel.com>2019-04-03 15:31:31 -0700
commitf74ad24c6034294c1915f5778adb259dd65c9208 (patch)
tree03cf8ba86d1f966b9a342b325bd2fb36bf1dcc59 /src
parente97d3fddc255b73f87a8b3dbd8fa505e34247504 (diff)
Refactor instance code
Issue-ID: MULTICLOUD-350 Change-Id: I2574d94e4ebada1e138913b2a03549dd90906d7b Signed-off-by: Kiran Kamineni <kiran.k.kamineni@intel.com>
Diffstat (limited to 'src')
-rw-r--r--src/k8splugin/api/api.go28
-rw-r--r--src/k8splugin/api/handler.go379
-rw-r--r--src/k8splugin/api/handler_test.go710
-rw-r--r--src/k8splugin/api/instancehandler.go191
-rw-r--r--src/k8splugin/api/instancehandler_test.go325
-rw-r--r--src/k8splugin/cmd/main.go20
-rw-r--r--src/k8splugin/internal/app/client.go176
-rw-r--r--src/k8splugin/internal/app/client_test.go92
-rw-r--r--src/k8splugin/internal/app/instance.go206
-rw-r--r--src/k8splugin/internal/app/instance_test.go (renamed from src/k8splugin/internal/app/vnfhelper_test.go)232
-rw-r--r--src/k8splugin/internal/app/vnfhelper.go195
-rw-r--r--src/k8splugin/mock_files/mock_plugins/mockplugin.go4
-rw-r--r--src/k8splugin/plugins/deployment/plugin.go2
-rw-r--r--src/k8splugin/plugins/deployment/plugin_test.go4
-rw-r--r--src/k8splugin/plugins/service/plugin.go1
-rw-r--r--src/k8splugin/plugins/service/plugin_test.go4
16 files changed, 1157 insertions, 1412 deletions
diff --git a/src/k8splugin/api/api.go b/src/k8splugin/api/api.go
index 2862a999..54147d2e 100644
--- a/src/k8splugin/api/api.go
+++ b/src/k8splugin/api/api.go
@@ -14,21 +14,30 @@ limitations under the License.
package api
import (
+ "k8splugin/internal/app"
"k8splugin/internal/rb"
"github.com/gorilla/mux"
)
-// NewRouter creates a router instance that serves the VNFInstance web methods
-func NewRouter(kubeconfig string, defClient rb.DefinitionManager,
- profileClient rb.ProfileManager) *mux.Router {
+// NewRouter creates a router that registers the various urls that are supported
+func NewRouter(defClient rb.DefinitionManager,
+ profileClient rb.ProfileManager,
+ instClient app.InstanceManager) *mux.Router {
+
router := mux.NewRouter()
- vnfInstanceHandler := router.PathPrefix("/v1/vnf_instances").Subrouter()
- vnfInstanceHandler.HandleFunc("/", CreateHandler).Methods("POST").Name("VNFCreation")
- vnfInstanceHandler.HandleFunc("/{cloudRegionID}/{namespace}", ListHandler).Methods("GET")
- vnfInstanceHandler.HandleFunc("/{cloudRegionID}/{namespace}/{externalVNFID}", DeleteHandler).Methods("DELETE")
- vnfInstanceHandler.HandleFunc("/{cloudRegionID}/{namespace}/{externalVNFID}", GetHandler).Methods("GET")
+ // Setup Instance handler routes
+ if instClient == nil {
+ instClient = app.NewInstanceClient()
+ }
+ instHandler := instanceHandler{client: instClient}
+ instRouter := router.PathPrefix("/v1").Subrouter()
+ instRouter.HandleFunc("/instance", instHandler.createHandler).Methods("POST")
+ instRouter.HandleFunc("/instance/{instID}", instHandler.getHandler).Methods("GET")
+ instRouter.HandleFunc("/instance/{instID}", instHandler.deleteHandler).Methods("DELETE")
+ // (TODO): Fix update method
+ // instRouter.HandleFunc("/{vnfInstanceId}", UpdateHandler).Methods("PUT")
//Setup resource bundle definition routes
if defClient == nil {
@@ -52,8 +61,5 @@ func NewRouter(kubeconfig string, defClient rb.DefinitionManager,
resRouter.HandleFunc("/definition/{rbname}/{rbversion}/profile/{prname}", profileHandler.getHandler).Methods("GET")
resRouter.HandleFunc("/definition/{rbname}/{rbversion}/profile/{prname}", profileHandler.deleteHandler).Methods("DELETE")
- // (TODO): Fix update method
- // vnfInstanceHandler.HandleFunc("/{vnfInstanceId}", UpdateHandler).Methods("PUT")
-
return router
}
diff --git a/src/k8splugin/api/handler.go b/src/k8splugin/api/handler.go
deleted file mode 100644
index 31ffad90..00000000
--- a/src/k8splugin/api/handler.go
+++ /dev/null
@@ -1,379 +0,0 @@
-/*
-Copyright 2018 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 api
-
-import (
- "encoding/json"
- "errors"
- "io"
- "log"
- "net/http"
- "os"
- "strings"
-
- "github.com/gorilla/mux"
- pkgerrors "github.com/pkg/errors"
- "k8s.io/client-go/kubernetes"
-
- helper "k8splugin/internal/app"
- "k8splugin/internal/db"
- "k8splugin/internal/rb"
-)
-
-//TODO: Separate the http handler code and backend code out
-var storeName = "rbinst"
-var tagData = "data"
-
-type instanceKey struct {
- Key string
-}
-
-func (dk instanceKey) String() string {
- return dk.Key
-}
-
-// GetVNFClient retrieves the client used to communicate with a Kubernetes Cluster
-var GetVNFClient = func(kubeConfigPath string) (kubernetes.Clientset, error) {
- client, err := helper.GetKubeClient(kubeConfigPath)
- if err != nil {
- return client, err
- }
- return client, nil
-}
-
-func validateBody(body interface{}) error {
- switch b := body.(type) {
- case CreateVnfRequest:
- if b.CloudRegionID == "" {
- werr := pkgerrors.Wrap(errors.New("Invalid/Missing CloudRegionID in POST request"), "CreateVnfRequest bad request")
- return werr
- }
- if b.CsarID == "" {
- werr := pkgerrors.Wrap(errors.New("Invalid/Missing CsarID in POST request"), "CreateVnfRequest bad request")
- return werr
- }
- if b.RBName == "" || b.RBVersion == "" {
- werr := pkgerrors.Wrap(errors.New("Invalid/Missing resource bundle parameters in POST request"), "CreateVnfRequest bad request")
- return werr
- }
- if b.ProfileName == "" {
- werr := pkgerrors.Wrap(errors.New("Invalid/Missing profile name in POST request"), "CreateVnfRequest bad request")
- return werr
- }
- if strings.Contains(b.CloudRegionID, "|") {
- werr := pkgerrors.Wrap(errors.New("Character \"|\" not allowed in CSAR ID"), "CreateVnfRequest bad request")
- return werr
- }
- case UpdateVnfRequest:
- if b.CloudRegionID == "" || b.CsarID == "" {
- werr := pkgerrors.Wrap(errors.New("Invalid/Missing Data in PUT request"), "UpdateVnfRequest bad request")
- return werr
- }
- }
- return nil
-}
-
-// CreateHandler is the POST method creates a new VNF instance resource.
-func CreateHandler(w http.ResponseWriter, r *http.Request) {
- var resource CreateVnfRequest
-
- err := json.NewDecoder(r.Body).Decode(&resource)
- switch {
- case err == io.EOF:
- http.Error(w, "Body empty", http.StatusBadRequest)
- return
- case err != nil:
- http.Error(w, err.Error(), http.StatusUnprocessableEntity)
- return
- }
-
- err = validateBody(resource)
- if err != nil {
- http.Error(w, err.Error(), http.StatusUnprocessableEntity)
- return
- }
-
- // (TODO): Read kubeconfig for specific Cloud Region from local file system
- // if present or download it from AAI
- // err := DownloadKubeConfigFromAAI(resource.CloudRegionID, os.Getenv("KUBE_CONFIG_DIR")
- kubeclient, err := GetVNFClient(os.Getenv("KUBE_CONFIG_DIR") + "/" + resource.CloudRegionID)
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
-
- /*
- uuid,
- {
- "deployment": ["cloud1-default-uuid-sisedeploy1", "cloud1-default-uuid-sisedeploy2", ... ]
- "service": ["cloud1-default-uuid-sisesvc1", "cloud1-default-uuid-sisesvc2", ... ]
- },
- nil
- */
- profile, err := rb.NewProfileClient().Get(resource.RBName, resource.RBVersion, resource.ProfileName)
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
- externalVNFID, resourceNameMap, err := helper.CreateVNF(resource.CsarID, resource.CloudRegionID,
- profile, &kubeclient)
- if err != nil {
- werr := pkgerrors.Wrap(err, "Read Kubernetes Data information error")
- http.Error(w, werr.Error(), http.StatusInternalServerError)
- return
- }
-
- namespace := profile.Namespace
- // cloud1-default-uuid
- internalVNFID := resource.CloudRegionID + "-" + namespace + "-" + externalVNFID
-
- // Persist in AAI database.
- log.Printf("Cloud Region ID: %s, Namespace: %s, VNF ID: %s ", resource.CloudRegionID, namespace, externalVNFID)
-
- // TODO: Uncomment when annotations are done
- // krd.AddNetworkAnnotationsToPod(kubeData, resource.Networks)
-
- // key: cloud1-default-uuid
- // value: "{"deployment":<>,"service":<>}"
- err = db.DBconn.Create(storeName, instanceKey{Key: internalVNFID}, tagData, resourceNameMap)
- if err != nil {
- werr := pkgerrors.Wrap(err, "Create VNF deployment DB error")
- http.Error(w, werr.Error(), http.StatusInternalServerError)
- return
- }
-
- resp := CreateVnfResponse{
- VNFID: externalVNFID,
- CloudRegionID: resource.CloudRegionID,
- Namespace: namespace,
- VNFComponents: resourceNameMap,
- }
-
- w.Header().Set("Content-Type", "application/json")
- w.WriteHeader(http.StatusCreated)
- json.NewEncoder(w).Encode(resp)
-}
-
-// ListHandler the existing VNF instances created in a given Kubernetes cluster
-func ListHandler(w http.ResponseWriter, r *http.Request) {
- vars := mux.Vars(r)
-
- cloudRegionID := vars["cloudRegionID"]
- namespace := vars["namespace"]
- prefix := cloudRegionID + "-" + namespace
-
- res, err := db.DBconn.ReadAll(storeName, tagData)
- if err != nil {
- http.Error(w, pkgerrors.Wrap(err, "Get VNF list error").Error(),
- http.StatusInternalServerError)
- return
- }
-
- // TODO: There is an edge case where if namespace is passed but is missing some characters
- // trailing, it will print the result with those excluding characters. This is because of
- // the way I am trimming the Prefix. This fix is needed.
-
- var editedList []string
-
- for key, value := range res {
- if len(value) > 0 {
- editedList = append(editedList, strings.TrimPrefix(key, prefix)[1:])
- }
- }
-
- if len(editedList) == 0 {
- editedList = append(editedList, "")
- }
-
- resp := ListVnfsResponse{
- VNFs: editedList,
- }
-
- w.Header().Set("Content-Type", "application/json")
- w.WriteHeader(http.StatusOK)
- json.NewEncoder(w).Encode(resp)
-}
-
-// DeleteHandler method terminates an individual VNF instance.
-func DeleteHandler(w http.ResponseWriter, r *http.Request) {
- vars := mux.Vars(r)
-
- cloudRegionID := vars["cloudRegionID"] // cloud1
- namespace := vars["namespace"] // default
- externalVNFID := vars["externalVNFID"] // uuid
-
- // cloud1-default-uuid
- internalVNFID := cloudRegionID + "-" + namespace + "-" + externalVNFID
-
- // key: cloud1-default-uuid
- // value: "{"deployment":<>,"service":<>}"
- res, err := db.DBconn.Read(storeName, instanceKey{Key: internalVNFID}, tagData)
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
-
- /*
- {
- "deployment": ["cloud1-default-uuid-sisedeploy1", "cloud1-default-uuid-sisedeploy2", ... ]
- "service": ["cloud1-default-uuid-sisesvc1", "cloud1-default-uuid-sisesvc2", ... ]
- },
- */
- data := make(map[string][]string)
- err = db.DBconn.Unmarshal(res, &data)
- if err != nil {
- werr := pkgerrors.Wrap(err, "Unmarshal VNF error")
- http.Error(w, werr.Error(), http.StatusInternalServerError)
- return
- }
-
- // (TODO): Read kubeconfig for specific Cloud Region from local file system
- // if present or download it from AAI
- // err := DownloadKubeConfigFromAAI(resource.CloudRegionID, os.Getenv("KUBE_CONFIG_DIR")
- kubeclient, err := GetVNFClient(os.Getenv("KUBE_CONFIG_DIR") + "/" + cloudRegionID)
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
- err = helper.DestroyVNF(data, namespace, &kubeclient)
- if err != nil {
- werr := pkgerrors.Wrap(err, "Delete VNF error")
- http.Error(w, werr.Error(), http.StatusInternalServerError)
- return
- }
-
- err = db.DBconn.Delete(storeName, instanceKey{Key: internalVNFID}, tagData)
- if err != nil {
- werr := pkgerrors.Wrap(err, "Delete VNF db record error")
- http.Error(w, werr.Error(), http.StatusInternalServerError)
- return
- }
-
- w.Header().Set("Content-Type", "application/json")
- w.WriteHeader(http.StatusAccepted)
-}
-
-// // UpdateHandler method to update a VNF instance.
-// func UpdateHandler(w http.ResponseWriter, r *http.Request) {
-// vars := mux.Vars(r)
-// id := vars["vnfInstanceId"]
-
-// var resource UpdateVnfRequest
-
-// if r.Body == nil {
-// http.Error(w, "Body empty", http.StatusBadRequest)
-// return
-// }
-
-// err := json.NewDecoder(r.Body).Decode(&resource)
-// if err != nil {
-// http.Error(w, err.Error(), http.StatusUnprocessableEntity)
-// return
-// }
-
-// err = validateBody(resource)
-// if err != nil {
-// http.Error(w, err.Error(), http.StatusUnprocessableEntity)
-// return
-// }
-
-// kubeData, err := utils.ReadCSARFromFileSystem(resource.CsarID)
-
-// if kubeData.Deployment == nil {
-// werr := pkgerrors.Wrap(err, "Update VNF deployment error")
-// http.Error(w, werr.Error(), http.StatusInternalServerError)
-// return
-// }
-// kubeData.Deployment.SetUID(types.UID(id))
-
-// if err != nil {
-// werr := pkgerrors.Wrap(err, "Update VNF deployment information error")
-// http.Error(w, werr.Error(), http.StatusInternalServerError)
-// return
-// }
-
-// // (TODO): Read kubeconfig for specific Cloud Region from local file system
-// // if present or download it from AAI
-// s, err := NewVNFInstanceService("../kubeconfig/config")
-// if err != nil {
-// http.Error(w, err.Error(), http.StatusInternalServerError)
-// return
-// }
-
-// err = s.Client.UpdateDeployment(kubeData.Deployment, resource.Namespace)
-// if err != nil {
-// werr := pkgerrors.Wrap(err, "Update VNF error")
-
-// http.Error(w, werr.Error(), http.StatusInternalServerError)
-// return
-// }
-
-// resp := UpdateVnfResponse{
-// DeploymentID: id,
-// }
-
-// w.Header().Set("Content-Type", "application/json")
-// w.WriteHeader(http.StatusCreated)
-
-// err = json.NewEncoder(w).Encode(resp)
-// if err != nil {
-// werr := pkgerrors.Wrap(err, "Parsing output of new VNF error")
-// http.Error(w, werr.Error(), http.StatusInternalServerError)
-// }
-// }
-
-// GetHandler retrieves information about a VNF instance by reading an individual VNF instance resource.
-func GetHandler(w http.ResponseWriter, r *http.Request) {
- vars := mux.Vars(r)
-
- cloudRegionID := vars["cloudRegionID"] // cloud1
- namespace := vars["namespace"] // default
- externalVNFID := vars["externalVNFID"] // uuid
-
- // cloud1-default-uuid
- internalVNFID := cloudRegionID + "-" + namespace + "-" + externalVNFID
-
- // key: cloud1-default-uuid
- // value: "{"deployment":<>,"service":<>}"
- res, err := db.DBconn.Read(storeName, instanceKey{Key: internalVNFID}, tagData)
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
-
- /*
- {
- "deployment": ["cloud1-default-uuid-sisedeploy1", "cloud1-default-uuid-sisedeploy2", ... ]
- "service": ["cloud1-default-uuid-sisesvc1", "cloud1-default-uuid-sisesvc2", ... ]
- },
- */
- data := make(map[string][]string)
- err = db.DBconn.Unmarshal(res, &data)
- if err != nil {
- werr := pkgerrors.Wrap(err, "Unmarshal VNF error")
- http.Error(w, werr.Error(), http.StatusInternalServerError)
- return
- }
-
- resp := GetVnfResponse{
- VNFID: externalVNFID,
- CloudRegionID: cloudRegionID,
- Namespace: namespace,
- VNFComponents: data,
- }
-
- w.Header().Set("Content-Type", "application/json")
- w.WriteHeader(http.StatusOK)
- json.NewEncoder(w).Encode(resp)
-}
diff --git a/src/k8splugin/api/handler_test.go b/src/k8splugin/api/handler_test.go
deleted file mode 100644
index cb377ea5..00000000
--- a/src/k8splugin/api/handler_test.go
+++ /dev/null
@@ -1,710 +0,0 @@
-/*
-Copyright 2018 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 api
-
-import (
- "bytes"
- "encoding/json"
- "io"
- "net/http"
- "net/http/httptest"
- "reflect"
- "testing"
-
- pkgerrors "github.com/pkg/errors"
- "k8s.io/client-go/kubernetes"
-
- helper "k8splugin/internal/app"
- "k8splugin/internal/db"
- "k8splugin/internal/rb"
-)
-
-type mockCSAR struct {
- externalVNFID string
- resourceYAMLNameMap map[string][]string
- err error
-}
-
-func (c *mockCSAR) CreateVNF(id string, r string, profile rb.Profile,
- kubeclient *kubernetes.Clientset) (string, map[string][]string, error) {
- return c.externalVNFID, c.resourceYAMLNameMap, c.err
-}
-
-func (c *mockCSAR) DestroyVNF(data map[string][]string, namespace string,
- kubeclient *kubernetes.Clientset) error {
- return c.err
-}
-
-func executeRequest(req *http.Request) *http.Response {
- router := NewRouter("", nil, nil)
- recorder := httptest.NewRecorder()
- router.ServeHTTP(recorder, req)
-
- return recorder.Result()
-}
-
-func checkResponseCode(t *testing.T, expected, actual int) {
- if expected != actual {
- t.Errorf("Expected response code %d. Got %d\n", expected, actual)
- }
-}
-
-func TestCreateHandler(t *testing.T) {
- testCases := []struct {
- label string
- input io.Reader
- expectedCode int
- mockGetVNFClientErr error
- mockCreateVNF *mockCSAR
- mockStore *db.MockDB
- }{
- {
- label: "Missing body failure",
- expectedCode: http.StatusBadRequest,
- },
- {
- label: "Invalid JSON request format",
- input: bytes.NewBuffer([]byte("invalid")),
- expectedCode: http.StatusUnprocessableEntity,
- },
- {
- label: "Missing parameter failure",
- input: bytes.NewBuffer([]byte(`{
- "csar_id": "testID",
- "rb-name": "test-rbdef",
- "rb-version": "v1",
- "oof_parameters": [{
- "key_values": {
- "key1": "value1",
- "key2": "value2"
- }
- }],
- "vnf_instance_name": "test",
- "vnf_instance_description": "vRouter_test_description"
- }`)),
- expectedCode: http.StatusUnprocessableEntity,
- },
- {
- label: "Fail to get the VNF client",
- input: bytes.NewBuffer([]byte(`{
- "cloud_region_id": "region1",
- "rb-name": "test-rbdef",
- "rb-version": "v1",
- "profile-name": "profile1",
- "rb_profile_id": "123e4567-e89b-12d3-a456-426655440000",
- "csar_id": "UUID-1"
- }`)),
- expectedCode: http.StatusInternalServerError,
- mockGetVNFClientErr: pkgerrors.New("Get VNF client error"),
- },
- {
- label: "Fail to create the VNF instance",
- input: bytes.NewBuffer([]byte(`{
- "cloud_region_id": "region1",
- "rb-name": "test-rbdef",
- "rb-version": "v1",
- "profile-name": "profile1",
- "rb_profile_id": "123e4567-e89b-12d3-a456-426655440000",
- "csar_id": "UUID-1"
- }`)),
- expectedCode: http.StatusInternalServerError,
- mockCreateVNF: &mockCSAR{
- err: pkgerrors.New("Internal error"),
- },
- mockStore: &db.MockDB{
- Items: map[string]map[string][]byte{
- rb.ProfileKey{RBName: "testresourcebundle", RBVersion: "v1",
- ProfileName: "profile1"}.String(): {
- "metadata": []byte(
- "{\"profile-name\":\"profile1\"," +
- "\"release-name\":\"testprofilereleasename\"," +
- "\"namespace\":\"testnamespace\"," +
- "\"rb-name\":\"testresourcebundle\"," +
- "\"rb-version\":\"v1\"," +
- "\"kubernetesversion\":\"1.12.3\"}"),
- // base64 encoding of vagrant/tests/vnfs/testrb/helm/profile
- "content": []byte("H4sICLmjT1wAA3Byb2ZpbGUudGFyAO1Y32/bNhD2s/6Kg/KyYZZsy" +
- "78K78lLMsxY5gRxmqIYhoKWaJsYJWokZdfo+r/vSFmunCZNBtQJ1vF7sXX36e54vDN5T" +
- "knGFlTpcEtS3jgO2ohBr2c/EXc/29Gg1+h0e1F32Ol1B1Gj3Ymifr8B7SPFc4BCaSIBG" +
- "lII/SXeY/r/KIIg8NZUKiayEaw7nt7mdOQBrAkvqBqBL1ArWULflRJbJz4SYpEt2FJSJ" +
- "QoZ21cAAlgwTnOiVyPQWFQLwVuqmCdMthKac7FNaVZWmqWjkRWRuuSvScF1gFZVwYOEr" +
- "luapjknaOazd186Z98S7tver+3j0f5v1/q/18f+7w56bdf/zwFF5ZqV/WtbH6YioVdCa" +
- "hRkJEVBVSFBvUNRmyNpesgwors0lmkqM8KNzRG8iqLIWN45GUGv57l+fkFUP9PH9GF6f" +
- "IgH+kP9b76b/o+GUb9r5J1O1I0a0D9mUBX+5/1/55g+io9/sf+DnuF1sA4Gbv+fA1++p" +
- "n0dH4+c/92oPaztv+n/fn84dOf/c+AETkW+lWy50hC1O69gguc1R6HEw5xoHAuaKIq9E" +
- "+8ELvCikCmaQJElVIJeURjnJMaPnaYJt+UoAVHYhu8Mwd+p/O9/RAtbUUBKtnj+aygUR" +
- "RNM2ZkB6PuY5hpvCzhY4L2fkSymsGF6Zd3sjIRo4u3OhJhrgmyC/ByfFnUeEG0DLrHSO" +
- "h+1WpvNJiQ23FDIZYuXVNW6mJyeT2fnAYZsX3qdcaoUSPpXwSQudr4FkmNEMZljnJxsQ" +
- "EggOPmgTgsT8UYyzbJlE5RY6A2RFK0kTGnJ5oU+SFcVH666TsCEkQz88QwmMx9+Gs8ms" +
- "ybaeDO5+eXy9Q28GV9fj6c3k/MZXF7D6eX0bHIzuZzi088wnr6FXyfTsyZQTBa6oe9za" +
- "eLHIJlJJE1M1maUHgSwEGVAKqcxW7AY15UtC7KksDS3uQyXAzmVKVNmOxWGl6AVzlKmb" +
- "VGozxcVeh7J2W01S2LOVAsHyj9ZlozgbP+74qVUk4RoMtrfMD98wCzGvEiwXHD3U5GFi" +
- "4Jzo/QhhI8fd0yFu3c/fa/d8zmZU67KsRRDefCt/Qu7YdQSw1PzNTS3W1QGnyRVef+N5" +
- "YHDKZao/4MP/ju/siEpp0SVQYbX5UNlxxJwizCFyzuMWXkLNySzIyZs4wBrTpXE23I62" +
- "wlPRZHp0qJCC7EWslxpSnS8uqgt/YmLr2btnZXaDhnwA4NPzueT8lEt126AyExPY44rS" +
- "YA1bJPl15JgRaEdM9CKv/f1YDHdE5e1cYVFdiUwoduDJC+5mBMe5nstbndCF9Zfxakpa" +
- "1aNP2LK/Xffhuc3fTNfUYlfzH8a/h97qhmVaikNPi2+nItq8exGtLA+SdW9rgUvUvqbq" +
- "YkDi6mRXNk/V1pUxy0uYsI1S+meU+XsPo2kJLnMOKZGy4J6Xt3XgZuHTayEKv3XZLjy+" +
- "yJ66WPQwcHBwcHBwcHBwcHBwcHBwcHhm8Q/mTHqWgAoAAA="),
- },
- rb.DefinitionKey{Name: "testresourcebundle", Version: "v1"}.String(): {
- "metadata": []byte(
- "{\"rb-name\":\"testresourcebundle\"," +
- "\"rb-version\":\"vault-consul-dev\"," +
- "\"chart-name\":\"vault-consul-dev\"," +
- "\"description\":\"testresourcebundle\"}"),
- // base64 encoding of vagrant/tests/vnfs/testrb/helm/vault-consul-dev
- "content": []byte("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" +
- "cOHDgwIEDB18K/AcxEDJDAHgAAA=="),
- },
- },
- },
- },
- {
- label: "Fail to create a VNF DB record",
- input: bytes.NewBuffer([]byte(`{
- "cloud_region_id": "region1",
- "rb-name": "test-rbdef",
- "rb-version": "v1",
- "profile-name": "profile1",
- "rb_profile_id": "123e4567-e89b-12d3-a456-426655440000",
- "csar_id": "UUID-1"
- }`)),
- expectedCode: http.StatusInternalServerError,
- mockCreateVNF: &mockCSAR{
- resourceYAMLNameMap: map[string][]string{},
- },
- mockStore: &db.MockDB{
- Err: pkgerrors.New("Internal error"),
- },
- },
- {
- label: "Succesful create a VNF",
- input: bytes.NewBuffer([]byte(`{
- "cloud_region_id": "region1",
- "rb-name": "test-rbdef",
- "rb-version": "v1",
- "profile-name": "profile1",
- "rb_profile_id": "123e4567-e89b-12d3-a456-426655440000",
- "csar_id": "UUID-1"
- }`)),
- expectedCode: http.StatusCreated,
- mockCreateVNF: &mockCSAR{
- resourceYAMLNameMap: map[string][]string{
- "deployment": []string{"cloud1-default-uuid-sisedeploy"},
- "service": []string{"cloud1-default-uuid-sisesvc"},
- },
- },
- mockStore: &db.MockDB{
- Items: map[string]map[string][]byte{
- rb.ProfileKey{RBName: "test-rbdef", RBVersion: "v1",
- ProfileName: "profile1"}.String(): {
- "metadata": []byte(
- "{\"profile-name\":\"profile1\"," +
- "\"release-name\":\"testprofilereleasename\"," +
- "\"namespace\":\"testnamespace\"," +
- "\"rb-name\":\"test-rbdef\"," +
- "\"rb-version\":\"v1\"," +
- "\"kubernetesversion\":\"1.12.3\"}"),
- // base64 encoding of vagrant/tests/vnfs/testrb/helm/profile
- "content": []byte("H4sICLmjT1wAA3Byb2ZpbGUudGFyAO1Y32/bNhD2s/6Kg/KyYZZsy" +
- "78K78lLMsxY5gRxmqIYhoKWaJsYJWokZdfo+r/vSFmunCZNBtQJ1vF7sXX36e54vDN5T" +
- "knGFlTpcEtS3jgO2ohBr2c/EXc/29Gg1+h0e1F32Ol1B1Gj3Ymifr8B7SPFc4BCaSIBG" +
- "lII/SXeY/r/KIIg8NZUKiayEaw7nt7mdOQBrAkvqBqBL1ArWULflRJbJz4SYpEt2FJSJ" +
- "QoZ21cAAlgwTnOiVyPQWFQLwVuqmCdMthKac7FNaVZWmqWjkRWRuuSvScF1gFZVwYOEr" +
- "luapjknaOazd186Z98S7tver+3j0f5v1/q/18f+7w56bdf/zwFF5ZqV/WtbH6YioVdCa" +
- "hRkJEVBVSFBvUNRmyNpesgwors0lmkqM8KNzRG8iqLIWN45GUGv57l+fkFUP9PH9GF6f" +
- "IgH+kP9b76b/o+GUb9r5J1O1I0a0D9mUBX+5/1/55g+io9/sf+DnuF1sA4Gbv+fA1++p" +
- "n0dH4+c/92oPaztv+n/fn84dOf/c+AETkW+lWy50hC1O69gguc1R6HEw5xoHAuaKIq9E" +
- "+8ELvCikCmaQJElVIJeURjnJMaPnaYJt+UoAVHYhu8Mwd+p/O9/RAtbUUBKtnj+aygUR" +
- "RNM2ZkB6PuY5hpvCzhY4L2fkSymsGF6Zd3sjIRo4u3OhJhrgmyC/ByfFnUeEG0DLrHSO" +
- "h+1WpvNJiQ23FDIZYuXVNW6mJyeT2fnAYZsX3qdcaoUSPpXwSQudr4FkmNEMZljnJxsQ" +
- "EggOPmgTgsT8UYyzbJlE5RY6A2RFK0kTGnJ5oU+SFcVH666TsCEkQz88QwmMx9+Gs8ms" +
- "ybaeDO5+eXy9Q28GV9fj6c3k/MZXF7D6eX0bHIzuZzi088wnr6FXyfTsyZQTBa6oe9za" +
- "eLHIJlJJE1M1maUHgSwEGVAKqcxW7AY15UtC7KksDS3uQyXAzmVKVNmOxWGl6AVzlKmb" +
- "VGozxcVeh7J2W01S2LOVAsHyj9ZlozgbP+74qVUk4RoMtrfMD98wCzGvEiwXHD3U5GFi" +
- "4Jzo/QhhI8fd0yFu3c/fa/d8zmZU67KsRRDefCt/Qu7YdQSw1PzNTS3W1QGnyRVef+N5" +
- "YHDKZao/4MP/ju/siEpp0SVQYbX5UNlxxJwizCFyzuMWXkLNySzIyZs4wBrTpXE23I62" +
- "wlPRZHp0qJCC7EWslxpSnS8uqgt/YmLr2btnZXaDhnwA4NPzueT8lEt126AyExPY44rS" +
- "YA1bJPl15JgRaEdM9CKv/f1YDHdE5e1cYVFdiUwoduDJC+5mBMe5nstbndCF9Zfxakpa" +
- "1aNP2LK/Xffhuc3fTNfUYlfzH8a/h97qhmVaikNPi2+nItq8exGtLA+SdW9rgUvUvqbq" +
- "YkDi6mRXNk/V1pUxy0uYsI1S+meU+XsPo2kJLnMOKZGy4J6Xt3XgZuHTayEKv3XZLjy+" +
- "yJ66WPQwcHBwcHBwcHBwcHBwcHBwcHhm8Q/mTHqWgAoAAA="),
- },
- rb.DefinitionKey{Name: "test-rbdef", Version: "v1"}.String(): {
- "metadata": []byte(
- "{\"rb-name\":\"test-rbdef\"," +
- "\"rb-version\":\"v1\"," +
- "\"chart-name\":\"vault-consul-dev\"," +
- "\"description\":\"testresourcebundle\"}"),
- // base64 encoding of vagrant/tests/vnfs/testrb/helm/vault-consul-dev
- "content": []byte("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" +
- "cOHDgwIEDB18K/AcxEDJDAHgAAA=="),
- },
- },
- },
- },
- }
-
- for _, testCase := range testCases {
- t.Run(testCase.label, func(t *testing.T) {
- GetVNFClient = func(configPath string) (kubernetes.Clientset, error) {
- return kubernetes.Clientset{}, testCase.mockGetVNFClientErr
- }
- if testCase.mockCreateVNF != nil {
- helper.CreateVNF = testCase.mockCreateVNF.CreateVNF
- }
- if testCase.mockStore != nil {
- db.DBconn = testCase.mockStore
- }
-
- request := httptest.NewRequest("POST", "/v1/vnf_instances/", testCase.input)
- result := executeRequest(request)
-
- if testCase.expectedCode != result.StatusCode {
- t.Fatalf("Request method returned: \n%v\n and it was expected: \n%v", result.StatusCode, testCase.expectedCode)
- }
- if result.StatusCode == http.StatusCreated {
- var response CreateVnfResponse
- err := json.NewDecoder(result.Body).Decode(&response)
- if err != nil {
- t.Fatalf("Parsing the returned response got an error (%s)", err)
- }
- }
- })
- }
-}
-
-func TestListHandler(t *testing.T) {
- testCases := []struct {
- label string
- expectedCode int
- expectedResponse []string
- mockStore *db.MockDB
- }{
- {
- label: "Fail to retrieve DB records",
- expectedCode: http.StatusInternalServerError,
- mockStore: &db.MockDB{
- Err: pkgerrors.New("Internal error"),
- },
- },
- {
- label: "Get empty list",
- expectedCode: http.StatusOK,
- expectedResponse: []string{""},
- mockStore: &db.MockDB{},
- },
- {
- label: "Succesful get a list of VNF",
- expectedCode: http.StatusOK,
- expectedResponse: []string{"uid1"},
- mockStore: &db.MockDB{
- Items: map[string]map[string][]byte{
- "uuid1": {
- "data": []byte("{}"),
- },
- },
- },
- },
- }
-
- for _, testCase := range testCases {
- t.Run(testCase.label, func(t *testing.T) {
- if testCase.mockStore != nil {
- db.DBconn = testCase.mockStore
- }
-
- request := httptest.NewRequest("GET", "/v1/vnf_instances/cloud1/default", nil)
- result := executeRequest(request)
-
- if testCase.expectedCode != result.StatusCode {
- t.Fatalf("Request method returned: \n%v\n and it was expected: \n%v",
- result.StatusCode, testCase.expectedCode)
- }
- if result.StatusCode == http.StatusOK {
- var response ListVnfsResponse
- err := json.NewDecoder(result.Body).Decode(&response)
- if err != nil {
- t.Fatalf("Parsing the returned response got an error (%s)", err)
- }
- if !reflect.DeepEqual(testCase.expectedResponse, response.VNFs) {
- t.Fatalf("TestListHandler returned:\n result=%v\n expected=%v",
- response.VNFs, testCase.expectedResponse)
- }
- }
- })
- }
-}
-
-func TestDeleteHandler(t *testing.T) {
- testCases := []struct {
- label string
- expectedCode int
- mockGetVNFClientErr error
- mockDeleteVNF *mockCSAR
- mockStore *db.MockDB
- }{
- {
- label: "Fail to read a VNF DB record",
- expectedCode: http.StatusInternalServerError,
- mockStore: &db.MockDB{
- Err: pkgerrors.New("Internal error"),
- },
- },
- {
- label: "Fail to find VNF record be deleted",
- expectedCode: http.StatusInternalServerError,
- mockStore: &db.MockDB{
- Items: map[string]map[string][]byte{},
- },
- },
- {
- label: "Fail to unmarshal the DB record",
- expectedCode: http.StatusInternalServerError,
- mockStore: &db.MockDB{
- Items: map[string]map[string][]byte{
- "cloudregion1-testnamespace-uuid1": {
- "data": []byte("{invalid format}"),
- },
- },
- },
- },
- {
- label: "Fail to get the VNF client",
- expectedCode: http.StatusInternalServerError,
- mockGetVNFClientErr: pkgerrors.New("Get VNF client error"),
- mockStore: &db.MockDB{
- Items: map[string]map[string][]byte{
- "cloudregion1-testnamespace-uuid1": {
- "data": []byte(
- "{\"deployment\": [\"deploy1\", \"deploy2\"]," +
- "\"service\": [\"svc1\", \"svc2\"]}"),
- },
- },
- },
- },
- {
- label: "Fail to destroy VNF",
- expectedCode: http.StatusInternalServerError,
- mockStore: &db.MockDB{
- Items: map[string]map[string][]byte{
- "cloudregion1-testnamespace-uuid1": {
- "data": []byte(
- "{\"deployment\": [\"deploy1\", \"deploy2\"]," +
- "\"service\": [\"svc1\", \"svc2\"]}"),
- },
- },
- },
- mockDeleteVNF: &mockCSAR{
- err: pkgerrors.New("Internal error"),
- },
- },
- {
- label: "Succesful delete a VNF",
- expectedCode: http.StatusAccepted,
- mockStore: &db.MockDB{
- Items: map[string]map[string][]byte{
- "cloudregion1-testnamespace-uuid1": {
- "data": []byte(
- "{\"deployment\": [\"deploy1\", \"deploy2\"]," +
- "\"service\": [\"svc1\", \"svc2\"]}"),
- },
- },
- },
- mockDeleteVNF: &mockCSAR{},
- },
- }
-
- for _, testCase := range testCases {
- t.Run(testCase.label, func(t *testing.T) {
- GetVNFClient = func(configPath string) (kubernetes.Clientset, error) {
- return kubernetes.Clientset{}, testCase.mockGetVNFClientErr
- }
- if testCase.mockStore != nil {
- db.DBconn = testCase.mockStore
- }
- if testCase.mockDeleteVNF != nil {
- helper.DestroyVNF = testCase.mockDeleteVNF.DestroyVNF
- }
-
- request := httptest.NewRequest("DELETE", "/v1/vnf_instances/cloudregion1/testnamespace/uuid1", nil)
- result := executeRequest(request)
-
- if testCase.expectedCode != result.StatusCode {
- t.Fatalf("Request method returned: %v and it was expected: %v", result.StatusCode, testCase.expectedCode)
- }
- })
- }
-}
-
-// TODO: Update this test when the UpdateVNF endpoint is fixed.
-/*
-func TestVNFInstanceUpdate(t *testing.T) {
- t.Run("Succesful update a VNF", func(t *testing.T) {
- payload := []byte(`{
- "cloud_region_id": "region1",
- "csar_id": "UUID-1",
- "oof_parameters": [{
- "key1": "value1",
- "key2": "value2",
- "key3": {}
- }],
- "network_parameters": {
- "oam_ip_address": {
- "connection_point": "string",
- "ip_address": "string",
- "workload_name": "string"
- }
- }
- }`)
- expected := &UpdateVnfResponse{
- DeploymentID: "1",
- }
-
- var result UpdateVnfResponse
-
- req := httptest.NewRequest("PUT", "/v1/vnf_instances/1", bytes.NewBuffer(payload))
-
- GetVNFClient = func(configPath string) (krd.VNFInstanceClientInterface, error) {
- return &mockClient{
- update: func() error {
- return nil
- },
- }, nil
- }
- utils.ReadCSARFromFileSystem = func(csarID string) (*krd.KubernetesData, error) {
- kubeData := &krd.KubernetesData{
- Deployment: &appsV1.Deployment{},
- Service: &coreV1.Service{},
- }
- return kubeData, nil
- }
-
- response := executeRequest(req)
- checkResponseCode(t, http.StatusCreated, response.Code)
-
- err := json.NewDecoder(response.Body).Decode(&result)
- if err != nil {
- t.Fatalf("TestVNFInstanceUpdate returned:\n result=%v\n expected=%v", err, expected.DeploymentID)
- }
-
- if result.DeploymentID != expected.DeploymentID {
- t.Fatalf("TestVNFInstanceUpdate returned:\n result=%v\n expected=%v", result.DeploymentID, expected.DeploymentID)
- }
- })
-}
-*/
-
-func TestGetHandler(t *testing.T) {
- testCases := []struct {
- label string
- expectedCode int
- expectedResponse *GetVnfResponse
- mockStore *db.MockDB
- }{
- {
- label: "Fail to retrieve DB record",
- expectedCode: http.StatusInternalServerError,
- mockStore: &db.MockDB{
- Err: pkgerrors.New("Internal error"),
- },
- },
- {
- label: "Not found DB record",
- expectedCode: http.StatusInternalServerError,
- mockStore: &db.MockDB{},
- },
- {
- label: "Fail to unmarshal the DB record",
- expectedCode: http.StatusInternalServerError,
- mockStore: &db.MockDB{
- Items: map[string]map[string][]byte{
- "cloud1-default-1": {
- "data": []byte("{invalid-format}"),
- },
- },
- },
- },
- {
- label: "Succesful get a list of VNF",
- expectedCode: http.StatusOK,
- expectedResponse: &GetVnfResponse{
- VNFID: "1",
- CloudRegionID: "cloud1",
- Namespace: "default",
- VNFComponents: map[string][]string{
- "deployment": []string{"deploy1", "deploy2"},
- "service": []string{"svc1", "svc2"},
- },
- },
- mockStore: &db.MockDB{
- Items: map[string]map[string][]byte{
- "cloud1-default-1": {
- "data": []byte(
- "{\"deployment\": [\"deploy1\", \"deploy2\"]," +
- "\"service\": [\"svc1\", \"svc2\"]}"),
- "cloud1-default-2": []byte("{}"),
- },
- },
- },
- },
- }
-
- for _, testCase := range testCases {
- t.Run(testCase.label, func(t *testing.T) {
- db.DBconn = testCase.mockStore
- request := httptest.NewRequest("GET", "/v1/vnf_instances/cloud1/default/1", nil)
- result := executeRequest(request)
-
- if testCase.expectedCode != result.StatusCode {
- t.Fatalf("Request method returned: %v and it was expected: %v",
- result.StatusCode, testCase.expectedCode)
- }
- if result.StatusCode == http.StatusOK {
- var response GetVnfResponse
- err := json.NewDecoder(result.Body).Decode(&response)
- if err != nil {
- t.Fatalf("Parsing the returned response got an error (%s)", err)
- }
- if !reflect.DeepEqual(testCase.expectedResponse, &response) {
- t.Fatalf("TestGetHandler returned:\n result=%v\n expected=%v",
- &response, testCase.expectedResponse)
- }
- }
- })
- }
-}
diff --git a/src/k8splugin/api/instancehandler.go b/src/k8splugin/api/instancehandler.go
new file mode 100644
index 00000000..4fc3cfc8
--- /dev/null
+++ b/src/k8splugin/api/instancehandler.go
@@ -0,0 +1,191 @@
+/*
+Copyright 2018 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 api
+
+import (
+ "encoding/json"
+ "errors"
+ "io"
+ "net/http"
+
+ "k8splugin/internal/app"
+
+ "github.com/gorilla/mux"
+ pkgerrors "github.com/pkg/errors"
+)
+
+// Used to store the backend implementation objects
+// Also simplifies the mocking needed for unit testing
+type instanceHandler struct {
+ // Interface that implements the Instance operations
+ client app.InstanceManager
+}
+
+func (i instanceHandler) validateBody(body interface{}) error {
+ switch b := body.(type) {
+ case app.InstanceRequest:
+ if b.CloudRegion == "" {
+ werr := pkgerrors.Wrap(errors.New("Invalid/Missing CloudRegion in POST request"), "CreateVnfRequest bad request")
+ return werr
+ }
+ if b.RBName == "" || b.RBVersion == "" {
+ werr := pkgerrors.Wrap(errors.New("Invalid/Missing resource bundle parameters in POST request"), "CreateVnfRequest bad request")
+ return werr
+ }
+ if b.ProfileName == "" {
+ werr := pkgerrors.Wrap(errors.New("Invalid/Missing profile name in POST request"), "CreateVnfRequest bad request")
+ return werr
+ }
+ }
+ return nil
+}
+
+func (i instanceHandler) createHandler(w http.ResponseWriter, r *http.Request) {
+ var resource app.InstanceRequest
+
+ err := json.NewDecoder(r.Body).Decode(&resource)
+ switch {
+ case err == io.EOF:
+ http.Error(w, "Body empty", http.StatusBadRequest)
+ return
+ case err != nil:
+ http.Error(w, err.Error(), http.StatusUnprocessableEntity)
+ return
+ }
+
+ // Check body for expected parameters
+ err = i.validateBody(resource)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusUnprocessableEntity)
+ return
+ }
+
+ resp, err := i.client.Create(resource)
+ 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(resp)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+// getHandler retrieves information about an instance via the ID
+func (i instanceHandler) getHandler(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ id := vars["instID"]
+
+ resp, err := i.client.Get(id)
+ 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(resp)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+// deleteHandler method terminates an instance via the ID
+func (i instanceHandler) deleteHandler(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ id := vars["instID"]
+
+ err := i.client.Delete(id)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusAccepted)
+}
+
+// // UpdateHandler method to update a VNF instance.
+// func UpdateHandler(w http.ResponseWriter, r *http.Request) {
+// vars := mux.Vars(r)
+// id := vars["vnfInstanceId"]
+
+// var resource UpdateVnfRequest
+
+// if r.Body == nil {
+// http.Error(w, "Body empty", http.StatusBadRequest)
+// return
+// }
+
+// err := json.NewDecoder(r.Body).Decode(&resource)
+// if err != nil {
+// http.Error(w, err.Error(), http.StatusUnprocessableEntity)
+// return
+// }
+
+// err = validateBody(resource)
+// if err != nil {
+// http.Error(w, err.Error(), http.StatusUnprocessableEntity)
+// return
+// }
+
+// kubeData, err := utils.ReadCSARFromFileSystem(resource.CsarID)
+
+// if kubeData.Deployment == nil {
+// werr := pkgerrors.Wrap(err, "Update VNF deployment error")
+// http.Error(w, werr.Error(), http.StatusInternalServerError)
+// return
+// }
+// kubeData.Deployment.SetUID(types.UID(id))
+
+// if err != nil {
+// werr := pkgerrors.Wrap(err, "Update VNF deployment information error")
+// http.Error(w, werr.Error(), http.StatusInternalServerError)
+// return
+// }
+
+// // (TODO): Read kubeconfig for specific Cloud Region from local file system
+// // if present or download it from AAI
+// s, err := NewVNFInstanceService("../kubeconfig/config")
+// if err != nil {
+// http.Error(w, err.Error(), http.StatusInternalServerError)
+// return
+// }
+
+// err = s.Client.UpdateDeployment(kubeData.Deployment, resource.Namespace)
+// if err != nil {
+// werr := pkgerrors.Wrap(err, "Update VNF error")
+
+// http.Error(w, werr.Error(), http.StatusInternalServerError)
+// return
+// }
+
+// resp := UpdateVnfResponse{
+// DeploymentID: id,
+// }
+
+// w.Header().Set("Content-Type", "application/json")
+// w.WriteHeader(http.StatusCreated)
+
+// err = json.NewEncoder(w).Encode(resp)
+// if err != nil {
+// werr := pkgerrors.Wrap(err, "Parsing output of new VNF error")
+// http.Error(w, werr.Error(), http.StatusInternalServerError)
+// }
+// }
diff --git a/src/k8splugin/api/instancehandler_test.go b/src/k8splugin/api/instancehandler_test.go
new file mode 100644
index 00000000..d01d5dfb
--- /dev/null
+++ b/src/k8splugin/api/instancehandler_test.go
@@ -0,0 +1,325 @@
+/*
+Copyright 2018 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 api
+
+import (
+ "bytes"
+ "encoding/json"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "net/http/httptest"
+ "reflect"
+ "testing"
+
+ "k8splugin/internal/app"
+
+ "github.com/gorilla/mux"
+ 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 mockInstanceClient struct {
+ app.InstanceManager
+ // Items and err will be used to customize each test
+ // via a localized instantiation of mockInstanceClient
+ items []app.InstanceResponse
+ err error
+}
+
+func (m *mockInstanceClient) Create(inp app.InstanceRequest) (app.InstanceResponse, error) {
+ if m.err != nil {
+ return app.InstanceResponse{}, m.err
+ }
+
+ return m.items[0], nil
+}
+
+func (m *mockInstanceClient) Get(id string) (app.InstanceResponse, error) {
+ if m.err != nil {
+ return app.InstanceResponse{}, m.err
+ }
+
+ return m.items[0], nil
+}
+
+func (m *mockInstanceClient) Delete(id string) error {
+ return m.err
+}
+
+func executeRequest(request *http.Request, router *mux.Router) *http.Response {
+ recorder := httptest.NewRecorder()
+ router.ServeHTTP(recorder, request)
+ resp := recorder.Result()
+ return resp
+}
+
+func TestInstanceCreateHandler(t *testing.T) {
+ testCases := []struct {
+ label string
+ input io.Reader
+ expected app.InstanceResponse
+ expectedCode int
+ instClient *mockInstanceClient
+ }{
+ {
+ label: "Missing body failure",
+ expectedCode: http.StatusBadRequest,
+ },
+ {
+ label: "Invalid JSON request format",
+ input: bytes.NewBuffer([]byte("invalid")),
+ expectedCode: http.StatusUnprocessableEntity,
+ },
+ {
+ label: "Missing parameter failure",
+ input: bytes.NewBuffer([]byte(`{
+ "rb-name": "test-rbdef",
+ "profile-name": "profile1",
+ "cloud-region": "kud"
+ }`)),
+ expectedCode: http.StatusUnprocessableEntity,
+ },
+ {
+ label: "Succesfully create an Instance",
+ input: bytes.NewBuffer([]byte(`{
+ "cloud-region": "region1",
+ "rb-name": "test-rbdef",
+ "rb-version": "v1",
+ "profile-name": "profile1"
+ }`)),
+ expected: app.InstanceResponse{
+ ID: "HaKpys8e",
+ RBName: "test-rbdef",
+ RBVersion: "v1",
+ ProfileName: "profile1",
+ CloudRegion: "region1",
+ Namespace: "testnamespace",
+ Resources: map[string][]string{
+ "deployment": []string{"test-deployment"},
+ "service": []string{"test-service"},
+ },
+ },
+ expectedCode: http.StatusCreated,
+ instClient: &mockInstanceClient{
+ items: []app.InstanceResponse{
+ {
+ ID: "HaKpys8e",
+ RBName: "test-rbdef",
+ RBVersion: "v1",
+ ProfileName: "profile1",
+ CloudRegion: "region1",
+ Namespace: "testnamespace",
+ Resources: map[string][]string{
+ "deployment": []string{"test-deployment"},
+ "service": []string{"test-service"},
+ },
+ },
+ },
+ },
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+
+ request := httptest.NewRequest("POST", "/v1/instance", testCase.input)
+ resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient))
+
+ if testCase.expectedCode != resp.StatusCode {
+ body, _ := ioutil.ReadAll(resp.Body)
+ t.Log(string(body))
+ t.Fatalf("Request method returned: \n%v\n and it was expected: \n%v", resp.StatusCode, testCase.expectedCode)
+ }
+
+ if resp.StatusCode == http.StatusCreated {
+ var response app.InstanceResponse
+ err := json.NewDecoder(resp.Body).Decode(&response)
+ if err != nil {
+ t.Fatalf("Parsing the returned response got an error (%s)", err)
+ }
+ }
+ })
+ }
+}
+
+func TestInstanceGetHandler(t *testing.T) {
+ testCases := []struct {
+ label string
+ input string
+ expectedCode int
+ expectedResponse *app.InstanceResponse
+ instClient *mockInstanceClient
+ }{
+ {
+ label: "Fail to retrieve Instance",
+ input: "HaKpys8e",
+ expectedCode: http.StatusInternalServerError,
+ instClient: &mockInstanceClient{
+ err: pkgerrors.New("Internal error"),
+ },
+ },
+ {
+ label: "Succesful get an Instance",
+ input: "HaKpys8e",
+ expectedCode: http.StatusOK,
+ expectedResponse: &app.InstanceResponse{
+ ID: "HaKpys8e",
+ RBName: "test-rbdef",
+ RBVersion: "v1",
+ ProfileName: "profile1",
+ CloudRegion: "region1",
+ Namespace: "testnamespace",
+ Resources: map[string][]string{
+ "deployment": []string{"test-deployment"},
+ "service": []string{"test-service"},
+ },
+ },
+ instClient: &mockInstanceClient{
+ items: []app.InstanceResponse{
+ {
+ ID: "HaKpys8e",
+ RBName: "test-rbdef",
+ RBVersion: "v1",
+ ProfileName: "profile1",
+ CloudRegion: "region1",
+ Namespace: "testnamespace",
+ Resources: map[string][]string{
+ "deployment": []string{"test-deployment"},
+ "service": []string{"test-service"},
+ },
+ },
+ },
+ },
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ request := httptest.NewRequest("GET", "/v1/instance/"+testCase.input, nil)
+ resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient))
+
+ if testCase.expectedCode != resp.StatusCode {
+ t.Fatalf("Request method returned: %v and it was expected: %v",
+ resp.StatusCode, testCase.expectedCode)
+ }
+ if resp.StatusCode == http.StatusOK {
+ var response app.InstanceResponse
+ err := json.NewDecoder(resp.Body).Decode(&response)
+ if err != nil {
+ t.Fatalf("Parsing the returned response got an error (%s)", err)
+ }
+ if !reflect.DeepEqual(testCase.expectedResponse, &response) {
+ t.Fatalf("TestGetHandler returned:\n result=%v\n expected=%v",
+ &response, testCase.expectedResponse)
+ }
+ }
+ })
+ }
+}
+
+func TestDeleteHandler(t *testing.T) {
+ testCases := []struct {
+ label string
+ input string
+ expectedCode int
+ instClient *mockInstanceClient
+ }{
+ {
+ label: "Fail to destroy VNF",
+ input: "HaKpys8e",
+ expectedCode: http.StatusInternalServerError,
+ instClient: &mockInstanceClient{
+ err: pkgerrors.New("Internal error"),
+ },
+ },
+ {
+ label: "Succesful delete a VNF",
+ input: "HaKpys8e",
+ expectedCode: http.StatusAccepted,
+ instClient: &mockInstanceClient{},
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ request := httptest.NewRequest("DELETE", "/v1/instance/"+testCase.input, nil)
+ resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient))
+
+ if testCase.expectedCode != resp.StatusCode {
+ t.Fatalf("Request method returned: %v and it was expected: %v", resp.StatusCode, testCase.expectedCode)
+ }
+ })
+ }
+}
+
+// TODO: Update this test when the UpdateVNF endpoint is fixed.
+/*
+func TestVNFInstanceUpdate(t *testing.T) {
+ t.Run("Succesful update a VNF", func(t *testing.T) {
+ payload := []byte(`{
+ "cloud_region_id": "region1",
+ "csar_id": "UUID-1",
+ "oof_parameters": [{
+ "key1": "value1",
+ "key2": "value2",
+ "key3": {}
+ }],
+ "network_parameters": {
+ "oam_ip_address": {
+ "connection_point": "string",
+ "ip_address": "string",
+ "workload_name": "string"
+ }
+ }
+ }`)
+ expected := &UpdateVnfResponse{
+ DeploymentID: "1",
+ }
+
+ var result UpdateVnfResponse
+
+ req := httptest.NewRequest("PUT", "/v1/vnf_instances/1", bytes.NewBuffer(payload))
+
+ GetVNFClient = func(configPath string) (krd.VNFInstanceClientInterface, error) {
+ return &mockClient{
+ update: func() error {
+ return nil
+ },
+ }, nil
+ }
+ utils.ReadCSARFromFileSystem = func(csarID string) (*krd.KubernetesData, error) {
+ kubeData := &krd.KubernetesData{
+ Deployment: &appsV1.Deployment{},
+ Service: &coreV1.Service{},
+ }
+ return kubeData, nil
+ }
+
+ response := executeRequest(req)
+ checkResponseCode(t, http.StatusCreated, response.Code)
+
+ err := json.NewDecoder(response.Body).Decode(&result)
+ if err != nil {
+ t.Fatalf("TestVNFInstanceUpdate returned:\n result=%v\n expected=%v", err, expected.DeploymentID)
+ }
+
+ if resp.DeploymentID != expected.DeploymentID {
+ t.Fatalf("TestVNFInstanceUpdate returned:\n result=%v\n expected=%v", resp.DeploymentID, expected.DeploymentID)
+ }
+ })
+}
+*/
diff --git a/src/k8splugin/cmd/main.go b/src/k8splugin/cmd/main.go
index e0e715be..e600c0d6 100644
--- a/src/k8splugin/cmd/main.go
+++ b/src/k8splugin/cmd/main.go
@@ -15,35 +15,29 @@ package main
import (
"context"
- "flag"
"log"
+ "math/rand"
"net/http"
"os"
"os/signal"
- "path/filepath"
-
- "github.com/gorilla/handlers"
- "k8s.io/client-go/util/homedir"
+ "time"
"k8splugin/api"
utils "k8splugin/internal"
+
+ "github.com/gorilla/handlers"
)
func main() {
- var kubeconfig string
-
- home := homedir.HomeDir()
- if home != "" {
- kubeconfig = *flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
- }
- flag.Parse()
err := utils.CheckInitialSettings()
if err != nil {
log.Fatal(err)
}
- httpRouter := api.NewRouter(kubeconfig, nil, nil)
+ rand.Seed(time.Now().UnixNano())
+
+ httpRouter := api.NewRouter(nil, nil, nil)
loggedRouter := handlers.LoggingHandler(os.Stdout, httpRouter)
log.Println("Starting Kubernetes Multicloud API")
diff --git a/src/k8splugin/internal/app/client.go b/src/k8splugin/internal/app/client.go
index 3555afdd..fa5fdfd5 100644
--- a/src/k8splugin/internal/app/client.go
+++ b/src/k8splugin/internal/app/client.go
@@ -14,31 +14,187 @@ limitations under the License.
package app
import (
- "errors"
+ "log"
+ "os"
+ "strings"
- pkgerrors "github.com/pkg/errors"
+ utils "k8splugin/internal"
+ pkgerrors "github.com/pkg/errors"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
+ "k8s.io/helm/pkg/tiller"
)
-// GetKubeClient loads the Kubernetes configuation values stored into the local configuration file
-var GetKubeClient = func(configPath string) (kubernetes.Clientset, error) {
- var clientset *kubernetes.Clientset
+type kubernetesClient struct {
+ clientSet *kubernetes.Clientset
+}
+// GetKubeClient loads the Kubernetes configuation values stored into the local configuration file
+func (k *kubernetesClient) init(configPath string) error {
if configPath == "" {
- return *clientset, errors.New("config not passed and is not found in ~/.kube. ")
+ return pkgerrors.New("config not passed and is not found in ~/.kube. ")
}
config, err := clientcmd.BuildConfigFromFlags("", configPath)
if err != nil {
- return kubernetes.Clientset{}, pkgerrors.Wrap(err, "setConfig: Build config from flags raised an error")
+ return pkgerrors.Wrap(err, "setConfig: Build config from flags raised an error")
+ }
+
+ k.clientSet, err = kubernetes.NewForConfig(config)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (k *kubernetesClient) ensureNamespace(namespace string) error {
+ namespacePlugin, ok := utils.LoadedPlugins["namespace"]
+ if !ok {
+ return pkgerrors.New("No plugin for namespace resource found")
+ }
+
+ symGetNamespaceFunc, err := namespacePlugin.Lookup("Get")
+ if err != nil {
+ return pkgerrors.Wrap(err, "Error fetching get namespace function")
+ }
+
+ ns, _ := symGetNamespaceFunc.(func(string, string, kubernetes.Interface) (string, error))(
+ namespace, namespace, k.clientSet)
+
+ if ns == "" {
+ log.Println("Creating " + namespace + " namespace")
+ symGetNamespaceFunc, err := namespacePlugin.Lookup("Create")
+ if err != nil {
+ return pkgerrors.Wrap(err, "Error fetching create namespace plugin")
+ }
+ namespaceResource := &utils.ResourceData{
+ Namespace: namespace,
+ }
+
+ _, err = symGetNamespaceFunc.(func(*utils.ResourceData, kubernetes.Interface) (string, error))(
+ namespaceResource, k.clientSet)
+ if err != nil {
+ return pkgerrors.Wrap(err, "Error creating "+namespace+" namespace")
+ }
+ }
+ return nil
+}
+
+func (k *kubernetesClient) createKind(kind string, files []string, namespace string) ([]string, error) {
+
+ log.Println("Processing items of Kind: " + kind)
+
+ //Iterate over each file of a particular kind here
+ var resourcesCreated []string
+ for _, f := range files {
+ if _, err := os.Stat(f); os.IsNotExist(err) {
+ return nil, pkgerrors.New("File " + f + "does not exists")
+ }
+
+ log.Println("Processing file: " + f)
+
+ //Populate the namespace from profile instead of instance body
+ genericKubeData := &utils.ResourceData{
+ YamlFilePath: f,
+ Namespace: namespace,
+ }
+
+ typePlugin, ok := utils.LoadedPlugins[strings.ToLower(kind)]
+ if !ok {
+ return nil, pkgerrors.New("No plugin for kind " + kind + " found")
+ }
+
+ symCreateResourceFunc, err := typePlugin.Lookup("Create")
+ if err != nil {
+ return nil, pkgerrors.Wrap(err, "Error fetching "+kind+" plugin")
+ }
+
+ createdResourceName, err := symCreateResourceFunc.(func(*utils.ResourceData, kubernetes.Interface) (string, error))(
+ genericKubeData, k.clientSet)
+ if err != nil {
+ return nil, pkgerrors.Wrap(err, "Error in plugin "+kind+" plugin")
+ }
+ log.Print(createdResourceName + " created")
+ resourcesCreated = append(resourcesCreated, createdResourceName)
+ }
+
+ return resourcesCreated, nil
+}
+
+func (k *kubernetesClient) createResources(resMap map[string][]string,
+ namespace string) (map[string][]string, error) {
+
+ err := k.ensureNamespace(namespace)
+ if err != nil {
+ return nil, pkgerrors.Wrap(err, "Creating Namespace")
+ }
+
+ createdResourceMap := make(map[string][]string)
+ // Create all the known kinds in the InstallOrder
+ for _, kind := range tiller.InstallOrder {
+ files, ok := resMap[kind]
+ if !ok {
+ log.Println("Kind " + kind + " not found. Skipping...")
+ continue
+ }
+
+ resourcesCreated, err := k.createKind(kind, files, namespace)
+ if err != nil {
+ return nil, pkgerrors.Wrap(err, "Error creating kind: "+kind)
+ }
+
+ createdResourceMap[kind] = resourcesCreated
+ delete(resMap, kind)
}
- clientset, err = kubernetes.NewForConfig(config)
+ //Create the remaining kinds from the resMap
+ for kind, files := range resMap {
+ resourcesCreated, err := k.createKind(kind, files, namespace)
+ if err != nil {
+ return nil, pkgerrors.Wrap(err, "Error creating kind: "+kind)
+ }
+
+ createdResourceMap[kind] = resourcesCreated
+ delete(resMap, kind)
+ }
+
+ return createdResourceMap, nil
+}
+
+func (k *kubernetesClient) deleteKind(kind string, resources []string, namespace string) error {
+ log.Println("Deleting items of Kind: " + kind)
+
+ typePlugin, ok := utils.LoadedPlugins[strings.ToLower(kind)]
+ if !ok {
+ return pkgerrors.New("No plugin for resource " + kind + " found")
+ }
+
+ symDeleteResourceFunc, err := typePlugin.Lookup("Delete")
if err != nil {
- return *clientset, err
+ return pkgerrors.Wrap(err, "Error fetching "+kind+" plugin")
+ }
+
+ for _, res := range resources {
+ log.Println("Deleting resource: " + res)
+ err = symDeleteResourceFunc.(func(string, string, kubernetes.Interface) error)(
+ res, namespace, k.clientSet)
+ if err != nil {
+ return pkgerrors.Wrap(err, "Error destroying "+res)
+ }
+ }
+ return nil
+}
+
+func (k *kubernetesClient) deleteResources(resMap map[string][]string, namespace string) error {
+ //TODO: Investigate if deletion should be in a particular order
+ for kind, resourceNames := range resMap {
+ err := k.deleteKind(kind, resourceNames, namespace)
+ if err != nil {
+ return pkgerrors.Wrap(err, "Deleting resources")
+ }
}
- return *clientset, nil
+ return nil
}
diff --git a/src/k8splugin/internal/app/client_test.go b/src/k8splugin/internal/app/client_test.go
index 71892725..5999cfa0 100644
--- a/src/k8splugin/internal/app/client_test.go
+++ b/src/k8splugin/internal/app/client_test.go
@@ -14,21 +14,105 @@ limitations under the License.
package app
import (
+ "os"
+ "plugin"
"reflect"
"testing"
+
+ utils "k8splugin/internal"
+
+ pkgerrors "github.com/pkg/errors"
+ "k8s.io/client-go/kubernetes"
)
-func TestGetKubeClient(t *testing.T) {
+func LoadMockPlugins(krdLoadedPlugins map[string]*plugin.Plugin) error {
+ if _, err := os.Stat("../../mock_files/mock_plugins/mockplugin.so"); os.IsNotExist(err) {
+ return pkgerrors.New("mockplugin.so does not exist. Please compile mockplugin.go to generate")
+ }
+
+ mockPlugin, err := plugin.Open("../../mock_files/mock_plugins/mockplugin.so")
+ if err != nil {
+ return pkgerrors.Wrap(err, "Opening mock plugins")
+ }
+
+ krdLoadedPlugins["namespace"] = mockPlugin
+ krdLoadedPlugins["deployment"] = mockPlugin
+ krdLoadedPlugins["service"] = mockPlugin
+
+ return nil
+}
+
+func TestInit(t *testing.T) {
t.Run("Successfully create Kube Client", func(t *testing.T) {
- clientset, err := GetKubeClient("../../mock_files/mock_configs/mock_config")
+ kubeClient := kubernetesClient{}
+ err := kubeClient.init("../../mock_files/mock_configs/mock_config")
if err != nil {
t.Fatalf("TestGetKubeClient returned an error (%s)", err)
}
- if reflect.TypeOf(clientset).Name() != "Clientset" {
- t.Fatalf("TestGetKubeClient returned :\n result=%v\n expected=%v", clientset, "Clientset")
+ name := reflect.TypeOf(kubeClient.clientSet).Elem().Name()
+ if name != "Clientset" {
+ t.Fatalf("TestGetKubeClient returned :\n result=%v\n expected=%v", name, "Clientset")
}
})
}
+
+func TestCreateResources(t *testing.T) {
+ oldkrdPluginData := utils.LoadedPlugins
+
+ defer func() {
+ utils.LoadedPlugins = oldkrdPluginData
+ }()
+
+ err := LoadMockPlugins(utils.LoadedPlugins)
+ if err != nil {
+ t.Fatalf("LoadMockPlugins returned an error (%s)", err)
+ }
+
+ k8 := kubernetesClient{
+ clientSet: &kubernetes.Clientset{},
+ }
+
+ t.Run("Successfully delete resources", func(t *testing.T) {
+ data := map[string][]string{
+ "Deployment": []string{"../../mock_files/mock_yamls/deployment.yaml"},
+ "Service": []string{"../../mock_files/mock_yamls/service.yaml"},
+ }
+
+ _, err := k8.createResources(data, "testnamespace")
+ if err != nil {
+ t.Fatalf("TestCreateResources returned an error (%s)", err)
+ }
+ })
+}
+
+func TestDeleteResources(t *testing.T) {
+ oldkrdPluginData := utils.LoadedPlugins
+
+ defer func() {
+ utils.LoadedPlugins = oldkrdPluginData
+ }()
+
+ err := LoadMockPlugins(utils.LoadedPlugins)
+ if err != nil {
+ t.Fatalf("LoadMockPlugins returned an error (%s)", err)
+ }
+
+ k8 := kubernetesClient{
+ clientSet: &kubernetes.Clientset{},
+ }
+
+ t.Run("Successfully delete resources", func(t *testing.T) {
+ data := map[string][]string{
+ "Deployment": []string{"deployment-1", "deployment-2"},
+ "Service": []string{"service-1", "service-2"},
+ }
+
+ err := k8.deleteResources(data, "test")
+ if err != nil {
+ t.Fatalf("TestCreateVNF returned an error (%s)", err)
+ }
+ })
+}
diff --git a/src/k8splugin/internal/app/instance.go b/src/k8splugin/internal/app/instance.go
new file mode 100644
index 00000000..a5b35fef
--- /dev/null
+++ b/src/k8splugin/internal/app/instance.go
@@ -0,0 +1,206 @@
+/*
+ * 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 app
+
+import (
+ "encoding/base64"
+ "encoding/json"
+ "math/rand"
+ "os"
+
+ "k8splugin/internal/db"
+ "k8splugin/internal/rb"
+
+ pkgerrors "github.com/pkg/errors"
+)
+
+// InstanceRequest contains the parameters needed for instantiation
+// of profiles
+type InstanceRequest struct {
+ RBName string `json:"rb-name"`
+ RBVersion string `json:"rb-version"`
+ ProfileName string `json:"profile-name"`
+ CloudRegion string `json:"cloud-region"`
+ Labels map[string]string `json:"labels"`
+}
+
+// InstanceResponse contains the response from instantiation
+type InstanceResponse struct {
+ ID string `json:"id"`
+ RBName string `json:"rb-name"`
+ RBVersion string `json:"rb-version"`
+ ProfileName string `json:"profile-name"`
+ CloudRegion string `json:"cloud-region"`
+ Namespace string `json:"namespace"`
+ Resources map[string][]string `json:"resources"`
+}
+
+// InstanceManager is an interface exposes the instantiation functionality
+type InstanceManager interface {
+ Create(i InstanceRequest) (InstanceResponse, error)
+ Get(id string) (InstanceResponse, error)
+ Delete(id string) error
+}
+
+// InstanceKey is used as the primary key in the db
+type InstanceKey struct {
+ ID string `json:"id"`
+}
+
+// We will use json marshalling to convert to string to
+// preserve the underlying structure.
+func (dk InstanceKey) String() string {
+ out, err := json.Marshal(dk)
+ if err != nil {
+ return ""
+ }
+
+ return string(out)
+}
+
+// InstanceClient implements the InstanceManager interface
+// It will also be used to maintain some localized state
+type InstanceClient struct {
+ storeName string
+ tagInst string
+}
+
+// Using 6 bytes of randomness to generate an 8 character string
+func generateInstanceID() string {
+ b := make([]byte, 6)
+ rand.Read(b)
+ return base64.URLEncoding.EncodeToString(b)
+}
+
+// NewInstanceClient returns an instance of the InstanceClient
+// which implements the InstanceManager
+func NewInstanceClient() *InstanceClient {
+ return &InstanceClient{
+ storeName: "rbdef",
+ tagInst: "instance",
+ }
+}
+
+// Create an entry for the resource bundle profile in the database
+func (v *InstanceClient) Create(i InstanceRequest) (InstanceResponse, error) {
+
+ // Name is required
+ if i.RBName == "" || i.RBVersion == "" || i.ProfileName == "" || i.CloudRegion == "" {
+ return InstanceResponse{},
+ pkgerrors.New("RBName, RBversion, ProfileName, CloudRegion are required to create a new instance")
+ }
+
+ //Check if profile exists
+ profile, err := rb.NewProfileClient().Get(i.RBName, i.RBVersion, i.ProfileName)
+ if err != nil {
+ return InstanceResponse{}, pkgerrors.New("Unable to find Profile to create instance")
+ }
+
+ overrideValues := []string{}
+
+ //Execute the kubernetes create command
+ resMap, err := rb.NewProfileClient().Resolve(i.RBName, i.RBVersion, i.ProfileName, overrideValues)
+ if err != nil {
+ return InstanceResponse{}, pkgerrors.Wrap(err, "Error resolving helm charts")
+ }
+
+ k8sClient := kubernetesClient{}
+ err = k8sClient.init(os.Getenv("KUBE_CONFIG_DIR") + "/" + i.CloudRegion)
+ if err != nil {
+ return InstanceResponse{}, pkgerrors.Wrap(err, "Getting CloudRegion Information")
+ }
+
+ createdResources, err := k8sClient.createResources(resMap, profile.Namespace)
+ if err != nil {
+ return InstanceResponse{}, pkgerrors.Wrap(err, "Create Kubernetes Resources")
+ }
+
+ id := generateInstanceID()
+
+ //Compose the return response
+ resp := InstanceResponse{
+ ID: id,
+ RBName: i.RBName,
+ RBVersion: i.RBVersion,
+ ProfileName: i.ProfileName,
+ CloudRegion: i.CloudRegion,
+ Namespace: profile.Namespace,
+ Resources: createdResources,
+ }
+
+ key := InstanceKey{
+ ID: id,
+ }
+ err = db.DBconn.Create(v.storeName, key, v.tagInst, resp)
+ if err != nil {
+ return InstanceResponse{}, pkgerrors.Wrap(err, "Creating Instance DB Entry")
+ }
+
+ return resp, nil
+}
+
+// Get returns the instance for corresponding ID
+func (v *InstanceClient) Get(id string) (InstanceResponse, error) {
+ key := InstanceKey{
+ ID: id,
+ }
+ value, err := db.DBconn.Read(v.storeName, key, v.tagInst)
+ if err != nil {
+ return InstanceResponse{}, pkgerrors.Wrap(err, "Get Instance")
+ }
+
+ //value is a byte array
+ if value != nil {
+ resp := InstanceResponse{}
+ err = db.DBconn.Unmarshal(value, &resp)
+ if err != nil {
+ return InstanceResponse{}, pkgerrors.Wrap(err, "Unmarshaling Instance Value")
+ }
+ return resp, nil
+ }
+
+ return InstanceResponse{}, pkgerrors.New("Error getting Instance")
+}
+
+// Delete the Instance from database
+func (v *InstanceClient) Delete(id string) error {
+ inst, err := v.Get(id)
+ if err != nil {
+ return pkgerrors.Wrap(err, "Error getting Instance")
+ }
+
+ k8sClient := kubernetesClient{}
+ err = k8sClient.init(os.Getenv("KUBE_CONFIG_DIR") + "/" + inst.CloudRegion)
+ if err != nil {
+ return pkgerrors.Wrap(err, "Getting CloudRegion Information")
+ }
+
+ err = k8sClient.deleteResources(inst.Resources, inst.Namespace)
+ if err != nil {
+ return pkgerrors.Wrap(err, "Deleting Instance Resources")
+ }
+
+ key := InstanceKey{
+ ID: id,
+ }
+ err = db.DBconn.Delete(v.storeName, key, v.tagInst)
+ if err != nil {
+ return pkgerrors.Wrap(err, "Delete Instance")
+ }
+
+ return nil
+}
diff --git a/src/k8splugin/internal/app/vnfhelper_test.go b/src/k8splugin/internal/app/instance_test.go
index 7587e5d6..480569c5 100644
--- a/src/k8splugin/internal/app/vnfhelper_test.go
+++ b/src/k8splugin/internal/app/instance_test.go
@@ -14,73 +14,27 @@ limitations under the License.
package app
import (
- "io/ioutil"
"log"
"os"
- "plugin"
+ "reflect"
"testing"
- pkgerrors "github.com/pkg/errors"
- yaml "gopkg.in/yaml.v2"
- "k8s.io/client-go/kubernetes"
-
utils "k8splugin/internal"
"k8splugin/internal/db"
"k8splugin/internal/rb"
)
-func LoadMockPlugins(krdLoadedPlugins *map[string]*plugin.Plugin) error {
- if _, err := os.Stat("../../mock_files/mock_plugins/mockplugin.so"); os.IsNotExist(err) {
- return pkgerrors.New("mockplugin.so does not exist. Please compile mockplugin.go to generate")
- }
-
- mockPlugin, err := plugin.Open("../../mock_files/mock_plugins/mockplugin.so")
- if err != nil {
- return pkgerrors.Cause(err)
- }
-
- (*krdLoadedPlugins)["namespace"] = mockPlugin
- (*krdLoadedPlugins)["deployment"] = mockPlugin
- (*krdLoadedPlugins)["service"] = mockPlugin
-
- return nil
-}
-
-func TestCreateVNF(t *testing.T) {
+func TestInstanceCreate(t *testing.T) {
oldkrdPluginData := utils.LoadedPlugins
- oldReadMetadataFile := ReadMetadataFile
-
defer func() {
utils.LoadedPlugins = oldkrdPluginData
- ReadMetadataFile = oldReadMetadataFile
}()
-
- err := LoadMockPlugins(&utils.LoadedPlugins)
+ err := LoadMockPlugins(utils.LoadedPlugins)
if err != nil {
- t.Fatalf("TestCreateVNF returned an error (%s)", err)
+ t.Fatalf("LoadMockPlugins returned an error (%s)", err)
}
- ReadMetadataFile = func(yamlFilePath string) (MetadataFile, error) {
- var seqFile MetadataFile
-
- if _, err := os.Stat(yamlFilePath); err == nil {
- rawBytes, err := ioutil.ReadFile("../../mock_files/mock_yamls/metadata.yaml")
- if err != nil {
- return seqFile, pkgerrors.Wrap(err, "Metadata YAML file read error")
- }
-
- err = yaml.Unmarshal(rawBytes, &seqFile)
- if err != nil {
- return seqFile, pkgerrors.Wrap(err, "Metadata YAML file unmarshall error")
- }
- }
-
- return seqFile, nil
- }
-
- kubeclient := kubernetes.Clientset{}
-
- t.Run("Successfully create VNF", func(t *testing.T) {
+ t.Run("Successfully create Instance", func(t *testing.T) {
db.DBconn = &db.MockDB{
Items: map[string]map[string][]byte{
rb.ProfileKey{RBName: "test-rbdef", RBVersion: "v1",
@@ -190,60 +144,180 @@ func TestCreateVNF(t *testing.T) {
},
},
}
- externaluuid, data, err := CreateVNF("uuid", "cloudregion1",
- rb.Profile{
- RBName: "test-rbdef",
- RBVersion: "v1",
- ProfileName: "profile1",
- ReleaseName: "testprofilereleasename",
- Namespace: "testnamespace",
- KubernetesVersion: "1.12.3",
- }, &kubeclient)
+
+ ic := NewInstanceClient()
+ input := InstanceRequest{
+ RBName: "test-rbdef",
+ RBVersion: "v1",
+ ProfileName: "profile1",
+ CloudRegion: "mock_config",
+ }
+
+ err := os.Setenv("KUBE_CONFIG_DIR", "../../mock_files/mock_configs")
if err != nil {
- t.Fatalf("TestCreateVNF returned an error (%s)", err)
+ t.Fatalf("TestInstanceCreate returned an error (%s)", err)
}
- log.Println(externaluuid)
+ ir, err := ic.Create(input)
+ if err != nil {
+ t.Fatalf("TestInstanceCreate returned an error (%s)", err)
+ }
+
+ log.Println(ir)
- if data == nil {
- t.Fatalf("TestCreateVNF returned empty data (%s)", data)
+ if len(ir.Resources) == 0 {
+ t.Fatalf("TestInstanceCreate returned empty data (%s)", ir)
}
})
}
-func TestDeleteVNF(t *testing.T) {
+func TestInstanceGet(t *testing.T) {
oldkrdPluginData := utils.LoadedPlugins
defer func() {
utils.LoadedPlugins = oldkrdPluginData
}()
- err := LoadMockPlugins(&utils.LoadedPlugins)
+ err := LoadMockPlugins(utils.LoadedPlugins)
if err != nil {
- t.Fatalf("TestCreateVNF returned an error (%s)", err)
+ t.Fatalf("LoadMockPlugins returned an error (%s)", err)
}
- kubeclient := kubernetes.Clientset{}
-
- t.Run("Successfully delete VNF", func(t *testing.T) {
- data := map[string][]string{
- "deployment": []string{"cloud1-default-uuid-sisedeploy"},
- "service": []string{"cloud1-default-uuid-sisesvc"},
+ t.Run("Successfully Get Instance", func(t *testing.T) {
+ db.DBconn = &db.MockDB{
+ Items: map[string]map[string][]byte{
+ InstanceKey{ID: "HaKpys8e"}.String(): {
+ "instance": []byte(
+ "{\"profile-name\":\"profile1\"," +
+ "\"id\":\"HaKpys8e\"," +
+ "\"namespace\":\"testnamespace\"," +
+ "\"rb-name\":\"test-rbdef\"," +
+ "\"rb-version\":\"v1\"," +
+ "\"cloud-region\":\"region1\"," +
+ "\"resources\": {" +
+ "\"deployment\": [\"test-deployment\"]," +
+ "\"service\": [\"test-service\"]" +
+ "}}"),
+ },
+ },
}
- err := DestroyVNF(data, "test", &kubeclient)
+ expected := InstanceResponse{
+ ID: "HaKpys8e",
+ RBName: "test-rbdef",
+ RBVersion: "v1",
+ ProfileName: "profile1",
+ CloudRegion: "region1",
+ Namespace: "testnamespace",
+ Resources: map[string][]string{
+ "deployment": []string{"test-deployment"},
+ "service": []string{"test-service"},
+ },
+ }
+ ic := NewInstanceClient()
+ id := "HaKpys8e"
+ data, err := ic.Get(id)
if err != nil {
- t.Fatalf("TestCreateVNF returned an error (%s)", err)
+ t.Fatalf("TestInstanceDelete returned an error (%s)", err)
+ }
+ if !reflect.DeepEqual(expected, data) {
+ t.Fatalf("TestInstanceGet returned:\n result=%v\n expected=%v",
+ data, expected)
+ }
+ })
+
+ t.Run("Get non-existing Instance", func(t *testing.T) {
+ db.DBconn = &db.MockDB{
+ Items: map[string]map[string][]byte{
+ InstanceKey{ID: "HaKpys8e"}.String(): {
+ "instance": []byte(
+ "{\"profile-name\":\"profile1\"," +
+ "\"id\":\"HaKpys8e\"," +
+ "\"namespace\":\"testnamespace\"," +
+ "\"rb-name\":\"test-rbdef\"," +
+ "\"rb-version\":\"v1\"," +
+ "\"cloud-region\":\"mock_config\"," +
+ "\"resources\": {" +
+ "\"deployment\": [\"deployment-1\",\"deployment-2\"]," +
+ "\"service\": [\"service-1\",\"service-2\"]" +
+ "}}"),
+ },
+ },
+ }
+
+ ic := NewInstanceClient()
+ id := "non-existing"
+ _, err := ic.Get(id)
+ if err == nil {
+ t.Fatal("Expected error, got pass", err)
}
})
}
-func TestReadMetadataFile(t *testing.T) {
- t.Run("Successfully read Metadata YAML file", func(t *testing.T) {
- _, err := ReadMetadataFile("../../mock_files//mock_yamls/metadata.yaml")
+func TestInstanceDelete(t *testing.T) {
+ oldkrdPluginData := utils.LoadedPlugins
+
+ defer func() {
+ utils.LoadedPlugins = oldkrdPluginData
+ }()
+
+ err := LoadMockPlugins(utils.LoadedPlugins)
+ if err != nil {
+ t.Fatalf("TestInstanceDelete returned an error (%s)", err)
+ }
+
+ t.Run("Successfully delete Instance", func(t *testing.T) {
+ db.DBconn = &db.MockDB{
+ Items: map[string]map[string][]byte{
+ InstanceKey{ID: "HaKpys8e"}.String(): {
+ "instance": []byte(
+ "{\"profile-name\":\"profile1\"," +
+ "\"id\":\"HaKpys8e\"," +
+ "\"namespace\":\"testnamespace\"," +
+ "\"rb-name\":\"test-rbdef\"," +
+ "\"rb-version\":\"v1\"," +
+ "\"cloud-region\":\"mock_config\"," +
+ "\"resources\": {" +
+ "\"deployment\": [\"deployment-1\",\"deployment-2\"]," +
+ "\"service\": [\"service-1\",\"service-2\"]" +
+ "}}"),
+ },
+ },
+ }
+
+ ic := NewInstanceClient()
+ id := "HaKpys8e"
+ err := ic.Delete(id)
if err != nil {
- t.Fatalf("TestReadMetadataFile returned an error (%s)", err)
+ t.Fatalf("TestInstanceDelete returned an error (%s)", err)
+ }
+ })
+
+ t.Run("Delete non-existing Instance", func(t *testing.T) {
+ db.DBconn = &db.MockDB{
+ Items: map[string]map[string][]byte{
+ InstanceKey{ID: "HaKpys8e"}.String(): {
+ "instance": []byte(
+ "{\"profile-name\":\"profile1\"," +
+ "\"id\":\"HaKpys8e\"," +
+ "\"namespace\":\"testnamespace\"," +
+ "\"rb-name\":\"test-rbdef\"," +
+ "\"rb-version\":\"v1\"," +
+ "\"cloud-region\":\"mock_config\"," +
+ "\"resources\": {" +
+ "\"deployment\": [\"deployment-1\",\"deployment-2\"]," +
+ "\"service\": [\"service-1\",\"service-2\"]" +
+ "}}"),
+ },
+ },
+ }
+
+ ic := NewInstanceClient()
+ id := "non-existing"
+ err := ic.Delete(id)
+ if err == nil {
+ t.Fatal("Expected error, got pass", err)
}
})
}
diff --git a/src/k8splugin/internal/app/vnfhelper.go b/src/k8splugin/internal/app/vnfhelper.go
deleted file mode 100644
index c5783d69..00000000
--- a/src/k8splugin/internal/app/vnfhelper.go
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
-Copyright 2018 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 app
-
-import (
- "encoding/hex"
- "io/ioutil"
- "log"
- "math/rand"
- "os"
- "strings"
-
- "k8s.io/client-go/kubernetes"
-
- pkgerrors "github.com/pkg/errors"
- yaml "gopkg.in/yaml.v2"
-
- utils "k8splugin/internal"
- "k8splugin/internal/rb"
-)
-
-func generateExternalVNFID() string {
- b := make([]byte, 2)
- rand.Read(b)
- return hex.EncodeToString(b)
-}
-
-func ensuresNamespace(namespace string, kubeclient kubernetes.Interface) error {
- namespacePlugin, ok := utils.LoadedPlugins["namespace"]
- if !ok {
- return pkgerrors.New("No plugin for namespace resource found")
- }
-
- symGetNamespaceFunc, err := namespacePlugin.Lookup("Get")
- if err != nil {
- return pkgerrors.Wrap(err, "Error fetching get namespace function")
- }
-
- ns, _ := symGetNamespaceFunc.(func(string, string, kubernetes.Interface) (string, error))(
- namespace, namespace, kubeclient)
-
- if ns == "" {
- log.Println("Creating " + namespace + " namespace")
- symGetNamespaceFunc, err := namespacePlugin.Lookup("Create")
- if err != nil {
- return pkgerrors.Wrap(err, "Error fetching create namespace plugin")
- }
- namespaceResource := &utils.ResourceData{
- Namespace: namespace,
- }
-
- _, err = symGetNamespaceFunc.(func(*utils.ResourceData, kubernetes.Interface) (string, error))(
- namespaceResource, kubeclient)
- if err != nil {
- return pkgerrors.Wrap(err, "Error creating "+namespace+" namespace")
- }
- }
- return nil
-}
-
-// CreateVNF reads the CSAR files from the files system and creates them one by one
-var CreateVNF = func(csarID string, cloudRegionID string, profile rb.Profile, kubeclient *kubernetes.Clientset) (string, map[string][]string, error) {
-
- overrideValues := []string{}
- //Make sure that the namespace exists before trying to create any resources
- if err := ensuresNamespace(profile.Namespace, kubeclient); err != nil {
- return "", nil, pkgerrors.Wrap(err, "Error while ensuring namespace: "+profile.Namespace)
- }
- externalVNFID := generateExternalVNFID()
- internalVNFID := cloudRegionID + "-" + profile.Namespace + "-" + externalVNFID
-
- metaMap, err := rb.NewProfileClient().Resolve(profile.RBName, profile.RBVersion, profile.ProfileName, overrideValues)
- if err != nil {
- return "", nil, pkgerrors.Wrap(err, "Error resolving helm charts")
- }
-
- resourceYAMLNameMap := make(map[string][]string)
- // Iterates over the resources defined in the metadata map to create kubernetes resources
- log.Printf("%d resource(s) type(s) to be processed", len(metaMap))
- for res, filePaths := range metaMap {
- //Convert resource to lower case as the map index is lowercase
- resource := strings.ToLower(res)
- log.Println("Processing items of " + string(resource) + " resource")
- var resourcesCreated []string
- for _, filepath := range filePaths {
-
- if _, err := os.Stat(filepath); os.IsNotExist(err) {
- return "", nil, pkgerrors.New("File " + filepath + "does not exists")
- }
- log.Println("Processing file: " + filepath)
-
- //Populate the namespace from profile instead of instance body
- genericKubeData := &utils.ResourceData{
- YamlFilePath: filepath,
- Namespace: profile.Namespace,
- VnfId: internalVNFID,
- }
-
- typePlugin, ok := utils.LoadedPlugins[resource]
- if !ok {
- return "", nil, pkgerrors.New("No plugin for resource " + resource + " found")
- }
-
- symCreateResourceFunc, err := typePlugin.Lookup("Create")
- if err != nil {
- return "", nil, pkgerrors.Wrap(err, "Error fetching "+resource+" plugin")
- }
-
- internalResourceName, err := symCreateResourceFunc.(func(*utils.ResourceData, kubernetes.Interface) (string, error))(
- genericKubeData, kubeclient)
- if err != nil {
- return "", nil, pkgerrors.Wrap(err, "Error in plugin "+resource+" plugin")
- }
- log.Print(internalResourceName + " succesful resource created")
- resourcesCreated = append(resourcesCreated, internalResourceName)
- }
- resourceYAMLNameMap[resource] = resourcesCreated
- }
-
- return externalVNFID, resourceYAMLNameMap, nil
-}
-
-// DestroyVNF deletes VNFs based on data passed
-var DestroyVNF = func(data map[string][]string, namespace string, kubeclient *kubernetes.Clientset) error {
- /* data:
- {
- "deployment": ["cloud1-default-uuid-sisedeploy1", "cloud1-default-uuid-sisedeploy2", ... ]
- "service": ["cloud1-default-uuid-sisesvc1", "cloud1-default-uuid-sisesvc2", ... ]
- },
- */
-
- for resourceName, resourceList := range data {
- typePlugin, ok := utils.LoadedPlugins[resourceName]
- if !ok {
- return pkgerrors.New("No plugin for resource " + resourceName + " found")
- }
-
- symDeleteResourceFunc, err := typePlugin.Lookup("Delete")
- if err != nil {
- return pkgerrors.Wrap(err, "Error fetching "+resourceName+" plugin")
- }
-
- for _, resourceName := range resourceList {
-
- log.Println("Deleting resource: " + resourceName)
-
- err = symDeleteResourceFunc.(func(string, string, kubernetes.Interface) error)(
- resourceName, namespace, kubeclient)
- if err != nil {
- return pkgerrors.Wrap(err, "Error destroying "+resourceName)
- }
- }
- }
-
- return nil
-}
-
-// MetadataFile stores the metadata of execution
-type MetadataFile struct {
- ResourceTypePathMap map[string][]string `yaml:"resources"`
-}
-
-// ReadMetadataFile reads the metadata yaml to return the order or reads
-var ReadMetadataFile = func(path string) (MetadataFile, error) {
- var metadataFile MetadataFile
-
- if _, err := os.Stat(path); os.IsNotExist(err) {
- return metadataFile, pkgerrors.Wrap(err, "Metadata YAML file does not exist")
- }
-
- log.Println("Reading metadata YAML: " + path)
- yamlFile, err := ioutil.ReadFile(path)
- if err != nil {
- return metadataFile, pkgerrors.Wrap(err, "Metadata YAML file read error")
- }
-
- err = yaml.Unmarshal(yamlFile, &metadataFile)
- if err != nil {
- return metadataFile, pkgerrors.Wrap(err, "Metadata YAML file unmarshal error")
- }
- log.Printf("metadata:\n%v", metadataFile)
-
- return metadataFile, nil
-}
diff --git a/src/k8splugin/mock_files/mock_plugins/mockplugin.go b/src/k8splugin/mock_files/mock_plugins/mockplugin.go
index 80a9d594..bdc2130c 100644
--- a/src/k8splugin/mock_files/mock_plugins/mockplugin.go
+++ b/src/k8splugin/mock_files/mock_plugins/mockplugin.go
@@ -23,12 +23,12 @@ func main() {}
// Create object in a specific Kubernetes resource
func Create(data *utils.ResourceData, client kubernetes.Interface) (string, error) {
- return "externalUUID", nil
+ return "resource-name", nil
}
// List of existing resources
func List(namespace string, client kubernetes.Interface) ([]string, error) {
- returnVal := []string{"cloud1-default-uuid1", "cloud1-default-uuid2"}
+ returnVal := []string{"resource-name-1", "resource-name-2"}
return returnVal, nil
}
diff --git a/src/k8splugin/plugins/deployment/plugin.go b/src/k8splugin/plugins/deployment/plugin.go
index b500c86e..7ac31753 100644
--- a/src/k8splugin/plugins/deployment/plugin.go
+++ b/src/k8splugin/plugins/deployment/plugin.go
@@ -41,8 +41,6 @@ func Create(data *utils.ResourceData, client kubernetes.Interface) (string, erro
return "", pkgerrors.New("Decoded object contains another resource different than Deployment")
}
deployment.Namespace = namespace
- deployment.Name = data.VnfId + "-" + deployment.Name
-
result, err := client.AppsV1().Deployments(namespace).Create(deployment)
if err != nil {
return "", pkgerrors.Wrap(err, "Create Deployment error")
diff --git a/src/k8splugin/plugins/deployment/plugin_test.go b/src/k8splugin/plugins/deployment/plugin_test.go
index 55e0c803..446f3329 100644
--- a/src/k8splugin/plugins/deployment/plugin_test.go
+++ b/src/k8splugin/plugins/deployment/plugin_test.go
@@ -28,7 +28,6 @@ import (
func TestCreateDeployment(t *testing.T) {
namespace := "test1"
name := "mock-deployment"
- internalVNFID := "1"
testCases := []struct {
label string
input *utils.ResourceData
@@ -47,7 +46,6 @@ func TestCreateDeployment(t *testing.T) {
{
label: "Successfully create a deployment",
input: &utils.ResourceData{
- VnfId: internalVNFID,
YamlFilePath: "../../mock_files/mock_yamls/deployment.yaml",
},
clientOutput: &appsV1.Deployment{
@@ -56,7 +54,7 @@ func TestCreateDeployment(t *testing.T) {
Namespace: namespace,
},
},
- expectedResult: internalVNFID + "-" + name,
+ expectedResult: name,
},
}
diff --git a/src/k8splugin/plugins/service/plugin.go b/src/k8splugin/plugins/service/plugin.go
index e9b45fc8..ea5aecad 100644
--- a/src/k8splugin/plugins/service/plugin.go
+++ b/src/k8splugin/plugins/service/plugin.go
@@ -42,7 +42,6 @@ func Create(data *utils.ResourceData, client kubernetes.Interface) (string, erro
return "", pkgerrors.New("Decoded object contains another resource different than Service")
}
service.Namespace = namespace
- service.Name = data.VnfId + "-" + service.Name
result, err := client.CoreV1().Services(namespace).Create(service)
if err != nil {
diff --git a/src/k8splugin/plugins/service/plugin_test.go b/src/k8splugin/plugins/service/plugin_test.go
index 7353f24b..d3614860 100644
--- a/src/k8splugin/plugins/service/plugin_test.go
+++ b/src/k8splugin/plugins/service/plugin_test.go
@@ -28,7 +28,6 @@ import (
func TestCreateService(t *testing.T) {
namespace := "test1"
name := "mock-service"
- internalVNFID := "1"
testCases := []struct {
label string
input *utils.ResourceData
@@ -47,7 +46,6 @@ func TestCreateService(t *testing.T) {
{
label: "Successfully create a service",
input: &utils.ResourceData{
- VnfId: internalVNFID,
YamlFilePath: "../../mock_files/mock_yamls/service.yaml",
},
clientOutput: &coreV1.Service{
@@ -56,7 +54,7 @@ func TestCreateService(t *testing.T) {
Namespace: namespace,
},
},
- expectedResult: internalVNFID + "-" + name,
+ expectedResult: name,
},
}