diff options
author | Kiran Kamineni <kiran.k.kamineni@intel.com> | 2019-03-28 12:53:13 -0700 |
---|---|---|
committer | Kiran Kamineni <kiran.k.kamineni@intel.com> | 2019-04-03 15:31:31 -0700 |
commit | f74ad24c6034294c1915f5778adb259dd65c9208 (patch) | |
tree | 03cf8ba86d1f966b9a342b325bd2fb36bf1dcc59 /src/k8splugin | |
parent | e97d3fddc255b73f87a8b3dbd8fa505e34247504 (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/k8splugin')
-rw-r--r-- | src/k8splugin/api/api.go | 28 | ||||
-rw-r--r-- | src/k8splugin/api/handler.go | 379 | ||||
-rw-r--r-- | src/k8splugin/api/handler_test.go | 710 | ||||
-rw-r--r-- | src/k8splugin/api/instancehandler.go | 191 | ||||
-rw-r--r-- | src/k8splugin/api/instancehandler_test.go | 325 | ||||
-rw-r--r-- | src/k8splugin/cmd/main.go | 20 | ||||
-rw-r--r-- | src/k8splugin/internal/app/client.go | 176 | ||||
-rw-r--r-- | src/k8splugin/internal/app/client_test.go | 92 | ||||
-rw-r--r-- | src/k8splugin/internal/app/instance.go | 206 | ||||
-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.go | 195 | ||||
-rw-r--r-- | src/k8splugin/mock_files/mock_plugins/mockplugin.go | 4 | ||||
-rw-r--r-- | src/k8splugin/plugins/deployment/plugin.go | 2 | ||||
-rw-r--r-- | src/k8splugin/plugins/deployment/plugin_test.go | 4 | ||||
-rw-r--r-- | src/k8splugin/plugins/service/plugin.go | 1 | ||||
-rw-r--r-- | src/k8splugin/plugins/service/plugin_test.go | 4 |
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, }, } |