summaryrefslogtreecommitdiffstats
path: root/src
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
parentbd3e38cf19c77e98bbd5be3e0f1f9806e0a6e331 (diff)
parenta1373742a2c3f980360e4980f3b23b0ff3480ae6 (diff)
Merge "Seed code for k8s multicloud plugin"
Diffstat (limited to 'src')
-rw-r--r--src/k8splugin/Gopkg.lock353
-rw-r--r--src/k8splugin/Gopkg.toml46
-rw-r--r--src/k8splugin/Makefile43
-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
-rw-r--r--src/k8splugin/cmd/main.go64
-rw-r--r--src/k8splugin/csar/parser.go207
-rw-r--r--src/k8splugin/csar/parser_test.go130
-rw-r--r--src/k8splugin/db/DB.go42
-rw-r--r--src/k8splugin/db/consul.go112
-rw-r--r--src/k8splugin/db/db_test.go40
-rw-r--r--src/k8splugin/krd/krd.go44
-rw-r--r--src/k8splugin/krd/krd_test.go34
-rw-r--r--src/k8splugin/krd/plugins.go44
-rw-r--r--src/k8splugin/mock_files/mock_configs/mock_config29
-rw-r--r--src/k8splugin/mock_files/mock_plugins/mockplugin.go43
-rw-r--r--src/k8splugin/mock_files/mock_yamls/deployment.yaml24
-rw-r--r--src/k8splugin/mock_files/mock_yamls/metadata.yaml16
-rw-r--r--src/k8splugin/mock_files/mock_yamls/service.yaml21
-rw-r--r--src/k8splugin/plugins/deployment/plugin.go136
-rw-r--r--src/k8splugin/plugins/namespace/plugin.go68
-rw-r--r--src/k8splugin/plugins/service/plugin.go131
24 files changed, 2516 insertions, 0 deletions
diff --git a/src/k8splugin/Gopkg.lock b/src/k8splugin/Gopkg.lock
new file mode 100644
index 00000000..e0276839
--- /dev/null
+++ b/src/k8splugin/Gopkg.lock
@@ -0,0 +1,353 @@
+# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
+
+
+[[projects]]
+ name = "github.com/ghodss/yaml"
+ packages = ["."]
+ revision = "0ca9ea5df5451ffdf184b4428c902747c2c11cd7"
+ version = "v1.0.0"
+
+[[projects]]
+ name = "github.com/gogo/protobuf"
+ packages = [
+ "proto",
+ "sortkeys"
+ ]
+ revision = "1adfc126b41513cc696b209667c8656ea7aac67c"
+ version = "v1.0.0"
+
+[[projects]]
+ branch = "master"
+ name = "github.com/golang/glog"
+ packages = ["."]
+ revision = "23def4e6c14b4da8ac2ed8007337bc5eb5007998"
+
+[[projects]]
+ name = "github.com/golang/protobuf"
+ packages = [
+ "proto",
+ "ptypes",
+ "ptypes/any",
+ "ptypes/duration",
+ "ptypes/timestamp"
+ ]
+ revision = "b4deda0973fb4c70b50d226b1af49f3da59f5265"
+ version = "v1.1.0"
+
+[[projects]]
+ branch = "master"
+ name = "github.com/google/gofuzz"
+ packages = ["."]
+ revision = "24818f796faf91cd76ec7bddd72458fbced7a6c1"
+
+[[projects]]
+ name = "github.com/googleapis/gnostic"
+ packages = [
+ "OpenAPIv2",
+ "compiler",
+ "extensions"
+ ]
+ revision = "7c663266750e7d82587642f65e60bc4083f1f84e"
+ version = "v0.2.0"
+
+[[projects]]
+ name = "github.com/gorilla/context"
+ packages = ["."]
+ revision = "08b5f424b9271eedf6f9f0ce86cb9396ed337a42"
+ version = "v1.1.1"
+
+[[projects]]
+ name = "github.com/gorilla/handlers"
+ packages = ["."]
+ revision = "90663712d74cb411cbef281bc1e08c19d1a76145"
+ version = "v1.3.0"
+
+[[projects]]
+ name = "github.com/gorilla/mux"
+ packages = ["."]
+ revision = "e3702bed27f0d39777b0b37b664b6280e8ef8fbf"
+ version = "v1.6.2"
+
+[[projects]]
+ name = "github.com/hashicorp/consul"
+ packages = ["api"]
+ revision = "e716d1b5f8be252b3e53906c6d5632e0228f30fa"
+ version = "v1.2.2"
+
+[[projects]]
+ branch = "master"
+ name = "github.com/hashicorp/go-cleanhttp"
+ packages = ["."]
+ revision = "d5fe4b57a186c716b0e00b8c301cbd9b4182694d"
+
+[[projects]]
+ branch = "master"
+ name = "github.com/hashicorp/go-rootcerts"
+ packages = ["."]
+ revision = "6bb64b370b90e7ef1fa532be9e591a81c3493e00"
+
+[[projects]]
+ name = "github.com/hashicorp/serf"
+ packages = ["coordinate"]
+ revision = "d6574a5bb1226678d7010325fb6c985db20ee458"
+ version = "v0.8.1"
+
+[[projects]]
+ branch = "master"
+ name = "github.com/howeyc/gopass"
+ packages = ["."]
+ revision = "bf9dde6d0d2c004a008c27aaee91170c786f6db8"
+
+[[projects]]
+ name = "github.com/imdario/mergo"
+ packages = ["."]
+ revision = "9316a62528ac99aaecb4e47eadd6dc8aa6533d58"
+ version = "v0.3.5"
+
+[[projects]]
+ name = "github.com/json-iterator/go"
+ packages = ["."]
+ revision = "ca39e5af3ece67bbcda3d0f4f56a8e24d9f2dad4"
+ version = "1.1.3"
+
+[[projects]]
+ branch = "master"
+ name = "github.com/mitchellh/go-homedir"
+ packages = ["."]
+ revision = "58046073cbffe2f25d425fe1331102f55cf719de"
+
+[[projects]]
+ branch = "master"
+ name = "github.com/mitchellh/mapstructure"
+ packages = ["."]
+ revision = "f15292f7a699fcc1a38a80977f80a046874ba8ac"
+
+[[projects]]
+ name = "github.com/modern-go/concurrent"
+ packages = ["."]
+ revision = "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94"
+ version = "1.0.3"
+
+[[projects]]
+ name = "github.com/modern-go/reflect2"
+ packages = ["."]
+ revision = "1df9eeb2bb81f327b96228865c5687bc2194af3f"
+ version = "1.0.0"
+
+[[projects]]
+ name = "github.com/pkg/errors"
+ packages = ["."]
+ revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
+ version = "v0.8.0"
+
+[[projects]]
+ name = "github.com/spf13/pflag"
+ packages = ["."]
+ revision = "583c0c0531f06d5278b7d917446061adc344b5cd"
+ version = "v1.0.1"
+
+[[projects]]
+ branch = "master"
+ name = "golang.org/x/crypto"
+ packages = ["ssh/terminal"]
+ revision = "8ac0e0d97ce45cd83d1d7243c060cb8461dda5e9"
+
+[[projects]]
+ branch = "master"
+ name = "golang.org/x/net"
+ packages = [
+ "context",
+ "http/httpguts",
+ "http2",
+ "http2/hpack",
+ "idna"
+ ]
+ revision = "db08ff08e8622530d9ed3a0e8ac279f6d4c02196"
+
+[[projects]]
+ branch = "master"
+ name = "golang.org/x/sys"
+ packages = [
+ "unix",
+ "windows"
+ ]
+ revision = "bff228c7b664c5fce602223a05fb708fd8654986"
+
+[[projects]]
+ name = "golang.org/x/text"
+ packages = [
+ "collate",
+ "collate/build",
+ "internal/colltab",
+ "internal/gen",
+ "internal/tag",
+ "internal/triegen",
+ "internal/ucd",
+ "language",
+ "secure/bidirule",
+ "transform",
+ "unicode/bidi",
+ "unicode/cldr",
+ "unicode/norm",
+ "unicode/rangetable"
+ ]
+ revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
+ version = "v0.3.0"
+
+[[projects]]
+ branch = "master"
+ name = "golang.org/x/time"
+ packages = ["rate"]
+ revision = "fbb02b2291d28baffd63558aa44b4b56f178d650"
+
+[[projects]]
+ name = "gopkg.in/inf.v0"
+ packages = ["."]
+ revision = "d2d2541c53f18d2a059457998ce2876cc8e67cbf"
+ version = "v0.9.1"
+
+[[projects]]
+ name = "gopkg.in/yaml.v2"
+ packages = ["."]
+ revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
+ version = "v2.2.1"
+
+[[projects]]
+ branch = "master"
+ name = "k8s.io/api"
+ packages = [
+ "admissionregistration/v1alpha1",
+ "admissionregistration/v1beta1",
+ "apps/v1",
+ "apps/v1beta1",
+ "apps/v1beta2",
+ "authentication/v1",
+ "authentication/v1beta1",
+ "authorization/v1",
+ "authorization/v1beta1",
+ "autoscaling/v1",
+ "autoscaling/v2beta1",
+ "batch/v1",
+ "batch/v1beta1",
+ "batch/v2alpha1",
+ "certificates/v1beta1",
+ "core/v1",
+ "events/v1beta1",
+ "extensions/v1beta1",
+ "networking/v1",
+ "policy/v1beta1",
+ "rbac/v1",
+ "rbac/v1alpha1",
+ "rbac/v1beta1",
+ "scheduling/v1alpha1",
+ "settings/v1alpha1",
+ "storage/v1",
+ "storage/v1alpha1",
+ "storage/v1beta1"
+ ]
+ revision = "72d6e4405f8143815cbd454ab04b38210a9f32fc"
+
+[[projects]]
+ name = "k8s.io/apimachinery"
+ packages = [
+ "pkg/api/errors",
+ "pkg/api/meta",
+ "pkg/api/resource",
+ "pkg/apis/meta/v1",
+ "pkg/apis/meta/v1/unstructured",
+ "pkg/apis/meta/v1beta1",
+ "pkg/conversion",
+ "pkg/conversion/queryparams",
+ "pkg/fields",
+ "pkg/labels",
+ "pkg/runtime",
+ "pkg/runtime/schema",
+ "pkg/runtime/serializer",
+ "pkg/runtime/serializer/json",
+ "pkg/runtime/serializer/protobuf",
+ "pkg/runtime/serializer/recognizer",
+ "pkg/runtime/serializer/streaming",
+ "pkg/runtime/serializer/versioning",
+ "pkg/selection",
+ "pkg/types",
+ "pkg/util/clock",
+ "pkg/util/errors",
+ "pkg/util/framer",
+ "pkg/util/intstr",
+ "pkg/util/json",
+ "pkg/util/net",
+ "pkg/util/runtime",
+ "pkg/util/sets",
+ "pkg/util/validation",
+ "pkg/util/validation/field",
+ "pkg/util/wait",
+ "pkg/util/yaml",
+ "pkg/version",
+ "pkg/watch",
+ "third_party/forked/golang/reflect"
+ ]
+ revision = "31dade610c053669d8054bfd847da657251e8c1a"
+ version = "kubernetes-1.10.3"
+
+[[projects]]
+ name = "k8s.io/client-go"
+ packages = [
+ "discovery",
+ "kubernetes",
+ "kubernetes/scheme",
+ "kubernetes/typed/admissionregistration/v1alpha1",
+ "kubernetes/typed/admissionregistration/v1beta1",
+ "kubernetes/typed/apps/v1",
+ "kubernetes/typed/apps/v1beta1",
+ "kubernetes/typed/apps/v1beta2",
+ "kubernetes/typed/authentication/v1",
+ "kubernetes/typed/authentication/v1beta1",
+ "kubernetes/typed/authorization/v1",
+ "kubernetes/typed/authorization/v1beta1",
+ "kubernetes/typed/autoscaling/v1",
+ "kubernetes/typed/autoscaling/v2beta1",
+ "kubernetes/typed/batch/v1",
+ "kubernetes/typed/batch/v1beta1",
+ "kubernetes/typed/batch/v2alpha1",
+ "kubernetes/typed/certificates/v1beta1",
+ "kubernetes/typed/core/v1",
+ "kubernetes/typed/events/v1beta1",
+ "kubernetes/typed/extensions/v1beta1",
+ "kubernetes/typed/networking/v1",
+ "kubernetes/typed/policy/v1beta1",
+ "kubernetes/typed/rbac/v1",
+ "kubernetes/typed/rbac/v1alpha1",
+ "kubernetes/typed/rbac/v1beta1",
+ "kubernetes/typed/scheduling/v1alpha1",
+ "kubernetes/typed/settings/v1alpha1",
+ "kubernetes/typed/storage/v1",
+ "kubernetes/typed/storage/v1alpha1",
+ "kubernetes/typed/storage/v1beta1",
+ "pkg/apis/clientauthentication",
+ "pkg/apis/clientauthentication/v1alpha1",
+ "pkg/version",
+ "plugin/pkg/client/auth/exec",
+ "rest",
+ "rest/watch",
+ "tools/auth",
+ "tools/clientcmd",
+ "tools/clientcmd/api",
+ "tools/clientcmd/api/latest",
+ "tools/clientcmd/api/v1",
+ "tools/metrics",
+ "tools/reference",
+ "transport",
+ "util/cert",
+ "util/flowcontrol",
+ "util/homedir",
+ "util/integer"
+ ]
+ revision = "23781f4d6632d88e869066eaebb743857aa1ef9b"
+ version = "v7.0.0"
+
+[solve-meta]
+ analyzer-name = "dep"
+ analyzer-version = 1
+ inputs-digest = "75cc26f2e82e49abeff97709158caea7f0c088191d8d4eb7a00eea2c88d00297"
+ solver-name = "gps-cdcl"
+ solver-version = 1
diff --git a/src/k8splugin/Gopkg.toml b/src/k8splugin/Gopkg.toml
new file mode 100644
index 00000000..219b502d
--- /dev/null
+++ b/src/k8splugin/Gopkg.toml
@@ -0,0 +1,46 @@
+# Gopkg.toml example
+#
+# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
+# for detailed Gopkg.toml documentation.
+#
+# required = ["github.com/user/thing/cmd/thing"]
+# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
+#
+# [[constraint]]
+# name = "github.com/user/project"
+# version = "1.0.0"
+#
+# [[constraint]]
+# name = "github.com/user/project2"
+# branch = "dev"
+# source = "github.com/myfork/project2"
+#
+# [[override]]
+# name = "github.com/x/y"
+# version = "2.4.0"
+#
+# [prune]
+# non-go = false
+# go-tests = true
+# unused-packages = true
+
+
+[[constraint]]
+ branch = "master"
+ name = "k8s.io/api"
+
+[[constraint]]
+ name = "k8s.io/apimachinery"
+ version = "kubernetes-1.10.3"
+
+[[constraint]]
+ name = "k8s.io/client-go"
+ version = "7.0.0"
+
+[prune]
+ go-tests = true
+ unused-packages = true
+
+[[constraint]]
+ name = "github.com/pkg/errors"
+ version = "0.8.0"
diff --git a/src/k8splugin/Makefile b/src/k8splugin/Makefile
new file mode 100644
index 00000000..586eca9c
--- /dev/null
+++ b/src/k8splugin/Makefile
@@ -0,0 +1,43 @@
+# 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.
+
+GOPATH := $(shell realpath "$(PWD)/../../")
+DEPENDENCIES := github.com/golang/dep/cmd/dep
+
+export GOPATH ...
+
+.PHONY: plugins
+
+build: clean dep plugins tests
+deploy: clean dep plugins build_binary tests
+
+build_binary:
+ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -tags netgo -ldflags '-w' -o ./k8plugin ./cmd/main.go
+
+tests:
+ go test -v ./... -cover
+
+format:
+ go fmt ./...
+
+plugins:
+ go build -buildmode=plugin -o ./plugins/deployment/deployment.so ./plugins/deployment/plugin.go
+ go build -buildmode=plugin -o ./plugins/namespace/namespace.so ./plugins/namespace/plugin.go
+ go build -buildmode=plugin -o ./plugins/service/service.so ./plugins/service/plugin.go
+ go build -buildmode=plugin -o ./mock_files/mock_plugins/mockplugin.so ./mock_files/mock_plugins/mockplugin.go
+
+dep:
+ go get -u $(DEPENDENCIES)
+ $(GOPATH)/bin/dep ensure
+
+clean:
+ find . -name "*so" -delete
+ @rm -f k8plugin
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"`
+}
diff --git a/src/k8splugin/cmd/main.go b/src/k8splugin/cmd/main.go
new file mode 100644
index 00000000..ee676549
--- /dev/null
+++ b/src/k8splugin/cmd/main.go
@@ -0,0 +1,64 @@
+/*
+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 main
+
+import (
+ "context"
+ "flag"
+ "log"
+ "net/http"
+ "os"
+ "os/signal"
+ "path/filepath"
+
+ "github.com/gorilla/handlers"
+ "k8s.io/client-go/util/homedir"
+
+ "k8splugin/api"
+)
+
+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 := api.CheckInitialSettings()
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ httpRouter := api.NewRouter(kubeconfig)
+ loggedRouter := handlers.LoggingHandler(os.Stdout, httpRouter)
+ log.Println("Starting Kubernetes Multicloud API")
+
+ httpServer := &http.Server{
+ Handler: loggedRouter,
+ Addr: ":8081", // Remove hardcoded port number
+ }
+
+ connectionsClose := make(chan struct{})
+ go func() {
+ c := make(chan os.Signal, 1)
+ signal.Notify(c, os.Interrupt)
+ <-c
+ httpServer.Shutdown(context.Background())
+ close(connectionsClose)
+ }()
+
+ log.Fatal(httpServer.ListenAndServe())
+}
diff --git a/src/k8splugin/csar/parser.go b/src/k8splugin/csar/parser.go
new file mode 100644
index 00000000..abd6ad92
--- /dev/null
+++ b/src/k8splugin/csar/parser.go
@@ -0,0 +1,207 @@
+/*
+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 csar
+
+import (
+ "encoding/hex"
+ "io/ioutil"
+ "log"
+ "math/rand"
+ "os"
+
+ "k8s.io/client-go/kubernetes"
+
+ pkgerrors "github.com/pkg/errors"
+ "gopkg.in/yaml.v2"
+
+ "k8splugin/krd"
+)
+
+func generateExternalVNFID(charLen int) string {
+ b := make([]byte, charLen/2)
+ rand.Read(b)
+ return hex.EncodeToString(b)
+}
+
+// CreateVNF reads the CSAR files from the files system and creates them one by one
+var CreateVNF = func(csarID string, cloudRegionID string, namespace string, kubeclient *kubernetes.Clientset) (string, map[string][]string, error) {
+ namespacePlugin, ok := krd.LoadedPlugins["namespace"]
+ if !ok {
+ return "", nil, pkgerrors.New("No plugin for namespace resource found")
+ }
+
+ symGetNamespaceFunc, err := namespacePlugin.Lookup("GetResource")
+ if err != nil {
+ return "", nil, pkgerrors.Wrap(err, "Error fetching namespace plugin")
+ }
+
+ present, err := symGetNamespaceFunc.(func(string, *kubernetes.Clientset) (bool, error))(
+ namespace, kubeclient)
+ if err != nil {
+ return "", nil, pkgerrors.Wrap(err, "Error in plugin namespace plugin")
+ }
+
+ if present == false {
+ symGetNamespaceFunc, err := namespacePlugin.Lookup("CreateResource")
+ if err != nil {
+ return "", nil, pkgerrors.Wrap(err, "Error fetching namespace plugin")
+ }
+
+ err = symGetNamespaceFunc.(func(string, *kubernetes.Clientset) error)(
+ namespace, kubeclient)
+ if err != nil {
+ return "", nil, pkgerrors.Wrap(err, "Error creating "+namespace+" namespace")
+ }
+ }
+
+ var path string
+
+ // uuid
+ externalVNFID := generateExternalVNFID(8)
+
+ // cloud1-default-uuid
+ internalVNFID := cloudRegionID + "-" + namespace + "-" + externalVNFID
+
+ csarDirPath := os.Getenv("CSAR_DIR") + "/" + csarID
+ metadataYAMLPath := csarDirPath + "/metadata.yaml"
+
+ seqFile, err := ReadMetadataFile(metadataYAMLPath)
+ if err != nil {
+ return "", nil, pkgerrors.Wrap(err, "Error while reading Metadata File: "+metadataYAMLPath)
+ }
+
+ resourceYAMLNameMap := make(map[string][]string)
+
+ for _, resource := range seqFile.ResourceTypePathMap {
+ for resourceName, resourceFileNames := range resource {
+ // Load/Use Deployment data/client
+
+ var resourceNameList []string
+
+ for _, filename := range resourceFileNames {
+ path = csarDirPath + "/" + filename
+
+ _, err = os.Stat(path)
+ if os.IsNotExist(err) {
+ return "", nil, pkgerrors.New("File " + path + "does not exists")
+ }
+
+ log.Println("Processing file: " + path)
+
+ genericKubeData := &krd.GenericKubeResourceData{
+ YamlFilePath: path,
+ Namespace: namespace,
+ InternalVNFID: internalVNFID,
+ }
+
+ typePlugin, ok := krd.LoadedPlugins[resourceName]
+ if !ok {
+ return "", nil, pkgerrors.New("No plugin for resource " + resourceName + " found")
+ }
+
+ symCreateResourceFunc, err := typePlugin.Lookup("CreateResource")
+ if err != nil {
+ return "", nil, pkgerrors.Wrap(err, "Error fetching "+resourceName+" plugin")
+ }
+
+ // cloud1-default-uuid-sisedeploy
+ internalResourceName, err := symCreateResourceFunc.(func(*krd.GenericKubeResourceData, *kubernetes.Clientset) (string, error))(
+ genericKubeData, kubeclient)
+ if err != nil {
+ return "", nil, pkgerrors.Wrap(err, "Error in plugin "+resourceName+" plugin")
+ }
+
+ // ["cloud1-default-uuid-sisedeploy1", "cloud1-default-uuid-sisedeploy2", ... ]
+ resourceNameList = append(resourceNameList, internalResourceName)
+
+ /*
+ {
+ "deployment": ["cloud1-default-uuid-sisedeploy1", "cloud1-default-uuid-sisedeploy2", ... ]
+ }
+ */
+ resourceYAMLNameMap[resourceName] = resourceNameList
+ }
+ }
+ }
+
+ /*
+ uuid,
+ {
+ "deployment": ["cloud1-default-uuid-sisedeploy1", "cloud1-default-uuid-sisedeploy2", ... ]
+ "service": ["cloud1-default-uuid-sisesvc1", "cloud1-default-uuid-sisesvc2", ... ]
+ },
+ nil
+ */
+ 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 := krd.LoadedPlugins[resourceName]
+ if !ok {
+ return pkgerrors.New("No plugin for resource " + resourceName + " found")
+ }
+
+ symDeleteResourceFunc, err := typePlugin.Lookup("DeleteResource")
+ 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.Clientset) 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(yamlFilePath string) (MetadataFile, error) {
+ var seqFile MetadataFile
+
+ if _, err := os.Stat(yamlFilePath); err == nil {
+ log.Println("Reading metadata YAML: " + yamlFilePath)
+ rawBytes, err := ioutil.ReadFile(yamlFilePath)
+ 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 read error")
+ }
+ }
+
+ return seqFile, nil
+}
diff --git a/src/k8splugin/csar/parser_test.go b/src/k8splugin/csar/parser_test.go
new file mode 100644
index 00000000..cec5395e
--- /dev/null
+++ b/src/k8splugin/csar/parser_test.go
@@ -0,0 +1,130 @@
+/*
+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 csar
+
+import (
+ "io/ioutil"
+ "k8s.io/client-go/kubernetes"
+ "log"
+ "os"
+ "plugin"
+ "testing"
+
+ pkgerrors "github.com/pkg/errors"
+ "gopkg.in/yaml.v2"
+
+ "k8splugin/krd"
+)
+
+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) {
+ oldkrdPluginData := krd.LoadedPlugins
+ oldReadMetadataFile := ReadMetadataFile
+
+ defer func() {
+ krd.LoadedPlugins = oldkrdPluginData
+ ReadMetadataFile = oldReadMetadataFile
+ }()
+
+ err := LoadMockPlugins(&krd.LoadedPlugins)
+ if err != nil {
+ t.Fatalf("TestCreateVNF 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) {
+ externaluuid, data, err := CreateVNF("uuid", "cloudregion1", "test", &kubeclient)
+ if err != nil {
+ t.Fatalf("TestCreateVNF returned an error (%s)", err)
+ }
+
+ log.Println(externaluuid)
+
+ if data == nil {
+ t.Fatalf("TestCreateVNF returned empty data (%s)", data)
+ }
+ })
+
+}
+
+func TestDeleteVNF(t *testing.T) {
+ oldkrdPluginData := krd.LoadedPlugins
+
+ defer func() {
+ krd.LoadedPlugins = oldkrdPluginData
+ }()
+
+ err := LoadMockPlugins(&krd.LoadedPlugins)
+ if err != nil {
+ t.Fatalf("TestCreateVNF 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"},
+ }
+
+ err := DestroyVNF(data, "test", &kubeclient)
+ if err != nil {
+ t.Fatalf("TestCreateVNF returned an error (%s)", 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")
+ if err != nil {
+ t.Fatalf("TestReadMetadataFile returned an error (%s)", err)
+ }
+ })
+}
diff --git a/src/k8splugin/db/DB.go b/src/k8splugin/db/DB.go
new file mode 100644
index 00000000..c8895088
--- /dev/null
+++ b/src/k8splugin/db/DB.go
@@ -0,0 +1,42 @@
+/*
+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 db
+
+import (
+ pkgerrors "github.com/pkg/errors"
+)
+
+// DBconn interface used to talk a concrete Database connection
+var DBconn DatabaseConnection
+
+// DatabaseConnection is an interface for accessing a database
+type DatabaseConnection interface {
+ InitializeDatabase() error
+ CheckDatabase() error
+ CreateEntry(string, string) error
+ ReadEntry(string) (string, bool, error)
+ DeleteEntry(string) error
+ ReadAll(string) ([]string, error)
+}
+
+// CreateDBClient creates the DB client
+var CreateDBClient = func(dbType string) error {
+ switch dbType {
+ case "consul":
+ DBconn = &ConsulDB{}
+ return nil
+ default:
+ return pkgerrors.New(dbType + "DB not supported")
+ }
+}
diff --git a/src/k8splugin/db/consul.go b/src/k8splugin/db/consul.go
new file mode 100644
index 00000000..9ab0d826
--- /dev/null
+++ b/src/k8splugin/db/consul.go
@@ -0,0 +1,112 @@
+/*
+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 db
+
+import (
+ consulapi "github.com/hashicorp/consul/api"
+ pkgerrors "github.com/pkg/errors"
+ "os"
+)
+
+// ConsulDB is an implementation of the DatabaseConnection interface
+type ConsulDB struct {
+ consulClient *consulapi.Client
+}
+
+// InitializeDatabase initialized the initial steps
+func (c *ConsulDB) InitializeDatabase() error {
+ if os.Getenv("DATABASE_IP") == "" {
+ return pkgerrors.New("DATABASE_IP environment variable not set.")
+ }
+ config := consulapi.DefaultConfig()
+ config.Address = os.Getenv("DATABASE_IP") + ":8500"
+
+ client, err := consulapi.NewClient(config)
+ if err != nil {
+ return err
+ }
+ c.consulClient = client
+ return nil
+}
+
+// CheckDatabase checks if the database is running
+func (c *ConsulDB) CheckDatabase() error {
+ kv := c.consulClient.KV()
+ _, _, err := kv.Get("test", nil)
+ if err != nil {
+ return pkgerrors.New("[ERROR] Cannot talk to Datastore. Check if it is running/reachable.")
+ }
+ return nil
+}
+
+// CreateEntry is used to create a DB entry
+func (c *ConsulDB) CreateEntry(key string, value string) error {
+ kv := c.consulClient.KV()
+
+ p := &consulapi.KVPair{Key: key, Value: []byte(value)}
+
+ _, err := kv.Put(p, nil)
+
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// ReadEntry returns the internalID for a particular externalID is present in a namespace
+func (c *ConsulDB) ReadEntry(key string) (string, bool, error) {
+
+ kv := c.consulClient.KV()
+
+ pair, _, err := kv.Get(key, nil)
+
+ if pair == nil {
+ return string("No value found for ID: " + key), false, err
+ }
+ return string(pair.Value), true, err
+}
+
+// DeleteEntry is used to delete an ID
+func (c *ConsulDB) DeleteEntry(key string) error {
+
+ kv := c.consulClient.KV()
+
+ _, err := kv.Delete(key, nil)
+
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// ReadAll is used to get all ExternalIDs in a namespace
+func (c *ConsulDB) ReadAll(prefix string) ([]string, error) {
+ kv := c.consulClient.KV()
+
+ pairs, _, err := kv.List(prefix, nil)
+
+ if len(pairs) == 0 {
+ return []string{""}, err
+ }
+
+ var res []string
+
+ for _, keypair := range pairs {
+ res = append(res, keypair.Key)
+ }
+
+ return res, err
+}
diff --git a/src/k8splugin/db/db_test.go b/src/k8splugin/db/db_test.go
new file mode 100644
index 00000000..7ad252f5
--- /dev/null
+++ b/src/k8splugin/db/db_test.go
@@ -0,0 +1,40 @@
+/*
+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 db
+
+import (
+ "reflect"
+ "testing"
+)
+
+func TestCreateDBClient(t *testing.T) {
+ oldDBconn := DBconn
+
+ defer func() {
+ DBconn = oldDBconn
+ }()
+
+ t.Run("Successfully create DB client", func(t *testing.T) {
+ expectedDB := ConsulDB{}
+
+ err := CreateDBClient("consul")
+ if err != nil {
+ t.Fatalf("TestCreateDBClient returned an error (%s)", err)
+ }
+
+ if !reflect.DeepEqual(DBconn, &expectedDB) {
+ t.Fatalf("TestCreateDBClient set DBconn as:\n result=%v\n expected=%v", DBconn, expectedDB)
+ }
+ })
+}
diff --git a/src/k8splugin/krd/krd.go b/src/k8splugin/krd/krd.go
new file mode 100644
index 00000000..2d06e104
--- /dev/null
+++ b/src/k8splugin/krd/krd.go
@@ -0,0 +1,44 @@
+/*
+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 krd
+
+import (
+ "errors"
+
+ pkgerrors "github.com/pkg/errors"
+
+ "k8s.io/client-go/kubernetes"
+ "k8s.io/client-go/tools/clientcmd"
+)
+
+// GetKubeClient loads the Kubernetes configuation values stored into the local configuration file
+var GetKubeClient = func(configPath string) (kubernetes.Clientset, error) {
+ var clientset *kubernetes.Clientset
+
+ if configPath == "" {
+ return *clientset, errors.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")
+ }
+
+ clientset, err = kubernetes.NewForConfig(config)
+ if err != nil {
+ return *clientset, err
+ }
+
+ return *clientset, nil
+}
diff --git a/src/k8splugin/krd/krd_test.go b/src/k8splugin/krd/krd_test.go
new file mode 100644
index 00000000..7047a74c
--- /dev/null
+++ b/src/k8splugin/krd/krd_test.go
@@ -0,0 +1,34 @@
+/*
+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 krd
+
+import (
+ "reflect"
+ "testing"
+)
+
+func TestGetKubeClient(t *testing.T) {
+ t.Run("Successfully create Kube Client", func(t *testing.T) {
+
+ clientset, err := GetKubeClient("../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")
+ }
+
+ })
+}
diff --git a/src/k8splugin/krd/plugins.go b/src/k8splugin/krd/plugins.go
new file mode 100644
index 00000000..612e3f6b
--- /dev/null
+++ b/src/k8splugin/krd/plugins.go
@@ -0,0 +1,44 @@
+/*
+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 krd
+
+import (
+ "plugin"
+
+ appsV1 "k8s.io/api/apps/v1"
+ coreV1 "k8s.io/api/core/v1"
+ "k8s.io/client-go/kubernetes"
+)
+
+// LoadedPlugins stores references to the stored plugins
+var LoadedPlugins = map[string]*plugin.Plugin{}
+
+// KubeResourceClient has the signature methods to create Kubernetes reources
+type KubeResourceClient interface {
+ CreateResource(GenericKubeResourceData, *kubernetes.Clientset) (string, error)
+ ListResources(string, string) (*[]string, error)
+ DeleteResource(string, string, *kubernetes.Clientset) error
+ GetResource(string, string, *kubernetes.Clientset) (string, error)
+}
+
+// GenericKubeResourceData stores all supported Kubernetes plugin types
+type GenericKubeResourceData struct {
+ YamlFilePath string
+ Namespace string
+ InternalVNFID string
+
+ // Add additional Kubernetes plugins below kinds
+ DeploymentData *appsV1.Deployment
+ ServiceData *coreV1.Service
+}
diff --git a/src/k8splugin/mock_files/mock_configs/mock_config b/src/k8splugin/mock_files/mock_configs/mock_config
new file mode 100644
index 00000000..9b86ff15
--- /dev/null
+++ b/src/k8splugin/mock_files/mock_configs/mock_config
@@ -0,0 +1,29 @@
+# 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.
+
+apiVersion: v1
+kind: Config
+clusters:
+- name: local
+ cluster:
+ insecure-skip-tls-verify: true
+ server: https://192.168.43.66:6443
+contexts:
+- context:
+ cluster: local
+ user: admin
+ name: kubelet-context
+current-context: kubelet-context
+users:
+- name: admin
+ user:
+ password: admin
+ username: admin
diff --git a/src/k8splugin/mock_files/mock_plugins/mockplugin.go b/src/k8splugin/mock_files/mock_plugins/mockplugin.go
new file mode 100644
index 00000000..9ceec342
--- /dev/null
+++ b/src/k8splugin/mock_files/mock_plugins/mockplugin.go
@@ -0,0 +1,43 @@
+/*
+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 main
+
+import (
+ "k8s.io/client-go/kubernetes"
+
+ "k8splugin/krd"
+)
+
+func main() {}
+
+// CreateResource object in a specific Kubernetes resource
+func CreateResource(kubedata *krd.GenericKubeResourceData, kubeclient *kubernetes.Clientset) (string, error) {
+ return "externalUUID", nil
+}
+
+// ListResources of existing resources
+func ListResources(limit int64, namespace string, kubeclient *kubernetes.Clientset) (*[]string, error) {
+ returnVal := []string{"cloud1-default-uuid1", "cloud1-default-uuid2"}
+ return &returnVal, nil
+}
+
+// DeleteResource existing resources
+func DeleteResource(name string, namespace string, kubeclient *kubernetes.Clientset) error {
+ return nil
+}
+
+// GetResource existing resource host
+func GetResource(namespace string, client *kubernetes.Clientset) (bool, error) {
+ return true, nil
+}
diff --git a/src/k8splugin/mock_files/mock_yamls/deployment.yaml b/src/k8splugin/mock_files/mock_yamls/deployment.yaml
new file mode 100644
index 00000000..eff2fc5a
--- /dev/null
+++ b/src/k8splugin/mock_files/mock_yamls/deployment.yaml
@@ -0,0 +1,24 @@
+# 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.
+
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: sise-deploy
+spec:
+ template:
+ metadata:
+ labels:
+ app: sise
+ spec:
+ containers:
+ - name: sise
+ image: mhausenblas/simpleservice:0.5.0 \ No newline at end of file
diff --git a/src/k8splugin/mock_files/mock_yamls/metadata.yaml b/src/k8splugin/mock_files/mock_yamls/metadata.yaml
new file mode 100644
index 00000000..dcc1c32e
--- /dev/null
+++ b/src/k8splugin/mock_files/mock_yamls/metadata.yaml
@@ -0,0 +1,16 @@
+# 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.
+
+resources:
+ - deployment:
+ - deployment.yaml
+ - service:
+ - service.yaml
diff --git a/src/k8splugin/mock_files/mock_yamls/service.yaml b/src/k8splugin/mock_files/mock_yamls/service.yaml
new file mode 100644
index 00000000..297ab1b7
--- /dev/null
+++ b/src/k8splugin/mock_files/mock_yamls/service.yaml
@@ -0,0 +1,21 @@
+# 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.
+
+apiVersion: v1
+kind: Service
+metadata:
+ name: sise-svc
+spec:
+ ports:
+ - port: 80
+ protocol: TCP
+ selector:
+ app: sise \ No newline at end of file
diff --git a/src/k8splugin/plugins/deployment/plugin.go b/src/k8splugin/plugins/deployment/plugin.go
new file mode 100644
index 00000000..2b4c7cb7
--- /dev/null
+++ b/src/k8splugin/plugins/deployment/plugin.go
@@ -0,0 +1,136 @@
+/*
+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 main
+
+import (
+ "io/ioutil"
+ "log"
+ "os"
+
+ "k8s.io/client-go/kubernetes"
+
+ pkgerrors "github.com/pkg/errors"
+
+ appsV1 "k8s.io/api/apps/v1"
+ metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/kubernetes/scheme"
+
+ "k8splugin/krd"
+)
+
+// CreateResource object in a specific Kubernetes Deployment
+func CreateResource(kubedata *krd.GenericKubeResourceData, kubeclient *kubernetes.Clientset) (string, error) {
+ if kubedata.Namespace == "" {
+ kubedata.Namespace = "default"
+ }
+
+ if _, err := os.Stat(kubedata.YamlFilePath); err != nil {
+ return "", pkgerrors.New("File " + kubedata.YamlFilePath + " not found")
+ }
+
+ log.Println("Reading deployment YAML")
+ rawBytes, err := ioutil.ReadFile(kubedata.YamlFilePath)
+ if err != nil {
+ return "", pkgerrors.Wrap(err, "Deployment YAML file read error")
+ }
+
+ log.Println("Decoding deployment YAML")
+ decode := scheme.Codecs.UniversalDeserializer().Decode
+ obj, _, err := decode(rawBytes, nil, nil)
+ if err != nil {
+ return "", pkgerrors.Wrap(err, "Deserialize deployment error")
+ }
+
+ switch o := obj.(type) {
+ case *appsV1.Deployment:
+ kubedata.DeploymentData = o
+ default:
+ return "", pkgerrors.New(kubedata.YamlFilePath + " contains another resource different than Deployment")
+ }
+
+ kubedata.DeploymentData.Namespace = kubedata.Namespace
+ kubedata.DeploymentData.Name = kubedata.InternalVNFID + "-" + kubedata.DeploymentData.Name
+
+ result, err := kubeclient.AppsV1().Deployments(kubedata.Namespace).Create(kubedata.DeploymentData)
+ if err != nil {
+ return "", pkgerrors.Wrap(err, "Create Deployment error")
+ }
+
+ return result.GetObjectMeta().GetName(), nil
+}
+
+// ListResources of existing deployments hosted in a specific Kubernetes Deployment
+func ListResources(limit int64, namespace string, kubeclient *kubernetes.Clientset) (*[]string, error) {
+ if namespace == "" {
+ namespace = "default"
+ }
+
+ opts := metaV1.ListOptions{
+ Limit: limit,
+ }
+ opts.APIVersion = "apps/v1"
+ opts.Kind = "Deployment"
+
+ list, err := kubeclient.AppsV1().Deployments(namespace).List(opts)
+ if err != nil {
+ return nil, pkgerrors.Wrap(err, "Get Deployment list error")
+ }
+
+ result := make([]string, 0, limit)
+ if list != nil {
+ for _, deployment := range list.Items {
+ result = append(result, deployment.Name)
+ }
+ }
+
+ return &result, nil
+}
+
+// DeleteResource existing deployments hosting in a specific Kubernetes Deployment
+func DeleteResource(name string, namespace string, kubeclient *kubernetes.Clientset) error {
+ if namespace == "" {
+ namespace = "default"
+ }
+
+ log.Println("Deleting deployment: " + name)
+
+ deletePolicy := metaV1.DeletePropagationForeground
+ err := kubeclient.AppsV1().Deployments(namespace).Delete(name, &metaV1.DeleteOptions{
+ PropagationPolicy: &deletePolicy,
+ })
+
+ if err != nil {
+ return pkgerrors.Wrap(err, "Delete Deployment error")
+ }
+
+ return nil
+}
+
+// GetResource existing deployment hosting in a specific Kubernetes Deployment
+func GetResource(name string, namespace string, kubeclient *kubernetes.Clientset) (string, error) {
+ if namespace == "" {
+ namespace = "default"
+ }
+
+ opts := metaV1.GetOptions{}
+ opts.APIVersion = "apps/v1"
+ opts.Kind = "Deployment"
+
+ deployment, err := kubeclient.AppsV1().Deployments(namespace).Get(name, opts)
+ if err != nil {
+ return "", pkgerrors.Wrap(err, "Get Deployment error")
+ }
+
+ return deployment.Name, nil
+}
diff --git a/src/k8splugin/plugins/namespace/plugin.go b/src/k8splugin/plugins/namespace/plugin.go
new file mode 100644
index 00000000..986de863
--- /dev/null
+++ b/src/k8splugin/plugins/namespace/plugin.go
@@ -0,0 +1,68 @@
+/*
+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 main
+
+import (
+ pkgerrors "github.com/pkg/errors"
+
+ coreV1 "k8s.io/api/core/v1"
+ metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/kubernetes"
+)
+
+// CreateResource is used to create a new Namespace
+func CreateResource(namespace string, client *kubernetes.Clientset) error {
+ namespaceStruct := &coreV1.Namespace{
+ ObjectMeta: metaV1.ObjectMeta{
+ Name: namespace,
+ },
+ }
+ _, err := client.CoreV1().Namespaces().Create(namespaceStruct)
+ if err != nil {
+ return pkgerrors.Wrap(err, "Create Namespace error")
+ }
+ return nil
+}
+
+// GetResource is used to check if a given namespace actually exists in Kubernetes
+func GetResource(namespace string, client *kubernetes.Clientset) (bool, error) {
+ opts := metaV1.ListOptions{}
+
+ namespaceList, err := client.CoreV1().Namespaces().List(opts)
+ if err != nil {
+ return false, pkgerrors.Wrap(err, "Get Namespace list error")
+ }
+
+ for _, ns := range namespaceList.Items {
+ if namespace == ns.Name {
+ return true, nil
+ }
+ }
+
+ return false, nil
+}
+
+// DeleteResource is used to delete a namespace
+func DeleteResource(namespace string, client *kubernetes.Clientset) error {
+ deletePolicy := metaV1.DeletePropagationForeground
+
+ err := client.CoreV1().Namespaces().Delete(namespace, &metaV1.DeleteOptions{
+ PropagationPolicy: &deletePolicy,
+ })
+
+ if err != nil {
+ return pkgerrors.Wrap(err, "Delete Namespace error")
+ }
+ return nil
+}
diff --git a/src/k8splugin/plugins/service/plugin.go b/src/k8splugin/plugins/service/plugin.go
new file mode 100644
index 00000000..36ef24f6
--- /dev/null
+++ b/src/k8splugin/plugins/service/plugin.go
@@ -0,0 +1,131 @@
+/*
+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 main
+
+import (
+ "io/ioutil"
+ "log"
+ "os"
+
+ "k8s.io/client-go/kubernetes"
+
+ pkgerrors "github.com/pkg/errors"
+
+ coreV1 "k8s.io/api/core/v1"
+ metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/kubernetes/scheme"
+
+ "k8splugin/krd"
+)
+
+// CreateResource object in a specific Kubernetes Deployment
+func CreateResource(kubedata *krd.GenericKubeResourceData, kubeclient *kubernetes.Clientset) (string, error) {
+ if kubedata.Namespace == "" {
+ kubedata.Namespace = "default"
+ }
+
+ if _, err := os.Stat(kubedata.YamlFilePath); err != nil {
+ return "", pkgerrors.New("File " + kubedata.YamlFilePath + " not found")
+ }
+
+ log.Println("Reading service YAML")
+ rawBytes, err := ioutil.ReadFile(kubedata.YamlFilePath)
+ if err != nil {
+ return "", pkgerrors.Wrap(err, "Service YAML file read error")
+ }
+
+ log.Println("Decoding service YAML")
+ decode := scheme.Codecs.UniversalDeserializer().Decode
+ obj, _, err := decode(rawBytes, nil, nil)
+ if err != nil {
+ return "", pkgerrors.Wrap(err, "Deserialize service error")
+ }
+
+ switch o := obj.(type) {
+ case *coreV1.Service:
+ kubedata.ServiceData = o
+ default:
+ return "", pkgerrors.New(kubedata.YamlFilePath + " contains another resource different than Service")
+ }
+
+ kubedata.ServiceData.Namespace = kubedata.Namespace
+ kubedata.ServiceData.Name = kubedata.InternalVNFID + "-" + kubedata.ServiceData.Name
+
+ result, err := kubeclient.CoreV1().Services(kubedata.Namespace).Create(kubedata.ServiceData)
+ if err != nil {
+ return "", pkgerrors.Wrap(err, "Create Service error")
+ }
+ return result.GetObjectMeta().GetName(), nil
+}
+
+// ListResources of existing deployments hosted in a specific Kubernetes Deployment
+func ListResources(limit int64, namespace string, kubeclient *kubernetes.Clientset) (*[]string, error) {
+ if namespace == "" {
+ namespace = "default"
+ }
+ opts := metaV1.ListOptions{
+ Limit: limit,
+ }
+ opts.APIVersion = "apps/v1"
+ opts.Kind = "Service"
+
+ list, err := kubeclient.CoreV1().Services(namespace).List(opts)
+ if err != nil {
+ return nil, pkgerrors.Wrap(err, "Get Service list error")
+ }
+ result := make([]string, 0, limit)
+ if list != nil {
+ for _, service := range list.Items {
+ result = append(result, service.Name)
+ }
+ }
+ return &result, nil
+}
+
+// DeleteResource deletes an existing Kubernetes service
+func DeleteResource(name string, namespace string, kubeclient *kubernetes.Clientset) error {
+ if namespace == "" {
+ namespace = "default"
+ }
+
+ log.Println("Deleting service: " + name)
+
+ deletePolicy := metaV1.DeletePropagationForeground
+ err := kubeclient.CoreV1().Services(namespace).Delete(name, &metaV1.DeleteOptions{
+ PropagationPolicy: &deletePolicy,
+ })
+ if err != nil {
+ return pkgerrors.Wrap(err, "Delete Service error")
+ }
+
+ return nil
+}
+
+// GetResource existing service hosting in a specific Kubernetes Service
+func GetResource(name string, namespace string, kubeclient *kubernetes.Clientset) (string, error) {
+ if namespace == "" {
+ namespace = "default"
+ }
+
+ opts := metaV1.GetOptions{}
+ opts.APIVersion = "apps/v1"
+ opts.Kind = "Service"
+
+ service, err := kubeclient.CoreV1().Services(namespace).Get(name, opts)
+ if err != nil {
+ return "", pkgerrors.Wrap(err, "Get Deployment error")
+ }
+
+ return service.Name, nil
+}