aboutsummaryrefslogtreecommitdiffstats
path: root/src/k8splugin/api
diff options
context:
space:
mode:
authorBin Hu <bh526r@att.com>2018-08-28 20:09:11 +0000
committerGerrit Code Review <gerrit@onap.org>2018-08-28 20:09:11 +0000
commit88579fa6f563a3bea8c39aa98159eb54d13d44a5 (patch)
tree24697d5dd4ba3c0578bef0f33cea51465b40ec27 /src/k8splugin/api
parentbd3e38cf19c77e98bbd5be3e0f1f9806e0a6e331 (diff)
parenta1373742a2c3f980360e4980f3b23b0ff3480ae6 (diff)
Merge "Seed code for k8s multicloud plugin"
Diffstat (limited to 'src/k8splugin/api')
-rw-r--r--src/k8splugin/api/api.go120
-rw-r--r--src/k8splugin/api/handler.go377
-rw-r--r--src/k8splugin/api/handler_test.go316
-rw-r--r--src/k8splugin/api/model.go76
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"`
+}