aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--deployments/Dockerfile5
-rw-r--r--kud/sites/examples/provider_nw_setup.sh65
-rw-r--r--kud/tests/ovn_provider_nw_test.sh191
-rwxr-xr-xkud/tests/plugin.sh43
-rw-r--r--src/k8splugin/api/api.go7
-rw-r--r--src/k8splugin/api/brokerhandler.go164
-rw-r--r--src/k8splugin/api/brokerhandler_test.go224
-rw-r--r--src/k8splugin/cmd/main.go11
-rw-r--r--src/k8splugin/go.mod3
-rw-r--r--src/k8splugin/go.sum1
-rw-r--r--src/k8splugin/internal/app/client.go146
-rw-r--r--src/k8splugin/internal/app/client_test.go6
-rw-r--r--src/k8splugin/internal/app/instance.go4
-rw-r--r--src/k8splugin/internal/auth/auth.go107
-rw-r--r--src/k8splugin/internal/auth/auth_test.go47
-rw-r--r--src/k8splugin/mock_files/mock_certs/auth_test_certificate.pem21
-rw-r--r--src/k8splugin/mock_files/mock_certs/auth_test_key.pem28
-rw-r--r--src/k8splugin/plugins/generic/plugin.go109
18 files changed, 1141 insertions, 41 deletions
diff --git a/deployments/Dockerfile b/deployments/Dockerfile
index 961f6766..1dcb5a30 100644
--- a/deployments/Dockerfile
+++ b/deployments/Dockerfile
@@ -31,10 +31,11 @@ RUN apt-get update && apt-get install -y -qq apt-transport-https curl \
&& apt-get update && apt install -y -qq ovn-common
WORKDIR /opt/multicloud/k8s
-ADD ./k8plugin ./
-ADD ./*.so ./
RUN chown onap:onap /opt/multicloud/k8s -R
+ADD --chown=onap ./k8plugin ./
+ADD --chown=onap ./bin/*.so ./
+
USER onap
CMD ["./k8plugin"]
diff --git a/kud/sites/examples/provider_nw_setup.sh b/kud/sites/examples/provider_nw_setup.sh
new file mode 100644
index 00000000..fbe0011d
--- /dev/null
+++ b/kud/sites/examples/provider_nw_setup.sh
@@ -0,0 +1,65 @@
+#!/bin/bash
+# SPDX-license-identifier: Apache-2.0
+##############################################################################
+# Copyright (c) 2018
+# 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 errexit
+set -o nounset
+set -o pipefail
+
+map_list=""
+function create_vlan {
+ local dev=$1
+ local id=$2
+ local name=$3
+
+ # Create VLAN for device
+ sudo ip link add link $dev name $name type vlan id $id
+ #sudo ip addr add $ip dev $name
+ sudo ip link set $name up
+}
+
+# Create provider network for interface
+function create_provider_network {
+ local provider_name=$1
+ local interface=$2
+
+ bridge_name=br-$provider_name
+ network_name=nw_$provider_name
+ port_name=server-localnet_$provider_name
+
+ # Create OVS bridge and move the interface to the bridge
+ sudo ovs-vsctl --may-exist add-br $bridge_name
+ sudo ovs-vsctl --may-exist add-port $bridge_name $interface
+
+ #Create OVN Switch
+ sudo ovn-nbctl --may-exist ls-add $provider_name
+ # Add port of type localnet to the Switch
+ sudo ovn-nbctl --may-exist lsp-add $provider_name $port_name
+ sudo ovn-nbctl lsp-set-addresses $port_name unknown
+ sudo ovn-nbctl lsp-set-type $port_name localnet
+ #Set port with the network name to map to ovs bridge
+ sudo ovn-nbctl lsp-set-options $port_name network_name=$network_name
+ # Prepare bridge to network mapping for OVS
+ map_list=${map_list}${network_name}:${bridge_name},
+}
+
+create_vlan eth1 100 eth1.100
+create_vlan eth1 200 eth1.200
+
+provider_net1=prod-net1
+provider_net2=prod-net2
+
+create_provider_network $provider_net1 eth1.100
+create_provider_network $provider_net2 eth1.200
+
+#Set OVS with the bridge to network mapping
+map_list=${map_list%?}
+sudo ovs-vsctl set open . external-ids:ovn-bridge-mappings=$map_list
+
+
diff --git a/kud/tests/ovn_provider_nw_test.sh b/kud/tests/ovn_provider_nw_test.sh
new file mode 100644
index 00000000..9b32d904
--- /dev/null
+++ b/kud/tests/ovn_provider_nw_test.sh
@@ -0,0 +1,191 @@
+#!/bin/bash
+# SPDX-license-identifier: Apache-2.0
+##############################################################################
+# Copyright (c) 2018
+# 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 errexit
+set -o nounset
+set -o pipefail
+
+source _common.sh
+source _functions.sh
+
+# populate_CSAR_ovn4nfv() - Create content used for OVN4NFV functional test
+function populate_CSAR_provider_network {
+ local csar_id=$1
+
+ _checks_args $csar_id
+ pushd ${CSAR_DIR}/${csar_id}
+
+ cat << MULTUS_NET > onap-ovn4nfvk8s-network.yaml
+apiVersion: "k8s.cni.cncf.io/v1"
+kind: NetworkAttachmentDefinition
+metadata:
+ name: $ovn_multus_network_name
+spec:
+ config: '{
+ "cniVersion": "0.3.1",
+ "name": "ovn4nfv-k8s-plugin",
+ "type": "ovn4nfvk8s-cni"
+ }'
+MULTUS_NET
+
+ cat << NETWORK > ovn-virt-net1.yaml
+apiVersion: v1
+kind: onapNetwork
+metadata:
+ name: ovn-virt-net1
+ cnitype : ovn4nfvk8s
+spec:
+ name: ovn-virt-net1
+ subnet: 10.1.20.0/24
+ gateway: 10.1.20.1/24
+NETWORK
+
+ cat << NETWORK > ovn-virt-net2.yaml
+apiVersion: v1
+kind: onapNetwork
+metadata:
+ name: ovn-virt-net2
+ cnitype : ovn4nfvk8s
+spec:
+ name: ovn-virt-net2
+ subnet: 10.1.21.0/24
+ gateway: 10.1.21.1/24
+NETWORK
+
+ cat << DEPLOYMENT > firewall.yaml
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: firewall
+ labels:
+ app: ovn4nfv
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: ovn4nfv
+ template:
+ metadata:
+ labels:
+ app: ovn4nfv
+ annotations:
+ k8s.v1.cni.cncf.io/networks: '[{ "name": "$ovn_multus_network_name"}]'
+ ovnNetwork: '[ { "name": ""ovn-virt-net1"", "interface": "net0" , "defaultGateway": "false", "ipAddress":"10.1.20.2"},
+ { "name": "prod-net1", "interface": "net1", "defaultGateway": "false", "ipAddress":"10.1.5.1/24"}]'
+ ovnNetworkRoutes: '[{ "dst": "0.0.0.0/0", "gw": "10.1.20.3", "dev": "net0" }]'
+
+ spec:
+ containers:
+ - name: firewall
+ image: "busybox"
+ command: ["top"]
+ stdin: true
+ tty: true
+DEPLOYMENT
+
+ cat << DEPLOYMENT > webcache.yaml
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: webcache
+ labels:
+ app: ovn4nfv
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: ovn4nfv
+ template:
+ metadata:
+ labels:
+ app: ovn4nfv
+ annotations:
+ k8s.v1.cni.cncf.io/networks: '[{ "name": "$ovn_multus_network_name"}]'
+ ovnNetwork: '[{ "name": "ovn-virt-net1", "interface": "net0" , "defaultGateway": "false", "ipAddress":"10.1.20.3"},
+ { "name": "ovn-virt-net2", "interface": "net1" , "defaultGateway": "false", "ipAddress":"10.1.21.2"}]'
+ ovnNetworkRoutes: '[{ "dst": "10.1.5.0/24", "gw": "10.1.20.2", "dev": "net0" },
+ { "dst": "0.0.0.0/0", "gw": "10.1.21.3", "dev": "net1" }]'
+
+ spec:
+ containers:
+ - name: webcache
+ image: "busybox"
+ command: ["top"]
+ stdin: true
+ tty: true
+DEPLOYMENT
+
+ cat << DEPLOYMENT > sdwan.yaml
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: sdwan
+ labels:
+ app: ovn4nfv
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: ovn4nfv
+ template:
+ metadata:
+ labels:
+ app: ovn4nfv
+ annotations:
+ k8s.v1.cni.cncf.io/networks: '[{ "name": "$ovn_multus_network_name"}]'
+ ovnNetwork: '[ { "name": ""ovn-virt-net2"", "interface": "net0" , "defaultGateway": "false", "ipAddress":"10.1.21.3"},
+ { "name": "prod-net2", "interface": "net1", "defaultGateway": "false", "ipAddress":"10.1.10.2/24"}]'
+ ovnNetworkRoutes: '[{ "dst": "0.0.0.0/0", "gw": "10.1.10.1", "dev": "net1" },
+ { "dst": "10.1.5.0/24", "gw": "10.1.21.2", "dev": "net0" },
+ { "dst": "10.1.20.0/24", "gw": "10.1.21.2", "dev": "net0" }]'
+
+ spec:
+ containers:
+ - name: sdwan
+ image: "busybox"
+ command: ["top"]
+ stdin: true
+ tty: true
+DEPLOYMENT
+ popd
+}
+
+csar_id=d5718572-3b9a-11e9-b210-d663bd873dda
+# Setup
+install_ovn_deps
+populate_CSAR_provider_network $csar_id
+
+pushd ${CSAR_DIR}/${csar_id}
+for net in ovn-virt-net1 ovn-virt-net2; do
+ cleanup_network $net.yaml
+ echo "Create OVN Network $net network"
+ init_network $net.yaml
+done
+kubectl apply -f onap-ovn4nfvk8s-network.yaml
+setup firewall webcache sdwan
+
+# Test
+deployment_pod=$(kubectl get pods | grep firewall | awk '{print $1}')
+echo "===== $deployment_pod details ====="
+kubectl exec -it $deployment_pod -- ip a
+multus_nic=$(kubectl exec -it $deployment_pod -- ifconfig | grep "net1")
+if [ -z "$multus_nic" ]; then
+ echo "The $deployment_pod pod doesn't contain the net1 nic"
+ exit 1
+fi
+
+# Teardown
+teardown firewall webcache sdwan
+cleanup_network ovn-virt-net1.yaml
+cleanup_network ovn-virt-net2.yaml
+popd
+
+
+
diff --git a/kud/tests/plugin.sh b/kud/tests/plugin.sh
index 2de55b61..1137f1b9 100755
--- a/kud/tests/plugin.sh
+++ b/kud/tests/plugin.sh
@@ -17,12 +17,14 @@ source _common.sh
source _functions.sh
base_url="http://localhost:8081"
+#Will resolve to file $KUBE_CONFIG_DIR/kud
cloud_region_id="kud"
namespace="testns"
csar_id="94e414f6-9ca4-11e8-bb6a-52540067263b"
rb_name="test-rbdef"
rb_version="v1"
profile_name="profile1"
+release_name="testrelease"
vnf_customization_uuid="ebe353d2-30b7-11e9-9515-525400277b3d"
# _build_generic_sim() - Creates a generic simulator image in case that doesn't exist
@@ -95,7 +97,7 @@ payload_raw="
\"profile-name\": \"${profile_name}\",
\"rb-name\": \"${rb_name}\",
\"rb-version\": \"${rb_version}\",
- \"release-name\": \"testrelease\",
+ \"release-name\": \"${release_name}\",
\"namespace\": \"$namespace\",
\"kubernetesversion\": \"$kubeversion\",
\"labels\": {
@@ -120,31 +122,22 @@ fi
print_msg "Instantiate Profile"
payload_raw="
{
- \"cloud_region_id\": \"$cloud_region_id\",
+ \"cloud-region\": \"$cloud_region_id\",
\"rb-name\":\"$rb_name\",
\"rb-version\":\"$rb_version\",
- \"profile-name\":\"$profile_name\",
- \"csar_id\": \"$csar_id\"
+ \"profile-name\":\"$profile_name\"
}
"
payload=$(echo $payload_raw | tr '\n' ' ')
-vnf_id=$(curl -s -d "$payload" "${base_url}/v1/vnf_instances/" | jq -r '.vnf_id')
+inst_id=$(curl -s -d "$payload" "${base_url}/v1/instance" | jq -r '.id')
print_msg "Validating Kubernetes"
-kubectl get --no-headers=true --namespace=${namespace} deployment ${cloud_region_id}-${namespace}-${vnf_id}-testrelease-vault-consul-dev
-kubectl get --no-headers=true --namespace=${namespace} service ${cloud_region_id}-${namespace}-${vnf_id}-override-vault-consul
-echo "VNF Instance created succesfully with id: $vnf_id"
-
-print_msg "Listing VNF Instances"
-vnf_id_list=$(curl -s -X GET "${base_url}/v1/vnf_instances/${cloud_region_id}/${namespace}" | jq -r '.vnf_id_list')
-if [[ "$vnf_id_list" != *"${vnf_id}"* ]]; then
- echo $vnf_id_list
- echo "VNF Instance not stored"
- exit 1
-fi
+kubectl get --no-headers=true --namespace=${namespace} deployment ${release_name}-vault-consul-dev
+kubectl get --no-headers=true --namespace=${namespace} service override-vault-consul
+echo "VNF Instance created succesfully with id: $inst_id"
-print_msg "Getting $vnf_id VNF Instance information"
-vnf_details=$(curl -s -X GET "${base_url}/v1/vnf_instances/${cloud_region_id}/${namespace}/${vnf_id}")
+print_msg "Getting $inst_id VNF Instance information"
+vnf_details=$(curl -s -X GET "${base_url}/v1/instance/${inst_id}")
if [[ -z "$vnf_details" ]]; then
echo "Cannot retrieved VNF Instance details"
exit 1
@@ -159,9 +152,17 @@ if [[ 500 -ne $(curl -o /dev/null -w %{http_code} -s -X GET "${base_url}/v1/rb/d
exit 1
fi
-print_msg "Deleting $vnf_id VNF Instance"
-curl -X DELETE "${base_url}/v1/vnf_instances/${cloud_region_id}/${namespace}/${vnf_id}"
-if [[ 404 -ne $(curl -o /dev/null -w %{http_code} -s -X GET "${base_url}${cloud_region_id}/${namespace}/${vnf_id}") ]]; then
+print_msg "Deleting $profile_name Resource Bundle Profile"
+curl -X DELETE "${base_url}/v1/rb/definition/$rb_name/$rb_version/profile/$profile_name"
+if [[ 500 -ne $(curl -o /dev/null -w %{http_code} -s -X GET "${base_url}/v1/rb/definition/$rb_name/$rb_version/profile/$profile_name") ]]; then
+ echo "Resource Bundle Profile not deleted"
+# TODO: Change the HTTP code for 404 when the resource is not found in the API
+ exit 1
+fi
+
+print_msg "Deleting $inst_id VNF Instance"
+curl -X DELETE "${base_url}/v1/instance/${inst_id}"
+if [[ 404 -ne $(curl -o /dev/null -w %{http_code} -s -X GET "${base_url}/${inst_id}") ]]; then
echo "VNF Instance not deleted"
exit 1
fi
diff --git a/src/k8splugin/api/api.go b/src/k8splugin/api/api.go
index 2003a809..604ccdc8 100644
--- a/src/k8splugin/api/api.go
+++ b/src/k8splugin/api/api.go
@@ -41,6 +41,13 @@ func NewRouter(defClient rb.DefinitionManager,
// (TODO): Fix update method
// instRouter.HandleFunc("/{vnfInstanceId}", UpdateHandler).Methods("PUT")
+ brokerHandler := brokerInstanceHandler{client: instClient}
+ instRouter.HandleFunc("/{cloud-owner}/{cloud-region}/infra_workload", brokerHandler.createHandler).Methods("POST")
+ instRouter.HandleFunc("/{cloud-owner}/{cloud-region}/infra_workload/{instID}",
+ brokerHandler.getHandler).Methods("GET")
+ instRouter.HandleFunc("/{cloud-owner}/{cloud-region}/infra_workload/{instID}",
+ brokerHandler.deleteHandler).Methods("DELETE")
+
//Setup resource bundle definition routes
if defClient == nil {
defClient = rb.NewDefinitionClient()
diff --git a/src/k8splugin/api/brokerhandler.go b/src/k8splugin/api/brokerhandler.go
new file mode 100644
index 00000000..28e44231
--- /dev/null
+++ b/src/k8splugin/api/brokerhandler.go
@@ -0,0 +1,164 @@
+/*
+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"
+ "io"
+ "net/http"
+
+ "k8splugin/internal/app"
+
+ "github.com/gorilla/mux"
+)
+
+// Used to store the backend implementation objects
+// Also simplifies the mocking needed for unit testing
+type brokerInstanceHandler struct {
+ // Interface that implements the Instance operations
+ client app.InstanceManager
+}
+
+type brokerRequest struct {
+ GenericVnfID string `json:"generic-vnf-id"`
+ VFModuleID string `json:"vf-module-id"`
+ VFModuleModelInvariantID string `json:"vf-module-model-invariant-id"`
+ VFModuleModelVersionID string `json:"vf-module-model-version-id"`
+ VFModuleModelCustomizationID string `json:"vf-module-model-customization-id"`
+ OOFDirectives map[string]interface{} `json:"oof_directives"`
+ SDNCDirections map[string]interface{} `json:"sdnc_directives"`
+ UserDirectives map[string]interface{} `json:"user_directives"`
+ TemplateType string `json:"template_type"`
+ TemplateData map[string]interface{} `json:"template_data"`
+}
+
+type brokerPOSTResponse struct {
+ TemplateType string `json:"template_type"`
+ WorkloadID string `json:"workload_id"`
+ TemplateResponse map[string][]string `json:"template_response"`
+}
+
+type brokerGETResponse struct {
+ TemplateType string `json:"template_type"`
+ WorkloadID string `json:"workload_id"`
+ WorkloadStatus string `json:"workload_status"`
+}
+
+func (b brokerInstanceHandler) createHandler(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ cloudRegion := vars["cloud-region"]
+
+ var req brokerRequest
+ err := json.NewDecoder(r.Body).Decode(&req)
+ switch {
+ case err == io.EOF:
+ http.Error(w, "Body empty", http.StatusBadRequest)
+ return
+ case err != nil:
+ http.Error(w, err.Error(), http.StatusUnprocessableEntity)
+ return
+ }
+
+ // Check body for expected parameters
+ if req.VFModuleModelCustomizationID == "" {
+ http.Error(w, "vf-module-model-customization-id is empty", http.StatusBadRequest)
+ return
+ }
+
+ rbName, ok := req.UserDirectives["definition-name"]
+ if !ok {
+ http.Error(w, "definition-name is missing from user-directives", http.StatusBadRequest)
+ return
+ }
+
+ rbVersion, ok := req.UserDirectives["definition-version"]
+ if !ok {
+ http.Error(w, "definition-version is missing from user-directives", http.StatusBadRequest)
+ return
+ }
+
+ profileName, ok := req.UserDirectives["profile-name"]
+ if !ok {
+ http.Error(w, "profile-name is missing from user-directives", http.StatusBadRequest)
+ return
+ }
+
+ // Setup the resource parameters for making the request
+ var instReq app.InstanceRequest
+ instReq.RBName = rbName.(string)
+ instReq.RBVersion = rbVersion.(string)
+ instReq.ProfileName = profileName.(string)
+ instReq.CloudRegion = cloudRegion
+
+ resp, err := b.client.Create(instReq)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ brokerResp := brokerPOSTResponse{
+ TemplateType: "heat",
+ WorkloadID: resp.ID,
+ TemplateResponse: resp.Resources,
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusCreated)
+ err = json.NewEncoder(w).Encode(brokerResp)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+// getHandler retrieves information about an instance via the ID
+func (b brokerInstanceHandler) getHandler(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ instanceID := vars["instID"]
+
+ resp, err := b.client.Get(instanceID)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ brokerResp := brokerGETResponse{
+ TemplateType: "heat",
+ WorkloadID: resp.ID,
+ WorkloadStatus: "CREATED",
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ err = json.NewEncoder(w).Encode(brokerResp)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+// deleteHandler method terminates an instance via the ID
+func (b brokerInstanceHandler) deleteHandler(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ instanceID := vars["instID"]
+
+ err := b.client.Delete(instanceID)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusAccepted)
+}
diff --git a/src/k8splugin/api/brokerhandler_test.go b/src/k8splugin/api/brokerhandler_test.go
new file mode 100644
index 00000000..f35a835b
--- /dev/null
+++ b/src/k8splugin/api/brokerhandler_test.go
@@ -0,0 +1,224 @@
+/*
+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"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "net/http/httptest"
+ "reflect"
+ "testing"
+
+ "k8splugin/internal/app"
+
+ pkgerrors "github.com/pkg/errors"
+)
+
+func TestBrokerCreateHandler(t *testing.T) {
+ testCases := []struct {
+ label string
+ input io.Reader
+ expected brokerPOSTResponse
+ expectedCode int
+ instClient *mockInstanceClient
+ }{
+ {
+ label: "Missing body failure",
+ expectedCode: http.StatusBadRequest,
+ },
+ {
+ label: "Invalid JSON request format",
+ input: bytes.NewBuffer([]byte("invalid")),
+ expectedCode: http.StatusUnprocessableEntity,
+ },
+ {
+ label: "Missing parameter failure",
+ input: bytes.NewBuffer([]byte(`{
+ "vf-module-model-customization-id": "84sdfkio938",
+ "user_directives": {
+ "definition-name": "test-rbdef",
+ "definition-version": "v1" }
+ }`)),
+ expectedCode: http.StatusBadRequest,
+ },
+ {
+ label: "Succesfully create an Instance",
+ input: bytes.NewBuffer([]byte(`{
+ "vf-module-model-customization-id": "84sdfkio938",
+ "user_directives": {
+ "definition-name": "test-rbdef",
+ "definition-version": "v1",
+ "profile-name": "profile1"
+ }
+ }`)),
+ expected: brokerPOSTResponse{
+ WorkloadID: "HaKpys8e",
+ TemplateType: "heat",
+ TemplateResponse: map[string][]string{
+ "deployment": []string{"test-deployment"},
+ "service": []string{"test-service"},
+ },
+ },
+ expectedCode: http.StatusCreated,
+ instClient: &mockInstanceClient{
+ items: []app.InstanceResponse{
+ {
+ ID: "HaKpys8e",
+ RBName: "test-rbdef",
+ RBVersion: "v1",
+ ProfileName: "profile1",
+ CloudRegion: "region1",
+ Namespace: "testnamespace",
+ Resources: map[string][]string{
+ "deployment": []string{"test-deployment"},
+ "service": []string{"test-service"},
+ },
+ },
+ },
+ },
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+
+ request := httptest.NewRequest("POST", "/v1/cloudowner/cloudregion/infra_workload", testCase.input)
+ resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient))
+
+ if testCase.expectedCode != resp.StatusCode {
+ body, _ := ioutil.ReadAll(resp.Body)
+ t.Log(string(body))
+ t.Fatalf("Request method returned: \n%v\n and it was expected: \n%v", resp.StatusCode, testCase.expectedCode)
+ }
+
+ if resp.StatusCode == http.StatusCreated {
+ var response brokerPOSTResponse
+ err := json.NewDecoder(resp.Body).Decode(&response)
+ if err != nil {
+ t.Fatalf("Parsing the returned response got an error (%s)", err)
+ }
+ if !reflect.DeepEqual(testCase.expected, response) {
+ t.Fatalf("TestGetHandler returned:\n result=%v\n expected=%v",
+ response, testCase.expected)
+ }
+ }
+ })
+ }
+}
+
+func TestBrokerGetHandler(t *testing.T) {
+ testCases := []struct {
+ label string
+ input string
+ expectedCode int
+ expectedResponse brokerGETResponse
+ instClient *mockInstanceClient
+ }{
+ {
+ label: "Fail to retrieve Instance",
+ input: "HaKpys8e",
+ expectedCode: http.StatusInternalServerError,
+ instClient: &mockInstanceClient{
+ err: pkgerrors.New("Internal error"),
+ },
+ },
+ {
+ label: "Succesful get an Instance",
+ input: "HaKpys8e",
+ expectedCode: http.StatusOK,
+ expectedResponse: brokerGETResponse{
+ TemplateType: "heat",
+ WorkloadID: "HaKpys8e",
+ WorkloadStatus: "CREATED",
+ },
+ instClient: &mockInstanceClient{
+ items: []app.InstanceResponse{
+ {
+ ID: "HaKpys8e",
+ RBName: "test-rbdef",
+ RBVersion: "v1",
+ ProfileName: "profile1",
+ CloudRegion: "region1",
+ Namespace: "testnamespace",
+ Resources: map[string][]string{
+ "deployment": []string{"test-deployment"},
+ "service": []string{"test-service"},
+ },
+ },
+ },
+ },
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ request := httptest.NewRequest("GET", "/v1/cloudowner/cloudregion/infra_workload/"+testCase.input, nil)
+ resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient))
+
+ if testCase.expectedCode != resp.StatusCode {
+ t.Fatalf("Request method returned: %v and it was expected: %v",
+ resp.StatusCode, testCase.expectedCode)
+ }
+ if resp.StatusCode == http.StatusOK {
+ var response brokerGETResponse
+ err := json.NewDecoder(resp.Body).Decode(&response)
+ if err != nil {
+ t.Fatalf("Parsing the returned response got an error (%s)", err)
+ }
+ if !reflect.DeepEqual(testCase.expectedResponse, response) {
+ t.Fatalf("TestGetHandler returned:\n result=%v\n expected=%v",
+ response, testCase.expectedResponse)
+ }
+ }
+ })
+ }
+}
+
+func TestBrokerDeleteHandler(t *testing.T) {
+ testCases := []struct {
+ label string
+ input string
+ expectedCode int
+ instClient *mockInstanceClient
+ }{
+ {
+ label: "Fail to destroy VNF",
+ input: "HaKpys8e",
+ expectedCode: http.StatusInternalServerError,
+ instClient: &mockInstanceClient{
+ err: pkgerrors.New("Internal error"),
+ },
+ },
+ {
+ label: "Succesful delete a VNF",
+ input: "HaKpys8e",
+ expectedCode: http.StatusAccepted,
+ instClient: &mockInstanceClient{},
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ request := httptest.NewRequest("DELETE", "/v1/cloudowner/cloudregion/infra_workload/"+testCase.input, nil)
+ resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient))
+
+ if testCase.expectedCode != resp.StatusCode {
+ t.Fatalf("Request method returned: %v and it was expected: %v", resp.StatusCode, testCase.expectedCode)
+ }
+ })
+ }
+}
diff --git a/src/k8splugin/cmd/main.go b/src/k8splugin/cmd/main.go
index 96e1c8e5..607e3fe1 100644
--- a/src/k8splugin/cmd/main.go
+++ b/src/k8splugin/cmd/main.go
@@ -24,6 +24,7 @@ import (
"k8splugin/api"
utils "k8splugin/internal"
+ "k8splugin/internal/auth"
"github.com/gorilla/handlers"
)
@@ -55,5 +56,13 @@ func main() {
close(connectionsClose)
}()
- log.Fatal(httpServer.ListenAndServe())
+ tlsConfig, err := auth.GetTLSConfig("ca.cert", "server.cert", "server.key")
+ if err != nil {
+ log.Println("Error Getting TLS Configuration. Starting without TLS...")
+ log.Fatal(httpServer.ListenAndServe())
+ } else {
+ httpServer.TLSConfig = tlsConfig
+ // empty strings because tlsconfig already has this information
+ err = httpServer.ListenAndServeTLS("", "")
+ }
}
diff --git a/src/k8splugin/go.mod b/src/k8splugin/go.mod
index 59c7a173..474e5102 100644
--- a/src/k8splugin/go.mod
+++ b/src/k8splugin/go.mod
@@ -69,6 +69,7 @@ require (
github.com/xdg/stringprep v1.0.0 // indirect
go.etcd.io/etcd v3.3.12+incompatible
go.mongodb.org/mongo-driver v1.0.0
+ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890 // indirect
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 // indirect
@@ -77,7 +78,7 @@ require (
gopkg.in/square/go-jose.v2 v2.2.2 // indirect
gopkg.in/yaml.v2 v2.2.1
k8s.io/api v0.0.0-20181126151915-b503174bad59
- k8s.io/apiextensions-apiserver v0.0.0-20181126155829-0cd23ebeb688 // indirect
+ k8s.io/apiextensions-apiserver v0.0.0-20181126155829-0cd23ebeb688
k8s.io/apimachinery v0.0.0-20181126123746-eddba98df674
k8s.io/apiserver v0.0.0-20181126153457-92fdef3a232a // indirect
k8s.io/cli-runtime v0.0.0-20190107235426-31214e12222d // indirect
diff --git a/src/k8splugin/go.sum b/src/k8splugin/go.sum
index 453a45d9..401999b8 100644
--- a/src/k8splugin/go.sum
+++ b/src/k8splugin/go.sum
@@ -261,6 +261,7 @@ k8s.io/client-go v9.0.0+incompatible h1:/PdJjifJTjMFe0G4ESclZDcwF1+bFePTJ2xf+MXj
k8s.io/client-go v9.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
k8s.io/client-go v10.0.0+incompatible h1:h3fciHPG0O5QEzATTFoRw/YGtDsU6pxrMrAhxiTtcq0=
k8s.io/client-go v10.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
+k8s.io/client-go v11.0.0+incompatible h1:X3ykd+Z4G8MojP9TVDOR+h/IrpYJEolfR8W2B/FGKrk=
k8s.io/helm v2.12.1+incompatible h1:Fw6it7ALJfqbbX95U3is3aswD6E8nh4aUYtvgzfna8A=
k8s.io/helm v2.12.1+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI=
k8s.io/helm v2.12.2+incompatible h1:vtddbkiGNMOd8maDDZDc111Rm9E5JeeNWDndows18i8=
diff --git a/src/k8splugin/internal/app/client.go b/src/k8splugin/internal/app/client.go
index fa5fdfd5..cd1ec8a2 100644
--- a/src/k8splugin/internal/app/client.go
+++ b/src/k8splugin/internal/app/client.go
@@ -21,17 +21,30 @@ import (
utils "k8splugin/internal"
pkgerrors "github.com/pkg/errors"
+ "k8s.io/apimachinery/pkg/api/meta"
+ "k8s.io/client-go/discovery"
+ "k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
+ "k8s.io/client-go/restmapper"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/helm/pkg/tiller"
)
-type kubernetesClient struct {
- clientSet *kubernetes.Clientset
+// KubernetesResource is the interface that is implemented
+type KubernetesResource interface {
+ Create(yamlFilePath string, namespace string, client *KubernetesClient) (string, error)
+ Delete(kind string, name string, namespace string, client *KubernetesClient) error
+}
+
+type KubernetesClient struct {
+ clientSet *kubernetes.Clientset
+ dynamicClient dynamic.Interface
+ discoverClient *discovery.DiscoveryClient
+ restMapper meta.RESTMapper
}
// GetKubeClient loads the Kubernetes configuation values stored into the local configuration file
-func (k *kubernetesClient) init(configPath string) error {
+func (k *KubernetesClient) init(configPath string) error {
if configPath == "" {
return pkgerrors.New("config not passed and is not found in ~/.kube. ")
}
@@ -46,10 +59,20 @@ func (k *kubernetesClient) init(configPath string) error {
return err
}
+ k.dynamicClient, err = dynamic.NewForConfig(config)
+ if err != nil {
+ return pkgerrors.Wrap(err, "Creating dynamic client")
+ }
+
+ k.discoverClient, err = discovery.NewDiscoveryClientForConfig(config)
+ if err != nil {
+ return pkgerrors.Wrap(err, "Creating discovery client")
+ }
+
return nil
}
-func (k *kubernetesClient) ensureNamespace(namespace string) error {
+func (k *KubernetesClient) ensureNamespace(namespace string) error {
namespacePlugin, ok := utils.LoadedPlugins["namespace"]
if !ok {
return pkgerrors.New("No plugin for namespace resource found")
@@ -82,7 +105,51 @@ func (k *kubernetesClient) ensureNamespace(namespace string) error {
return nil
}
-func (k *kubernetesClient) createKind(kind string, files []string, namespace string) ([]string, error) {
+func (k *KubernetesClient) createGeneric(kind string, files []string, namespace string) ([]string, error) {
+
+ log.Println("Processing items of Kind: " + kind)
+
+ //Check if have the mapper before loading the plugin
+ err := k.updateMapper()
+ if err != nil {
+ return nil, pkgerrors.Wrap(err, "Unable to create RESTMapper")
+ }
+
+ pluginObject, ok := utils.LoadedPlugins["generic"]
+ if !ok {
+ return nil, pkgerrors.New("No generic plugin found")
+ }
+
+ symbol, err := pluginObject.Lookup("ExportedVariable")
+ if err != nil {
+ return nil, pkgerrors.Wrap(err, "No ExportedVariable symbol found")
+ }
+
+ genericPlugin, ok := symbol.(KubernetesResource)
+ if !ok {
+ return nil, pkgerrors.New("ExportedVariable is not KubernetesResource type")
+ }
+
+ //Iterate over each file of a particular kind here
+ var resourcesCreated []string
+ for _, f := range files {
+ if _, err := os.Stat(f); os.IsNotExist(err) {
+ return nil, pkgerrors.New("File " + f + "does not exists")
+ }
+
+ log.Println("Processing file: " + f)
+
+ name, err := genericPlugin.Create(f, namespace, k)
+ if err != nil {
+ return nil, pkgerrors.Wrap(err, "Error in generic plugin")
+ }
+
+ resourcesCreated = append(resourcesCreated, name)
+ }
+ return resourcesCreated, nil
+}
+
+func (k *KubernetesClient) createKind(kind string, files []string, namespace string) ([]string, error) {
log.Println("Processing items of Kind: " + kind)
@@ -103,7 +170,8 @@ func (k *kubernetesClient) createKind(kind string, files []string, namespace str
typePlugin, ok := utils.LoadedPlugins[strings.ToLower(kind)]
if !ok {
- return nil, pkgerrors.New("No plugin for kind " + kind + " found")
+ log.Println("No plugin for kind " + kind + " found. Using generic Plugin")
+ return k.createGeneric(kind, files, namespace)
}
symCreateResourceFunc, err := typePlugin.Lookup("Create")
@@ -123,7 +191,7 @@ func (k *kubernetesClient) createKind(kind string, files []string, namespace str
return resourcesCreated, nil
}
-func (k *kubernetesClient) createResources(resMap map[string][]string,
+func (k *KubernetesClient) createResources(resMap map[string][]string,
namespace string) (map[string][]string, error) {
err := k.ensureNamespace(namespace)
@@ -163,17 +231,47 @@ func (k *kubernetesClient) createResources(resMap map[string][]string,
return createdResourceMap, nil
}
-func (k *kubernetesClient) deleteKind(kind string, resources []string, namespace string) error {
+func (k *KubernetesClient) deleteGeneric(kind string, resources []string, namespace string) error {
+ log.Println("Deleting items of Kind: " + kind)
+
+ pluginObject, ok := utils.LoadedPlugins["generic"]
+ if !ok {
+ return pkgerrors.New("No generic plugin found")
+ }
+
+ symbol, err := pluginObject.Lookup("ExportedVariable")
+ if err != nil {
+ return pkgerrors.Wrap(err, "No ExportedVariable symbol found")
+ }
+
+ //Assert that it implements the KubernetesResource
+ genericPlugin, ok := symbol.(KubernetesResource)
+ if !ok {
+ return pkgerrors.New("ExportedVariable is not KubernetesResource type")
+ }
+
+ for _, res := range resources {
+ err = genericPlugin.Delete(kind, res, namespace, k)
+ if err != nil {
+ return pkgerrors.Wrap(err, "Error in generic plugin")
+ }
+ }
+
+ return nil
+}
+
+func (k *KubernetesClient) deleteKind(kind string, resources []string, namespace string) error {
log.Println("Deleting items of Kind: " + kind)
typePlugin, ok := utils.LoadedPlugins[strings.ToLower(kind)]
if !ok {
- return pkgerrors.New("No plugin for resource " + kind + " found")
+ log.Println("No plugin for kind " + kind + " found. Using generic Plugin")
+ return k.deleteGeneric(kind, resources, namespace)
}
symDeleteResourceFunc, err := typePlugin.Lookup("Delete")
if err != nil {
- return pkgerrors.Wrap(err, "Error fetching "+kind+" plugin")
+ return pkgerrors.Wrap(err, "Error findinf Delete symbol in plugin")
}
for _, res := range resources {
@@ -187,7 +285,7 @@ func (k *kubernetesClient) deleteKind(kind string, resources []string, namespace
return nil
}
-func (k *kubernetesClient) deleteResources(resMap map[string][]string, namespace string) error {
+func (k *KubernetesClient) deleteResources(resMap map[string][]string, namespace string) error {
//TODO: Investigate if deletion should be in a particular order
for kind, resourceNames := range resMap {
err := k.deleteKind(kind, resourceNames, namespace)
@@ -198,3 +296,29 @@ func (k *kubernetesClient) deleteResources(resMap map[string][]string, namespace
return nil
}
+
+func (k *KubernetesClient) updateMapper() error {
+ //Create restMapper if not already done
+ if k.restMapper != nil {
+ return nil
+ }
+
+ groupResources, err := restmapper.GetAPIGroupResources(k.discoverClient)
+ if err != nil {
+ return pkgerrors.Wrap(err, "Get GroupResources")
+ }
+
+ k.restMapper = restmapper.NewDiscoveryRESTMapper(groupResources)
+ return nil
+}
+
+//GetMapper returns the RESTMapper that was created for this client
+func (k *KubernetesClient) GetMapper() meta.RESTMapper {
+ return k.restMapper
+}
+
+//GetDynamicClient returns the dynamic client that is needed for
+//unstructured REST calls to the apiserver
+func (k *KubernetesClient) GetDynamicClient() dynamic.Interface {
+ return k.dynamicClient
+}
diff --git a/src/k8splugin/internal/app/client_test.go b/src/k8splugin/internal/app/client_test.go
index 5999cfa0..b3436431 100644
--- a/src/k8splugin/internal/app/client_test.go
+++ b/src/k8splugin/internal/app/client_test.go
@@ -45,7 +45,7 @@ func LoadMockPlugins(krdLoadedPlugins map[string]*plugin.Plugin) error {
func TestInit(t *testing.T) {
t.Run("Successfully create Kube Client", func(t *testing.T) {
- kubeClient := kubernetesClient{}
+ kubeClient := KubernetesClient{}
err := kubeClient.init("../../mock_files/mock_configs/mock_config")
if err != nil {
t.Fatalf("TestGetKubeClient returned an error (%s)", err)
@@ -71,7 +71,7 @@ func TestCreateResources(t *testing.T) {
t.Fatalf("LoadMockPlugins returned an error (%s)", err)
}
- k8 := kubernetesClient{
+ k8 := KubernetesClient{
clientSet: &kubernetes.Clientset{},
}
@@ -100,7 +100,7 @@ func TestDeleteResources(t *testing.T) {
t.Fatalf("LoadMockPlugins returned an error (%s)", err)
}
- k8 := kubernetesClient{
+ k8 := KubernetesClient{
clientSet: &kubernetes.Clientset{},
}
diff --git a/src/k8splugin/internal/app/instance.go b/src/k8splugin/internal/app/instance.go
index a5b35fef..93305c30 100644
--- a/src/k8splugin/internal/app/instance.go
+++ b/src/k8splugin/internal/app/instance.go
@@ -118,7 +118,7 @@ func (v *InstanceClient) Create(i InstanceRequest) (InstanceResponse, error) {
return InstanceResponse{}, pkgerrors.Wrap(err, "Error resolving helm charts")
}
- k8sClient := kubernetesClient{}
+ k8sClient := KubernetesClient{}
err = k8sClient.init(os.Getenv("KUBE_CONFIG_DIR") + "/" + i.CloudRegion)
if err != nil {
return InstanceResponse{}, pkgerrors.Wrap(err, "Getting CloudRegion Information")
@@ -183,7 +183,7 @@ func (v *InstanceClient) Delete(id string) error {
return pkgerrors.Wrap(err, "Error getting Instance")
}
- k8sClient := kubernetesClient{}
+ k8sClient := KubernetesClient{}
err = k8sClient.init(os.Getenv("KUBE_CONFIG_DIR") + "/" + inst.CloudRegion)
if err != nil {
return pkgerrors.Wrap(err, "Getting CloudRegion Information")
diff --git a/src/k8splugin/internal/auth/auth.go b/src/k8splugin/internal/auth/auth.go
new file mode 100644
index 00000000..3da8f2af
--- /dev/null
+++ b/src/k8splugin/internal/auth/auth.go
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2018 Intel Corporation, Inc
+ *
+ * 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 auth
+
+import (
+ "crypto/tls"
+ "crypto/x509"
+ "encoding/base64"
+ "encoding/pem"
+ "io/ioutil"
+ "log"
+
+ pkgerrors "github.com/pkg/errors"
+)
+
+// GetTLSConfig initializes a tlsConfig using the CA's certificate
+// This config is then used to enable the server for mutual TLS
+func GetTLSConfig(caCertFile string, certFile string, keyFile string) (*tls.Config, error) {
+
+ // Initialize tlsConfig once
+ caCert, err := ioutil.ReadFile(caCertFile)
+
+ if err != nil {
+ return nil, pkgerrors.Wrap(err, "Read CA Cert file")
+ }
+
+ caCertPool := x509.NewCertPool()
+ caCertPool.AppendCertsFromPEM(caCert)
+
+ tlsConfig := &tls.Config{
+ // Change to RequireAndVerify once we have mandatory certs
+ ClientAuth: tls.VerifyClientCertIfGiven,
+ ClientCAs: caCertPool,
+ MinVersion: tls.VersionTLS12,
+ }
+
+ certPEMBlk, err := readPEMBlock(certFile)
+ if err != nil {
+ return nil, pkgerrors.Wrap(err, "Read Cert File")
+ }
+
+ keyPEMBlk, err := readPEMBlock(keyFile)
+ if err != nil {
+ return nil, pkgerrors.Wrap(err, "Read Key File")
+ }
+
+ tlsConfig.Certificates = make([]tls.Certificate, 1)
+ tlsConfig.Certificates[0], err = tls.X509KeyPair(certPEMBlk, keyPEMBlk)
+ if err != nil {
+ return nil, pkgerrors.Wrap(err, "Load x509 cert and key")
+ }
+
+ tlsConfig.BuildNameToCertificate()
+ return tlsConfig, nil
+}
+
+func readPEMBlock(filename string) ([]byte, error) {
+
+ pemData, err := ioutil.ReadFile(filename)
+ if err != nil {
+ return nil, pkgerrors.Wrap(err, "Read PEM File")
+ }
+
+ pemBlock, rest := pem.Decode(pemData)
+ if len(rest) > 0 {
+ log.Println("Pemfile has extra data")
+ }
+
+ if x509.IsEncryptedPEMBlock(pemBlock) {
+ password, err := ioutil.ReadFile(filename + ".pass")
+ if err != nil {
+ return nil, pkgerrors.Wrap(err, "Read Password File")
+ }
+
+ pByte, err := base64.StdEncoding.DecodeString(string(password))
+ if err != nil {
+ return nil, pkgerrors.Wrap(err, "Decode PEM Password")
+ }
+
+ pemData, err = x509.DecryptPEMBlock(pemBlock, pByte)
+ if err != nil {
+ return nil, pkgerrors.Wrap(err, "Decrypt PEM Data")
+ }
+ var newPEMBlock pem.Block
+ newPEMBlock.Type = pemBlock.Type
+ newPEMBlock.Bytes = pemData
+ // Converting back to PEM from DER data you get from
+ // DecryptPEMBlock
+ pemData = pem.EncodeToMemory(&newPEMBlock)
+ }
+
+ return pemData, nil
+}
diff --git a/src/k8splugin/internal/auth/auth_test.go b/src/k8splugin/internal/auth/auth_test.go
new file mode 100644
index 00000000..49494eee
--- /dev/null
+++ b/src/k8splugin/internal/auth/auth_test.go
@@ -0,0 +1,47 @@
+/*
+* Copyright 2018 TechMahindra
+*
+* 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 auth
+
+import (
+ "crypto/tls"
+ "testing"
+)
+
+//Unit test to varify GetTLSconfig func and varify the tls config min version to be 771
+//Assuming cert file name as auth_test.cert
+func TestGetTLSConfig(t *testing.T) {
+ _, err := GetTLSConfig("filedoesnotexist.cert", "filedoesnotexist.cert", "filedoesnotexist.cert")
+ if err == nil {
+ t.Errorf("Test failed, expected error but got none")
+ }
+ tlsConfig, err := GetTLSConfig("../../mock_files/mock_certs/auth_test_certificate.pem",
+ "../../mock_files/mock_certs/auth_test_certificate.pem",
+ "../../mock_files/mock_certs/auth_test_key.pem")
+ if err != nil {
+ t.Fatal("Test Failed as GetTLSConfig returned error: " + err.Error())
+ }
+ expected := tls.VersionTLS12
+ actual := tlsConfig.MinVersion
+ if tlsConfig != nil {
+ if int(actual) != expected {
+ t.Errorf("Test Failed due to version mismatch")
+ }
+ if tlsConfig == nil {
+ t.Errorf("Test Failed due to GetTLSConfig returned nil")
+ }
+ }
+}
diff --git a/src/k8splugin/mock_files/mock_certs/auth_test_certificate.pem b/src/k8splugin/mock_files/mock_certs/auth_test_certificate.pem
new file mode 100644
index 00000000..42e77491
--- /dev/null
+++ b/src/k8splugin/mock_files/mock_certs/auth_test_certificate.pem
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDXTCCAkWgAwIBAgIJAKAHJi8eUs73MA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQwHhcNMTgwNTE1MDQ0MDQwWhcNMTkwNTE1MDQ0MDQwWjBF
+MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
+ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
+CgKCAQEA5PHDk+RRFh5o3Xe2nZuLn0Vo+5BjnHp/ul2NNYSG00Slc8F86gW4xcNA
+wm6xC8tYCSangV2lFG3E8H2L7SCEVaM5VDV2GCOpOoMihc+2Qenk/YbHwuYenwjo
+OgTK4aCItqjcAJ2DB1KC7AxARxHBDu9Kif+M/pc49so+G9ObQuS8k2vmTTaRYkMK
+ZvbTJcWsc0vbNnPhxcG5PVj4unlaREM+yQDm18/glUkkBNnYKMHABRvPnBrDktTT
+BQWsqkbQTw7ZuLOrl9rCzVTstZX9wEXarrstIdQJj3KZjbFOp2opND8bjNIjcdVt
+iRFnP1nHQYr7EgRqcx/YMJZ+gmCy3wIDAQABo1AwTjAdBgNVHQ4EFgQU9qPNwwIu
+kZh41sJqFtnMC2blSYMwHwYDVR0jBBgwFoAU9qPNwwIukZh41sJqFtnMC2blSYMw
+DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEA4+daLY1wE10IMPaOKurG
+QBvfYeO/rgNXGeg0TisTIKAfx/We9Hmwo/37Bd2Nk5gxfy/DIJ4lMbrzXjgWITlm
+XOrS5QfluwvaEcREtHFtPFa3NZqn2VzKNDFTR+rJj7I5o600NKdcPrGeQ1i/vny2
+K0g68ogw2jfufcuePvZTYGst8RclomPr7ZXxI24kIjcE1MbiViy68sQueTXBEr5s
+Th6RsvPfVnLxjR/m/V6VJl31nn4T6hbmKzXCHo/X7aC3I8Isui4bQGKgfAxyBkhE
+0T7tP+GgymiEKQ6qJ/1c4HFFSuFRUQjLnK7uJu9jM/HMKoLCPayx6birHZRIMF94
+pg==
+-----END CERTIFICATE-----
diff --git a/src/k8splugin/mock_files/mock_certs/auth_test_key.pem b/src/k8splugin/mock_files/mock_certs/auth_test_key.pem
new file mode 100644
index 00000000..5f01f572
--- /dev/null
+++ b/src/k8splugin/mock_files/mock_certs/auth_test_key.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDk8cOT5FEWHmjd
+d7adm4ufRWj7kGOcen+6XY01hIbTRKVzwXzqBbjFw0DCbrELy1gJJqeBXaUUbcTw
+fYvtIIRVozlUNXYYI6k6gyKFz7ZB6eT9hsfC5h6fCOg6BMrhoIi2qNwAnYMHUoLs
+DEBHEcEO70qJ/4z+lzj2yj4b05tC5LyTa+ZNNpFiQwpm9tMlxaxzS9s2c+HFwbk9
+WPi6eVpEQz7JAObXz+CVSSQE2dgowcAFG8+cGsOS1NMFBayqRtBPDtm4s6uX2sLN
+VOy1lf3ARdquuy0h1AmPcpmNsU6naik0PxuM0iNx1W2JEWc/WcdBivsSBGpzH9gw
+ln6CYLLfAgMBAAECggEAYB3H2D0QddLKf8AUoNJ+qZ1AV+zkhPtAyIMiF4fN+sBl
+HdXrlWxViGFSvM4v8h2qlhzuUfd4qLz042ox5pmyNSnTlbDkJXpDP9dyFO+BOubx
+Ribhksd9r5LTvBfq/RKikt0NkAyQx/AyGtuB2NRxUs3PY2QwU2o1dhauQIx0MH5/
+6D8PgQf6+5njKQaKa4e8Kp4kB+KjnALvt6JgYuNJUHWap+nnDbuuVy5dl1bKkAZ+
+qa7CITKWO4kE2EqaCb2asFc2w3538+w72UJZtwQCmOaxtKpRSl9fQXu54N8jIGoZ
+1FvEj5x3X6QkglE+iVJYaX3RmiJ3uzZ2LICDr89vEQKBgQD7fquIw4p1idSxz3Cm
+5o3Y5kD0CKm61ZaRJWKd+tNlSsxubmV9HROYW6vj2xEPSDvyp1na00pDXxMJQLLc
+O5Awd1SaU+d45jnoi70fsEY8X0WH1rDTYfnU+zQBmpbGqX5qTIfpy4yoADiUD1CQ
+EBdaSBWiKPx2jFSct58TwDP9YwKBgQDpC64TScZYz7uQq4gAbDso/7TjNwgt/Bw8
+JgLSdx1UdUclh81smTujsouyCFwJSvRjKik8e/Qt0f5patukFbFRINxUGUDhOKbA
+7zqeNQbeYaP7Rvw+3z01CU2BTBmB/EWa2xWDam8B9xQvjiHSOrubqkt3sIQJb045
+hzuigdV7VQKBgQD7Gnd0nyCwyMSIIMGuswYv6X4y6i9lr3qdQ4GakOTe/vbsz+cf
+K5f0CJuwbnszEgFg/zzVIx/D8rqUA3hSMlp+ObdMO7gi22Q4TsWvTRZjkxBeV7rH
+48xJneNIMqyWgIcK5YzSn3y6BTZ4hm3+2UInz09iUJ/6UZTtwNzhIIgIVwKBgQCT
+LxRHDE4gIzrT+PHRSonmr/DfnA8nc9WlS2B26lH02IkRs/5Su0iGb6p4y3zNRbCp
+vKQElki2c60ZiSqlLCosEfP1jWmDlRMEQVMlPlpTMxmtBr0jPDzc9T4lDhoCFYEk
+d3/T2vG3LQRrsHm92+hHPTuioTIS/2BJRxar4RIibQKBgQC8zoayoQ7zfEYxy3Ax
+OSao8g85hj0EAJk/VKQP2POgz6KoPay3JE9D7P7OvkebTyv/pijAuTPby4XipCNI
+K0JbFC2Kn7RW/ZV23UdnoO9crh2omOh+/52prStWXKoc+/pJe70Af+4rU7FUiI7F
+y1mIE9krIoVis6iYsyFEmkP7iw==
+-----END PRIVATE KEY-----
diff --git a/src/k8splugin/plugins/generic/plugin.go b/src/k8splugin/plugins/generic/plugin.go
new file mode 100644
index 00000000..b0cf609c
--- /dev/null
+++ b/src/k8splugin/plugins/generic/plugin.go
@@ -0,0 +1,109 @@
+/*
+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 (
+ "log"
+
+ pkgerrors "github.com/pkg/errors"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+ "k8s.io/apimachinery/pkg/runtime/schema"
+ "k8s.io/client-go/kubernetes/scheme"
+
+ utils "k8splugin/internal"
+ "k8splugin/internal/app"
+)
+
+type genericPlugin struct {
+}
+
+var kindToGVRMap = map[string]schema.GroupVersionResource{
+ "ConfigMap": schema.GroupVersionResource{Group: "", Version: "v1", Resource: "configmaps"},
+ "StatefulSet": schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "statefulsets"},
+ "Job": schema.GroupVersionResource{Group: "batch", Version: "v1", Resource: "jobs"},
+ "Pod": schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"},
+ "DaemonSet": schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "daemonsets"},
+ "CustomResourceDefinition": schema.GroupVersionResource{
+ Group: "apiextensions.k8s.io", Version: "v1beta1", Resource: "customresourcedefinitions",
+ },
+}
+
+// Create deployment object in a specific Kubernetes cluster
+func (g genericPlugin) Create(yamlFilePath string, namespace string, client *app.KubernetesClient) (string, error) {
+ if namespace == "" {
+ namespace = "default"
+ }
+
+ //Decode the yaml file to create a runtime.Object
+ obj, err := utils.DecodeYAML(yamlFilePath, nil)
+ if err != nil {
+ return "", pkgerrors.Wrap(err, "Decode deployment object error")
+ }
+
+ //Convert the runtime.Object to an unstructured Object
+ unstruct := &unstructured.Unstructured{}
+ err = scheme.Scheme.Convert(obj, unstruct, nil)
+ if err != nil {
+ return "", pkgerrors.Wrap(err, "Converting to unstructured object")
+ }
+
+ dynClient := client.GetDynamicClient()
+ mapper := client.GetMapper()
+
+ gvk := unstruct.GroupVersionKind()
+ mapping, err := mapper.RESTMapping(schema.GroupKind{Group: gvk.Group, Kind: gvk.Kind}, gvk.Version)
+ if err != nil {
+ return "", pkgerrors.Wrap(err, "Mapping kind to resource error")
+ }
+
+ gvr := mapping.Resource
+
+ createdObj, err := dynClient.Resource(gvr).Namespace(namespace).Create(unstruct, metav1.CreateOptions{})
+ if err != nil {
+ return "", pkgerrors.Wrap(err, "Create object error")
+ }
+
+ return createdObj.GetName(), nil
+}
+
+// Delete an existing deployment hosted in a specific Kubernetes cluster
+func (g genericPlugin) Delete(kind string, name string, namespace string, client *app.KubernetesClient) error {
+ if namespace == "" {
+ namespace = "default"
+ }
+
+ deletePolicy := metav1.DeletePropagationForeground
+ opts := &metav1.DeleteOptions{
+ PropagationPolicy: &deletePolicy,
+ }
+
+ dynClient := client.GetDynamicClient()
+ gvr, ok := kindToGVRMap[kind]
+ if !ok {
+ return pkgerrors.New("GVR not found for: " + kind)
+ }
+
+ log.Printf("Using gvr: %s, %s, %s", gvr.Group, gvr.Version, gvr.Resource)
+
+ err := dynClient.Resource(gvr).Namespace(namespace).Delete(name, opts)
+ if err != nil {
+ return pkgerrors.Wrap(err, "Delete object error")
+ }
+
+ return nil
+}
+
+// ExportedVariable is what we will look for when calling the generic plugin
+var ExportedVariable genericPlugin