Seed code for k8s multicloud plugin 13/61413/15
authorShashank Kumar Shankar <shashank.kumar.shankar@intel.com>
Mon, 20 Aug 2018 22:50:50 +0000 (15:50 -0700)
committerVictor Morales <victor.morales@intel.com>
Fri, 24 Aug 2018 22:51:16 +0000 (15:51 -0700)
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>
34 files changed:
.gitignore [new file with mode: 0644]
.gitreview [new file with mode: 0644]
README.md [new file with mode: 0644]
deployments/Dockerfile [new file with mode: 0644]
deployments/build.sh [new file with mode: 0755]
deployments/docker-compose.yml [new file with mode: 0644]
doc/create_vl.png [new file with mode: 0644]
doc/create_vnf.png [new file with mode: 0644]
doc/sampleCommands.rst [new file with mode: 0644]
doc/swagger.yaml [new file with mode: 0644]
src/k8splugin/Gopkg.lock [new file with mode: 0644]
src/k8splugin/Gopkg.toml [new file with mode: 0644]
src/k8splugin/Makefile [new file with mode: 0644]
src/k8splugin/api/api.go [new file with mode: 0644]
src/k8splugin/api/handler.go [new file with mode: 0644]
src/k8splugin/api/handler_test.go [new file with mode: 0644]
src/k8splugin/api/model.go [new file with mode: 0644]
src/k8splugin/cmd/main.go [new file with mode: 0644]
src/k8splugin/csar/parser.go [new file with mode: 0644]
src/k8splugin/csar/parser_test.go [new file with mode: 0644]
src/k8splugin/db/DB.go [new file with mode: 0644]
src/k8splugin/db/consul.go [new file with mode: 0644]
src/k8splugin/db/db_test.go [new file with mode: 0644]
src/k8splugin/krd/krd.go [new file with mode: 0644]
src/k8splugin/krd/krd_test.go [new file with mode: 0644]
src/k8splugin/krd/plugins.go [new file with mode: 0644]
src/k8splugin/mock_files/mock_configs/mock_config [new file with mode: 0644]
src/k8splugin/mock_files/mock_plugins/mockplugin.go [new file with mode: 0644]
src/k8splugin/mock_files/mock_yamls/deployment.yaml [new file with mode: 0644]
src/k8splugin/mock_files/mock_yamls/metadata.yaml [new file with mode: 0644]
src/k8splugin/mock_files/mock_yamls/service.yaml [new file with mode: 0644]
src/k8splugin/plugins/deployment/plugin.go [new file with mode: 0644]
src/k8splugin/plugins/namespace/plugin.go [new file with mode: 0644]
src/k8splugin/plugins/service/plugin.go [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..f0b583f
--- /dev/null
@@ -0,0 +1,23 @@
+# IDE
+.DS_Store
+.vscode
+*-workspace
+
+# Directories
+pkg
+bin
+target
+src/github.com
+src/golang.org
+src/k8splugin/vendor
+src/k8splugin/kubeconfig/*
+deployments/k8plugin
+
+# Binaries
+*.so
+src/k8splugin/csar/mock_plugins/*.so
+src/k8splugin/plugins/**/*.so
+
+# Tests
+*.test
+*.out
diff --git a/.gitreview b/.gitreview
new file mode 100644 (file)
index 0000000..aab7df3
--- /dev/null
@@ -0,0 +1,4 @@
+[gerrit]
+host=gerrit.onap.org
+port=29418
+project=multicloud/k8s.git
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..0e62378
--- /dev/null
+++ b/README.md
@@ -0,0 +1,41 @@
+<!-- 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. -->
+
+# MultiCloud-k8-plugin
+
+MultiCloud Kubernetes plugin for ONAP multicloud.
+
+# Installation
+
+Requirements:
+* Go 1.10
+* Dep
+
+Steps:
+
+* Clone repo in GOPATH src:
+    * `cd $GOPATH/src && git clone https://git.onap.org/multicloud/k8s`
+
+* Run unit tests:
+    *  `make build`
+
+* Compile to build Binary:
+    * `make deploy`
+
+# Archietecture
+
+Create Virtual Network Function
+
+![Create VNF](./doc/create_vnf.png)
+
+Create Virtual Link
+
+![Create VL](./doc/create_vl.png)
diff --git a/deployments/Dockerfile b/deployments/Dockerfile
new file mode 100644 (file)
index 0000000..407af50
--- /dev/null
@@ -0,0 +1,21 @@
+FROM debian:jessie
+
+ARG HTTP_PROXY=${HTTP_PROXY}
+ARG HTTPS_PROXY=${HTTPS_PROXY}
+
+ENV http_proxy $HTTP_PROXY
+ENV https_proxy $HTTPS_PROXY
+ENV no_proxy $NO_PROXY
+
+ENV CSAR_DIR "/opt/csar"
+ENV KUBE_CONFIG_DIR "/opt/kubeconfig"
+ENV DATABASE_TYPE "consul"
+ENV DATABASE_IP "127.0.0.1"
+
+EXPOSE 8081
+
+WORKDIR /opt/multicloud/k8s
+ADD ./k8plugin ./
+ADD ./*.so ./
+
+CMD ["./k8plugin"]
diff --git a/deployments/build.sh b/deployments/build.sh
new file mode 100755 (executable)
index 0000000..667be5f
--- /dev/null
@@ -0,0 +1,32 @@
+#!/bin/bash
+# SPDX-license-identifier: Apache-2.0
+##############################################################################
+# Copyright (c) 2018 Intel Corporation
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+set -o nounset
+set -o pipefail
+set -o xtrace
+
+function generate_binary {
+    GOPATH=$(go env GOPATH)
+    rm -f k8plugin
+    rm -f *.so
+    $GOPATH/bin/dep ensure -v
+    for plugin in deployment namespace service; do
+        CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -buildmode=plugin -a -tags netgo -o ./$plugin.so ../src/k8splugin/plugins/$plugin/plugin.go
+    done
+    CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -a -tags netgo -o ./k8plugin ../src/k8splugin/cmd/main.go
+}
+
+function build_image {
+    echo "Start build docker image."
+    docker-compose build --no-cache
+}
+
+generate_binary
+build_image
diff --git a/deployments/docker-compose.yml b/deployments/docker-compose.yml
new file mode 100644 (file)
index 0000000..0d347b1
--- /dev/null
@@ -0,0 +1,41 @@
+# 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.
+
+version: '3'
+
+services:
+  multicloud-k8s:
+    image: nexus3.onap.org:10003/onap/multicloud/k8plugin
+    build:
+      context: ./
+      args:
+        - HTTP_PROXY=$HTTP_PROXY
+        - HTTPS_PROXY=$HTTPS_PROXY
+    ports:
+      - "8081:8081"
+    environment:
+      - CSAR_DIR=/opt/csar
+      - KUBE_CONFIG_DIR=/opt/kubeconfig
+      - DATABASE_TYPE=consul
+      - DATABASE_IP=consul-svr
+    depends_on:
+      - "consul"
+    volumes:
+      - /opt/csar:/opt/csar
+      - /opt/kubeconfig:/opt/kubeconfig
+  consul:
+    image: consul
+    hostname: consul-svr
+    environment:
+      - CONSUL_LOCAL_CONFIG={"datacenter":"us_west","server":true}
+    command: ["agent", "-server", "-bootstrap-expect=1"]
+    volumes:
+      - /opt/consul/config:/consul/config
diff --git a/doc/create_vl.png b/doc/create_vl.png
new file mode 100644 (file)
index 0000000..803b6da
Binary files /dev/null and b/doc/create_vl.png differ
diff --git a/doc/create_vnf.png b/doc/create_vnf.png
new file mode 100644 (file)
index 0000000..27b50d7
Binary files /dev/null and b/doc/create_vnf.png differ
diff --git a/doc/sampleCommands.rst b/doc/sampleCommands.rst
new file mode 100644 (file)
index 0000000..e8b53cf
--- /dev/null
@@ -0,0 +1,71 @@
+# 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.
+
+# Sample Commands:
+
+* POST
+    URL:`localhost:8081/v1/vnf_instances/cloudregion1/namespacetest`
+    Request Body:
+
+    ```
+    {
+           "cloud_region_id": "region1",
+           "csar_id": "uuid",
+        "namespace": "test",
+           "oof_parameters": [{
+                   "key1": "value1",
+                   "key2": "value2",
+                   "key3": {}
+           }],
+           "network_parameters": {
+                   "oam_ip_address": {
+                           "connection_point": "string",
+                           "ip_address": "string",
+                           "workload_name": "string"
+                   }
+           }
+    }
+    ```
+
+    Expected Response:
+    ```
+    {
+        "response": "Created Deployment:nginx-deployment"
+    }
+    ```
+
+    The above POST request will download the following YAML file and run it on the Kubernetes cluster.
+
+    ```
+    apiVersion: apps/v1
+    kind: Deployment
+    metadata:
+    name: nginx-deployment
+    labels:
+        app: nginx
+    spec:
+    replicas: 3
+    selector:
+        matchLabels:
+        app: nginx
+    template:
+        metadata:
+        labels:
+            app: nginx
+        spec:
+        containers:
+        - name: nginx
+            image: nginx:1.7.9
+            ports:
+            - containerPort: 80
+    ```
+* GET
+    URL: `localhost:8081/v1/vnf_instances`
diff --git a/doc/swagger.yaml b/doc/swagger.yaml
new file mode 100644 (file)
index 0000000..3b7e36b
--- /dev/null
@@ -0,0 +1,184 @@
+# 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.
+
+swagger: "2.0"
+info:
+  description: "API reference for MultiCloud Kubernetes Plugin."
+  version: "1.0.0"
+  title: "API reference for MultiCloud Kubernetes Plugin."
+  contact:
+    url: "https://wiki.onap.org/display/DW/Support+for+K8S+%28Kubernetes%29+based+Cloud+regions"
+  license:
+    name: "Apache 2.0"
+    url: "http://www.apache.org/licenses/LICENSE-2.0.html"
+basePath: "/v1.0"
+schemes:
+- "http"
+paths:
+  /vnf_instances:
+    post:
+      tags:
+      - "Deployment of VNF Containers"
+      summary: "Create Kubernetes based VNFs."
+      description: "Endpoint to create Kubernetes based VNFs."
+      consumes:
+      - "application/json"
+      produces:
+      - "application/json"
+      parameters:
+      - in: "body"
+        name: "body"
+        description: "Create new VNF containers"
+        required: true
+        schema:
+          $ref: "#/definitions/POSTRequest"
+      responses:
+        200:
+          description: "successful operation"
+          schema:
+            $ref: "#/definitions/POSTResponse"
+  /vnf_instances/:
+    get:
+      tags:
+      - "Deployment of VNF Containers"
+      summary: "List all Kubernetes based VNFs."
+      description: "Endpoint to list all Kubernetes based VNF."
+      produces:
+      - "application/json"
+      responses:
+        200:
+          description: "successful operation"
+          schema:
+            $ref: "#/definitions/GETSResponse"
+  /vnf_instances/{name}:
+    get:
+      tags:
+      - "Deployment of VNF Containers"
+      summary: "Get details of a Kubernetes based VNFs."
+      description: "Endpoint to get details of a Kubernetes based VNFs."
+      produces:
+      - "application/json"
+      parameters:
+      - name: "name"
+        in: "path"
+        description: "Name used to query"
+        required: true
+        type: "string"
+      responses:
+        200:
+          description: "successful operation"
+          schema:
+            $ref: "#/definitions/GETResponse"
+    patch:
+      tags:
+      - "Deployment of VNF Containers"
+      summary: "Update a Kubernetes based VNFs."
+      description: "Endp to update a Kubernetes based VNFs."
+      produces:
+        - "application/json"
+      parameters:
+        - name: "name"
+          in: "path"
+          description: "Name used to patch"
+          required: true
+          type: "string"
+        - name: "body"
+          in: "body"
+          description: "Patch an existing Kubernetes based VNFs."
+          required: true
+          schema:
+            $ref: "#/definitions/PATCHRequest"
+      responses:
+        200:
+          description: "successful operation"
+          schema:
+            $ref: "#/definitions/PATCHResponse"
+    delete:
+      tags:
+        - "Deployment of VNF Containers"
+      summary: "Delete a Kubernetes based VNFs."
+      description: "Endpoint to delete a Kubernetes based VNFs."
+      produces:
+        - "application/json"
+      parameters:
+        - name: "name"
+          in: "path"
+          description: "Name used to delete"
+          required: true
+          type: "string"
+      responses:
+        200:
+          description: "successful operation"
+          schema:
+            $ref: "#/definitions/DELETEResponse"
+definitions:
+  POSTRequest:
+    type: "object"
+    properties:
+      cloud_region_id:
+        type: "string"
+      csar_id:
+        type: "string"
+      namespace:
+        type: "string"
+      oof_parameters:
+        items:
+          type: "object"
+          additionalProperties: true
+          example:
+            key1: value1
+            key2: value2
+            key3: {}
+      network_parameters:
+        type: "object"
+        properties:
+          oam_ip_address:
+            type: "object"
+            properties:
+              connection_point:
+                type: "string"
+              ip_address:
+                type: "string"
+              workload_name:
+                type: "string"
+  POSTResponse:
+    type: "object"
+    properties:
+      vnf_id:
+        type: "string"
+      name:
+        type: "string"
+  GETSResponse:
+    type: "object"
+    properties:
+      vnf_list:
+        items:
+          type: "string"
+  GETResponse:
+    type: "object"
+    properties:
+      response:
+        type: "string"
+  PATCHRequest:
+    type: "object"
+    properties:
+      name:
+        type: "string"
+  PATCHResponse:
+    type: "object"
+    properties:
+      response:
+        type: "string"
+  DELETEResponse:
+    type: "object"
+    properties:
+      response:
+        type: "string"
\ No newline at end of file
diff --git a/src/k8splugin/Gopkg.lock b/src/k8splugin/Gopkg.lock
new file mode 100644 (file)
index 0000000..e027683
--- /dev/null
@@ -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 (file)
index 0000000..219b502
--- /dev/null
@@ -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 (file)
index 0000000..586eca9
--- /dev/null
@@ -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 (file)
index 0000000..651d931
--- /dev/null
@@ -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 (file)
index 0000000..27d060a
--- /dev/null
@@ -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 (file)
index 0000000..df573d9
--- /dev/null
@@ -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 (file)
index 0000000..0e4863c
--- /dev/null
@@ -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 (file)
index 0000000..ee67654
--- /dev/null
@@ -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 (file)
index 0000000..abd6ad9
--- /dev/null
@@ -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 (file)
index 0000000..cec5395
--- /dev/null
@@ -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 (file)
index 0000000..c889508
--- /dev/null
@@ -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 (file)
index 0000000..9ab0d82
--- /dev/null
@@ -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 (file)
index 0000000..7ad252f
--- /dev/null
@@ -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 (file)
index 0000000..2d06e10
--- /dev/null
@@ -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 (file)
index 0000000..7047a74
--- /dev/null
@@ -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 (file)
index 0000000..612e3f6
--- /dev/null
@@ -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 (file)
index 0000000..9b86ff1
--- /dev/null
@@ -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 (file)
index 0000000..9ceec34
--- /dev/null
@@ -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 (file)
index 0000000..eff2fc5
--- /dev/null
@@ -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 (file)
index 0000000..dcc1c32
--- /dev/null
@@ -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 (file)
index 0000000..297ab1b
--- /dev/null
@@ -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 (file)
index 0000000..2b4c7cb
--- /dev/null
@@ -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 (file)
index 0000000..986de86
--- /dev/null
@@ -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 (file)
index 0000000..36ef24f
--- /dev/null
@@ -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
+}