diff options
author | Shashank Kumar Shankar <shashank.kumar.shankar@intel.com> | 2018-08-20 15:50:50 -0700 |
---|---|---|
committer | Victor Morales <victor.morales@intel.com> | 2018-08-24 15:51:16 -0700 |
commit | a1373742a2c3f980360e4980f3b23b0ff3480ae6 (patch) | |
tree | ce2fb583dea15b8a546d794d21786fdf0f666539 /src/k8splugin/api | |
parent | 6ff216219ccb4567baeb34c9dba73daabb60f629 (diff) |
Seed code for k8s multicloud plugin
This patch provides the initial seed code for the multicloud Kubernetes
plugin and also provides the plugin feature to add new Kubernetes
kinds.
Change-Id: Ie5ee414656665070cde2834c4855ac2ebc179a9a
Issue-ID: MULTICLOUD-301
Signed-off-by: Shashank Kumar Shankar <shashank.kumar.shankar@intel.com>
Signed-off-by: Victor Morales <victor.morales@intel.com>
Diffstat (limited to 'src/k8splugin/api')
-rw-r--r-- | src/k8splugin/api/api.go | 120 | ||||
-rw-r--r-- | src/k8splugin/api/handler.go | 377 | ||||
-rw-r--r-- | src/k8splugin/api/handler_test.go | 316 | ||||
-rw-r--r-- | src/k8splugin/api/model.go | 76 |
4 files changed, 889 insertions, 0 deletions
diff --git a/src/k8splugin/api/api.go b/src/k8splugin/api/api.go new file mode 100644 index 00000000..651d9311 --- /dev/null +++ b/src/k8splugin/api/api.go @@ -0,0 +1,120 @@ +/* +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 ( + "os" + "path/filepath" + "plugin" + "strings" + + "github.com/gorilla/mux" + pkgerrors "github.com/pkg/errors" + + "k8splugin/db" + "k8splugin/krd" +) + +// CheckEnvVariables checks for required Environment variables +func CheckEnvVariables() error { + envList := []string{"CSAR_DIR", "KUBE_CONFIG_DIR", "DATABASE_TYPE", "DATABASE_IP"} + for _, env := range envList { + if _, ok := os.LookupEnv(env); !ok { + return pkgerrors.New("environment variable " + env + " not set") + } + } + + return nil +} + +// CheckDatabaseConnection checks if the database is up and running and +// plugin can talk to it +func CheckDatabaseConnection() error { + err := db.CreateDBClient(os.Getenv("DATABASE_TYPE")) + if err != nil { + return pkgerrors.Cause(err) + } + + err = db.DBconn.InitializeDatabase() + if err != nil { + return pkgerrors.Cause(err) + } + + err = db.DBconn.CheckDatabase() + if err != nil { + return pkgerrors.Cause(err) + } + return nil +} + +// LoadPlugins loads all the compiled .so plugins +func LoadPlugins() error { + pluginsDir, ok := os.LookupEnv("PLUGINS_DIR") + if !ok { + pluginsDir, _ = filepath.Abs(filepath.Dir(os.Args[0])) + } + err := filepath.Walk(pluginsDir, + func(path string, info os.FileInfo, err error) error { + if strings.Contains(path, ".so") { + p, err := plugin.Open(path) + if err != nil { + return pkgerrors.Cause(err) + } + + krd.LoadedPlugins[info.Name()[:len(info.Name())-3]] = p + } + return err + }) + if err != nil { + return err + } + + return nil +} + +// CheckInitialSettings is used to check initial settings required to start api +func CheckInitialSettings() error { + err := CheckEnvVariables() + if err != nil { + return pkgerrors.Cause(err) + } + + err = CheckDatabaseConnection() + if err != nil { + return pkgerrors.Cause(err) + } + + err = LoadPlugins() + if err != nil { + return pkgerrors.Cause(err) + } + + return nil +} + +// NewRouter creates a router instance that serves the VNFInstance web methods +func NewRouter(kubeconfig string) *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") + + // (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 new file mode 100644 index 00000000..27d060aa --- /dev/null +++ b/src/k8splugin/api/handler.go @@ -0,0 +1,377 @@ +/* +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" + "log" + "net/http" + "os" + "strings" + + "github.com/gorilla/mux" + pkgerrors "github.com/pkg/errors" + "k8s.io/client-go/kubernetes" + + "k8splugin/csar" + "k8splugin/db" + "k8splugin/krd" +) + +// GetVNFClient retrieves the client used to communicate with a Kubernetes Cluster +var GetVNFClient = func(kubeConfigPath string) (kubernetes.Clientset, error) { + client, err := krd.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 strings.Contains(b.CloudRegionID, "|") || strings.Contains(b.Namespace, "|") { + 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 + + 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 + } + + // (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 + */ + externalVNFID, resourceNameMap, err := csar.CreateVNF(resource.CsarID, resource.CloudRegionID, resource.Namespace, &kubeclient) + if err != nil { + werr := pkgerrors.Wrap(err, "Read Kubernetes Data information error") + http.Error(w, werr.Error(), http.StatusInternalServerError) + return + } + + // cloud1-default-uuid + internalVNFID := resource.CloudRegionID + "-" + resource.Namespace + "-" + externalVNFID + + // Persist in AAI database. + log.Printf("Cloud Region ID: %s, Namespace: %s, VNF ID: %s ", resource.CloudRegionID, resource.Namespace, externalVNFID) + + // TODO: Uncomment when annotations are done + // krd.AddNetworkAnnotationsToPod(kubeData, resource.Networks) + + // "{"deployment":<>,"service":<>}" + out, err := json.Marshal(resourceNameMap) + if err != nil { + werr := pkgerrors.Wrap(err, "Create VNF deployment error") + http.Error(w, werr.Error(), http.StatusInternalServerError) + return + } + serializedResourceNameMap := string(out) + + // key: cloud1-default-uuid + // value: "{"deployment":<>,"service":<>}" + err = db.DBconn.CreateEntry(internalVNFID, serializedResourceNameMap) + if err != nil { + werr := pkgerrors.Wrap(err, "Create VNF deployment error") + http.Error(w, werr.Error(), http.StatusInternalServerError) + return + } + + resp := CreateVnfResponse{ + VNFID: externalVNFID, + CloudRegionID: resource.CloudRegionID, + Namespace: resource.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 + + internalVNFIDs, err := db.DBconn.ReadAll(prefix) + if err != nil { + werr := pkgerrors.Wrap(err, "Get VNF list error") + http.Error(w, werr.Error(), http.StatusInternalServerError) + return + } + + if len(internalVNFIDs) == 0 { + w.WriteHeader(http.StatusNotFound) + 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 _, id := range internalVNFIDs { + if len(id) > 0 { + editedList = append(editedList, strings.TrimPrefix(id, 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 + + // (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 + } + + // key: cloud1-default-uuid + // value: "{"deployment":<>,"service":<>}" + serializedResourceNameMap, found, err := db.DBconn.ReadEntry(internalVNFID) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if found == false { + w.WriteHeader(http.StatusNotFound) + return + } + + /* + { + "deployment": ["cloud1-default-uuid-sisedeploy1", "cloud1-default-uuid-sisedeploy2", ... ] + "service": ["cloud1-default-uuid-sisesvc1", "cloud1-default-uuid-sisesvc2", ... ] + }, + */ + deserializedResourceNameMap := make(map[string][]string) + err = json.Unmarshal([]byte(serializedResourceNameMap), &deserializedResourceNameMap) + if err != nil { + werr := pkgerrors.Wrap(err, "Delete VNF error") + http.Error(w, werr.Error(), http.StatusInternalServerError) + return + } + + err = csar.DestroyVNF(deserializedResourceNameMap, namespace, &kubeclient) + if err != nil { + werr := pkgerrors.Wrap(err, "Delete VNF error") + http.Error(w, werr.Error(), http.StatusInternalServerError) + return + } + + err = db.DBconn.DeleteEntry(internalVNFID) + if err != nil { + werr := pkgerrors.Wrap(err, "Delete VNF 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":<>}" + serializedResourceNameMap, found, err := db.DBconn.ReadEntry(internalVNFID) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if found == false { + w.WriteHeader(http.StatusNotFound) + return + } + + /* + { + "deployment": ["cloud1-default-uuid-sisedeploy1", "cloud1-default-uuid-sisedeploy2", ... ] + "service": ["cloud1-default-uuid-sisesvc1", "cloud1-default-uuid-sisesvc2", ... ] + }, + */ + deserializedResourceNameMap := make(map[string][]string) + err = json.Unmarshal([]byte(serializedResourceNameMap), &deserializedResourceNameMap) + if err != nil { + werr := pkgerrors.Wrap(err, "Get VNF error") + http.Error(w, werr.Error(), http.StatusInternalServerError) + return + } + + resp := GetVnfResponse{ + VNFID: externalVNFID, + CloudRegionID: cloudRegionID, + Namespace: namespace, + VNFComponents: deserializedResourceNameMap, + } + + 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 new file mode 100644 index 00000000..df573d94 --- /dev/null +++ b/src/k8splugin/api/handler_test.go @@ -0,0 +1,316 @@ +/* +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" + "k8s.io/client-go/kubernetes" + "net/http" + "net/http/httptest" + "reflect" + "testing" + + "k8splugin/csar" + "k8splugin/db" +) + +type mockDB struct { + db.DatabaseConnection +} + +func (c *mockDB) InitializeDatabase() error { + return nil +} + +func (c *mockDB) CheckDatabase() error { + return nil +} + +func (c *mockDB) CreateEntry(key string, value string) error { + return nil +} + +func (c *mockDB) ReadEntry(key string) (string, bool, error) { + str := "{\"deployment\":[\"cloud1-default-uuid-sisedeploy\"],\"service\":[\"cloud1-default-uuid-sisesvc\"]}" + return str, true, nil +} + +func (c *mockDB) DeleteEntry(key string) error { + return nil +} + +func (c *mockDB) ReadAll(key string) ([]string, error) { + returnVal := []string{"cloud1-default-uuid1", "cloud1-default-uuid2"} + return returnVal, nil +} + +func executeRequest(req *http.Request) *httptest.ResponseRecorder { + router := NewRouter("") + recorder := httptest.NewRecorder() + router.ServeHTTP(recorder, req) + + return recorder +} + +func checkResponseCode(t *testing.T, expected, actual int) { + if expected != actual { + t.Errorf("Expected response code %d. Got %d\n", expected, actual) + } +} + +func TestVNFInstanceCreation(t *testing.T) { + t.Run("Succesful create a VNF", func(t *testing.T) { + payload := []byte(`{ + "cloud_region_id": "region1", + "namespace": "test", + "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" + } + } + }`) + + data := map[string][]string{ + "deployment": []string{"cloud1-default-uuid-sisedeploy"}, + "service": []string{"cloud1-default-uuid-sisesvc"}, + } + + expected := &CreateVnfResponse{ + VNFID: "test_UUID", + CloudRegionID: "region1", + Namespace: "test", + VNFComponents: data, + } + + var result CreateVnfResponse + + req, _ := http.NewRequest("POST", "/v1/vnf_instances/", bytes.NewBuffer(payload)) + + GetVNFClient = func(configPath string) (kubernetes.Clientset, error) { + return kubernetes.Clientset{}, nil + } + + csar.CreateVNF = func(id string, r string, n string, kubeclient *kubernetes.Clientset) (string, map[string][]string, error) { + return "externaluuid", data, nil + } + + db.DBconn = &mockDB{} + + response := executeRequest(req) + checkResponseCode(t, http.StatusCreated, response.Code) + + err := json.NewDecoder(response.Body).Decode(&result) + if err != nil { + t.Fatalf("TestVNFInstanceCreation returned:\n result=%v\n expected=%v", err, expected.VNFComponents) + } + }) + t.Run("Missing body failure", func(t *testing.T) { + req, _ := http.NewRequest("POST", "/v1/vnf_instances/", nil) + response := executeRequest(req) + + checkResponseCode(t, http.StatusBadRequest, response.Code) + }) + t.Run("Invalid JSON request format", func(t *testing.T) { + payload := []byte("invalid") + req, _ := http.NewRequest("POST", "/v1/vnf_instances/", bytes.NewBuffer(payload)) + response := executeRequest(req) + checkResponseCode(t, http.StatusUnprocessableEntity, response.Code) + }) + t.Run("Missing parameter failure", func(t *testing.T) { + payload := []byte(`{ + "csar_id": "testID", + "oof_parameters": { + "key_values": { + "key1": "value1", + "key2": "value2" + } + }, + "vnf_instance_name": "test", + "vnf_instance_description": "vRouter_test_description" + }`) + req, _ := http.NewRequest("POST", "/v1/vnf_instances/", bytes.NewBuffer(payload)) + response := executeRequest(req) + checkResponseCode(t, http.StatusUnprocessableEntity, response.Code) + }) +} + +func TestVNFInstancesRetrieval(t *testing.T) { + t.Run("Succesful get a list of VNF", func(t *testing.T) { + expected := &ListVnfsResponse{ + VNFs: []string{"uuid1", "uuid2"}, + } + var result ListVnfsResponse + + req, _ := http.NewRequest("GET", "/v1/vnf_instances/cloud1/default", nil) + + db.DBconn = &mockDB{} + + response := executeRequest(req) + checkResponseCode(t, http.StatusOK, response.Code) + + err := json.NewDecoder(response.Body).Decode(&result) + if err != nil { + t.Fatalf("TestVNFInstancesRetrieval returned:\n result=%v\n expected=list", err) + } + if !reflect.DeepEqual(*expected, result) { + t.Fatalf("TestVNFInstancesRetrieval returned:\n result=%v\n expected=%v", result, *expected) + } + }) + t.Run("Get empty list", func(t *testing.T) { + req, _ := http.NewRequest("GET", "/v1/vnf_instances/cloudregion1/testnamespace", nil) + db.DBconn = &mockDB{} + response := executeRequest(req) + checkResponseCode(t, http.StatusOK, response.Code) + }) +} + +func TestVNFInstanceDeletion(t *testing.T) { + t.Run("Succesful delete a VNF", func(t *testing.T) { + req, _ := http.NewRequest("DELETE", "/v1/vnf_instances/cloudregion1/testnamespace/1", nil) + + GetVNFClient = func(configPath string) (kubernetes.Clientset, error) { + return kubernetes.Clientset{}, nil + } + + csar.DestroyVNF = func(d map[string][]string, n string, kubeclient *kubernetes.Clientset) error { + return nil + } + + db.DBconn = &mockDB{} + + response := executeRequest(req) + checkResponseCode(t, http.StatusAccepted, response.Code) + + if result := response.Body.String(); result != "" { + t.Fatalf("TestVNFInstanceDeletion returned:\n result=%v\n expected=%v", result, "") + } + }) + // t.Run("Malformed delete request", func(t *testing.T) { + // req, _ := http.NewRequest("DELETE", "/v1/vnf_instances/foo", nil) + // response := executeRqequest(req) + // checkResponseCode(t, http.StatusBadRequest, response.Code) + // }) +} + +// 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, _ := http.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 TestVNFInstanceRetrieval(t *testing.T) { + t.Run("Succesful get a VNF", func(t *testing.T) { + + data := map[string][]string{ + "deployment": []string{"cloud1-default-uuid-sisedeploy"}, + "service": []string{"cloud1-default-uuid-sisesvc"}, + } + + expected := GetVnfResponse{ + VNFID: "1", + CloudRegionID: "cloud1", + Namespace: "default", + VNFComponents: data, + } + + req, _ := http.NewRequest("GET", "/v1/vnf_instances/cloud1/default/1", nil) + + GetVNFClient = func(configPath string) (kubernetes.Clientset, error) { + return kubernetes.Clientset{}, nil + } + + db.DBconn = &mockDB{} + + response := executeRequest(req) + checkResponseCode(t, http.StatusOK, response.Code) + + var result GetVnfResponse + + err := json.NewDecoder(response.Body).Decode(&result) + if err != nil { + t.Fatalf("TestVNFInstanceRetrieval returned:\n result=%v\n expected=%v", err, expected) + } + + if !reflect.DeepEqual(expected, result) { + t.Fatalf("TestVNFInstanceRetrieval returned:\n result=%v\n expected=%v", result, expected) + } + }) + t.Run("VNF not found", func(t *testing.T) { + req, _ := http.NewRequest("GET", "/v1/vnf_instances/cloudregion1/testnamespace/1", nil) + response := executeRequest(req) + + checkResponseCode(t, http.StatusOK, response.Code) + }) +} diff --git a/src/k8splugin/api/model.go b/src/k8splugin/api/model.go new file mode 100644 index 00000000..0e4863c4 --- /dev/null +++ b/src/k8splugin/api/model.go @@ -0,0 +1,76 @@ +/* +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 + +// CreateVnfRequest contains the VNF creation request parameters +type CreateVnfRequest struct { + CloudRegionID string `json:"cloud_region_id"` + CsarID string `json:"csar_id"` + OOFParams []map[string]interface{} `json:"oof_parameters"` + NetworkParams NetworkParameters `json:"network_parameters"` + Namespace string `json:"namespace"` + Name string `json:"vnf_instance_name"` + Description string `json:"vnf_instance_description"` +} + +// CreateVnfResponse contains the VNF creation response parameters +type CreateVnfResponse struct { + VNFID string `json:"vnf_id"` + CloudRegionID string `json:"cloud_region_id"` + Namespace string `json:"namespace"` + VNFComponents map[string][]string `json:"vnf_components"` +} + +// ListVnfsResponse contains the list of VNFs response parameters +type ListVnfsResponse struct { + VNFs []string `json:"vnf_id_list"` +} + +// NetworkParameters contains the networking info required by the VNF instance +type NetworkParameters struct { + OAMI OAMIPParams `json:"oam_ip_address"` + // Add other network parameters if necessary. +} + +// OAMIPParams contains the management networking info required by the VNF instance +type OAMIPParams struct { + ConnectionPoint string `json:"connection_point"` + IPAddress string `json:"ip_address"` + WorkLoadName string `json:"workload_name"` +} + +// UpdateVnfRequest contains the VNF creation parameters +type UpdateVnfRequest struct { + CloudRegionID string `json:"cloud_region_id"` + CsarID string `json:"csar_id"` + OOFParams []map[string]interface{} `json:"oof_parameters"` + NetworkParams NetworkParameters `json:"network_parameters"` + Namespace string `json:"namespace"` + Name string `json:"vnf_instance_name"` + Description string `json:"vnf_instance_description"` +} + +// UpdateVnfResponse contains the VNF update response parameters +type UpdateVnfResponse struct { + DeploymentID string `json:"vnf_id"` + Name string `json:"name"` +} + +// GetVnfResponse returns information about a specific VNF instance +type GetVnfResponse struct { + VNFID string `json:"vnf_id"` + CloudRegionID string `json:"cloud_region_id"` + Namespace string `json:"namespace"` + VNFComponents map[string][]string `json:"vnf_components"` +} |