aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorShashank Kumar Shankar <shashank.kumar.shankar@intel.com>2018-08-20 15:50:50 -0700
committerVictor Morales <victor.morales@intel.com>2018-08-24 15:51:16 -0700
commita1373742a2c3f980360e4980f3b23b0ff3480ae6 (patch)
treece2fb583dea15b8a546d794d21786fdf0f666539 /src
parent6ff216219ccb4567baeb34c9dba73daabb60f629 (diff)
Seed code for k8s multicloud plugin
This patch provides the initial seed code for the multicloud Kubernetes plugin and also provides the plugin feature to add new Kubernetes kinds. Change-Id: Ie5ee414656665070cde2834c4855ac2ebc179a9a Issue-ID: MULTICLOUD-301 Signed-off-by: Shashank Kumar Shankar <shashank.kumar.shankar@intel.com> Signed-off-by: Victor Morales <victor.morales@intel.com>
Diffstat (limited to 'src')
-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
+}