diff options
51 files changed, 2826 insertions, 988 deletions
@@ -6,6 +6,7 @@ .*.swp *.log coverage.html +docs/build # Directories pkg diff --git a/deployments/build.sh b/deployments/build.sh index 90da6f95..c6d4a244 100755 --- a/deployments/build.sh +++ b/deployments/build.sh @@ -35,7 +35,9 @@ function _cleanup { echo "Cleaning previous execution" docker-compose kill image=$(grep "image.*k8plugin" docker-compose.yml) - docker images ${image#*:} -q | xargs docker rmi -f + if [[ -n ${image} ]]; then + docker images ${image#*:} -q | xargs docker rmi -f + fi docker ps -a --filter "status=exited" -q | xargs docker rm } diff --git a/deployments/docker-compose.yml b/deployments/docker-compose.yml index 73d5651c..a72bd096 100644 --- a/deployments/docker-compose.yml +++ b/deployments/docker-compose.yml @@ -28,33 +28,28 @@ services: environment: - CSAR_DIR=/opt/csar - KUBE_CONFIG_DIR=/opt/kubeconfig - - DATABASE_TYPE=consul + - DATABASE_TYPE=mongo - DATABASE_IP=172.19.0.2 - PLUGINS_DIR=/opt/multicloud/k8s - HTTP_PROXY=$HTTP_PROXY - HTTPS_PROXY=$HTTPS_PROXY - NO_PROXY=$NO_PROXY,172.19.0.2 depends_on: - - consul + - mongo links: - - consul + - mongo volumes: - /opt/csar:/opt/csar - /opt/kubeconfig:/opt/kubeconfig - consul: - image: consul + mongo: + image: mongo networks: multicloud_net: ipv4_address: 172.19.0.2 environment: - CONSUL_CLIENT_INTERFACE: 'eth0' - CONSUL_BIND_INTERFACE: 'eth0' HTTP_PROXY: $HTTP_PROXY HTTPS_PROXY: $HTTPS_PROXY NO_PROXY: $NO_PROXY - command: ["agent", "-server", "-bootstrap-expect=1"] - volumes: - - /opt/consul/config:/consul/config networks: multicloud_net: diff --git a/deployments/start.sh b/deployments/start.sh index da2eacee..d1b9f68a 100755 --- a/deployments/start.sh +++ b/deployments/start.sh @@ -19,13 +19,13 @@ export IMAGE_NAME="nexus3.onap.org:10003/onap/multicloud/k8s" export CSAR_DIR=/opt/csar export KUBE_CONFIG_DIR=/opt/kubeconfig -export DATABASE_TYPE=consul +export DATABASE_TYPE=mongo export PLUGINS_DIR=$k8s_path/src/k8splugin/plugins -echo "Starting consul services" +echo "Starting mongo services" docker-compose kill -docker-compose up -d consul -export DATABASE_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $(docker ps -aqf "name=consul")) +docker-compose up -d mongo +export DATABASE_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $(docker ps -aqf "name=mongo")) export no_proxy=$no_proxy,$DATABASE_IP export NO_PROXY=$NO_PROXY,$DATABASE_IP diff --git a/docs/bare_metal_provisioning.rst b/docs/bare_metal_provisioning.rst new file mode 100644 index 00000000..2cb74afe --- /dev/null +++ b/docs/bare_metal_provisioning.rst @@ -0,0 +1,148 @@ +.. 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. + +*********************** +Bare-Metal Provisioning +*********************** + +The Kubernetes Reference Deployment, aka KRD, has been designed to be consumed +by Virtual Machines as well as Bare-Metal servers. The *vagrant/aio.sh* +script contains the bash instructions for provisioning an All-in-One Kubernetes +deployment in a Bare-Metal server. This document lists the Hardware & Software +requirements and walkthrough the instructions that *vagrant/aio.sh* contains. + +Hardware Requirements +##################### + ++-----------+--------+ +| Concept | Amount | ++===========+========+ +| CPUs | 8 | ++-----------+--------+ +| Memory | 32GB | ++-----------+--------+ +| Hard Disk | 150GB | ++-----------+--------+ + +Software Requirements +##################### + +- Ubuntu Server 16.04 LTS + +vagrant/aio.sh +############## + +This bash script provides an automated process for deploying an All-in-One +Kubernetes cluster. Given that the ansible inventory file created by this +script doesn't specify any information about user and password, it's necessary +to execute this script as root user. + +The following two instructions start the provisioning process. + +.. code-block:: bash + + $ sudo su + # wget -O - https://git.onap.org/multicloud/k8s/plain/vagrant/aio.sh | bash + +In overall, this script can be summarized in three general phases: + +1. Cloning and configuring the KRD project. +2. Enabiling Nested-Virtualization. +3. Deploying KRD services. + +**Cloning and configuring the KRD project** + +KRD requires multiple files(bash scripts and ansible playbooks) to operate. +Therefore, it's necessary to clone the *ONAP multicloud/k8s* project to get +access to the *vagrant* folder. + +.. code-block:: bash + + git clone https://git.onap.org/multicloud/k8s/ + +Ansible works agains multiple systems, the way for selecting them is through the +usage of the inventory. The inventory file is a static source for determining the +target servers used for the execution of ansible tasks. The *aio.sh* script creates +an inventory file for addressing those tasks to localhost. + +.. code-block:: bash + + cat <<EOL > inventory/hosts.ini + [all] + localhost + + [kube-master] + localhost + + [kube-node] + localhost + + [etcd] + localhost + + [ovn-central] + localhost + + [ovn-controller] + localhost + + [virtlet] + localhost + + [k8s-cluster:children] + kube-node + kube-master + EOL + +KRD consumes kubespray_ for provisioning a Kubernetes base deployment. As part +of the deployment process, this tool downloads and configures *kubectl* binary. +This action conflicts with *andrewrothstein.kubectl* ansible role. Therefore is +necessary to remove those instructions from all the ansible playbooks. + +.. _kubespray: https://github.com/kubernetes-incubator/kubespray + +.. code-block:: bash + + # sed -i '/andrewrothstein.kubectl/d' playbooks/configure-*.yml + +Ansible uses SSH protocol for executing remote instructions. The following +instructions create and register ssh keys which avoid the usage of passwords. + +.. code-block:: bash + + # echo -e "\n\n\n" | ssh-keygen -t rsa -N "" + # cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys + # chmod og-wx ~/.ssh/authorized_keys + +**Enabling Nested-Virtualization** + +KRD installs Virtlet_ Kubernetes CRI for running Virtual Machine workloads. +Nested-virtualization gives the ability of running a Virtual Machine within +another. The *node.sh* bash script contains the instructions for enabling +Nested-Virtualization. + +.. _Virtlet : https://github.com/Mirantis/virtlet + +.. code-block:: bash + + # ./node.sh + +**Deploying KRD services** + +Finally, the KRD provisioning process can be started through the use of +*installer.sh* bash script. The output of this script is collected in the +*krd_installer.log* file for future reference. + +.. code-block:: bash + + # ./installer.sh | tee krd_installer.log + +.. image:: ./img/installer_workflow.png diff --git a/docs/img/installer_workflow.png b/docs/img/installer_workflow.png Binary files differnew file mode 100644 index 00000000..95d1bdb5 --- /dev/null +++ b/docs/img/installer_workflow.png diff --git a/docs/index.rst b/docs/index.rst index 127f0b0e..173076b4 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -18,5 +18,6 @@ Table of contents .. toctree:: :maxdepth: 3 - Project Architecture <krd_architecture> - Sample Commands <sampleCommands> + KRD Project Architecture <krd_architecture> + Bare Metal All-in-One KRD deployment<bare_metal_provisioning> + Kubernetes MultiCloud API sample ommands <sampleCommands> diff --git a/src/k8splugin/api/api.go b/src/k8splugin/api/api.go index 46afadd6..06f5009f 100644 --- a/src/k8splugin/api/api.go +++ b/src/k8splugin/api/api.go @@ -14,7 +14,7 @@ limitations under the License. package api import ( - "k8splugin/vnfd" + "k8splugin/rb" "os" "path/filepath" "plugin" @@ -106,13 +106,13 @@ func NewRouter(kubeconfig string) *mux.Router { vnfInstanceHandler.HandleFunc("/{cloudRegionID}/{namespace}/{externalVNFID}", DeleteHandler).Methods("DELETE") vnfInstanceHandler.HandleFunc("/{cloudRegionID}/{namespace}/{externalVNFID}", GetHandler).Methods("GET") - vnfdRouter := router.PathPrefix("/v1/vnfd").Subrouter() - vh := vnfdHandler{vnfdClient: vnfd.GetVNFDClient()} - vnfdRouter.HandleFunc("", vh.vnfdCreateHandler).Methods("POST") - vnfdRouter.HandleFunc("/{vnfdID}/upload", vh.vnfdUploadHandler).Methods("POST") - vnfdRouter.HandleFunc("", vh.vnfdListHandler).Methods("GET") - vnfdRouter.HandleFunc("/{vnfdID}", vh.vnfdGetHandler).Methods("GET") - vnfdRouter.HandleFunc("/{vnfdID}", vh.vnfdDeleteHandler).Methods("DELETE") + resRouter := router.PathPrefix("/v1/rb").Subrouter() + rbdef := rbDefinitionHandler{client: rb.NewDefinitionClient()} + resRouter.HandleFunc("/definition", rbdef.createHandler).Methods("POST") + resRouter.HandleFunc("/definition/{rbdID}/content", rbdef.uploadHandler).Methods("POST") + resRouter.HandleFunc("/definition", rbdef.listHandler).Methods("GET") + resRouter.HandleFunc("/definition/{rbdID}", rbdef.getHandler).Methods("GET") + resRouter.HandleFunc("/definition/{rbdID}", rbdef.deleteHandler).Methods("DELETE") // (TODO): Fix update method // vnfInstanceHandler.HandleFunc("/{vnfInstanceId}", UpdateHandler).Methods("PUT") diff --git a/src/k8splugin/api/vnfdhandler.go b/src/k8splugin/api/defhandler.go index ff777826..222baaee 100644 --- a/src/k8splugin/api/vnfdhandler.go +++ b/src/k8splugin/api/defhandler.go @@ -18,24 +18,24 @@ package api import ( "encoding/json" + "io/ioutil" + "k8splugin/rb" "net/http" - "k8splugin/vnfd" - "github.com/gorilla/mux" ) // Used to store backend implementations objects // Also simplifies mocking for unit testing purposes -type vnfdHandler struct { - // Interface that implements vnfDefinition operations +type rbDefinitionHandler struct { + // Interface that implements bundle Definition operations // We will set this variable with a mock interface for testing - vnfdClient vnfd.VNFDefinitionInterface + client rb.DefinitionManager } -// vnfdCreateHandler handles creation of the vnfd entry in the database -func (h vnfdHandler) vnfdCreateHandler(w http.ResponseWriter, r *http.Request) { - var v vnfd.VNFDefinition +// createHandler handles creation of the definition entry in the database +func (h rbDefinitionHandler) createHandler(w http.ResponseWriter, r *http.Request) { + var v rb.Definition if r.Body == nil { http.Error(w, "Empty body", http.StatusBadRequest) @@ -54,7 +54,7 @@ func (h vnfdHandler) vnfdCreateHandler(w http.ResponseWriter, r *http.Request) { return } - ret, err := h.vnfdClient.Create(v) + ret, err := h.client.Create(v) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -69,15 +69,36 @@ func (h vnfdHandler) vnfdCreateHandler(w http.ResponseWriter, r *http.Request) { } } -// vnfdUploadHandler handles upload of the vnf tar file into the database +// uploadHandler handles upload of the bundle tar file into the database // Note: This will be implemented in a different patch -func (h vnfdHandler) vnfdUploadHandler(w http.ResponseWriter, r *http.Request) { +func (h rbDefinitionHandler) uploadHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + uuid := vars["rbdID"] + + if r.Body == nil { + http.Error(w, "Empty Body", http.StatusBadRequest) + return + } + + inpBytes, err := ioutil.ReadAll(r.Body) + if err != nil { + http.Error(w, "Unable to read body", http.StatusBadRequest) + return + } + + err = h.client.Upload(uuid, inpBytes) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) } -// vnfdListHandler handles GET (list) operations on the /v1/vnfd endpoint -// Returns a list of vnfd.VNFDefinitions -func (h vnfdHandler) vnfdListHandler(w http.ResponseWriter, r *http.Request) { - ret, err := h.vnfdClient.List() +// listHandler handles GET (list) operations on the endpoint +// Returns a list of rb.Definitions +func (h rbDefinitionHandler) listHandler(w http.ResponseWriter, r *http.Request) { + ret, err := h.client.List() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -92,13 +113,13 @@ func (h vnfdHandler) vnfdListHandler(w http.ResponseWriter, r *http.Request) { } } -// vnfdGetHandler handles GET operations on a particular VNFID -// Returns a vnfd.VNFDefinition -func (h vnfdHandler) vnfdGetHandler(w http.ResponseWriter, r *http.Request) { +// getHandler handles GET operations on a particular ids +// Returns a rb.Definition +func (h rbDefinitionHandler) getHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) - vnfdID := vars["vnfdID"] + id := vars["rbdID"] - ret, err := h.vnfdClient.Get(vnfdID) + ret, err := h.client.Get(id) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -113,12 +134,12 @@ func (h vnfdHandler) vnfdGetHandler(w http.ResponseWriter, r *http.Request) { } } -// vnfdDeleteHandler handles DELETE operations on a particular VNFID -func (h vnfdHandler) vnfdDeleteHandler(w http.ResponseWriter, r *http.Request) { +// deleteHandler handles DELETE operations on a particular bundle definition id +func (h rbDefinitionHandler) deleteHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) - vnfdID := vars["vnfdID"] + id := vars["rbdID"] - err := h.vnfdClient.Delete(vnfdID) + err := h.client.Delete(id) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return diff --git a/src/k8splugin/api/vnfdhandler_test.go b/src/k8splugin/api/defhandler_test.go index e393be6f..9739ab12 100644 --- a/src/k8splugin/api/vnfdhandler_test.go +++ b/src/k8splugin/api/defhandler_test.go @@ -20,7 +20,7 @@ import ( "bytes" "encoding/json" "io" - "k8splugin/vnfd" + "k8splugin/rb" "net/http" "net/http/httptest" "reflect" @@ -32,54 +32,58 @@ import ( //Creating an embedded interface via anonymous variable //This allows us to make mockDB satisfy the DatabaseConnection //interface even if we are not implementing all the methods in it -type mockVNFDefinition struct { - vnfd.VNFDefinitionInterface +type mockRBDefinition struct { + rb.DefinitionManager // Items and err will be used to customize each test - // via a localized instantiation of mockVNFDefinition - Items []vnfd.VNFDefinition + // via a localized instantiation of mockRBDefinition + Items []rb.Definition Err error } -func (m *mockVNFDefinition) Create(inp vnfd.VNFDefinition) (vnfd.VNFDefinition, error) { +func (m *mockRBDefinition) Create(inp rb.Definition) (rb.Definition, error) { if m.Err != nil { - return vnfd.VNFDefinition{}, m.Err + return rb.Definition{}, m.Err } return m.Items[0], nil } -func (m *mockVNFDefinition) List() ([]vnfd.VNFDefinition, error) { +func (m *mockRBDefinition) List() ([]rb.Definition, error) { if m.Err != nil { - return []vnfd.VNFDefinition{}, m.Err + return []rb.Definition{}, m.Err } return m.Items, nil } -func (m *mockVNFDefinition) Get(vnfID string) (vnfd.VNFDefinition, error) { +func (m *mockRBDefinition) Get(id string) (rb.Definition, error) { if m.Err != nil { - return vnfd.VNFDefinition{}, m.Err + return rb.Definition{}, m.Err } return m.Items[0], nil } -func (m *mockVNFDefinition) Delete(vnfID string) error { +func (m *mockRBDefinition) Delete(id string) error { return m.Err } -func TestVnfdCreateHandler(t *testing.T) { +func (m *mockRBDefinition) Upload(id string, inp []byte) error { + return m.Err +} + +func TestRBDefCreateHandler(t *testing.T) { testCases := []struct { label string reader io.Reader - expected vnfd.VNFDefinition + expected rb.Definition expectedCode int - vnfdClient *mockVNFDefinition + rbDefClient *mockRBDefinition }{ { label: "Missing Body Failure", expectedCode: http.StatusBadRequest, - vnfdClient: &mockVNFDefinition{}, + rbDefClient: &mockRBDefinition{}, }, { label: "Create without UUID", @@ -89,18 +93,18 @@ func TestVnfdCreateHandler(t *testing.T) { "description":"test description", "service-type":"firewall" }`)), - expected: vnfd.VNFDefinition{ + expected: rb.Definition{ UUID: "123e4567-e89b-12d3-a456-426655440000", - Name: "testvnf", + Name: "testresourcebundle", Description: "test description", ServiceType: "firewall", }, - vnfdClient: &mockVNFDefinition{ + rbDefClient: &mockRBDefinition{ //Items that will be returned by the mocked Client - Items: []vnfd.VNFDefinition{ + Items: []rb.Definition{ { UUID: "123e4567-e89b-12d3-a456-426655440000", - Name: "testvnf", + Name: "testresourcebundle", Description: "test description", ServiceType: "firewall", }, @@ -111,15 +115,15 @@ func TestVnfdCreateHandler(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.label, func(t *testing.T) { - vh := vnfdHandler{vnfdClient: testCase.vnfdClient} - req, err := http.NewRequest("POST", "/v1/vnfd", testCase.reader) + vh := rbDefinitionHandler{client: testCase.rbDefClient} + req, err := http.NewRequest("POST", "/v1/resource/definition", testCase.reader) if err != nil { t.Fatal(err) } rr := httptest.NewRecorder() - hr := http.HandlerFunc(vh.vnfdCreateHandler) + hr := http.HandlerFunc(vh.createHandler) hr.ServeHTTP(rr, req) //Check returned code @@ -129,11 +133,11 @@ func TestVnfdCreateHandler(t *testing.T) { //Check returned body only if statusCreated if rr.Code == http.StatusCreated { - got := vnfd.VNFDefinition{} + got := rb.Definition{} json.NewDecoder(rr.Body).Decode(&got) if reflect.DeepEqual(testCase.expected, got) == false { - t.Errorf("vnfdCreateHandler returned unexpected body: got %v;"+ + t.Errorf("createHandler returned unexpected body: got %v;"+ " expected %v", got, testCase.expected) } } @@ -141,43 +145,43 @@ func TestVnfdCreateHandler(t *testing.T) { } } -func TestVnfdListHandler(t *testing.T) { +func TestRBDefListHandler(t *testing.T) { testCases := []struct { label string - expected []vnfd.VNFDefinition + expected []rb.Definition expectedCode int - vnfdClient *mockVNFDefinition + rbDefClient *mockRBDefinition }{ { - label: "List VNF Definitions", + label: "List Bundle Definitions", expectedCode: http.StatusOK, - expected: []vnfd.VNFDefinition{ + expected: []rb.Definition{ { UUID: "123e4567-e89b-12d3-a456-426655440000", - Name: "testvnf", + Name: "testresourcebundle", Description: "test description", ServiceType: "firewall", }, { UUID: "123e4567-e89b-12d3-a456-426655441111", - Name: "testvnf2", + Name: "testresourcebundle2", Description: "test description", ServiceType: "dns", }, }, - vnfdClient: &mockVNFDefinition{ + rbDefClient: &mockRBDefinition{ // list of definitions that will be returned by the mockclient - Items: []vnfd.VNFDefinition{ + Items: []rb.Definition{ { UUID: "123e4567-e89b-12d3-a456-426655440000", - Name: "testvnf", + Name: "testresourcebundle", Description: "test description", ServiceType: "firewall", }, { UUID: "123e4567-e89b-12d3-a456-426655441111", - Name: "testvnf2", + Name: "testresourcebundle2", Description: "test description", ServiceType: "dns", }, @@ -188,14 +192,14 @@ func TestVnfdListHandler(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.label, func(t *testing.T) { - vh := vnfdHandler{vnfdClient: testCase.vnfdClient} - req, err := http.NewRequest("GET", "/v1/vnfd", nil) + vh := rbDefinitionHandler{client: testCase.rbDefClient} + req, err := http.NewRequest("GET", "/v1/resource/definition", nil) if err != nil { t.Fatal(err) } rr := httptest.NewRecorder() - hr := http.HandlerFunc(vh.vnfdListHandler) + hr := http.HandlerFunc(vh.listHandler) hr.ServeHTTP(rr, req) //Check returned code @@ -205,11 +209,11 @@ func TestVnfdListHandler(t *testing.T) { //Check returned body only if statusOK if rr.Code == http.StatusOK { - got := []vnfd.VNFDefinition{} + got := []rb.Definition{} json.NewDecoder(rr.Body).Decode(&got) if reflect.DeepEqual(testCase.expected, got) == false { - t.Errorf("vnfdListHandler returned unexpected body: got %v;"+ + t.Errorf("listHandler returned unexpected body: got %v;"+ " expected %v", got, testCase.expected) } } @@ -217,31 +221,31 @@ func TestVnfdListHandler(t *testing.T) { } } -func TestVnfdGetHandler(t *testing.T) { +func TestRBDefGetHandler(t *testing.T) { testCases := []struct { label string - expected vnfd.VNFDefinition + expected rb.Definition inpUUID string expectedCode int - vnfdClient *mockVNFDefinition + rbDefClient *mockRBDefinition }{ { - label: "Get VNF Definition", + label: "Get Bundle Definition", expectedCode: http.StatusOK, - expected: vnfd.VNFDefinition{ + expected: rb.Definition{ UUID: "123e4567-e89b-12d3-a456-426655441111", - Name: "testvnf2", + Name: "testresourcebundle2", Description: "test description", ServiceType: "dns", }, inpUUID: "123e4567-e89b-12d3-a456-426655441111", - vnfdClient: &mockVNFDefinition{ + rbDefClient: &mockRBDefinition{ // list of definitions that will be returned by the mockclient - Items: []vnfd.VNFDefinition{ + Items: []rb.Definition{ { UUID: "123e4567-e89b-12d3-a456-426655441111", - Name: "testvnf2", + Name: "testresourcebundle2", Description: "test description", ServiceType: "dns", }, @@ -249,12 +253,12 @@ func TestVnfdGetHandler(t *testing.T) { }, }, { - label: "Get Non-Exiting VNF Definition", + label: "Get Non-Exiting Bundle Definition", expectedCode: http.StatusInternalServerError, inpUUID: "123e4567-e89b-12d3-a456-426655440000", - vnfdClient: &mockVNFDefinition{ + rbDefClient: &mockRBDefinition{ // list of definitions that will be returned by the mockclient - Items: []vnfd.VNFDefinition{}, + Items: []rb.Definition{}, Err: pkgerrors.New("Internal Error"), }, }, @@ -262,14 +266,14 @@ func TestVnfdGetHandler(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.label, func(t *testing.T) { - vh := vnfdHandler{vnfdClient: testCase.vnfdClient} - req, err := http.NewRequest("GET", "/v1/vnfd/"+testCase.inpUUID, nil) + vh := rbDefinitionHandler{client: testCase.rbDefClient} + req, err := http.NewRequest("GET", "/v1/resource/definition/"+testCase.inpUUID, nil) if err != nil { t.Fatal(err) } rr := httptest.NewRecorder() - hr := http.HandlerFunc(vh.vnfdGetHandler) + hr := http.HandlerFunc(vh.getHandler) hr.ServeHTTP(rr, req) //Check returned code @@ -279,11 +283,11 @@ func TestVnfdGetHandler(t *testing.T) { //Check returned body only if statusOK if rr.Code == http.StatusOK { - got := vnfd.VNFDefinition{} + got := rb.Definition{} json.NewDecoder(rr.Body).Decode(&got) if reflect.DeepEqual(testCase.expected, got) == false { - t.Errorf("vnfdListHandler returned unexpected body: got %v;"+ + t.Errorf("listHandler returned unexpected body: got %v;"+ " expected %v", got, testCase.expected) } } @@ -291,25 +295,25 @@ func TestVnfdGetHandler(t *testing.T) { } } -func TestVnfdDeleteHandler(t *testing.T) { +func TestRBDefDeleteHandler(t *testing.T) { testCases := []struct { label string inpUUID string expectedCode int - vnfdClient *mockVNFDefinition + rbDefClient *mockRBDefinition }{ { - label: "Delete VNF Definition", + label: "Delete Bundle Definition", expectedCode: http.StatusNoContent, inpUUID: "123e4567-e89b-12d3-a456-426655441111", - vnfdClient: &mockVNFDefinition{}, + rbDefClient: &mockRBDefinition{}, }, { - label: "Delete Non-Exiting VNF Definition", + label: "Delete Non-Exiting Bundle Definition", expectedCode: http.StatusInternalServerError, inpUUID: "123e4567-e89b-12d3-a456-426655440000", - vnfdClient: &mockVNFDefinition{ + rbDefClient: &mockRBDefinition{ Err: pkgerrors.New("Internal Error"), }, }, @@ -317,14 +321,75 @@ func TestVnfdDeleteHandler(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.label, func(t *testing.T) { - vh := vnfdHandler{vnfdClient: testCase.vnfdClient} - req, err := http.NewRequest("GET", "/v1/vnfd/"+testCase.inpUUID, nil) + vh := rbDefinitionHandler{client: testCase.rbDefClient} + req, err := http.NewRequest("GET", "/v1/resource/definition/"+testCase.inpUUID, nil) + if err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + hr := http.HandlerFunc(vh.deleteHandler) + + hr.ServeHTTP(rr, req) + //Check returned code + if rr.Code != testCase.expectedCode { + t.Fatalf("Expected %d; Got: %d", testCase.expectedCode, rr.Code) + } + }) + } +} + +func TestRBDefUploadHandler(t *testing.T) { + + testCases := []struct { + label string + inpUUID string + body io.Reader + expectedCode int + rbDefClient *mockRBDefinition + }{ + { + label: "Upload Bundle Definition Content", + expectedCode: http.StatusOK, + inpUUID: "123e4567-e89b-12d3-a456-426655441111", + body: bytes.NewBuffer([]byte{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xff, 0xf2, 0x48, 0xcd, + }), + rbDefClient: &mockRBDefinition{}, + }, + { + label: "Upload Invalid Bundle Definition Content", + expectedCode: http.StatusInternalServerError, + inpUUID: "123e4567-e89b-12d3-a456-426655440000", + body: bytes.NewBuffer([]byte{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xff, 0xf2, 0x48, 0xcd, + }), + rbDefClient: &mockRBDefinition{ + Err: pkgerrors.New("Internal Error"), + }, + }, + { + label: "Upload Empty Body Content", + expectedCode: http.StatusBadRequest, + inpUUID: "123e4567-e89b-12d3-a456-426655440000", + rbDefClient: &mockRBDefinition{}, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + vh := rbDefinitionHandler{client: testCase.rbDefClient} + req, err := http.NewRequest("POST", + "/v1/resource/definition/"+testCase.inpUUID+"/content", testCase.body) + if err != nil { t.Fatal(err) } rr := httptest.NewRecorder() - hr := http.HandlerFunc(vh.vnfdDeleteHandler) + hr := http.HandlerFunc(vh.uploadHandler) hr.ServeHTTP(rr, req) //Check returned code diff --git a/src/k8splugin/api/handler.go b/src/k8splugin/api/handler.go index 53fa2317..4c49ba78 100644 --- a/src/k8splugin/api/handler.go +++ b/src/k8splugin/api/handler.go @@ -30,6 +30,10 @@ import ( "k8splugin/krd" ) +//TODO: Separate the http handler code and backend code out +var storeName = "rbinst" +var tagData = "data" + // GetVNFClient retrieves the client used to communicate with a Kubernetes Cluster var GetVNFClient = func(kubeConfigPath string) (kubernetes.Clientset, error) { client, err := krd.GetKubeClient(kubeConfigPath) @@ -117,17 +121,9 @@ func CreateHandler(w http.ResponseWriter, r *http.Request) { // TODO: Uncomment when annotations are done // krd.AddNetworkAnnotationsToPod(kubeData, resource.Networks) - // "{"deployment":<>,"service":<>}" - serializedResourceNameMap, err := db.Serialize(resourceNameMap) - if err != nil { - werr := pkgerrors.Wrap(err, "Create VNF deployment JSON Marshalling error") - http.Error(w, werr.Error(), http.StatusInternalServerError) - return - } - // key: cloud1-default-uuid // value: "{"deployment":<>,"service":<>}" - err = db.DBconn.Create(internalVNFID, serializedResourceNameMap) + err = db.DBconn.Create(storeName, internalVNFID, tagData, resourceNameMap) if err != nil { werr := pkgerrors.Wrap(err, "Create VNF deployment DB error") http.Error(w, werr.Error(), http.StatusInternalServerError) @@ -154,27 +150,22 @@ func ListHandler(w http.ResponseWriter, r *http.Request) { namespace := vars["namespace"] prefix := cloudRegionID + "-" + namespace - internalVNFIDs, err := db.DBconn.ReadAll(prefix) + res, err := db.DBconn.ReadAll(storeName, tagData) if err != nil { http.Error(w, pkgerrors.Wrap(err, "Get VNF list error").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:]) + for key, value := range res { + if len(value) > 0 { + editedList = append(editedList, strings.TrimPrefix(key, prefix)[1:]) } } @@ -204,25 +195,20 @@ func DeleteHandler(w http.ResponseWriter, r *http.Request) { // key: cloud1-default-uuid // value: "{"deployment":<>,"service":<>}" - serializedResourceNameMap, err := db.DBconn.Read(internalVNFID) + res, err := db.DBconn.Read(storeName, internalVNFID, tagData) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } - if serializedResourceNameMap == "" { - 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 = db.DeSerialize(serializedResourceNameMap, &deserializedResourceNameMap) + data := make(map[string][]string) + err = db.DBconn.Unmarshal(res, &data) if err != nil { werr := pkgerrors.Wrap(err, "Unmarshal VNF error") http.Error(w, werr.Error(), http.StatusInternalServerError) @@ -237,14 +223,14 @@ func DeleteHandler(w http.ResponseWriter, r *http.Request) { http.Error(w, err.Error(), http.StatusInternalServerError) return } - err = csar.DestroyVNF(deserializedResourceNameMap, namespace, &kubeclient) + err = csar.DestroyVNF(data, namespace, &kubeclient) if err != nil { werr := pkgerrors.Wrap(err, "Delete VNF error") http.Error(w, werr.Error(), http.StatusInternalServerError) return } - err = db.DBconn.Delete(internalVNFID) + err = db.DBconn.Delete(storeName, internalVNFID, tagData) if err != nil { werr := pkgerrors.Wrap(err, "Delete VNF db record error") http.Error(w, werr.Error(), http.StatusInternalServerError) @@ -337,25 +323,20 @@ func GetHandler(w http.ResponseWriter, r *http.Request) { // key: cloud1-default-uuid // value: "{"deployment":<>,"service":<>}" - serializedResourceNameMap, err := db.DBconn.Read(internalVNFID) + res, err := db.DBconn.Read(storeName, internalVNFID, tagData) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } - if serializedResourceNameMap == "" { - 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 = db.DeSerialize(serializedResourceNameMap, &deserializedResourceNameMap) + data := make(map[string][]string) + err = db.DBconn.Unmarshal(res, &data) if err != nil { werr := pkgerrors.Wrap(err, "Unmarshal VNF error") http.Error(w, werr.Error(), http.StatusInternalServerError) @@ -366,7 +347,7 @@ func GetHandler(w http.ResponseWriter, r *http.Request) { VNFID: externalVNFID, CloudRegionID: cloudRegionID, Namespace: namespace, - VNFComponents: deserializedResourceNameMap, + VNFComponents: data, } w.Header().Set("Content-Type", "application/json") diff --git a/src/k8splugin/api/handler_test.go b/src/k8splugin/api/handler_test.go index 3336bbc2..a3aeff7a 100644 --- a/src/k8splugin/api/handler_test.go +++ b/src/k8splugin/api/handler_test.go @@ -24,7 +24,6 @@ import ( "reflect" "testing" - "github.com/hashicorp/consul/api" pkgerrors "github.com/pkg/errors" "k8s.io/client-go/kubernetes" @@ -194,37 +193,18 @@ func TestListHandler(t *testing.T) { }, }, { - label: "Get result from DB non-records", - expectedCode: http.StatusNotFound, - mockStore: &db.MockDB{}, - }, - { label: "Get empty list", expectedCode: http.StatusOK, expectedResponse: []string{""}, - mockStore: &db.MockDB{ - Items: api.KVPairs{ - &api.KVPair{ - Key: "", - Value: []byte("{}"), - }, - }, - }, + mockStore: &db.MockDB{}, }, { label: "Succesful get a list of VNF", expectedCode: http.StatusOK, - expectedResponse: []string{"uid1", "uid2"}, + expectedResponse: []string{"uid1"}, mockStore: &db.MockDB{ - Items: api.KVPairs{ - &api.KVPair{ - Key: "uuid1", - Value: []byte("{}"), - }, - &api.KVPair{ - Key: "uuid2", - Value: []byte("{}"), - }, + Items: map[string][]byte{ + "uuid1": []byte("{}"), }, }, }, @@ -275,20 +255,17 @@ func TestDeleteHandler(t *testing.T) { }, { label: "Fail to find VNF record be deleted", - expectedCode: http.StatusNotFound, + expectedCode: http.StatusInternalServerError, mockStore: &db.MockDB{ - Items: api.KVPairs{}, + Items: map[string][]byte{}, }, }, { label: "Fail to unmarshal the DB record", expectedCode: http.StatusInternalServerError, mockStore: &db.MockDB{ - Items: api.KVPairs{ - &api.KVPair{ - Key: "cloudregion1-testnamespace-uuid1", - Value: []byte("{invalid format}"), - }, + Items: map[string][]byte{ + "cloudregion1-testnamespace-uuid1": []byte("{invalid format}"), }, }, }, @@ -297,14 +274,10 @@ func TestDeleteHandler(t *testing.T) { expectedCode: http.StatusInternalServerError, mockGetVNFClientErr: pkgerrors.New("Get VNF client error"), mockStore: &db.MockDB{ - Items: api.KVPairs{ - &api.KVPair{ - Key: "cloudregion1-testnamespace-uuid1", - Value: []byte("{" + - "\"deployment\": [\"deploy1\", \"deploy2\"]," + - "\"service\": [\"svc1\", \"svc2\"]" + - "}"), - }, + Items: map[string][]byte{ + "cloudregion1-testnamespace-uuid1": []byte( + "{\"deployment\": [\"deploy1\", \"deploy2\"]," + + "\"service\": [\"svc1\", \"svc2\"]}"), }, }, }, @@ -312,14 +285,10 @@ func TestDeleteHandler(t *testing.T) { label: "Fail to destroy VNF", expectedCode: http.StatusInternalServerError, mockStore: &db.MockDB{ - Items: api.KVPairs{ - &api.KVPair{ - Key: "cloudregion1-testnamespace-uuid1", - Value: []byte("{" + - "\"deployment\": [\"deploy1\", \"deploy2\"]," + - "\"service\": [\"svc1\", \"svc2\"]" + - "}"), - }, + Items: map[string][]byte{ + "cloudregion1-testnamespace-uuid1": []byte( + "{\"deployment\": [\"deploy1\", \"deploy2\"]," + + "\"service\": [\"svc1\", \"svc2\"]}"), }, }, mockDeleteVNF: &mockCSAR{ @@ -330,14 +299,10 @@ func TestDeleteHandler(t *testing.T) { label: "Succesful delete a VNF", expectedCode: http.StatusAccepted, mockStore: &db.MockDB{ - Items: api.KVPairs{ - &api.KVPair{ - Key: "cloudregion1-testnamespace-uuid1", - Value: []byte("{" + - "\"deployment\": [\"deploy1\", \"deploy2\"]," + - "\"service\": [\"svc1\", \"svc2\"]" + - "}"), - }, + Items: map[string][]byte{ + "cloudregion1-testnamespace-uuid1": []byte( + "{\"deployment\": [\"deploy1\", \"deploy2\"]," + + "\"service\": [\"svc1\", \"svc2\"]}"), }, }, mockDeleteVNF: &mockCSAR{}, @@ -440,18 +405,15 @@ func TestGetHandler(t *testing.T) { }, { label: "Not found DB record", - expectedCode: http.StatusNotFound, + expectedCode: http.StatusInternalServerError, mockStore: &db.MockDB{}, }, { label: "Fail to unmarshal the DB record", expectedCode: http.StatusInternalServerError, mockStore: &db.MockDB{ - Items: api.KVPairs{ - &api.KVPair{ - Key: "cloud1-default-1", - Value: []byte("{invalid-format}"), - }, + Items: map[string][]byte{ + "cloud1-default-1": []byte("{invalid-format}"), }, }, }, @@ -468,18 +430,11 @@ func TestGetHandler(t *testing.T) { }, }, mockStore: &db.MockDB{ - Items: api.KVPairs{ - &api.KVPair{ - Key: "cloud1-default-1", - Value: []byte("{" + - "\"deployment\": [\"deploy1\", \"deploy2\"]," + - "\"service\": [\"svc1\", \"svc2\"]" + - "}"), - }, - &api.KVPair{ - Key: "cloud1-default-2", - Value: []byte("{}"), - }, + Items: map[string][]byte{ + "cloud1-default-1": []byte( + "{\"deployment\": [\"deploy1\", \"deploy2\"]," + + "\"service\": [\"svc1\", \"svc2\"]}"), + "cloud1-default-2": []byte("{}"), }, }, }, diff --git a/src/k8splugin/db/consul.go b/src/k8splugin/db/consul.go index d7507242..a61a4c10 100644 --- a/src/k8splugin/db/consul.go +++ b/src/k8splugin/db/consul.go @@ -54,50 +54,64 @@ func NewConsulStore(store ConsulKVStore) (Store, error) { // HealthCheck verifies if the database is up and running func (c *ConsulStore) HealthCheck() error { - _, err := c.Read("test") + _, err := c.Read("test", "test", "test") if err != nil { return pkgerrors.New("[ERROR] Cannot talk to Datastore. Check if it is running/reachable.") } return nil } +// Unmarshal implements any unmarshaling that is needed when using consul +func (c *ConsulStore) Unmarshal(inp []byte, out interface{}) error { + return nil +} + // Create is used to create a DB entry -func (c *ConsulStore) Create(key, value string) error { +func (c *ConsulStore) Create(root, key, tag string, data interface{}) error { + + value, err := Serialize(data) + if err != nil { + return pkgerrors.Wrap(err, "Serializing input data") + } + p := &api.KVPair{ Key: key, Value: []byte(value), } - _, err := c.client.Put(p, nil) + _, err = c.client.Put(p, nil) return err } // Read method returns the internalID for a particular externalID -func (c *ConsulStore) Read(key string) (string, error) { +func (c *ConsulStore) Read(root, key, tag string) ([]byte, error) { + key = root + "/" + key + "/" + tag pair, _, err := c.client.Get(key, nil) if err != nil { - return "", err + return nil, err } if pair == nil { - return "", nil + return nil, nil } - return string(pair.Value), nil + return pair.Value, nil } // Delete method removes an internalID from the Database -func (c *ConsulStore) Delete(key string) error { +func (c *ConsulStore) Delete(root, key, tag string) error { _, err := c.client.Delete(key, nil) return err } // ReadAll is used to get all ExternalIDs in a namespace -func (c *ConsulStore) ReadAll(prefix string) ([]string, error) { - pairs, _, err := c.client.List(prefix, nil) +func (c *ConsulStore) ReadAll(root, tag string) (map[string][]byte, error) { + pairs, _, err := c.client.List(root, nil) if err != nil { return nil, err } - var result []string + + //TODO: Filter results by tag and return it + result := make(map[string][]byte) for _, keypair := range pairs { - result = append(result, keypair.Key) + result[keypair.Key] = keypair.Value } return result, nil diff --git a/src/k8splugin/db/consul_test.go b/src/k8splugin/db/consul_test.go index ede1a5e9..754112ad 100644 --- a/src/k8splugin/db/consul_test.go +++ b/src/k8splugin/db/consul_test.go @@ -107,12 +107,14 @@ func TestConsulCreate(t *testing.T) { }{ { label: "Sucessful register a record to Consul Database", - input: map[string]string{"key": "test-key", "value": "test-value"}, - mock: &mockConsulKVStore{}, + input: map[string]string{"root": "rbinst", "key": "test-key", + "tag": "data", "value": "test-value"}, + mock: &mockConsulKVStore{}, }, { label: "Fail to create a new record in Consul Database", - input: map[string]string{"key": "test-key", "value": "test-value"}, + input: map[string]string{"root": "rbinst", "key": "test-key", + "tag": "data", "value": "test-value"}, mock: &mockConsulKVStore{ Err: pkgerrors.New("DB error"), }, @@ -123,7 +125,8 @@ func TestConsulCreate(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.label, func(t *testing.T) { client, _ := NewConsulStore(testCase.mock) - err := client.Create(testCase.input["key"], testCase.input["value"]) + err := client.Create(testCase.input["root"], testCase.input["key"], + testCase.input["tag"], testCase.input["value"]) if err != nil { if testCase.expectedError == "" { t.Fatalf("Create method return an un-expected (%s)", err) @@ -139,18 +142,19 @@ func TestConsulCreate(t *testing.T) { func TestConsulRead(t *testing.T) { testCases := []struct { label string - input string + input map[string]string mock *mockConsulKVStore expectedError string expectedResult string }{ { label: "Sucessful retrieve a record from Consul Database", - input: "test", + input: map[string]string{"root": "rbinst", "key": "test", + "tag": "data"}, mock: &mockConsulKVStore{ Items: api.KVPairs{ &api.KVPair{ - Key: "test", + Key: "rbinst/test/data", Value: []byte("test-value"), }, }, @@ -159,12 +163,14 @@ func TestConsulRead(t *testing.T) { }, { label: "Fail retrieve a non-existing record from Consul Database", - input: "test", - mock: &mockConsulKVStore{}, + input: map[string]string{"root": "rbinst", "key": "test-key", + "tag": "data"}, + mock: &mockConsulKVStore{}, }, { label: "Fail retrieve a record from Consul Database", - input: "test", + input: map[string]string{"root": "rbinst", "key": "test-key", + "tag": "data"}, mock: &mockConsulKVStore{ Err: pkgerrors.New("DB error"), }, @@ -175,7 +181,8 @@ func TestConsulRead(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.label, func(t *testing.T) { client, _ := NewConsulStore(testCase.mock) - result, err := client.Read(testCase.input) + result, err := client.Read(testCase.input["root"], testCase.input["key"], + testCase.input["tag"]) if err != nil { if testCase.expectedError == "" { t.Fatalf("Read method return an un-expected (%s)", err) @@ -187,7 +194,7 @@ func TestConsulRead(t *testing.T) { if testCase.expectedError != "" && testCase.expectedResult == "" { t.Fatalf("Read method was expecting \"%s\" error message", testCase.expectedError) } - if !reflect.DeepEqual(testCase.expectedResult, result) { + if !reflect.DeepEqual(testCase.expectedResult, string(result)) { t.Fatalf("Read method returned: \n%v\n and it was expected: \n%v", result, testCase.expectedResult) } @@ -199,14 +206,15 @@ func TestConsulRead(t *testing.T) { func TestConsulDelete(t *testing.T) { testCases := []struct { label string - input string + input map[string]string mock *mockConsulKVStore expectedError string }{ { label: "Sucessful delete a record to Consul Database", - input: "test", - mock: &mockConsulKVStore{}, + input: map[string]string{"root": "rbinst", "key": "test-key", + "tag": "data"}, + mock: &mockConsulKVStore{}, }, { label: "Fail to delete a record in Consul Database", @@ -220,7 +228,8 @@ func TestConsulDelete(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.label, func(t *testing.T) { client, _ := NewConsulStore(testCase.mock) - err := client.Delete(testCase.input) + err := client.Delete(testCase.input["root"], testCase.input["key"], + testCase.input["tag"]) if err != nil { if testCase.expectedError == "" { t.Fatalf("Delete method return an un-expected (%s)", err) @@ -236,14 +245,15 @@ func TestConsulDelete(t *testing.T) { func TestConsulReadAll(t *testing.T) { testCases := []struct { label string - input string + input map[string]string mock *mockConsulKVStore expectedError string - expectedResult []string + expectedResult map[string][]byte }{ { label: "Sucessful retrieve a list from Consul Database", - input: "test", + input: map[string]string{"root": "rbinst", "key": "test-key", + "tag": "data"}, mock: &mockConsulKVStore{ Items: api.KVPairs{ &api.KVPair{ @@ -256,16 +266,20 @@ func TestConsulReadAll(t *testing.T) { }, }, }, - expectedResult: []string{"test", "test2"}, + expectedResult: map[string][]byte{"test": []byte("test-value"), + "test2": []byte("test-value2")}, }, { label: "Sucessful retrieve an empty list from Consul Database", - input: "test", - mock: &mockConsulKVStore{}, + input: map[string]string{"root": "rbinst", "key": "test-key", + "tag": "data"}, + mock: &mockConsulKVStore{}, + expectedResult: map[string][]byte{}, }, { label: "Fail retrieve a record from Consul Database", - input: "test", + input: map[string]string{"root": "rbinst", "key": "test-key", + "tag": "data"}, mock: &mockConsulKVStore{ Err: pkgerrors.New("DB error"), }, @@ -276,7 +290,8 @@ func TestConsulReadAll(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.label, func(t *testing.T) { client, _ := NewConsulStore(testCase.mock) - result, err := client.ReadAll(testCase.input) + result, err := client.ReadAll(testCase.input["root"], + testCase.input["tag"]) if err != nil { if testCase.expectedError == "" { t.Fatalf("ReadAll method return an un-expected (%s)", err) diff --git a/src/k8splugin/db/mongo.go b/src/k8splugin/db/mongo.go new file mode 100644 index 00000000..311f044c --- /dev/null +++ b/src/k8splugin/db/mongo.go @@ -0,0 +1,323 @@ +/* + * 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 db + +import ( + "github.com/mongodb/mongo-go-driver/bson" + "github.com/mongodb/mongo-go-driver/bson/primitive" + "github.com/mongodb/mongo-go-driver/mongo" + "github.com/mongodb/mongo-go-driver/mongo/options" + pkgerrors "github.com/pkg/errors" + "golang.org/x/net/context" + "log" + "os" +) + +// MongoCollection defines the a subset of MongoDB operations +// Note: This interface is defined mainly for mock testing +type MongoCollection interface { + InsertOne(ctx context.Context, document interface{}, + opts ...*options.InsertOneOptions) (*mongo.InsertOneResult, error) + FindOne(ctx context.Context, filter interface{}, + opts ...*options.FindOneOptions) *mongo.SingleResult + FindOneAndUpdate(ctx context.Context, filter interface{}, + update interface{}, opts ...*options.FindOneAndUpdateOptions) *mongo.SingleResult + DeleteOne(ctx context.Context, filter interface{}, + opts ...*options.DeleteOptions) (*mongo.DeleteResult, error) + Find(ctx context.Context, filter interface{}, + opts ...*options.FindOptions) (mongo.Cursor, error) +} + +// MongoStore is an implementation of the db.Store interface +type MongoStore struct { + db *mongo.Database +} + +// This exists only for allowing us to mock the collection object +// for testing purposes +var getCollection = func(coll string, m *MongoStore) MongoCollection { + return m.db.Collection(coll) +} + +// This exists only for allowing us to mock the DecodeBytes function +// Mainly because we cannot construct a SingleResult struct from our +// tests. All fields in that struct are private. +var decodeBytes = func(sr *mongo.SingleResult) (bson.Raw, error) { + return sr.DecodeBytes() +} + +// NewMongoStore initializes a Mongo Database with the name provided +// If a database with that name exists, it will be returned +func NewMongoStore(name string, store *mongo.Database) (Store, error) { + if store == nil { + ip := "mongodb://" + os.Getenv("DATABASE_IP") + ":27017" + mongoClient, err := mongo.NewClient(ip) + if err != nil { + return nil, err + } + + err = mongoClient.Connect(context.Background()) + if err != nil { + return nil, err + } + store = mongoClient.Database(name) + } + + return &MongoStore{ + db: store, + }, nil +} + +// HealthCheck verifies if the database is up and running +func (m *MongoStore) HealthCheck() error { + + _, err := decodeBytes(m.db.RunCommand(context.Background(), bson.D{{"serverStatus", 1}})) + if err != nil { + return pkgerrors.Wrap(err, "Error getting server status") + } + + return nil +} + +// validateParams checks to see if any parameters are empty +func (m *MongoStore) validateParams(args ...string) bool { + for _, v := range args { + if v == "" { + return false + } + } + + return true +} + +// Create is used to create a DB entry +func (m *MongoStore) Create(coll, key, tag string, data interface{}) error { + if data == nil || !m.validateParams(coll, key, tag) { + return pkgerrors.New("No Data to store") + } + + c := getCollection(coll, m) + ctx := context.Background() + + //Insert the data and then add the objectID to the masterTable + res, err := c.InsertOne(ctx, bson.D{ + {tag, data}, + }) + if err != nil { + return pkgerrors.Errorf("Error inserting into database: %s", err.Error()) + } + + //Add objectID of created data to masterKey document + //Create masterkey document if it does not exist + filter := bson.D{{"key", key}} + + _, err = decodeBytes( + c.FindOneAndUpdate( + ctx, + filter, + bson.D{ + {"$set", bson.D{ + {tag, res.InsertedID}, + }}, + }, + options.FindOneAndUpdate().SetUpsert(true).SetReturnDocument(options.After))) + + if err != nil { + return pkgerrors.Errorf("Error updating master table: %s", err.Error()) + } + + return nil +} + +// Unmarshal implements an unmarshaler for bson data that +// is produced from the mongo database +func (m *MongoStore) Unmarshal(inp []byte, out interface{}) error { + err := bson.Unmarshal(inp, out) + if err != nil { + return pkgerrors.Wrap(err, "Unmarshaling bson") + } + return nil +} + +// Read method returns the data stored for this key and for this particular tag +func (m *MongoStore) Read(coll, key, tag string) ([]byte, error) { + if !m.validateParams(coll, key, tag) { + return nil, pkgerrors.New("Mandatory fields are missing") + } + + c := getCollection(coll, m) + ctx := context.Background() + + //Get the masterkey document based on given key + filter := bson.D{{"key", key}} + keydata, err := decodeBytes(c.FindOne(context.Background(), filter)) + if err != nil { + return nil, pkgerrors.Errorf("Error finding master table: %s", err.Error()) + } + + //Read the tag objectID from document + tagoid, ok := keydata.Lookup(tag).ObjectIDOK() + if !ok { + return nil, pkgerrors.Errorf("Error finding objectID for tag %s", tag) + } + + //Use tag objectID to read the data from store + filter = bson.D{{"_id", tagoid}} + tagdata, err := decodeBytes(c.FindOne(ctx, filter)) + if err != nil { + return nil, pkgerrors.Errorf("Error reading found object: %s", err.Error()) + } + + //Return the data as a byte array + return tagdata.Lookup(tag).Value, nil +} + +// Helper function that deletes an object by its ID +func (m *MongoStore) deleteObjectByID(coll string, objID primitive.ObjectID) error { + + c := getCollection(coll, m) + ctx := context.Background() + + _, err := c.DeleteOne(ctx, bson.D{{"_id", objID}}) + if err != nil { + return pkgerrors.Errorf("Error Deleting from database: %s", err.Error()) + } + + log.Printf("Deleted Obj with ID %s", objID.String()) + return nil +} + +// Delete method removes a document from the Database that matches key +// TODO: delete all referenced docs if tag is empty string +func (m *MongoStore) Delete(coll, key, tag string) error { + if !m.validateParams(coll, key, tag) { + return pkgerrors.New("Mandatory fields are missing") + } + + c := getCollection(coll, m) + ctx := context.Background() + + //Get the masterkey document based on given key + filter := bson.D{{"key", key}} + //Remove the tag ID entry from masterkey table + update := bson.D{ + { + "$unset", bson.D{ + {tag, ""}, + }, + }, + } + keydata, err := decodeBytes(c.FindOneAndUpdate(ctx, filter, update, + options.FindOneAndUpdate().SetReturnDocument(options.Before))) + if err != nil { + return pkgerrors.Errorf("Error decoding master table after update: %s", + err.Error()) + } + + //Read the tag objectID from document + elems, err := keydata.Elements() + if err != nil { + return pkgerrors.Errorf("Error reading elements from database: %s", err.Error()) + } + + tagoid, ok := keydata.Lookup(tag).ObjectIDOK() + if !ok { + return pkgerrors.Errorf("Error finding objectID for tag %s", tag) + } + + //Use tag objectID to read the data from store + err = m.deleteObjectByID(coll, tagoid) + if err != nil { + return pkgerrors.Errorf("Error deleting from database: %s", err.Error()) + } + + //Delete master table if no more tags left + //_id, key and tag should be elements in before doc + //if master table needs to be removed too + if len(elems) == 3 { + keyid, ok := keydata.Lookup("_id").ObjectIDOK() + if !ok { + return pkgerrors.Errorf("Error finding objectID for key %s", key) + } + err = m.deleteObjectByID(coll, keyid) + if err != nil { + return pkgerrors.Errorf("Error deleting master table from database: %s", err.Error()) + } + } + + return nil +} + +// ReadAll is used to get all documents in db of a particular tag +func (m *MongoStore) ReadAll(coll, tag string) (map[string][]byte, error) { + if !m.validateParams(coll, tag) { + return nil, pkgerrors.New("Missing collection or tag name") + } + + c := getCollection(coll, m) + ctx := context.Background() + + //Get all master tables in this collection + filter := bson.D{ + {"key", bson.D{ + {"$exists", true}, + }}, + } + cursor, err := c.Find(ctx, filter) + if err != nil { + return nil, pkgerrors.Errorf("Error reading from database: %s", err.Error()) + } + defer cursor.Close(ctx) + + //Iterate over all the master tables + result := make(map[string][]byte) + for cursor.Next(ctx) { + d, err := cursor.DecodeBytes() + if err != nil { + log.Printf("Unable to decode data in Readall: %s", err.Error()) + continue + } + + //Read key of each master table + key, ok := d.Lookup("key").StringValueOK() + if !ok { + log.Printf("Unable to read key string from mastertable %s", err.Error()) + continue + } + + //Get objectID of tag document + tid, ok := d.Lookup(tag).ObjectIDOK() + if !ok { + log.Printf("Did not find tag: %s", tag) + continue + } + + //Find tag document and unmarshal it into []byte + tagData, err := decodeBytes(c.FindOne(ctx, bson.D{{"_id", tid}})) + if err != nil { + log.Printf("Unable to decode tag data %s", err.Error()) + continue + } + result[key] = tagData.Lookup(tag).Value + } + + if len(result) == 0 { + return result, pkgerrors.Errorf("Did not find any objects with tag: %s", tag) + } + + return result, nil +} diff --git a/src/k8splugin/db/mongo_test.go b/src/k8splugin/db/mongo_test.go new file mode 100644 index 00000000..1663e774 --- /dev/null +++ b/src/k8splugin/db/mongo_test.go @@ -0,0 +1,530 @@ +// +build unit + +/* + * 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 db + +import ( + "bytes" + "context" + "github.com/mongodb/mongo-go-driver/bson" + "github.com/mongodb/mongo-go-driver/mongo" + "github.com/mongodb/mongo-go-driver/mongo/options" + pkgerrors "github.com/pkg/errors" + "reflect" + "strings" + "testing" +) + +// Implements the mongo.Cursor interface +type mockCursor struct { + mongo.Cursor + err error + bson bson.Raw + count int +} + +func (mc *mockCursor) Next(ctx context.Context) bool { + if mc.count > 0 { + mc.count = mc.count - 1 + return true + } + return false +} + +func (mc *mockCursor) DecodeBytes() (bson.Raw, error) { + return mc.bson, mc.err +} + +func (mc *mockCursor) Close(ctx context.Context) error { + return nil +} + +//Implements the functions used currently in mongo.go +type mockCollection struct { + Err error + mCursor mongo.Cursor +} + +func (c *mockCollection) InsertOne(ctx context.Context, document interface{}, + opts ...*options.InsertOneOptions) (*mongo.InsertOneResult, error) { + + if c.Err != nil { + return nil, c.Err + } + + return &mongo.InsertOneResult{InsertedID: "_id1234"}, nil +} + +func (c *mockCollection) FindOne(ctx context.Context, filter interface{}, + opts ...*options.FindOneOptions) *mongo.SingleResult { + + return &mongo.SingleResult{} +} + +func (c *mockCollection) FindOneAndUpdate(ctx context.Context, filter interface{}, + update interface{}, opts ...*options.FindOneAndUpdateOptions) *mongo.SingleResult { + + return &mongo.SingleResult{} +} + +func (c *mockCollection) DeleteOne(ctx context.Context, filter interface{}, + opts ...*options.DeleteOptions) (*mongo.DeleteResult, error) { + + return nil, c.Err +} + +func (c *mockCollection) Find(ctx context.Context, filter interface{}, + opts ...*options.FindOptions) (mongo.Cursor, error) { + + return c.mCursor, c.Err +} + +func TestCreate(t *testing.T) { + testCases := []struct { + label string + input map[string]interface{} + mockColl *mockCollection + bson bson.Raw + expectedError string + }{ + { + label: "Successfull creation of entry", + input: map[string]interface{}{ + "coll": "collname", + "key": "keyvalue", + "tag": "tagName", + "data": "Data In String Format", + }, + bson: bson.Raw{'\x08', '\x00', '\x00', '\x00', '\x0A', 'x', '\x00', '\x00'}, + mockColl: &mockCollection{}, + }, + { + label: "UnSuccessfull creation of entry", + input: map[string]interface{}{ + "coll": "collname", + "key": "keyvalue", + "tag": "tagName", + "data": "Data In String Format", + }, + mockColl: &mockCollection{ + Err: pkgerrors.New("DB Error"), + }, + expectedError: "DB Error", + }, + { + label: "Missing input fields", + input: map[string]interface{}{ + "coll": "", + "key": "", + "tag": "", + "data": "", + }, + expectedError: "No Data to store", + mockColl: &mockCollection{}, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + m, _ := NewMongoStore("name", &mongo.Database{}) + // Override the getCollection function with our mocked version + getCollection = func(coll string, m *MongoStore) MongoCollection { + return testCase.mockColl + } + + decodeBytes = func(sr *mongo.SingleResult) (bson.Raw, error) { + return testCase.bson, testCase.mockColl.Err + } + + err := m.Create(testCase.input["coll"].(string), testCase.input["key"].(string), + testCase.input["tag"].(string), testCase.input["data"]) + if err != nil { + if testCase.expectedError == "" { + t.Fatalf("Create method returned an un-expected (%s)", err) + } + if !strings.Contains(string(err.Error()), testCase.expectedError) { + t.Fatalf("Create method returned an error (%s)", err) + } + } + }) + } +} + +func TestRead(t *testing.T) { + testCases := []struct { + label string + input map[string]interface{} + mockColl *mockCollection + bson bson.Raw + expectedError string + expected []byte + }{ + { + label: "Successfull Read of entry", + input: map[string]interface{}{ + "coll": "collname", + "key": "keyvalue", + "tag": "metadata", + }, + // Binary form of + // { + // "_id" : ObjectId("5c115156777ff85654248ae1"), + // "key" : "b82c4bb1-09ff-6093-4d58-8327b94e1e20", + // "metadata" : ObjectId("5c115156c9755047e318bbfd") + // } + bson: bson.Raw{ + '\x5a', '\x00', '\x00', '\x00', '\x07', '\x5f', '\x69', '\x64', + '\x00', '\x5c', '\x11', '\x51', '\x56', '\x77', '\x7f', '\xf8', + '\x56', '\x54', '\x24', '\x8a', '\xe1', '\x02', '\x6b', '\x65', + '\x79', '\x00', '\x25', '\x00', '\x00', '\x00', '\x62', '\x38', + '\x32', '\x63', '\x34', '\x62', '\x62', '\x31', '\x2d', '\x30', + '\x39', '\x66', '\x66', '\x2d', '\x36', '\x30', '\x39', '\x33', + '\x2d', '\x34', '\x64', '\x35', '\x38', '\x2d', '\x38', '\x33', + '\x32', '\x37', '\x62', '\x39', '\x34', '\x65', '\x31', '\x65', + '\x32', '\x30', '\x00', '\x07', '\x6d', '\x65', '\x74', '\x61', + '\x64', '\x61', '\x74', '\x61', '\x00', '\x5c', '\x11', '\x51', + '\x56', '\xc9', '\x75', '\x50', '\x47', '\xe3', '\x18', '\xbb', + '\xfd', '\x00', + }, + mockColl: &mockCollection{}, + // This is not the document because we are mocking decodeBytes + expected: []byte{92, 17, 81, 86, 201, 117, 80, 71, 227, 24, 187, 253}, + }, + { + label: "UnSuccessfull Read of entry: object not found", + input: map[string]interface{}{ + "coll": "collname", + "key": "keyvalue", + "tag": "badtag", + }, + // Binary form of + // { + // "_id" : ObjectId("5c115156777ff85654248ae1"), + // "key" : "b82c4bb1-09ff-6093-4d58-8327b94e1e20", + // "metadata" : ObjectId("5c115156c9755047e318bbfd") + // } + bson: bson.Raw{ + '\x5a', '\x00', '\x00', '\x00', '\x07', '\x5f', '\x69', '\x64', + '\x00', '\x5c', '\x11', '\x51', '\x56', '\x77', '\x7f', '\xf8', + '\x56', '\x54', '\x24', '\x8a', '\xe1', '\x02', '\x6b', '\x65', + '\x79', '\x00', '\x25', '\x00', '\x00', '\x00', '\x62', '\x38', + '\x32', '\x63', '\x34', '\x62', '\x62', '\x31', '\x2d', '\x30', + '\x39', '\x66', '\x66', '\x2d', '\x36', '\x30', '\x39', '\x33', + '\x2d', '\x34', '\x64', '\x35', '\x38', '\x2d', '\x38', '\x33', + '\x32', '\x37', '\x62', '\x39', '\x34', '\x65', '\x31', '\x65', + '\x32', '\x30', '\x00', '\x07', '\x6d', '\x65', '\x74', '\x61', + '\x64', '\x61', '\x74', '\x61', '\x00', '\x5c', '\x11', '\x51', + '\x56', '\xc9', '\x75', '\x50', '\x47', '\xe3', '\x18', '\xbb', + '\xfd', '\x00', + }, + mockColl: &mockCollection{}, + expectedError: "Error finding objectID", + }, + { + label: "UnSuccessfull Read of entry", + input: map[string]interface{}{ + "coll": "collname", + "key": "keyvalue", + "tag": "tagName", + }, + mockColl: &mockCollection{ + Err: pkgerrors.New("DB Error"), + }, + expectedError: "DB Error", + }, + { + label: "Missing input fields", + input: map[string]interface{}{ + "coll": "", + "key": "", + "tag": "", + }, + expectedError: "Mandatory fields are missing", + mockColl: &mockCollection{}, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + m, _ := NewMongoStore("name", &mongo.Database{}) + // Override the getCollection function with our mocked version + getCollection = func(coll string, m *MongoStore) MongoCollection { + return testCase.mockColl + } + + decodeBytes = func(sr *mongo.SingleResult) (bson.Raw, error) { + return testCase.bson, testCase.mockColl.Err + } + got, err := m.Read(testCase.input["coll"].(string), testCase.input["key"].(string), + testCase.input["tag"].(string)) + if err != nil { + if testCase.expectedError == "" { + t.Fatalf("Read method returned an un-expected (%s)", err) + } + if !strings.Contains(string(err.Error()), testCase.expectedError) { + t.Fatalf("Read method returned an error (%s)", err) + } + } else { + if bytes.Compare(got, testCase.expected) != 0 { + t.Fatalf("Read returned unexpected data: %s, expected: %s", + string(got), testCase.expected) + } + } + }) + } +} + +func TestDelete(t *testing.T) { + testCases := []struct { + label string + input map[string]interface{} + mockColl *mockCollection + bson bson.Raw + expectedError string + }{ + { + label: "Successfull Delete of entry", + input: map[string]interface{}{ + "coll": "collname", + "key": "keyvalue", + "tag": "metadata", + }, + // Binary form of + // { + // "_id" : ObjectId("5c115156777ff85654248ae1"), + // "key" : "b82c4bb1-09ff-6093-4d58-8327b94e1e20", + // "metadata" : ObjectId("5c115156c9755047e318bbfd") + // } + bson: bson.Raw{ + '\x5a', '\x00', '\x00', '\x00', '\x07', '\x5f', '\x69', '\x64', + '\x00', '\x5c', '\x11', '\x51', '\x56', '\x77', '\x7f', '\xf8', + '\x56', '\x54', '\x24', '\x8a', '\xe1', '\x02', '\x6b', '\x65', + '\x79', '\x00', '\x25', '\x00', '\x00', '\x00', '\x62', '\x38', + '\x32', '\x63', '\x34', '\x62', '\x62', '\x31', '\x2d', '\x30', + '\x39', '\x66', '\x66', '\x2d', '\x36', '\x30', '\x39', '\x33', + '\x2d', '\x34', '\x64', '\x35', '\x38', '\x2d', '\x38', '\x33', + '\x32', '\x37', '\x62', '\x39', '\x34', '\x65', '\x31', '\x65', + '\x32', '\x30', '\x00', '\x07', '\x6d', '\x65', '\x74', '\x61', + '\x64', '\x61', '\x74', '\x61', '\x00', '\x5c', '\x11', '\x51', + '\x56', '\xc9', '\x75', '\x50', '\x47', '\xe3', '\x18', '\xbb', + '\xfd', '\x00', + }, + mockColl: &mockCollection{}, + }, + { + label: "UnSuccessfull Delete of entry", + input: map[string]interface{}{ + "coll": "collname", + "key": "keyvalue", + "tag": "tagName", + }, + mockColl: &mockCollection{ + Err: pkgerrors.New("DB Error"), + }, + expectedError: "DB Error", + }, + { + label: "UnSuccessfull Delete, key not found", + input: map[string]interface{}{ + "coll": "collname", + "key": "keyvalue", + "tag": "tagName", + }, + bson: bson.Raw{ + '\x5a', '\x00', '\x00', '\x00', '\x07', '\x5f', '\x69', '\x64', + '\x00', '\x5c', '\x11', '\x51', '\x56', '\x77', '\x7f', '\xf8', + '\x56', '\x54', '\x24', '\x8a', '\xe1', '\x02', '\x6b', '\x65', + '\x79', '\x00', '\x25', '\x00', '\x00', '\x00', '\x62', '\x38', + '\x32', '\x63', '\x34', '\x62', '\x62', '\x31', '\x2d', '\x30', + '\x39', '\x66', '\x66', '\x2d', '\x36', '\x30', '\x39', '\x33', + '\x2d', '\x34', '\x64', '\x35', '\x38', '\x2d', '\x38', '\x33', + '\x32', '\x37', '\x62', '\x39', '\x34', '\x65', '\x31', '\x65', + '\x32', '\x30', '\x00', '\x07', '\x6d', '\x65', '\x74', '\x61', + '\x64', '\x61', '\x74', '\x61', '\x00', '\x5c', '\x11', '\x51', + '\x56', '\xc9', '\x75', '\x50', '\x47', '\xe3', '\x18', '\xbb', + '\xfd', '\x00', + }, + mockColl: &mockCollection{}, + expectedError: "Error finding objectID", + }, + { + label: "Missing input fields", + input: map[string]interface{}{ + "coll": "", + "key": "", + "tag": "", + }, + expectedError: "Mandatory fields are missing", + mockColl: &mockCollection{}, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + m, _ := NewMongoStore("name", &mongo.Database{}) + // Override the getCollection function with our mocked version + getCollection = func(coll string, m *MongoStore) MongoCollection { + return testCase.mockColl + } + + decodeBytes = func(sr *mongo.SingleResult) (bson.Raw, error) { + return testCase.bson, testCase.mockColl.Err + } + err := m.Delete(testCase.input["coll"].(string), testCase.input["key"].(string), + testCase.input["tag"].(string)) + if err != nil { + if testCase.expectedError == "" { + t.Fatalf("Delete method returned an un-expected (%s)", err) + } + if !strings.Contains(string(err.Error()), testCase.expectedError) { + t.Fatalf("Delete method returned an error (%s)", err) + } + } + }) + } +} + +func TestReadAll(t *testing.T) { + testCases := []struct { + label string + input map[string]interface{} + mockColl *mockCollection + bson bson.Raw + expectedError string + expected map[string][]byte + }{ + { + label: "Successfully Read all entries", + input: map[string]interface{}{ + "coll": "collname", + "tag": "metadata", + }, + mockColl: &mockCollection{ + mCursor: &mockCursor{ + // Binary form of + // { + // "_id" : ObjectId("5c115156777ff85654248ae1"), + // "key" : "b82c4bb1-09ff-6093-4d58-8327b94e1e20", + // "metadata" : ObjectId("5c115156c9755047e318bbfd") + // } + bson: bson.Raw{ + '\x5a', '\x00', '\x00', '\x00', '\x07', '\x5f', '\x69', '\x64', + '\x00', '\x5c', '\x11', '\x51', '\x56', '\x77', '\x7f', '\xf8', + '\x56', '\x54', '\x24', '\x8a', '\xe1', '\x02', '\x6b', '\x65', + '\x79', '\x00', '\x25', '\x00', '\x00', '\x00', '\x62', '\x38', + '\x32', '\x63', '\x34', '\x62', '\x62', '\x31', '\x2d', '\x30', + '\x39', '\x66', '\x66', '\x2d', '\x36', '\x30', '\x39', '\x33', + '\x2d', '\x34', '\x64', '\x35', '\x38', '\x2d', '\x38', '\x33', + '\x32', '\x37', '\x62', '\x39', '\x34', '\x65', '\x31', '\x65', + '\x32', '\x30', '\x00', '\x07', '\x6d', '\x65', '\x74', '\x61', + '\x64', '\x61', '\x74', '\x61', '\x00', '\x5c', '\x11', '\x51', + '\x56', '\xc9', '\x75', '\x50', '\x47', '\xe3', '\x18', '\xbb', + '\xfd', '\x00', + }, + count: 1, + }, + }, + expected: map[string][]byte{ + "b82c4bb1-09ff-6093-4d58-8327b94e1e20": []byte{ + 92, 17, 81, 86, 201, 117, 80, 71, 227, 24, 187, 253}, + }, + }, + { + label: "UnSuccessfully Read of all entries", + input: map[string]interface{}{ + "coll": "collname", + "tag": "tagName", + }, + mockColl: &mockCollection{ + Err: pkgerrors.New("DB Error"), + }, + expectedError: "DB Error", + }, + { + label: "UnSuccessfull Readall, tag not found", + input: map[string]interface{}{ + "coll": "collname", + "tag": "tagName", + }, + mockColl: &mockCollection{ + mCursor: &mockCursor{ + // Binary form of + // { + // "_id" : ObjectId("5c115156777ff85654248ae1"), + // "key" : "b82c4bb1-09ff-6093-4d58-8327b94e1e20", + // "metadata" : ObjectId("5c115156c9755047e318bbfd") + // } + bson: bson.Raw{ + '\x5a', '\x00', '\x00', '\x00', '\x07', '\x5f', '\x69', '\x64', + '\x00', '\x5c', '\x11', '\x51', '\x56', '\x77', '\x7f', '\xf8', + '\x56', '\x54', '\x24', '\x8a', '\xe1', '\x02', '\x6b', '\x65', + '\x79', '\x00', '\x25', '\x00', '\x00', '\x00', '\x62', '\x38', + '\x32', '\x63', '\x34', '\x62', '\x62', '\x31', '\x2d', '\x30', + '\x39', '\x66', '\x66', '\x2d', '\x36', '\x30', '\x39', '\x33', + '\x2d', '\x34', '\x64', '\x35', '\x38', '\x2d', '\x38', '\x33', + '\x32', '\x37', '\x62', '\x39', '\x34', '\x65', '\x31', '\x65', + '\x32', '\x30', '\x00', '\x07', '\x6d', '\x65', '\x74', '\x61', + '\x64', '\x61', '\x74', '\x61', '\x00', '\x5c', '\x11', '\x51', + '\x56', '\xc9', '\x75', '\x50', '\x47', '\xe3', '\x18', '\xbb', + '\xfd', '\x00', + }, + count: 1, + }, + }, + expectedError: "Did not find any objects with tag", + }, + { + label: "Missing input fields", + input: map[string]interface{}{ + "coll": "", + "tag": "", + }, + expectedError: "Missing collection or tag name", + mockColl: &mockCollection{}, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + m, _ := NewMongoStore("name", &mongo.Database{}) + // Override the getCollection function with our mocked version + getCollection = func(coll string, m *MongoStore) MongoCollection { + return testCase.mockColl + } + + decodeBytes = func(sr *mongo.SingleResult) (bson.Raw, error) { + return testCase.mockColl.mCursor.DecodeBytes() + } + + got, err := m.ReadAll(testCase.input["coll"].(string), testCase.input["tag"].(string)) + if err != nil { + if testCase.expectedError == "" { + t.Fatalf("Readall method returned an un-expected (%s)", err) + } + if !strings.Contains(string(err.Error()), testCase.expectedError) { + t.Fatalf("Readall method returned an error (%s)", err) + } + } else { + if reflect.DeepEqual(got, testCase.expected) == false { + t.Fatalf("Readall returned unexpected data: %v, expected: %v", + got, testCase.expected) + } + } + }) + } +} diff --git a/src/k8splugin/db/store.go b/src/k8splugin/db/store.go index c1a8b31f..a235597a 100644 --- a/src/k8splugin/db/store.go +++ b/src/k8splugin/db/store.go @@ -25,21 +25,38 @@ var DBconn Store // Store is an interface for accessing a database type Store interface { + // Returns nil if db health is good HealthCheck() error - Create(string, string) error - Read(string) (string, error) - // Update(string) (string, error) - Delete(string) error + // Unmarshal implements any unmarshaling needed for the database + Unmarshal(inp []byte, out interface{}) error - ReadAll(string) ([]string, error) + // Creates a new master table with key and links data with tag and + // creates a pointer to the newly added data in the master table + Create(table, key, tag string, data interface{}) error + + // Reads data for a particular key with specific tag. + Read(table, key, tag string) ([]byte, error) + + //TODO: Update(context.Context, string, interface{}) error + + // Deletes a specific tag data for key. + // TODO: If tag is empty, it will delete all tags under key. + Delete(table, key, tag string) error + + // Reads all master tables and data from the specified tag in table + ReadAll(table, tag string) (map[string][]byte, error) } // CreateDBClient creates the DB client func CreateDBClient(dbType string) error { var err error switch dbType { + case "mongo": + // create a mongodb database with k8splugin as the name + DBconn, err = NewMongoStore("k8splugin", nil) case "consul": + // create a consul kv store DBconn, err = NewConsulStore(nil) default: return pkgerrors.New(dbType + "DB not supported") diff --git a/src/k8splugin/db/store_test.go b/src/k8splugin/db/store_test.go index 9bbe4a92..eed7065f 100644 --- a/src/k8splugin/db/store_test.go +++ b/src/k8splugin/db/store_test.go @@ -23,9 +23,9 @@ import ( func TestCreateDBClient(t *testing.T) { t.Run("Successfully create DB client", func(t *testing.T) { - expected := &ConsulStore{} + expected := &MongoStore{} - err := CreateDBClient("consul") + err := CreateDBClient("mongo") if err != nil { t.Fatalf("CreateDBClient returned an error (%s)", err) } diff --git a/src/k8splugin/db/testing.go b/src/k8splugin/db/testing.go index 672fcbfb..4b7e6078 100644 --- a/src/k8splugin/db/testing.go +++ b/src/k8splugin/db/testing.go @@ -16,7 +16,8 @@ limitations under the License. package db import ( - "github.com/hashicorp/consul/api" + "encoding/json" + pkgerrors "github.com/pkg/errors" ) //Creating an embedded interface via anonymous variable @@ -24,42 +25,45 @@ import ( //interface even if we are not implementing all the methods in it type MockDB struct { Store - Items api.KVPairs + Items map[string][]byte Err error } -func (m *MockDB) Create(key string, value string) error { +func (m *MockDB) Create(table, key, tag string, data interface{}) error { return m.Err } -func (m *MockDB) Read(key string) (string, error) { +// MockDB uses simple JSON and not BSON +func (m *MockDB) Unmarshal(inp []byte, out interface{}) error { + err := json.Unmarshal(inp, out) + if err != nil { + return pkgerrors.Wrap(err, "Unmarshaling json") + } + return nil +} + +func (m *MockDB) Read(table, key, tag string) ([]byte, error) { if m.Err != nil { - return "", m.Err + return nil, m.Err } - for _, kvpair := range m.Items { - if kvpair.Key == key { - return string(kvpair.Value), nil + for k, v := range m.Items { + if k == key { + return v, nil } } - return "", nil + return nil, m.Err } -func (m *MockDB) Delete(key string) error { +func (m *MockDB) Delete(table, key, tag string) error { return m.Err } -func (m *MockDB) ReadAll(prefix string) ([]string, error) { +func (m *MockDB) ReadAll(table, tag string) (map[string][]byte, error) { if m.Err != nil { - return []string{}, m.Err - } - - var res []string - - for _, keypair := range m.Items { - res = append(res, keypair.Key) + return nil, m.Err } - return res, nil + return m.Items, nil } diff --git a/src/k8splugin/go.mod b/src/k8splugin/go.mod index d0b32af4..a4f86586 100644 --- a/src/k8splugin/go.mod +++ b/src/k8splugin/go.mod @@ -1,35 +1,45 @@ module k8splugin require ( - github.com/ghodss/yaml v1.0.0 - github.com/gogo/protobuf v1.0.0 - github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b - github.com/golang/protobuf v1.1.0 - github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf - github.com/googleapis/gnostic v0.2.0 - github.com/gorilla/context v1.1.1 + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/ghodss/yaml v1.0.0 // indirect + github.com/go-stack/stack v1.8.0 // indirect + github.com/gogo/protobuf v1.0.0 // indirect + github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect + github.com/golang/protobuf v1.2.0 // indirect + github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect + github.com/google/go-cmp v0.2.0 // indirect + github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf // indirect + github.com/googleapis/gnostic v0.2.0 // indirect + github.com/gorilla/context v1.1.1 // indirect github.com/gorilla/handlers v1.3.0 github.com/gorilla/mux v1.6.2 - github.com/hashicorp/consul v1.2.2 - github.com/hashicorp/go-cleanhttp v0.0.0-20171218145408-d5fe4b57a186 - github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90 + github.com/hashicorp/consul v1.4.0 + github.com/hashicorp/go-cleanhttp v0.5.0 // indirect + github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90 // indirect github.com/hashicorp/go-uuid v1.0.0 - github.com/hashicorp/serf v0.8.1 - github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c - github.com/imdario/mergo v0.3.5 - github.com/json-iterator/go v0.0.0-20180315132816-ca39e5af3ece - github.com/mitchellh/go-homedir v0.0.0-20180801233206-58046073cbff - github.com/mitchellh/mapstructure v0.0.0-20180715050151-f15292f7a699 - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd - github.com/modern-go/reflect2 v0.0.0-20180228065516-1df9eeb2bb81 + github.com/hashicorp/serf v0.8.1 // indirect + github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c // indirect + github.com/imdario/mergo v0.3.5 // indirect + github.com/json-iterator/go v0.0.0-20180315132816-ca39e5af3ece // indirect + github.com/mitchellh/mapstructure v1.1.2 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v0.0.0-20180228065516-1df9eeb2bb81 // indirect + github.com/mongodb/mongo-go-driver v0.1.0 github.com/pkg/errors v0.8.0 - github.com/spf13/pflag v1.0.1 - golang.org/x/crypto v0.0.0-20180608092829-8ac0e0d97ce4 - golang.org/x/net v0.0.0-20180611182652-db08ff08e862 - golang.org/x/sys v0.0.0-20180611080425-bff228c7b664 - golang.org/x/text v0.3.0 - golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 - gopkg.in/inf.v0 v0.9.1 + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/spf13/pflag v1.0.1 // indirect + github.com/stretchr/testify v1.2.2 // indirect + github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51 // indirect + github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c // indirect + github.com/xdg/stringprep v1.0.0 // indirect + golang.org/x/crypto v0.0.0-20180608092829-8ac0e0d97ce4 // indirect + golang.org/x/net v0.0.0-20180724234803-3673e40ba225 + golang.org/x/sync v0.0.0-20181108010431-42b317875d0f // indirect + golang.org/x/sys v0.0.0-20180611080425-bff228c7b664 // indirect + golang.org/x/text v0.3.0 // indirect + golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.2.1 k8s.io/api v0.0.0-20180607235014-72d6e4405f81 k8s.io/apimachinery v0.0.0-20180515182440-31dade610c05 diff --git a/src/k8splugin/go.sum b/src/k8splugin/go.sum index 6e46c11c..f742e1f3 100644 --- a/src/k8splugin/go.sum +++ b/src/k8splugin/go.sum @@ -1,24 +1,33 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.0.0 h1:2jyBKDKU/8v3v2xVR2PtiWQviFUyiaGk2rpfyFT8rTM= github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/protobuf v1.1.0 h1:0iH4Ffd/meGoXqF2lSAhZHt8X+cPgkfn/cb6Cce5Vpc= -github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck= github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g= github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/handlers v1.3.0 h1:tsg9qP3mjt1h4Roxp+M1paRjrVBfPSOpBuVclh6YluI= github.com/gorilla/handlers v1.3.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/hashicorp/consul v1.2.2 h1:C5FurAZWLQ+XAjmL9g6rXbPlwxyyz8DvTL0WCAxTLAo= -github.com/hashicorp/consul v1.2.2/go.mod h1:mFrjN1mfidgJfYP1xrJCF+AfRhr6Eaqhb2+sfyn/OOI= -github.com/hashicorp/go-cleanhttp v0.0.0-20171218145408-d5fe4b57a186 h1:URgjUo+bs1KwatoNbwG0uCO4dHN4r1jsp4a5AGgHRjo= -github.com/hashicorp/go-cleanhttp v0.0.0-20171218145408-d5fe4b57a186/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/consul v1.4.0 h1:PQTW4xCuAExEiSbhrsFsikzbW5gVBoi74BjUvYFyKHw= +github.com/hashicorp/consul v1.4.0/go.mod h1:mFrjN1mfidgJfYP1xrJCF+AfRhr6Eaqhb2+sfyn/OOI= +github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90 h1:9HVkPxOpo+yO93Ah4yrO67d/qh0fbLLWbKqhYjyHq9A= github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90/go.mod h1:o4zcYY1e0GEZI6eSEr+43QDYmuGglw1qSO6qdHUHCgg= github.com/hashicorp/go-uuid v1.0.0 h1:RS8zrF7PhGwyNPOtxSClXXj9HA8feRnJzgnI1RJCSnM= @@ -31,28 +40,41 @@ github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/json-iterator/go v0.0.0-20180315132816-ca39e5af3ece h1:3HJXp/18JmMk5sjBP3LDUBtWjczCvynxaeAF6b6kWp8= github.com/json-iterator/go v0.0.0-20180315132816-ca39e5af3ece/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/mitchellh/go-homedir v0.0.0-20180801233206-58046073cbff h1:jM4Eo4qMmmcqePS3u6X2lcEELtVuXWkWJIS/pRI3oSk= -github.com/mitchellh/go-homedir v0.0.0-20180801233206-58046073cbff/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/mapstructure v0.0.0-20180715050151-f15292f7a699 h1:KXZJFdun9knAVAR8tg/aHJEr5DgtcbqyvzacK+CDCaI= -github.com/mitchellh/mapstructure v0.0.0-20180715050151-f15292f7a699/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180228065516-1df9eeb2bb81 h1:ImOHKpmdLPXWX5KSYquUWXKaopEPuY7TPPUo18u9aOI= github.com/modern-go/reflect2 v0.0.0-20180228065516-1df9eeb2bb81/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mongodb/mongo-go-driver v0.1.0 h1:LcpPFw0tNumIAakvNrkI9S9wdX0iOxvMLw/+hcAdHaU= +github.com/mongodb/mongo-go-driver v0.1.0/go.mod h1:NK/HWDIIZkaYsnYa0hmtP443T5ELr0KDecmIioVuuyU= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51 h1:BP2bjP495BBPaBcS5rmqviTfrOkN5rO5ceKAMRZCRFc= +github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v1.0.0 h1:d9X0esnoa3dFsV0FG35rAT0RIhYFlPq7MiP+DW89La0= +github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= golang.org/x/crypto v0.0.0-20180608092829-8ac0e0d97ce4 h1:wviDUSmtheHRBfoY8B9U8ELl2USoXi2YFwdGdpIIkzI= golang.org/x/crypto v0.0.0-20180608092829-8ac0e0d97ce4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/net v0.0.0-20180611182652-db08ff08e862 h1:JZi6BqOZ+iSgmLWe6llhGrNnEnK+YB/MRkStwnEfbqM= -golang.org/x/net v0.0.0-20180611182652-db08ff08e862/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225 h1:kNX+jCowfMYzvlSvJu5pQWEmyWFrBXJ3PBy10xKMXK8= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180611080425-bff228c7b664 h1:GvcVmbE8Pa64iW3MTrVA9mxHx1HEjSSWV6zF1JSlFcg= golang.org/x/sys v0.0.0-20180611080425-bff228c7b664/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 h1:+DCIGbF/swA92ohVg0//6X2IVY3KZs6p9mix0ziNYJM= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= diff --git a/src/k8splugin/rb/archive.go b/src/k8splugin/rb/archive.go new file mode 100644 index 00000000..8eb0fbed --- /dev/null +++ b/src/k8splugin/rb/archive.go @@ -0,0 +1,65 @@ +/* + * 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 rb + +import ( + "archive/tar" + "compress/gzip" + pkgerrors "github.com/pkg/errors" + "io" +) + +func isTarGz(r io.Reader) error { + //Check if it is a valid gz + gzf, err := gzip.NewReader(r) + if err != nil { + return pkgerrors.Errorf("Invalid gz format %s", err.Error()) + } + + //Check if it is a valid tar file + //Unfortunately this can only be done by inspecting all the tar contents + tarR := tar.NewReader(gzf) + first := true + + for true { + header, err := tarR.Next() + + if err == io.EOF { + //Check if we have just a gzip file without a tar archive inside + if first { + return pkgerrors.New("Empty or non-existant Tar file found") + } + //End of archive + break + } + + if err != nil { + return pkgerrors.Errorf("Error reading tar file %s", err.Error()) + } + + //Check if files are of type directory and regular file + if header.Typeflag != tar.TypeDir && + header.Typeflag != tar.TypeReg { + return pkgerrors.Errorf("Unknown header in tar %s, %s", + header.Name, string(header.Typeflag)) + } + + first = false + } + + return nil +} diff --git a/src/k8splugin/rb/archive_test.go b/src/k8splugin/rb/archive_test.go new file mode 100644 index 00000000..a327dfd4 --- /dev/null +++ b/src/k8splugin/rb/archive_test.go @@ -0,0 +1,66 @@ +/* + * 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 rb + +import ( + "bytes" + "testing" +) + +func TestIsTarGz(t *testing.T) { + + t.Run("Valid tar.gz", func(t *testing.T) { + content := []byte{ + 0x1f, 0x8b, 0x08, 0x08, 0xb0, 0x6b, 0xf4, 0x5b, + 0x00, 0x03, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x74, + 0x61, 0x72, 0x00, 0xed, 0xce, 0x41, 0x0a, 0xc2, + 0x30, 0x10, 0x85, 0xe1, 0xac, 0x3d, 0x45, 0x4e, + 0x50, 0x12, 0xd2, 0xc4, 0xe3, 0x48, 0xa0, 0x01, + 0x4b, 0x52, 0x0b, 0xed, 0x88, 0x1e, 0xdf, 0x48, + 0x11, 0x5c, 0x08, 0xa5, 0x8b, 0x52, 0x84, 0xff, + 0xdb, 0xbc, 0x61, 0x66, 0x16, 0x4f, 0xd2, 0x2c, + 0x8d, 0x3c, 0x45, 0xed, 0xc8, 0x54, 0x21, 0xb4, + 0xef, 0xb4, 0x67, 0x6f, 0xbe, 0x73, 0x61, 0x9d, + 0xb2, 0xce, 0xd5, 0x55, 0xf0, 0xde, 0xd7, 0x3f, + 0xdb, 0xd6, 0x49, 0x69, 0xb3, 0x67, 0xa9, 0x8f, + 0xfb, 0x2c, 0x71, 0xd2, 0x5a, 0xc5, 0xee, 0x92, + 0x73, 0x8e, 0x43, 0x7f, 0x4b, 0x3f, 0xff, 0xd6, + 0xee, 0x7f, 0xea, 0x9a, 0x4a, 0x19, 0x1f, 0xe3, + 0x54, 0xba, 0xd3, 0xd1, 0x55, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x1b, 0xbc, 0x00, 0xb5, 0xe8, + 0x4a, 0xf9, 0x00, 0x28, 0x00, 0x00, + } + + err := isTarGz(bytes.NewBuffer(content)) + if err != nil { + t.Errorf("Error reading valid Zip file %s", err.Error()) + } + }) + + t.Run("Invalid tar.gz", func(t *testing.T) { + content := []byte{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xff, 0xf2, 0x48, 0xcd, + } + + err := isTarGz(bytes.NewBuffer(content)) + if err == nil { + t.Errorf("Error should NOT be nil") + } + }) +} diff --git a/src/k8splugin/rb/definition.go b/src/k8splugin/rb/definition.go new file mode 100644 index 00000000..02ecc5ae --- /dev/null +++ b/src/k8splugin/rb/definition.go @@ -0,0 +1,155 @@ +/* + * 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 rb + +import ( + "bytes" + "encoding/base64" + "k8splugin/db" + "log" + + uuid "github.com/hashicorp/go-uuid" + pkgerrors "github.com/pkg/errors" +) + +// Definition contains the parameters needed for resource bundle (rb) definitions +// It implements the interface for managing the definitions +type Definition struct { + Name string `json:"name"` + Description string `json:"description"` + UUID string `json:"uuid,omitempty"` + ServiceType string `json:"service-type"` +} + +// DefinitionManager is an interface exposes the resource bundle definition functionality +type DefinitionManager interface { + Create(def Definition) (Definition, error) + List() ([]Definition, error) + Get(resID string) (Definition, error) + Delete(resID string) error + Upload(resID string, inp []byte) error +} + +// DefinitionClient implements the DefinitionManager +// It will also be used to maintain some localized state +type DefinitionClient struct { + storeName string + tagMeta, tagContent string +} + +// NewDefinitionClient returns an instance of the DefinitionClient +// which implements the DefinitionManager +// Uses rbdef collection in underlying db +func NewDefinitionClient() *DefinitionClient { + return &DefinitionClient{ + storeName: "rbdef", + tagMeta: "metadata", + tagContent: "content", + } +} + +// Create an entry for the resource in the database +func (v *DefinitionClient) Create(def Definition) (Definition, error) { + // If UUID is empty, we will generate one + if def.UUID == "" { + def.UUID, _ = uuid.GenerateUUID() + } + key := def.UUID + + err := db.DBconn.Create(v.storeName, key, v.tagMeta, def) + if err != nil { + return Definition{}, pkgerrors.Wrap(err, "Creating DB Entry") + } + + return def, nil +} + +// List all resource entries in the database +func (v *DefinitionClient) List() ([]Definition, error) { + res, err := db.DBconn.ReadAll(v.storeName, v.tagMeta) + if err != nil || len(res) == 0 { + return []Definition{}, pkgerrors.Wrap(err, "Listing Resource Bundle Definitions") + } + + var results []Definition + for key, value := range res { + if len(value) > 0 { + def := Definition{} + err = db.DBconn.Unmarshal(value, &def) + if err != nil { + log.Printf("Error Unmarshaling value for: %s", key) + continue + } + results = append(results, def) + } + } + + return results, nil +} + +// Get returns the Resource Bundle Definition for corresponding ID +func (v *DefinitionClient) Get(id string) (Definition, error) { + value, err := db.DBconn.Read(v.storeName, id, v.tagMeta) + if err != nil { + return Definition{}, pkgerrors.Wrap(err, "Get Resource Bundle definition") + } + + if value != nil { + def := Definition{} + err = db.DBconn.Unmarshal(value, &def) + if err != nil { + return Definition{}, pkgerrors.Wrap(err, "Unmarshaling Value") + } + return def, nil + } + + return Definition{}, pkgerrors.New("Error getting Resource Bundle Definition") +} + +// Delete the Resource Bundle definition from database +func (v *DefinitionClient) Delete(id string) error { + err := db.DBconn.Delete(v.storeName, id, v.tagMeta) + if err != nil { + return pkgerrors.Wrap(err, "Delete Resource Bundle Definitions") + } + + return nil +} + +// Upload the contents of resource bundle into database +func (v *DefinitionClient) Upload(id string, inp []byte) error { + + //ignore the returned data here + _, err := v.Get(id) + if err != nil { + return pkgerrors.Errorf("Invalid ID provided: %s", err.Error()) + } + + err = isTarGz(bytes.NewBuffer(inp)) + if err != nil { + return pkgerrors.Errorf("Error in file format: %s", err.Error()) + } + + //Encode given byte stream to text for storage + encodedStr := base64.StdEncoding.EncodeToString(inp) + err = db.DBconn.Create(v.storeName, id, encodedStr, v.tagContent) + if err != nil { + return pkgerrors.Errorf("Error uploading data to db: %s", err.Error()) + } + + return nil +} diff --git a/src/k8splugin/rb/definition_test.go b/src/k8splugin/rb/definition_test.go new file mode 100644 index 00000000..1e488678 --- /dev/null +++ b/src/k8splugin/rb/definition_test.go @@ -0,0 +1,396 @@ +// +build unit + +/* + * 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 rb + +import ( + "k8splugin/db" + "reflect" + "strings" + "testing" + + pkgerrors "github.com/pkg/errors" +) + +func TestCreate(t *testing.T) { + testCases := []struct { + label string + inp Definition + expectedError string + mockdb *db.MockDB + expected Definition + }{ + { + label: "Create Resource Bundle Definition", + inp: Definition{ + UUID: "123e4567-e89b-12d3-a456-426655440000", + Name: "testresourcebundle", + Description: "testresourcebundle", + ServiceType: "firewall", + }, + expected: Definition{ + UUID: "123e4567-e89b-12d3-a456-426655440000", + Name: "testresourcebundle", + Description: "testresourcebundle", + ServiceType: "firewall", + }, + expectedError: "", + mockdb: &db.MockDB{}, + }, + { + label: "Failed Create Resource Bundle Definition", + expectedError: "Error Creating Definition", + mockdb: &db.MockDB{ + Err: pkgerrors.New("Error Creating Definition"), + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + db.DBconn = testCase.mockdb + impl := NewDefinitionClient() + got, err := impl.Create(testCase.inp) + if err != nil { + if testCase.expectedError == "" { + t.Fatalf("Create returned an unexpected error %s", err) + } + if strings.Contains(err.Error(), testCase.expectedError) == false { + t.Fatalf("Create returned an unexpected error %s", err) + } + } else { + if reflect.DeepEqual(testCase.expected, got) == false { + t.Errorf("Create Resource Bundle returned unexpected body: got %v;"+ + " expected %v", got, testCase.expected) + } + } + }) + } +} + +func TestList(t *testing.T) { + + testCases := []struct { + label string + expectedError string + mockdb *db.MockDB + expected []Definition + }{ + { + label: "List Resource Bundle Definition", + expected: []Definition{ + { + UUID: "123e4567-e89b-12d3-a456-426655440000", + Name: "testresourcebundle", + Description: "testresourcebundle", + ServiceType: "firewall", + }, + { + UUID: "123e4567-e89b-12d3-a456-426655441111", + Name: "testresourcebundle2", + Description: "testresourcebundle2", + ServiceType: "dns", + }, + }, + expectedError: "", + mockdb: &db.MockDB{ + Items: map[string][]byte{ + "123e4567-e89b-12d3-a456-426655440000": []byte( + "{\"name\":\"testresourcebundle\"," + + "\"description\":\"testresourcebundle\"," + + "\"uuid\":\"123e4567-e89b-12d3-a456-426655440000\"," + + "\"service-type\":\"firewall\"}"), + "123e4567-e89b-12d3-a456-426655441111": []byte( + "{\"name\":\"testresourcebundle2\"," + + "\"description\":\"testresourcebundle2\"," + + "\"uuid\":\"123e4567-e89b-12d3-a456-426655441111\"," + + "\"service-type\":\"dns\"}"), + }, + }, + }, + { + label: "List Error", + expectedError: "DB Error", + mockdb: &db.MockDB{ + Err: pkgerrors.New("DB Error"), + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + db.DBconn = testCase.mockdb + impl := NewDefinitionClient() + got, err := impl.List() + if err != nil { + if testCase.expectedError == "" { + t.Fatalf("List returned an unexpected error %s", err) + } + if strings.Contains(err.Error(), testCase.expectedError) == false { + t.Fatalf("List returned an unexpected error %s", err) + } + } else { + if reflect.DeepEqual(testCase.expected, got) == false { + t.Errorf("List Resource Bundle returned unexpected body: got %v;"+ + " expected %v", got, testCase.expected) + } + } + }) + } +} + +func TestGet(t *testing.T) { + + testCases := []struct { + label string + expectedError string + mockdb *db.MockDB + inp string + expected Definition + }{ + { + label: "Get Resource Bundle Definition", + inp: "123e4567-e89b-12d3-a456-426655440000", + expected: Definition{ + UUID: "123e4567-e89b-12d3-a456-426655440000", + Name: "testresourcebundle", + Description: "testresourcebundle", + ServiceType: "firewall", + }, + expectedError: "", + mockdb: &db.MockDB{ + Items: map[string][]byte{ + "123e4567-e89b-12d3-a456-426655440000": []byte( + "{\"name\":\"testresourcebundle\"," + + "\"description\":\"testresourcebundle\"," + + "\"uuid\":\"123e4567-e89b-12d3-a456-426655440000\"," + + "\"service-type\":\"firewall\"}"), + }, + }, + }, + { + label: "Get Error", + expectedError: "DB Error", + mockdb: &db.MockDB{ + Err: pkgerrors.New("DB Error"), + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + db.DBconn = testCase.mockdb + impl := NewDefinitionClient() + got, err := impl.Get(testCase.inp) + if err != nil { + if testCase.expectedError == "" { + t.Fatalf("Get returned an unexpected error %s", err) + } + if strings.Contains(err.Error(), testCase.expectedError) == false { + t.Fatalf("Get returned an unexpected error %s", err) + } + } else { + if reflect.DeepEqual(testCase.expected, got) == false { + t.Errorf("Get Resource Bundle returned unexpected body: got %v;"+ + " expected %v", got, testCase.expected) + } + } + }) + } +} + +func TestDelete(t *testing.T) { + + testCases := []struct { + label string + inp string + expectedError string + mockdb *db.MockDB + }{ + { + label: "Delete Resource Bundle Definition", + inp: "123e4567-e89b-12d3-a456-426655440000", + mockdb: &db.MockDB{}, + }, + { + label: "Delete Error", + expectedError: "DB Error", + mockdb: &db.MockDB{ + Err: pkgerrors.New("DB Error"), + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + db.DBconn = testCase.mockdb + impl := NewDefinitionClient() + err := impl.Delete(testCase.inp) + if err != nil { + if testCase.expectedError == "" { + t.Fatalf("Delete returned an unexpected error %s", err) + } + if strings.Contains(err.Error(), testCase.expectedError) == false { + t.Fatalf("Delete returned an unexpected error %s", err) + } + } + }) + } +} + +func TestUpload(t *testing.T) { + testCases := []struct { + label string + inp string + content []byte + expectedError string + mockdb *db.MockDB + }{ + { + label: "Upload Resource Bundle Definition", + inp: "123e4567-e89b-12d3-a456-426655440000", + content: []byte{ + 0x1f, 0x8b, 0x08, 0x08, 0xb0, 0x6b, 0xf4, 0x5b, + 0x00, 0x03, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x74, + 0x61, 0x72, 0x00, 0xed, 0xce, 0x41, 0x0a, 0xc2, + 0x30, 0x10, 0x85, 0xe1, 0xac, 0x3d, 0x45, 0x4e, + 0x50, 0x12, 0xd2, 0xc4, 0xe3, 0x48, 0xa0, 0x01, + 0x4b, 0x52, 0x0b, 0xed, 0x88, 0x1e, 0xdf, 0x48, + 0x11, 0x5c, 0x08, 0xa5, 0x8b, 0x52, 0x84, 0xff, + 0xdb, 0xbc, 0x61, 0x66, 0x16, 0x4f, 0xd2, 0x2c, + 0x8d, 0x3c, 0x45, 0xed, 0xc8, 0x54, 0x21, 0xb4, + 0xef, 0xb4, 0x67, 0x6f, 0xbe, 0x73, 0x61, 0x9d, + 0xb2, 0xce, 0xd5, 0x55, 0xf0, 0xde, 0xd7, 0x3f, + 0xdb, 0xd6, 0x49, 0x69, 0xb3, 0x67, 0xa9, 0x8f, + 0xfb, 0x2c, 0x71, 0xd2, 0x5a, 0xc5, 0xee, 0x92, + 0x73, 0x8e, 0x43, 0x7f, 0x4b, 0x3f, 0xff, 0xd6, + 0xee, 0x7f, 0xea, 0x9a, 0x4a, 0x19, 0x1f, 0xe3, + 0x54, 0xba, 0xd3, 0xd1, 0x55, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x1b, 0xbc, 0x00, 0xb5, 0xe8, + 0x4a, 0xf9, 0x00, 0x28, 0x00, 0x00, + }, + mockdb: &db.MockDB{ + Items: map[string][]byte{ + "123e4567-e89b-12d3-a456-426655440000": []byte( + "{\"name\":\"testresourcebundle\"," + + "\"description\":\"testresourcebundle\"," + + "\"uuid\":\"123e4567-e89b-12d3-a456-426655440000\"," + + "\"service-type\":\"firewall\"}"), + }, + }, + }, + { + label: "Upload with an Invalid Resource Bundle Definition", + inp: "123e4567-e89b-12d3-a456-426655440000", + expectedError: "Invalid ID provided", + content: []byte{ + 0x1f, 0x8b, 0x08, 0x08, 0xb0, 0x6b, 0xf4, 0x5b, + 0x00, 0x03, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x74, + 0x61, 0x72, 0x00, 0xed, 0xce, 0x41, 0x0a, 0xc2, + 0x30, 0x10, 0x85, 0xe1, 0xac, 0x3d, 0x45, 0x4e, + 0x50, 0x12, 0xd2, 0xc4, 0xe3, 0x48, 0xa0, 0x01, + 0x4b, 0x52, 0x0b, 0xed, 0x88, 0x1e, 0xdf, 0x48, + 0x11, 0x5c, 0x08, 0xa5, 0x8b, 0x52, 0x84, 0xff, + 0xdb, 0xbc, 0x61, 0x66, 0x16, 0x4f, 0xd2, 0x2c, + 0x8d, 0x3c, 0x45, 0xed, 0xc8, 0x54, 0x21, 0xb4, + 0xef, 0xb4, 0x67, 0x6f, 0xbe, 0x73, 0x61, 0x9d, + 0xb2, 0xce, 0xd5, 0x55, 0xf0, 0xde, 0xd7, 0x3f, + 0xdb, 0xd6, 0x49, 0x69, 0xb3, 0x67, 0xa9, 0x8f, + 0xfb, 0x2c, 0x71, 0xd2, 0x5a, 0xc5, 0xee, 0x92, + 0x73, 0x8e, 0x43, 0x7f, 0x4b, 0x3f, 0xff, 0xd6, + 0xee, 0x7f, 0xea, 0x9a, 0x4a, 0x19, 0x1f, 0xe3, + 0x54, 0xba, 0xd3, 0xd1, 0x55, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x1b, 0xbc, 0x00, 0xb5, 0xe8, + 0x4a, 0xf9, 0x00, 0x28, 0x00, 0x00, + }, + mockdb: &db.MockDB{ + Items: map[string][]byte{ + "123e4567-e89b-12d3-a456-426655441111": []byte( + "{\"name\":\"testresourcebundle\"," + + "\"description\":\"testresourcebundle\"," + + "\"uuid\":\"123e4567-e89b-12d3-a456-426655440000\"," + + "\"service-type\":\"firewall\"}"), + }, + }, + }, + { + label: "Invalid File Format Error", + inp: "123e4567-e89b-12d3-a456-426655440000", + expectedError: "Error in file format", + content: []byte{ + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xff, 0xf2, 0x48, 0xcd, + }, + mockdb: &db.MockDB{ + Items: map[string][]byte{ + "123e4567-e89b-12d3-a456-426655440000": []byte( + "{\"name\":\"testresourcebundle\"," + + "\"description\":\"testresourcebundle\"," + + "\"uuid\":\"123e4567-e89b-12d3-a456-426655440000\"," + + "\"service-type\":\"firewall\"}"), + }, + }, + }, + { + label: "Upload Error", + expectedError: "DB Error", + content: []byte{ + 0x1f, 0x8b, 0x08, 0x08, 0xb0, 0x6b, 0xf4, 0x5b, + 0x00, 0x03, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x74, + 0x61, 0x72, 0x00, 0xed, 0xce, 0x41, 0x0a, 0xc2, + 0x30, 0x10, 0x85, 0xe1, 0xac, 0x3d, 0x45, 0x4e, + 0x50, 0x12, 0xd2, 0xc4, 0xe3, 0x48, 0xa0, 0x01, + 0x4b, 0x52, 0x0b, 0xed, 0x88, 0x1e, 0xdf, 0x48, + 0x11, 0x5c, 0x08, 0xa5, 0x8b, 0x52, 0x84, 0xff, + 0xdb, 0xbc, 0x61, 0x66, 0x16, 0x4f, 0xd2, 0x2c, + 0x8d, 0x3c, 0x45, 0xed, 0xc8, 0x54, 0x21, 0xb4, + 0xef, 0xb4, 0x67, 0x6f, 0xbe, 0x73, 0x61, 0x9d, + 0xb2, 0xce, 0xd5, 0x55, 0xf0, 0xde, 0xd7, 0x3f, + 0xdb, 0xd6, 0x49, 0x69, 0xb3, 0x67, 0xa9, 0x8f, + 0xfb, 0x2c, 0x71, 0xd2, 0x5a, 0xc5, 0xee, 0x92, + 0x73, 0x8e, 0x43, 0x7f, 0x4b, 0x3f, 0xff, 0xd6, + 0xee, 0x7f, 0xea, 0x9a, 0x4a, 0x19, 0x1f, 0xe3, + 0x54, 0xba, 0xd3, 0xd1, 0x55, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x1b, 0xbc, 0x00, 0xb5, 0xe8, + 0x4a, 0xf9, 0x00, 0x28, 0x00, 0x00, + }, + mockdb: &db.MockDB{ + Err: pkgerrors.New("DB Error"), + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.label, func(t *testing.T) { + db.DBconn = testCase.mockdb + impl := NewDefinitionClient() + err := impl.Upload(testCase.inp, testCase.content) + if err != nil { + if testCase.expectedError == "" { + t.Errorf("Upload returned an unexpected error %s", err) + } + if strings.Contains(err.Error(), testCase.expectedError) == false { + t.Errorf("Upload returned an unexpected error %s", err) + } + } + }) + } +} diff --git a/src/k8splugin/vnfd/vnfd.go b/src/k8splugin/vnfd/vnfd.go deleted file mode 100644 index 0fb81dbd..00000000 --- a/src/k8splugin/vnfd/vnfd.go +++ /dev/null @@ -1,134 +0,0 @@ -/* - * 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 vnfd - -import ( - "k8splugin/db" - "log" - - uuid "github.com/hashicorp/go-uuid" - pkgerrors "github.com/pkg/errors" -) - -// VNFDefinition contains the parameters needed for VNF Definitions -// It implements the interface for managing the definitions -type VNFDefinition struct { - Name string `json:"name"` - Description string `json:"description"` - UUID string `json:"uuid,omitempty"` - ServiceType string `json:"service-type"` -} - -// VNFDefinitionInterface is an interface exposes the VNFDefinition functionality -type VNFDefinitionInterface interface { - Create(vnfd VNFDefinition) (VNFDefinition, error) - List() ([]VNFDefinition, error) - Get(vnfID string) (VNFDefinition, error) - Delete(vnfID string) error -} - -// VNFDefinitionClient implements the VNFDefinitionInterface -// It will also be used to maintain some localized state -type VNFDefinitionClient struct { - keyPrefix string -} - -// GetVNFDClient Returns an instance of the VNFDefinitionClient -// which implements the VNFDefinitionInterface interface -func GetVNFDClient() *VNFDefinitionClient { - return &VNFDefinitionClient{ - keyPrefix: "vnfd/"} -} - -// Create creates an entry for the VNF in the database -func (v *VNFDefinitionClient) Create(vnfd VNFDefinition) (VNFDefinition, error) { - // If UUID is empty, we will generate one - if vnfd.UUID == "" { - vnfd.UUID, _ = uuid.GenerateUUID() - } - key := v.keyPrefix + vnfd.UUID - - serData, err := db.Serialize(v) - if err != nil { - return VNFDefinition{}, pkgerrors.Wrap(err, "Serialize VNF Definition") - } - - err = db.DBconn.Create(key, serData) - if err != nil { - return VNFDefinition{}, pkgerrors.Wrap(err, "Creating DB Entry") - } - - return vnfd, nil -} - -// List lists all vnf entries in the database -func (v *VNFDefinitionClient) List() ([]VNFDefinition, error) { - strArray, err := db.DBconn.ReadAll(v.keyPrefix) - if err != nil { - return []VNFDefinition{}, pkgerrors.Wrap(err, "Listing VNF Definitions") - } - - var retData []VNFDefinition - - for _, key := range strArray { - value, err := db.DBconn.Read(key) - if err != nil { - log.Printf("Error Reading Key: %s", key) - continue - } - if value != "" { - vnfd := VNFDefinition{} - err = db.DeSerialize(value, &vnfd) - if err != nil { - log.Printf("Error Deserializing Value: %s", value) - continue - } - retData = append(retData, vnfd) - } - } - - return retData, nil -} - -// Get returns the VNF Definition for corresponding ID -func (v *VNFDefinitionClient) Get(vnfID string) (VNFDefinition, error) { - value, err := db.DBconn.Read(v.keyPrefix + vnfID) - if err != nil { - return VNFDefinition{}, pkgerrors.Wrap(err, "Get VNF Definitions") - } - - if value != "" { - vnfd := VNFDefinition{} - err = db.DeSerialize(value, &vnfd) - if err != nil { - return VNFDefinition{}, pkgerrors.Wrap(err, "Deserializing Value") - } - return vnfd, nil - } - - return VNFDefinition{}, pkgerrors.New("Error getting VNF Definition") -} - -// Delete deletes the VNF Definition from database -func (v *VNFDefinitionClient) Delete(vnfID string) error { - err := db.DBconn.Delete(v.keyPrefix + vnfID) - if err != nil { - return pkgerrors.Wrap(err, "Delete VNF Definitions") - } - - return nil -} diff --git a/src/k8splugin/vnfd/vnfd_test.go b/src/k8splugin/vnfd/vnfd_test.go deleted file mode 100644 index 3230d3ef..00000000 --- a/src/k8splugin/vnfd/vnfd_test.go +++ /dev/null @@ -1,262 +0,0 @@ -// +build unit - -/* - * 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 vnfd - -import ( - "k8splugin/db" - "reflect" - "strings" - "testing" - - "github.com/hashicorp/consul/api" - pkgerrors "github.com/pkg/errors" -) - -func TestCreate(t *testing.T) { - testCases := []struct { - label string - inp VNFDefinition - expectedError string - mockdb *db.MockDB - expected VNFDefinition - }{ - { - label: "Create VNF Definition", - inp: VNFDefinition{ - UUID: "123e4567-e89b-12d3-a456-426655440000", - Name: "testvnf", - Description: "testvnf", - ServiceType: "firewall", - }, - expected: VNFDefinition{ - UUID: "123e4567-e89b-12d3-a456-426655440000", - Name: "testvnf", - Description: "testvnf", - ServiceType: "firewall", - }, - expectedError: "", - mockdb: &db.MockDB{}, - }, - { - label: "Failed Create VNF Definition", - expectedError: "Error Creating Definition", - mockdb: &db.MockDB{ - Err: pkgerrors.New("Error Creating Definition"), - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.label, func(t *testing.T) { - db.DBconn = testCase.mockdb - vimpl := GetVNFDClient() - got, err := vimpl.Create(testCase.inp) - if err != nil { - if testCase.expectedError == "" { - t.Fatalf("Create returned an unexpected error %s", err) - } - if strings.Contains(err.Error(), testCase.expectedError) == false { - t.Fatalf("Create returned an unexpected error %s", err) - } - } else { - if reflect.DeepEqual(testCase.expected, got) == false { - t.Errorf("Create VNF returned unexpected body: got %v;"+ - " expected %v", got, testCase.expected) - } - } - }) - } -} - -func TestList(t *testing.T) { - - testCases := []struct { - label string - expectedError string - mockdb *db.MockDB - expected []VNFDefinition - }{ - { - label: "List VNF Definition", - expected: []VNFDefinition{ - { - UUID: "123e4567-e89b-12d3-a456-426655440000", - Name: "testvnf", - Description: "testvnf", - ServiceType: "firewall", - }, - { - UUID: "123e4567-e89b-12d3-a456-426655441111", - Name: "testvnf2", - Description: "testvnf2", - ServiceType: "dns", - }, - }, - expectedError: "", - mockdb: &db.MockDB{ - Items: api.KVPairs{ - &api.KVPair{ - Key: "vnfd/123e4567-e89b-12d3-a456-426655440000", - Value: []byte("{\"name\":\"testvnf\"," + - "\"description\":\"testvnf\"," + - "\"uuid\":\"123e4567-e89b-12d3-a456-426655440000\"," + - "\"service-type\":\"firewall\"}"), - }, - &api.KVPair{ - Key: "vnfd/123e4567-e89b-12d3-a456-426655441111", - Value: []byte("{\"name\":\"testvnf2\"," + - "\"description\":\"testvnf2\"," + - "\"uuid\":\"123e4567-e89b-12d3-a456-426655441111\"," + - "\"service-type\":\"dns\"}"), - }, - }, - }, - }, - { - label: "List Error", - expectedError: "DB Error", - mockdb: &db.MockDB{ - Err: pkgerrors.New("DB Error"), - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.label, func(t *testing.T) { - db.DBconn = testCase.mockdb - vimpl := GetVNFDClient() - got, err := vimpl.List() - if err != nil { - if testCase.expectedError == "" { - t.Fatalf("List returned an unexpected error %s", err) - } - if strings.Contains(err.Error(), testCase.expectedError) == false { - t.Fatalf("List returned an unexpected error %s", err) - } - } else { - if reflect.DeepEqual(testCase.expected, got) == false { - t.Errorf("List VNF returned unexpected body: got %v;"+ - " expected %v", got, testCase.expected) - } - } - }) - } -} - -func TestGet(t *testing.T) { - - testCases := []struct { - label string - expectedError string - mockdb *db.MockDB - inp string - expected VNFDefinition - }{ - { - label: "Get VNF Definition", - inp: "123e4567-e89b-12d3-a456-426655440000", - expected: VNFDefinition{ - UUID: "123e4567-e89b-12d3-a456-426655440000", - Name: "testvnf", - Description: "testvnf", - ServiceType: "firewall", - }, - expectedError: "", - mockdb: &db.MockDB{ - Items: api.KVPairs{ - &api.KVPair{ - Key: "vnfd/123e4567-e89b-12d3-a456-426655440000", - Value: []byte("{\"name\":\"testvnf\"," + - "\"description\":\"testvnf\"," + - "\"uuid\":\"123e4567-e89b-12d3-a456-426655440000\"," + - "\"service-type\":\"firewall\"}"), - }, - }, - }, - }, - { - label: "Get Error", - expectedError: "DB Error", - mockdb: &db.MockDB{ - Err: pkgerrors.New("DB Error"), - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.label, func(t *testing.T) { - db.DBconn = testCase.mockdb - vimpl := GetVNFDClient() - got, err := vimpl.Get(testCase.inp) - if err != nil { - if testCase.expectedError == "" { - t.Fatalf("Get returned an unexpected error %s", err) - } - if strings.Contains(err.Error(), testCase.expectedError) == false { - t.Fatalf("Get returned an unexpected error %s", err) - } - } else { - if reflect.DeepEqual(testCase.expected, got) == false { - t.Errorf("Get VNF returned unexpected body: got %v;"+ - " expected %v", got, testCase.expected) - } - } - }) - } -} - -func TestDelete(t *testing.T) { - - testCases := []struct { - label string - inp string - expectedError string - mockdb *db.MockDB - expected []VNFDefinition - }{ - { - label: "Delete VNF Definition", - inp: "123e4567-e89b-12d3-a456-426655440000", - mockdb: &db.MockDB{}, - }, - { - label: "Delete Error", - expectedError: "DB Error", - mockdb: &db.MockDB{ - Err: pkgerrors.New("DB Error"), - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.label, func(t *testing.T) { - db.DBconn = testCase.mockdb - vimpl := GetVNFDClient() - err := vimpl.Delete(testCase.inp) - if err != nil { - if testCase.expectedError == "" { - t.Fatalf("Delete returned an unexpected error %s", err) - } - if strings.Contains(err.Error(), testCase.expectedError) == false { - t.Fatalf("Delete returned an unexpected error %s", err) - } - } - }) - } -} @@ -24,6 +24,7 @@ deps = whitelist_externals = bash commands = bash -c "find {toxinidir} -not -path {toxinidir}/.tox/\* \ -not -path {toxinidir}/pkg/dep/\* \ + -not -path {toxinidir}/pkg/mod/\* \ -not -path {toxinidir}/src/k8splugin/vendor/\* \ -not -path {toxinidir}/src/github.com/\* \ -name \*.sh -type f \ diff --git a/vagrant/Vagrantfile b/vagrant/Vagrantfile index 735e750e..3314fe94 100644 --- a/vagrant/Vagrantfile +++ b/vagrant/Vagrantfile @@ -23,7 +23,7 @@ nodes = YAML.load_file(pdf) # Inventory file creation File.open(File.dirname(__FILE__) + "/inventory/hosts.ini", "w") do |inventory_file| - inventory_file.puts("[all:vars]\nansible_connection=ssh\nansible_ssh_user=vagrant\nansible_ssh_pass=vagrant\n\n[all]") + inventory_file.puts("[all]") nodes.each do |node| inventory_file.puts("#{node['name']}\tansible_ssh_host=#{node['ip']} ansible_ssh_port=22") end @@ -59,6 +59,7 @@ end Vagrant.configure("2") do |config| config.vm.box = box[provider][:name] config.vm.box_version = box[provider][:version] + config.ssh.insert_key = false if ENV['http_proxy'] != nil and ENV['https_proxy'] != nil if Vagrant.has_plugin?('vagrant-proxyconf') @@ -114,10 +115,16 @@ Vagrant.configure("2") do |config| config.vm.define :installer, primary: true, autostart: false do |installer| installer.vm.hostname = "multicloud" installer.vm.network :private_network, :ip => "10.10.10.2", :type => :static - installer.vm.synced_folder '../', '/root/go/src/k8-plugin-multicloud/', type: sync_type - installer.vm.provision 'shell' do |sh| - sh.path = "installer.sh" - sh.args = ['-p', '-v', '-w', '/root/go/src/k8-plugin-multicloud/vagrant'] + installer.vm.synced_folder '../', '/home/vagrant/multicloud-k8s/', type: sync_type + installer.vm.provision 'shell', privileged: false do |sh| + sh.env = {'KRD_PLUGIN_ENABLED': 'true'} + sh.inline = <<-SHELL + cp /vagrant/insecure_keys/key.pub /home/vagrant/.ssh/id_rsa.pub + cp /vagrant/insecure_keys/key /home/vagrant/.ssh/id_rsa + chown vagrant /home/vagrant/.ssh/id_rsa + chmod 400 /home/vagrant/.ssh/id_rsa + cd /home/vagrant/multicloud-k8s/vagrant/ && ./installer.sh | tee krd_installer.log + SHELL end end end diff --git a/vagrant/aio.sh b/vagrant/aio.sh new file mode 100755 index 00000000..413e4672 --- /dev/null +++ b/vagrant/aio.sh @@ -0,0 +1,58 @@ +#!/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 + +if [[ $(whoami) != 'root' ]];then + echo "This bash script must be executed as root user" + exit 1 +fi + +echo "Cloning and configuring KRD project..." +git clone https://git.onap.org/multicloud/k8s/ +cd k8s/vagrant/ +cat <<EOL > inventory/hosts.ini +[all] +localhost + +[kube-master] +localhost + +[kube-node] +localhost + +[etcd] +localhost + +[ovn-central] +localhost + +[ovn-controller] +localhost + +[virtlet] +localhost + +[k8s-cluster:children] +kube-node +kube-master +EOL +sed -i '/andrewrothstein.kubectl/d' playbooks/configure-*.yml +echo -e "\n\n\n" | ssh-keygen -t rsa -N "" +cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys +chmod og-wx ~/.ssh/authorized_keys + +echo "Enabling nested-virtualization" +./node.sh + +echo "Deploying KRD project" +./installer.sh | tee krd_installer.log diff --git a/vagrant/galaxy-requirements.yml b/vagrant/galaxy-requirements.yml index 4b252964..55e105a6 100644 --- a/vagrant/galaxy-requirements.yml +++ b/vagrant/galaxy-requirements.yml @@ -10,8 +10,8 @@ - src: andrewrothstein.go version: v2.1.10 - src: andrewrothstein.kubectl - version: v1.1.12 + version: v1.1.16 - src: andrewrothstein.kubernetes-helm version: v1.2.9 - src: geerlingguy.docker - version: 2.5.1 + version: 2.5.2 diff --git a/vagrant/insecure_keys/key b/vagrant/insecure_keys/key new file mode 100644 index 00000000..7d6a0839 --- /dev/null +++ b/vagrant/insecure_keys/key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzI +w+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoP +kcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2 +hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NO +Td0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcW +yLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQIBIwKCAQEA4iqWPJXtzZA68mKd +ELs4jJsdyky+ewdZeNds5tjcnHU5zUYE25K+ffJED9qUWICcLZDc81TGWjHyAqD1 +Bw7XpgUwFgeUJwUlzQurAv+/ySnxiwuaGJfhFM1CaQHzfXphgVml+fZUvnJUTvzf +TK2Lg6EdbUE9TarUlBf/xPfuEhMSlIE5keb/Zz3/LUlRg8yDqz5w+QWVJ4utnKnK +iqwZN0mwpwU7YSyJhlT4YV1F3n4YjLswM5wJs2oqm0jssQu/BT0tyEXNDYBLEF4A +sClaWuSJ2kjq7KhrrYXzagqhnSei9ODYFShJu8UWVec3Ihb5ZXlzO6vdNQ1J9Xsf +4m+2ywKBgQD6qFxx/Rv9CNN96l/4rb14HKirC2o/orApiHmHDsURs5rUKDx0f9iP +cXN7S1uePXuJRK/5hsubaOCx3Owd2u9gD6Oq0CsMkE4CUSiJcYrMANtx54cGH7Rk +EjFZxK8xAv1ldELEyxrFqkbE4BKd8QOt414qjvTGyAK+OLD3M2QdCQKBgQDtx8pN +CAxR7yhHbIWT1AH66+XWN8bXq7l3RO/ukeaci98JfkbkxURZhtxV/HHuvUhnPLdX +3TwygPBYZFNo4pzVEhzWoTtnEtrFueKxyc3+LjZpuo+mBlQ6ORtfgkr9gBVphXZG +YEzkCD3lVdl8L4cw9BVpKrJCs1c5taGjDgdInQKBgHm/fVvv96bJxc9x1tffXAcj +3OVdUN0UgXNCSaf/3A/phbeBQe9xS+3mpc4r6qvx+iy69mNBeNZ0xOitIjpjBo2+ +dBEjSBwLk5q5tJqHmy/jKMJL4n9ROlx93XS+njxgibTvU6Fp9w+NOFD/HvxB3Tcz +6+jJF85D5BNAG3DBMKBjAoGBAOAxZvgsKN+JuENXsST7F89Tck2iTcQIT8g5rwWC +P9Vt74yboe2kDT531w8+egz7nAmRBKNM751U/95P9t88EDacDI/Z2OwnuFQHCPDF +llYOUI+SpLJ6/vURRbHSnnn8a/XG+nzedGH5JGqEJNQsz+xT2axM0/W/CRknmGaJ +kda/AoGANWrLCz708y7VYgAtW2Uf1DPOIYMdvo6fxIB5i9ZfISgcJ/bbCUkFrhoH ++vq/5CIWxCPp0f85R4qxxQ5ihxJ0YDQT9Jpx4TMss4PSavPaBH3RXow5Ohe+bYoQ +NE5OgEXk2wVfZczCZpigBKbKZHNYcelXtTt/nP3rsCuGcM4h53s= +-----END RSA PRIVATE KEY----- diff --git a/vagrant/insecure_keys/key.pub b/vagrant/insecure_keys/key.pub new file mode 100644 index 00000000..18a9c00f --- /dev/null +++ b/vagrant/insecure_keys/key.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzIw+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoPkcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NOTd0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcWyLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQ== vagrant insecure public key diff --git a/vagrant/installer.sh b/vagrant/installer.sh index 5fdcaeb5..271f44f5 100755 --- a/vagrant/installer.sh +++ b/vagrant/installer.sh @@ -9,25 +9,11 @@ ############################################################################## set -o errexit -set -o nounset set -o pipefail -# usage() - Prints the usage of the program -function usage { - cat <<EOF -usage: $0 [-a addons] [-p] [-v] [-w dir ] -Optional Argument: - -a List of Kubernetes AddOns to be installed ( e.g. "ovn-kubernetes virtlet multus") - -p Installation of ONAP MultiCloud Kubernetes plugin - -v Enable verbosity - -w Working directory - -t Running healthchecks -EOF -} - # _install_go() - Install GoLang package function _install_go { - version=$(grep "go_version" ${krd_playbooks}/krd-vars.yml | awk -F ': ' '{print $2}') + version=$(grep "go_version" ${krd_playbooks}/krd-vars.yml | awk -F "'" '{print $2}') local tarball=go$version.linux-amd64.tar.gz if $(go version &>/dev/null); then @@ -35,37 +21,31 @@ function _install_go { fi wget https://dl.google.com/go/$tarball - tar -C /usr/local -xzf $tarball + sudo tar -C /usr/local -xzf $tarball rm $tarball export PATH=$PATH:/usr/local/go/bin - sed -i "s|^PATH=.*|PATH=\"$PATH\"|" /etc/environment - export INSTALL_DIRECTORY=/usr/local/bin - curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh + sudo sed -i "s|^PATH=.*|PATH=\"$PATH\"|" /etc/environment } # _install_pip() - Install Python Package Manager function _install_pip { if $(pip --version &>/dev/null); then - return + sudo apt-get install -y python-dev + curl -sL https://bootstrap.pypa.io/get-pip.py | sudo python + else + sudo -E pip install --upgrade pip fi - apt-get install -y python-dev - curl -sL https://bootstrap.pypa.io/get-pip.py | python - pip install --upgrade pip } # _install_ansible() - Install and Configure Ansible program function _install_ansible { - mkdir -p /etc/ansible/ - cat <<EOL > /etc/ansible/ansible.cfg -[defaults] -host_key_checking = false -EOL + sudo mkdir -p /etc/ansible/ if $(ansible --version &>/dev/null); then return fi _install_pip - pip install ansible + sudo -E pip install ansible } # _install_docker() - Download and install docker-engine @@ -75,36 +55,33 @@ function _install_docker { if $(docker version &>/dev/null); then return fi - apt-get install -y software-properties-common linux-image-extra-$(uname -r) linux-image-extra-virtual apt-transport-https ca-certificates curl + sudo apt-get install -y software-properties-common linux-image-extra-$(uname -r) linux-image-extra-virtual apt-transport-https ca-certificates curl curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - - add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" - apt-get update - apt-get install -y docker-ce + sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" + sudo apt-get update + sudo apt-get install -y docker-ce - mkdir -p /etc/systemd/system/docker.service.d + sudo mkdir -p /etc/systemd/system/docker.service.d if [ $http_proxy ]; then - cat <<EOL > /etc/systemd/system/docker.service.d/http-proxy.conf -[Service] -Environment="HTTP_PROXY=$http_proxy" -EOL + echo "[Service]" | sudo tee /etc/systemd/system/docker.service.d/http-proxy.conf + echo "Environment=\"HTTP_PROXY=$http_proxy\"" | sudo tee --append /etc/systemd/system/docker.service.d/http-proxy.conf fi if [ $https_proxy ]; then - cat <<EOL > /etc/systemd/system/docker.service.d/https-proxy.conf -[Service] -Environment="HTTPS_PROXY=$https_proxy" -EOL + echo "[Service]" | sudo tee /etc/systemd/system/docker.service.d/https-proxy.conf + echo "Environment=\"HTTPS_PROXY=$https_proxy\"" | sudo tee --append /etc/systemd/system/docker.service.d/https-proxy.conf fi if [ $no_proxy ]; then - cat <<EOL > /etc/systemd/system/docker.service.d/no-proxy.conf -[Service] -Environment="NO_PROXY=$no_proxy" -EOL + echo "[Service]" | sudo tee /etc/systemd/system/docker.service.d/no-proxy.conf + echo "Environment=\"NO_PROXY=$no_proxy\"" | sudo tee --append /etc/systemd/system/docker.service.d/no-proxy.conf + fi + sudo systemctl daemon-reload + echo "DOCKER_OPTS=\"-H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock --max-concurrent-downloads $max_concurrent_downloads \"" | sudo tee --append /etc/default/docker + if [[ -z $(groups | grep docker) ]]; then + sudo usermod -aG docker $USER + newgrp docker fi - systemctl daemon-reload - echo "DOCKER_OPTS=\"-H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock --max-concurrent-downloads $max_concurrent_downloads \"" >> /etc/default/docker - usermod -aG docker $USER - systemctl restart docker + sudo systemctl restart docker sleep 10 } @@ -113,48 +90,51 @@ function install_k8s { echo "Deploying kubernetes" local dest_folder=/opt version=$(grep "kubespray_version" ${krd_playbooks}/krd-vars.yml | awk -F ': ' '{print $2}') + local_release_dir=$(grep "local_release_dir" $krd_inventory_folder/group_vars/k8s-cluster.yml | awk -F "\"" '{print $2}') local tarball=v$version.tar.gz - apt-get install -y sshpass + sudo apt-get install -y sshpass + _install_docker _install_ansible wget https://github.com/kubernetes-incubator/kubespray/archive/$tarball - tar -C $dest_folder -xzf $tarball + sudo tar -C $dest_folder -xzf $tarball + sudo mv $dest_folder/kubespray-$version/ansible.cfg /etc/ansible/ansible.cfg + sudo chown -R $USER $dest_folder/kubespray-$version + sudo mkdir -p ${local_release_dir}/containers rm $tarball - pushd $dest_folder/kubespray-$version - pip install -r requirements.txt - rm -f $krd_inventory_folder/group_vars/all.yml 2> /dev/null - if [[ -n "${verbose+x}" ]]; then - echo "kube_log_level: 5" >> $krd_inventory_folder/group_vars/all.yml - else - echo "kube_log_level: 2" >> $krd_inventory_folder/group_vars/all.yml - fi - if [[ -n "${http_proxy+x}" ]]; then - echo "http_proxy: \"$http_proxy\"" >> $krd_inventory_folder/group_vars/all.yml - fi - if [[ -n "${https_proxy+x}" ]]; then - echo "https_proxy: \"$https_proxy\"" >> $krd_inventory_folder/group_vars/all.yml - fi - ansible-playbook $verbose -i $krd_inventory cluster.yml -b | tee $log_folder/setup-kubernetes.log - popd + sudo -E pip install -r $dest_folder/kubespray-$version/requirements.txt + rm -f $krd_inventory_folder/group_vars/all.yml 2> /dev/null + if [[ -n "${verbose}" ]]; then + echo "kube_log_level: 5" | tee $krd_inventory_folder/group_vars/all.yml + else + echo "kube_log_level: 2" | tee $krd_inventory_folder/group_vars/all.yml + fi + echo "kubeadm_enabled: true" | tee --append $krd_inventory_folder/group_vars/all.yml + if [[ -n "${http_proxy}" ]]; then + echo "http_proxy: \"$http_proxy\"" | tee --append $krd_inventory_folder/group_vars/all.yml + fi + if [[ -n "${https_proxy}" ]]; then + echo "https_proxy: \"$https_proxy\"" | tee --append $krd_inventory_folder/group_vars/all.yml + fi + ansible-playbook $verbose -i $krd_inventory $dest_folder/kubespray-$version/cluster.yml --become --become-user=root | sudo tee $log_folder/setup-kubernetes.log # Configure environment mkdir -p $HOME/.kube - mv $krd_inventory_folder/artifacts/admin.conf $HOME/.kube/config + cp $krd_inventory_folder/artifacts/admin.conf $HOME/.kube/config } # install_addons() - Install Kubenertes AddOns function install_addons { echo "Installing Kubernetes AddOns" - apt-get install -y sshpass _install_ansible - ansible-galaxy install -r $krd_folder/galaxy-requirements.yml --ignore-errors + sudo ansible-galaxy install $verbose -r $krd_folder/galaxy-requirements.yml --ignore-errors - ansible-playbook $verbose -i $krd_inventory $krd_playbooks/configure-krd.yml | tee $log_folder/setup-krd.log - for addon in $addons; do + ansible-playbook $verbose -i $krd_inventory $krd_playbooks/configure-krd.yml | sudo tee $log_folder/setup-krd.log + for addon in ${KRD_ADDONS:-virtlet ovn4nfv}; do echo "Deploying $addon using configure-$addon.yml playbook.." - ansible-playbook $verbose -i $krd_inventory $krd_playbooks/configure-${addon}.yml | tee $log_folder/setup-${addon}.log - if [[ -n "${testing_enabled+x}" ]]; then + ansible-playbook $verbose -i $krd_inventory $krd_playbooks/configure-${addon}.yml | sudo tee $log_folder/setup-${addon}.log + if [[ "${testing_enabled}" == "true" ]]; then pushd $krd_tests bash ${addon}.sh popd @@ -167,18 +147,16 @@ function install_plugin { echo "Installing multicloud/k8s plugin" _install_go _install_docker - pip install docker-compose + sudo -E pip install docker-compose - mkdir -p /opt/{kubeconfig,consul/config} - cp $HOME/.kube/config /opt/kubeconfig/krd + sudo mkdir -p /opt/{kubeconfig,consul/config} + sudo cp $HOME/.kube/config /opt/kubeconfig/krd export KUBE_CONFIG_DIR=/opt/kubeconfig - echo "export KUBE_CONFIG_DIR=${KUBE_CONFIG_DIR}" >> /etc/environment + echo "export KUBE_CONFIG_DIR=${KUBE_CONFIG_DIR}" | sudo tee --append /etc/environment - GOPATH=$(go env GOPATH) - pushd $GOPATH/src/k8-plugin-multicloud/deployments - ./build.sh - - if [[ -n "${testing_enabled+x}" ]]; then + pushd $krd_folder/../deployments + sudo ./build.sh + if [[ "${testing_enabled}" == "true" ]]; then docker-compose up -d pushd $krd_tests for functional_test in plugin plugin_edgex; do @@ -207,57 +185,47 @@ function _print_kubernetes_info { echo "Admin password: secret" >> $k8s_info_file } -# Configuration values -addons="virtlet ovn-kubernetes multus" -krd_folder="$(dirname "$0")" -verbose="" +if ! sudo -n "true"; then + echo "" + echo "passwordless sudo is needed for '$(id -nu)' user." + echo "Please fix your /etc/sudoers file. You likely want an" + echo "entry like the following one..." + echo "" + echo "$(id -nu) ALL=(ALL) NOPASSWD: ALL" + exit 1 +fi + +if [[ -n "${KRD_DEBUG}" ]]; then + set -o xtrace + verbose="-vvv" +fi -while getopts "a:pvw:t" opt; do - case $opt in - a) - addons="$OPTARG" - ;; - p) - plugin_enabled="true" - ;; - v) - set -o xtrace - verbose="-vvv" - ;; - w) - krd_folder="$OPTARG" - ;; - t) - testing_enabled="true" - ;; - ?) - usage - exit - ;; - esac -done +# Configuration values log_folder=/var/log/krd -krd_inventory_folder=$krd_folder/inventory +krd_folder=$(pwd) +export krd_inventory_folder=$krd_folder/inventory krd_inventory=$krd_inventory_folder/hosts.ini krd_playbooks=$krd_folder/playbooks krd_tests=$krd_folder/tests k8s_info_file=$krd_folder/k8s_info.log +testing_enabled=${KRD_ENABLE_TESTS:-false} -mkdir -p $log_folder -mkdir -p /opt/csar +sudo mkdir -p $log_folder +sudo mkdir -p /opt/csar +sudo chown -R $USER /opt/csar export CSAR_DIR=/opt/csar -echo "export CSAR_DIR=${CSAR_DIR}" >> /etc/environment +echo "export CSAR_DIR=${CSAR_DIR}" | sudo tee --append /etc/environment # Install dependencies # Setup proxy variables if [ -f $krd_folder/sources.list ]; then - mv /etc/apt/sources.list /etc/apt/sources.list.backup - cp $krd_folder/sources.list /etc/apt/sources.list + sudo mv /etc/apt/sources.list /etc/apt/sources.list.backup + sudo cp $krd_folder/sources.list /etc/apt/sources.list fi -apt-get update +sudo apt-get update install_k8s install_addons -if [[ -n "${plugin_enabled+x}" ]]; then +if [[ "${KRD_PLUGIN_ENABLED:-false}" ]]; then install_plugin fi _print_kubernetes_info diff --git a/vagrant/inventory/group_vars/k8s-cluster.yml b/vagrant/inventory/group_vars/k8s-cluster.yml index f038d4f2..4de3a276 100644 --- a/vagrant/inventory/group_vars/k8s-cluster.yml +++ b/vagrant/inventory/group_vars/k8s-cluster.yml @@ -57,7 +57,7 @@ kubeconfig_localhost: true local_volumes_enabled: true ## Change this to use another Kubernetes version, e.g. a current beta release -kube_version: v1.11.3 +kube_version: v1.12.3 # Helm deployment helm_enabled: true @@ -66,4 +66,17 @@ helm_enabled: true # NOTE: Ipvs is based on netfilter hook function, but uses hash table as the underlying data structure and # works in the kernel space # https://kubernetes.io/docs/concepts/services-networking/service/#proxy-mode-ipvs -kube_proxy_mode: ipvs +#kube_proxy_mode: ipvs + +# Download container images only once then push to cluster nodes in batches +download_run_once: true + +# Where the binaries will be downloaded. +# Note: ensure that you've enough disk space (about 1G) +local_release_dir: "/tmp/releases" + +# Makes the installer node a delegate for pushing images while running +# the deployment with ansible. This maybe the case if cluster nodes +# cannot access each over via ssh or you want to use local docker +# images as a cache for multiple clusters. +download_localhost: true diff --git a/vagrant/playbooks/configure-istio.yml b/vagrant/playbooks/configure-istio.yml index 25a343f0..2bd4e853 100644 --- a/vagrant/playbooks/configure-istio.yml +++ b/vagrant/playbooks/configure-istio.yml @@ -9,15 +9,15 @@ ############################################################################## - hosts: localhost - become: yes pre_tasks: - name: Load krd variables include_vars: file: krd-vars.yml roles: - - andrewrothstein.kubectl + - role: andrewrothstein.kubectl + kubectl_ver: "v{{ kubectl_version }}" - role: andrewrothstein.kubernetes-helm - kubernetes_helm_ver: v2.9.1 + kubernetes_helm_ver: "v{{ helm_client_version }}" tasks: - name: create istio folder file: @@ -35,6 +35,7 @@ dest: "{{ istio_dest }}" remote_src: yes - name: copy istioctl binary to usr/local/bin folder + become: yes command: "mv {{ istio_dest }}/istio-{{ istio_version }}/bin/istioctl /usr/local/bin/" when: istio_source_type == "tarball" - name: create network objects diff --git a/vagrant/playbooks/configure-krd.yml b/vagrant/playbooks/configure-krd.yml index c8146ed8..22e6419f 100644 --- a/vagrant/playbooks/configure-krd.yml +++ b/vagrant/playbooks/configure-krd.yml @@ -12,5 +12,5 @@ tasks: - name: copy admin.conf file to kube-nodes copy: - src: "{{ ansible_env.HOME}}/.kube/config" + src: "{{ lookup('env','krd_inventory_folder') }}/artifacts/admin.conf" dest: "/etc/kubernetes/admin.conf" diff --git a/vagrant/playbooks/configure-multus.yml b/vagrant/playbooks/configure-multus.yml index 33e72757..23fe546a 100644 --- a/vagrant/playbooks/configure-multus.yml +++ b/vagrant/playbooks/configure-multus.yml @@ -14,7 +14,9 @@ include_vars: file: krd-vars.yml roles: - - { role: andrewrothstein.go, when: multus_source_type == "source" } + - role: andrewrothstein.go + go_ver: "{{ go_version }}" + when: multus_source_type == "source" environment: PATH: "{{ ansible_env.PATH }}:/usr/local/go/bin/" tasks: @@ -78,8 +80,13 @@ } - hosts: localhost + pre_tasks: + - name: Load krd variables + include_vars: + file: krd-vars.yml roles: - - andrewrothstein.kubectl + - role: andrewrothstein.kubectl + kubectl_ver: "v{{ kubectl_version }}" tasks: - name: define a CRD network object specification blockinfile: diff --git a/vagrant/playbooks/configure-nfd.yml b/vagrant/playbooks/configure-nfd.yml index 90bad671..d47a7bcc 100644 --- a/vagrant/playbooks/configure-nfd.yml +++ b/vagrant/playbooks/configure-nfd.yml @@ -46,9 +46,13 @@ - node-feature-discovery-daemonset.json.template - hosts: localhost - become: yes + pre_tasks: + - name: Load krd variables + include_vars: + file: krd-vars.yml roles: - - andrewrothstein.kubectl + - role: andrewrothstein.kubectl + kubectl_ver: "v{{ kubectl_version }}" tasks: - name: create service accounts command: "/usr/local/bin/kubectl apply -f /tmp/{{ item }}" diff --git a/vagrant/playbooks/configure-ovn-kubernetes.yml b/vagrant/playbooks/configure-ovn-kubernetes.yml index cea102f2..e3042ff4 100644 --- a/vagrant/playbooks/configure-ovn-kubernetes.yml +++ b/vagrant/playbooks/configure-ovn-kubernetes.yml @@ -14,8 +14,13 @@ central_node_ip: "{{ hostvars[groups['ovn-central'][0]]['ansible_ssh_host'] }}" environment: PATH: "{{ ansible_env.PATH }}:/usr/local/go/bin/" + pre_tasks: + - name: Load krd variables + include_vars: + file: krd-vars.yml roles: - role: andrewrothstein.go + go_ver: "{{ go_version }}" tasks: - name: Load krd variables include_vars: diff --git a/vagrant/playbooks/configure-ovn4nfv.yml b/vagrant/playbooks/configure-ovn4nfv.yml new file mode 100644 index 00000000..c864b8c3 --- /dev/null +++ b/vagrant/playbooks/configure-ovn4nfv.yml @@ -0,0 +1,98 @@ +--- +# 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 +############################################################################## +- import_playbook: configure-ovn.yml +- import_playbook: configure-multus.yml + +- hosts: kube-master:kube-node + environment: + PATH: "{{ ansible_env.PATH }}:/usr/local/go/bin/" + roles: + - role: andrewrothstein.go + tasks: + - name: Load krd variables + include_vars: + file: krd-vars.yml + - name: clone ovn4nfv-k8s-plugin repo + git: + repo: "{{ ovn4nfv_url }}" + dest: "{{ ovn4nfv_dest }}" + version: "{{ ovn4nfv_version }}" + force: yes + when: ovn4nfv_source_type == "source" + - name: clean ovn4nfvk8s left over files + make: + chdir: "{{ ovn4nfv_dest }}" + target: clean + - name: build ovn4nfvk8s-cni + make: + chdir: "{{ ovn4nfv_dest }}" + target: ovn4nfvk8s-cni + become: yes + environment: + GOPATH: "{{ go_path }}" + - name: copy ovn4nfvk8s-cni to cni folder + command: "mv {{ ovn4nfv_dest }}/ovn4nfvk8s-cni /opt/cni/bin/ovn4nfvk8s-cni" + become: yes + - name: create ovn4k8s config file + become: yes + blockinfile: + path: /etc/openvswitch/ovn4nfv_k8s.conf + create: yes + block: | + [logging] + loglevel=5 + logfile=/var/log/openvswitch/ovn4k8s.log + + [cni] + conf-dir=/etc/cni/net.d + plugin=ovn4nfvk8s-cni + + [kubernetes] + kubeconfig=/etc/kubernetes/admin.conf + - name: create ovnkube logging directory + file: + path: /var/log/openvswitch + state: directory + +- hosts: kube-master + environment: + PATH: "{{ ansible_env.PATH }}:/usr/local/go/bin/" + become: yes + tasks: + - name: Load krd variables + include_vars: + file: krd-vars.yml + - name: build ovn4nfvk8s + make: + chdir: "{{ ovn4nfv_dest }}" + target: ovn4nfvk8s + environment: + GOPATH: "{{ go_path }}" + - name: copy ovn4nfvk8s to /usr/bin folder + command: "mv {{ ovn4nfv_dest }}/ovn4nfvk8s /usr/bin/ovn4nfvk8s" + - name: create ovn4nfvk8s systemd service + blockinfile: + path: /etc/systemd/system/ovn4nfvk8s.service + create: yes + block: | + [Unit] + Description=OVN4NFV Kubernetes Daemon + + [Service] + ExecStart=/usr/bin/ovn4nfvk8s \ + -k8s-kubeconfig=/etc/kubernetes/admin.conf + + [Install] + WantedBy=multi-user.target + - name: start ovn4nfvk8s systemd service + service: + name: ovn4nfvk8s + state: started + enabled: yes diff --git a/vagrant/playbooks/configure-virtlet.yml b/vagrant/playbooks/configure-virtlet.yml index 98aa74cc..66deb5cb 100644 --- a/vagrant/playbooks/configure-virtlet.yml +++ b/vagrant/playbooks/configure-virtlet.yml @@ -8,7 +8,6 @@ # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## - hosts: localhost - become: yes vars: images_file: /tmp/images.yaml pre_tasks: @@ -16,8 +15,10 @@ include_vars: file: krd-vars.yml roles: - - andrewrothstein.kubectl - - { role: geerlingguy.docker, when: virtlet_source_type == "source" } + - role: andrewrothstein.kubectl + kubectl_ver: "v{{ kubectl_version }}" + - role: geerlingguy.docker + when: virtlet_source_type == "source" tasks: - name: create Virtlet binary folder file: @@ -65,10 +66,12 @@ - name: configure proxy values for docker service block: - name: create docker config folder + become: yes file: state: directory path: "/etc/systemd/system/docker.service.d" - name: Configure docker service to use http_proxy env value + become: yes blockinfile: dest: "/etc/systemd/system/docker.service.d/http-proxy.conf" create: yes @@ -78,6 +81,7 @@ when: - lookup('env','http_proxy') != "fooproxy" - name: Configure docker service to use https_proxy env value + become: yes blockinfile: dest: "/etc/systemd/system/docker.service.d/https-proxy.conf" create: yes @@ -87,6 +91,7 @@ when: - lookup('env','https_proxy') != "fooproxy" - name: Configure docker service to use no_proxy env value + become: yes blockinfile: dest: "/etc/systemd/system/docker.service.d/no-proxy.conf" create: yes @@ -96,8 +101,10 @@ when: - lookup('env','no_proxy') != "fooproxy" - name: reload systemd + become: yes command: systemctl daemon-reload - name: restart docker service + become: yes service: name: docker state: restarted @@ -134,7 +141,6 @@ delay: 10 - hosts: virtlet - become: yes tasks: - name: Load krd variables include_vars: @@ -144,18 +150,21 @@ state: directory path: "{{ criproxy_dest }}" - name: disable AppArmor in all nodes + become: yes service: name: apparmor state: stopped enabled: no when: ansible_os_family == "Debian" - name: modify args for kubelet service + become: yes lineinfile: dest: /etc/systemd/system/kubelet.service line: " --container-runtime=remote --container-runtime-endpoint=unix:///run/criproxy.sock --image-service-endpoint=unix:///run/criproxy.sock --enable-controller-attach-detach=false \\" insertafter: '^ExecStart=/usr/local/bin/kubelet *' state: present - name: create dockershim service + become: yes blockinfile: path: /etc/systemd/system/dockershim.service create: yes @@ -208,6 +217,7 @@ path: "{{ criproxy_dest }}/criproxy" mode: "+x" - name: create criproxy service + become: yes blockinfile: path: /etc/systemd/system/criproxy.service create: yes @@ -224,6 +234,7 @@ [Install] WantedBy=kubelet.service - name: start criproxy and dockershim services + become: yes service: name: "{{ item }}" state: started @@ -232,6 +243,7 @@ - dockershim - criproxy - name: restart kubelet services + become: yes service: name: kubelet state: restarted diff --git a/vagrant/playbooks/krd-vars.yml b/vagrant/playbooks/krd-vars.yml index 9c2de308..15b7a1a4 100644 --- a/vagrant/playbooks/krd-vars.yml +++ b/vagrant/playbooks/krd-vars.yml @@ -11,12 +11,12 @@ base_dest: /tmp multus_dest: "{{ base_dest }}/multus-cni" -multus_source_type: "tarball" -multus_version: 3.1 -multus_url: "https://github.com/intel/multus-cni/releases/download/v{{ multus_version }}/multus-cni_v{{ multus_version }}_linux_amd64.tar.gz" -#multus_source_type: "source" -#multus_version: def72938cd2fb272eb3a6f64a8162b1049404357 -#multus_url: "https://github.com/intel/multus-cni" +#multus_source_type: "tarball" +#multus_version: 3.1 +#multus_url: "https://github.com/intel/multus-cni/releases/download/v{{ multus_version }}/multus-cni_v{{ multus_version }}_linux_amd64.tar.gz" +multus_source_type: "source" +multus_version: 366f2120cb88c85deab6343b7062fd38fdb0ece9 +multus_url: "https://github.com/ritusood/multus-cni" ovn_kubernetes_dest: "{{ base_dest }}/ovn-kubernetes" ovn_kubernetes_source_type: "tarball" @@ -35,7 +35,7 @@ criproxy_url: "https://github.com/Mirantis/criproxy/releases/download/v{{ cripro #criproxy_url: "https://github.com/Mirantis/criproxy" virtlet_dest: "{{ base_dest }}/virtlet" virtlet_source_type: "binary" -virtlet_version: 1.4.1 +virtlet_version: 1.4.2 virtlet_url: "https://github.com/Mirantis/virtlet/releases/download/v{{ virtlet_version }}/virtletctl" #virtlet_source_type: "source" #virtlet_version: 68e11b8f1db2c78b063126899f0e60910700975d @@ -51,5 +51,13 @@ istio_source_type: "tarball" istio_version: 1.0.3 istio_url: "https://github.com/istio/istio/releases/download/{{ istio_version }}/istio-{{ istio_version }}-linux.tar.gz" -go_version: 1.11.1 -kubespray_version: 2.7.0 +go_path: "{{ base_dest }}/go" +ovn4nfv_dest: "{{ go_path }}/src/ovn4nfv-k8s-plugin" +ovn4nfv_source_type: "source" +ovn4nfv_version: 5026d1d89b05eac5e004279b742df6745a73d93a +ovn4nfv_url: "https://git.opnfv.org/ovn4nfv-k8s-plugin/" + +go_version: '1.11' +kubespray_version: 2.8.0 +kubectl_version: 1.11.2 +helm_client_version: 2.9.1 diff --git a/vagrant/setup.sh b/vagrant/setup.sh index c8fe2e28..674462e7 100755 --- a/vagrant/setup.sh +++ b/vagrant/setup.sh @@ -11,7 +11,7 @@ set -o nounset set -o pipefail -vagrant_version=2.2.0 +vagrant_version=2.2.2 if ! $(vagrant version &>/dev/null); then enable_vagrant_install=true else @@ -177,9 +177,10 @@ modprobe vhost_net ${INSTALLER_CMD} ${packages[@]} if ! which pip; then curl -sL https://bootstrap.pypa.io/get-pip.py | sudo python +else + sudo -H -E pip install --upgrade pip fi -sudo -H pip install --upgrade pip -sudo -H pip install tox +sudo -H -E pip install tox if [[ ${http_proxy+x} ]]; then vagrant plugin install vagrant-proxyconf fi diff --git a/vagrant/tests/_common.sh b/vagrant/tests/_common.sh index ac226da0..620c00af 100755 --- a/vagrant/tests/_common.sh +++ b/vagrant/tests/_common.sh @@ -21,6 +21,27 @@ virtlet_image=virtlet.cloud/fedora virtlet_deployment_name=virtlet-deployment plugin_deployment_name=plugin-deployment plugin_service_name=plugin-service +ovn4nfv_deployment_name=ovn4nfv-deployment +onap_private_net=onap-private-net +unprotected_private_net=unprotected-private-net +protected_private_net=protected-private-net +ovn_multus_network_name=ovn-networkobj + +# vFirewall vars +demo_artifacts_version=1.3.0 +vfw_private_ip_0='192.168.10.3' +vfw_private_ip_1='192.168.20.2' +vfw_private_ip_2='10.10.100.3' +vpg_private_ip_0='192.168.10.2' +vpg_private_ip_1='10.0.100.2' +vsn_private_ip_0='192.168.20.3' +vsn_private_ip_1='10.10.100.4' +dcae_collector_ip='10.0.4.1' +dcae_collector_port='8081' +protected_net_gw='192.168.20.100' +protected_net_cidr='192.168.20.0/24' +protected_private_net_cidr='192.168.10.0/24' +onap_private_net_cidr='10.10.0.0/16' # populate_CSAR_containers_vFW() - This function creates the content of CSAR file # required for vFirewal using only containers @@ -33,59 +54,59 @@ function populate_CSAR_containers_vFW { cat << META > metadata.yaml resources: network: - - unprotected-private-net-cidr-network.yaml - - protected-private-net-cidr-network.yaml - - onap-private-net-cidr-network.yaml + - $unprotected_private_net.yaml + - $protected_private_net.yaml + - $onap_private_net.yaml deployment: - $packetgen_deployment_name.yaml - $firewall_deployment_name.yaml - $sink_deployment_name.yaml META - cat << NET > unprotected-private-net-cidr-network.yaml + cat << NET > $unprotected_private_net.yaml apiVersion: "k8s.cni.cncf.io/v1" kind: NetworkAttachmentDefinition metadata: - name: unprotected-private-net-cidr + name: $unprotected_private_net spec: config: '{ "name": "unprotected", "type": "bridge", "ipam": { "type": "host-local", - "subnet": "192.168.10.0/24" + "subnet": "$protected_private_net_cidr" } }' NET - cat << NET > protected-private-net-cidr-network.yaml + cat << NET > $protected_private_net.yaml apiVersion: "k8s.cni.cncf.io/v1" kind: NetworkAttachmentDefinition metadata: - name: protected-private-net-cidr + name: $protected_private_net spec: config: '{ "name": "protected", "type": "bridge", "ipam": { "type": "host-local", - "subnet": "192.168.20.0/24" + "subnet": "$protected_net_cidr" } }' NET - cat << NET > onap-private-net-cidr-network.yaml + cat << NET > $onap_private_net.yaml apiVersion: "k8s.cni.cncf.io/v1" kind: NetworkAttachmentDefinition metadata: - name: onap-private-net-cidr + name: $onap_private_net spec: config: '{ "name": "onap", "type": "bridge", "ipam": { "type": "host-local", - "subnet": "10.10.0.0/16" + "subnet": "$onap_private_net_cidr" } }' NET @@ -108,8 +129,8 @@ spec: app: vFirewall annotations: k8s.v1.cni.cncf.io/networks: '[ - { "name": "unprotected-private-net-cidr", "interfaceRequest": "eth1" }, - { "name": "onap-private-net-cidr", "interfaceRequest": "eth2" } + { "name": "$unprotected_private_net", "interfaceRequest": "eth1" }, + { "name": "$onap_private_net", "interfaceRequest": "eth2" } ]' spec: containers: @@ -141,9 +162,9 @@ spec: app: vFirewall annotations: k8s.v1.cni.cncf.io/networks: '[ - { "name": "unprotected-private-net-cidr", "interfaceRequest": "eth1" }, - { "name": "protected-private-net-cidr", "interfaceRequest": "eth2" }, - { "name": "onap-private-net-cidr", "interfaceRequest": "eth3" } + { "name": "$unprotected_private_net", "interfaceRequest": "eth1" }, + { "name": "$protected_private_net", "interfaceRequest": "eth2" }, + { "name": "$onap_private_net", "interfaceRequest": "eth3" } ]' spec: containers: @@ -166,14 +187,16 @@ spec: selector: matchLabels: app: vFirewall + context: darkstat template: metadata: labels: app: vFirewall + context: darkstat annotations: k8s.v1.cni.cncf.io/networks: '[ - { "name": "protected-private-net-cidr", "interfaceRequest": "eth1" }, - { "name": "onap-private-net-cidr", "interfaceRequest": "eth2" } + { "name": "$protected_private_net", "interfaceRequest": "eth1" }, + { "name": "$onap_private_net", "interfaceRequest": "eth2" } ]' spec: containers: @@ -182,6 +205,15 @@ spec: imagePullPolicy: IfNotPresent tty: true stdin: true + securityContext: + privileged: true + - name: darkstat + image: electrocucaracha/darkstat + imagePullPolicy: IfNotPresent + tty: true + stdin: true + ports: + - containerPort: 667 DEPLOYMENT popd } @@ -199,17 +231,17 @@ function populate_CSAR_vms_containers_vFW { cat << META > metadata.yaml resources: network: - - unprotected-private-net-cidr-network.yaml - - protected-private-net-cidr-network.yaml - - onap-private-net-cidr-network.yaml + - onap-ovn4nfvk8s-network.yaml + onapNetwork: + - $unprotected_private_net.yaml + - $protected_private_net.yaml + - $onap_private_net.yaml deployment: - $packetgen_deployment_name.yaml - $firewall_deployment_name.yaml - $sink_deployment_name.yaml service: - sink-service.yaml - ingress: - - sink-ingress.yaml META cat << SERVICE > sink-service.yaml @@ -217,8 +249,6 @@ apiVersion: v1 kind: Service metadata: name: sink-service - labels: - app: vFirewall spec: type: NodePort ports: @@ -229,71 +259,66 @@ spec: context: darkstat SERVICE - cat << INGRESS > sink-ingress.yaml -apiVersion: extensions/v1beta1 -kind: Ingress -metadata: - name: sink-ingress -spec: - rules: - - host: sink.vfirewall.demo.com - http: - paths: - - backend: - serviceName: sink-service - servicePort: 667 -INGRESS - - cat << NET > unprotected-private-net-cidr-network.yaml + cat << MULTUS_NET > onap-ovn4nfvk8s-network.yaml apiVersion: "k8s.cni.cncf.io/v1" kind: NetworkAttachmentDefinition metadata: - name: unprotected-private-net-cidr + name: $ovn_multus_network_name spec: config: '{ - "name": "unprotected", - "type": "bridge", - "ipam": { - "type": "host-local", - "subnet": "192.168.10.0/24" - } -}' + "cniVersion": "0.3.1", + "name": "ovn4nfv-k8s-plugin", + "type": "ovn4nfvk8s-cni" + }' +MULTUS_NET + + cat << NET > $unprotected_private_net.yaml +apiVersion: v1 +kind: onapNetwork +metadata: + name: $unprotected_private_net + cnitype : ovn4nfvk8s +spec: + name: $unprotected_private_net + subnet: $protected_private_net_cidr + gateway: 192.168.10.1/24 NET - cat << NET > protected-private-net-cidr-network.yaml -apiVersion: "k8s.cni.cncf.io/v1" -kind: NetworkAttachmentDefinition + cat << NET > $protected_private_net.yaml +apiVersion: v1 +kind: onapNetwork metadata: - name: protected-private-net-cidr + name: $protected_private_net + cnitype : ovn4nfvk8s spec: - config: '{ - "name": "protected", - "type": "bridge", - "ipam": { - "type": "host-local", - "subnet": "192.168.20.0/24" - } -}' + name: $protected_private_net + subnet: $protected_net_cidr + gateway: $protected_net_gw/24 NET - cat << NET > onap-private-net-cidr-network.yaml -apiVersion: "k8s.cni.cncf.io/v1" -kind: NetworkAttachmentDefinition + cat << NET > $onap_private_net.yaml +apiVersion: v1 +kind: onapNetwork metadata: - name: onap-private-net-cidr + name: $onap_private_net + cnitype : ovn4nfvk8s spec: - config: '{ - "name": "onap", - "type": "bridge", - "ipam": { - "type": "host-local", - "subnet": "10.10.0.0/16" - } -}' + name: $onap_private_net + subnet: $onap_private_net_cidr + gateway: 10.10.0.1/16 NET proxy="apt:" - cloud_init_proxy="" + cloud_init_proxy=" + - export demo_artifacts_version=$demo_artifacts_version + - export vfw_private_ip_0=$vfw_private_ip_0 + - export vsn_private_ip_0=$vsn_private_ip_0 + - export protected_net_cidr=$protected_net_cidr + - export dcae_collector_ip=$dcae_collector_ip + - export dcae_collector_port=$dcae_collector_port + - export protected_net_gw=$protected_net_gw + - export protected_private_net_cidr=$protected_private_net_cidr +" if [[ -n "${http_proxy+x}" ]]; then proxy+=" http_proxy: $http_proxy" @@ -350,9 +375,10 @@ spec: VirtletSSHKeys: | $ssh_key VirtletRootVolumeSize: 5Gi - k8s.v1.cni.cncf.io/networks: '[ - { "name": "unprotected-private-net-cidr", "interfaceRequest": "eth1" }, - { "name": "onap-private-net-cidr", "interfaceRequest": "eth2" } + k8s.v1.cni.cncf.io/networks: '[{ "name": "$ovn_multus_network_name"}]' + ovnNetwork: '[ + { "name": "$unprotected_private_net", "ipAddress": "$vpg_private_ip_0", "interface": "eth1" , "defaultGateway": "false"}, + { "name": "$onap_private_net", "ipAddress": "$vpg_private_ip_1", "interface": "eth2" , "defaultGateway": "false"} ]' kubernetes.io/target-runtime: virtlet.cloud spec: @@ -417,10 +443,11 @@ spec: VirtletSSHKeys: | $ssh_key VirtletRootVolumeSize: 5Gi - k8s.v1.cni.cncf.io/networks: '[ - { "name": "unprotected-private-net-cidr", "interfaceRequest": "eth1" }, - { "name": "protected-private-net-cidr", "interfaceRequest": "eth2" }, - { "name": "onap-private-net-cidr", "interfaceRequest": "eth3" } + k8s.v1.cni.cncf.io/networks: '[{ "name": "$ovn_multus_network_name"}]' + ovnNetwork: '[ + { "name": "$unprotected_private_net", "ipAddress": "$vfw_private_ip_0", "interface": "eth1" , "defaultGateway": "false"}, + { "name": "$protected_private_net", "ipAddress": "$vfw_private_ip_1", "interface": "eth2", "defaultGateway": "false" }, + { "name": "$onap_private_net", "ipAddress": "$vfw_private_ip_2", "interface": "eth3" , "defaultGateway": "false"} ]' kubernetes.io/target-runtime: virtlet.cloud spec: @@ -463,9 +490,10 @@ spec: app: vFirewall context: darkstat annotations: - k8s.v1.cni.cncf.io/networks: '[ - { "name": "protected-private-net-cidr", "interfaceRequest": "eth1" }, - { "name": "onap-private-net-cidr", "interfaceRequest": "eth2" } + k8s.v1.cni.cncf.io/networks: '[{ "name": "$ovn_multus_network_name"}]' + ovnNetwork: '[ + { "name": "$protected_private_net", "ipAddress": "$vsn_private_ip_0", "interface": "eth1", "defaultGateway": "false" }, + { "name": "$onap_private_net", "ipAddress": "$vsn_private_ip_1", "interface": "eth2" , "defaultGateway": "false"} ]' spec: containers: @@ -499,65 +527,74 @@ function populate_CSAR_vms_vFW { cat << META > metadata.yaml resources: network: - - unprotected-private-net-cidr-network.yaml - - protected-private-net-cidr-network.yaml - - onap-private-net-cidr-network.yaml + - $unprotected_private_net.yaml + - $protected_private_net.yaml + - $onap_private_net.yaml deployment: - $packetgen_deployment_name.yaml - $firewall_deployment_name.yaml - $sink_deployment_name.yaml META - cat << NET > unprotected-private-net-cidr-network.yaml + cat << NET > $unprotected_private_net.yaml apiVersion: "k8s.cni.cncf.io/v1" kind: NetworkAttachmentDefinition metadata: - name: unprotected-private-net-cidr + name: $unprotected_private_net spec: config: '{ "name": "unprotected", "type": "bridge", "ipam": { "type": "host-local", - "subnet": "192.168.10.0/24" + "subnet": "$protected_private_net_cidr" } }' NET - cat << NET > protected-private-net-cidr-network.yaml + cat << NET > $protected_private_net.yaml apiVersion: "k8s.cni.cncf.io/v1" kind: NetworkAttachmentDefinition metadata: - name: protected-private-net-cidr + name: $protected_private_net spec: config: '{ "name": "protected", "type": "bridge", "ipam": { "type": "host-local", - "subnet": "192.168.20.0/24" + "subnet": "$protected_net_cidr" } }' NET - cat << NET > onap-private-net-cidr-network.yaml + cat << NET > $onap_private_net.yaml apiVersion: "k8s.cni.cncf.io/v1" kind: NetworkAttachmentDefinition metadata: - name: onap-private-net-cidr + name: $onap_private_net spec: config: '{ "name": "onap", "type": "bridge", "ipam": { "type": "host-local", - "subnet": "10.10.0.0/16" + "subnet": "$onap_private_net_cidr" } }' NET proxy="apt:" - cloud_init_proxy="" + cloud_init_proxy=" + - export demo_artifacts_version=$demo_artifacts_version + - export vfw_private_ip_0=$vfw_private_ip_0 + - export vsn_private_ip_0=$vsn_private_ip_0 + - export protected_net_cidr=$protected_net_cidr + - export dcae_collector_ip=$dcae_collector_ip + - export dcae_collector_port=$dcae_collector_port + - export protected_net_gw=$protected_net_gw + - export protected_private_net_cidr=$protected_private_net_cidr +" if [[ -n "${http_proxy+x}" ]]; then proxy+=" http_proxy: $http_proxy" @@ -615,8 +652,8 @@ spec: $ssh_key VirtletRootVolumeSize: 5Gi k8s.v1.cni.cncf.io/networks: '[ - { "name": "unprotected-private-net-cidr", "interfaceRequest": "eth1" }, - { "name": "onap-private-net-cidr", "interfaceRequest": "eth2" } + { "name": "$unprotected_private_net", "interfaceRequest": "eth1" }, + { "name": "$onap_private_net", "interfaceRequest": "eth2" } ]' kubernetes.io/target-runtime: virtlet.cloud spec: @@ -682,9 +719,9 @@ spec: $ssh_key VirtletRootVolumeSize: 5Gi k8s.v1.cni.cncf.io/networks: '[ - { "name": "unprotected-private-net-cidr", "interfaceRequest": "eth1" }, - { "name": "protected-private-net-cidr", "interfaceRequest": "eth2" }, - { "name": "onap-private-net-cidr", "interfaceRequest": "eth3" } + { "name": "$unprotected_private_net", "interfaceRequest": "eth1" }, + { "name": "$protected_private_net", "interfaceRequest": "eth2" }, + { "name": "$onap_private_net", "interfaceRequest": "eth3" } ]' kubernetes.io/target-runtime: virtlet.cloud spec: @@ -748,8 +785,8 @@ spec: $ssh_key VirtletRootVolumeSize: 5Gi k8s.v1.cni.cncf.io/networks: '[ - { "name": "protected-private-net-cidr", "interfaceRequest": "eth1" }, - { "name": "onap-private-net-cidr", "interfaceRequest": "eth2" } + { "name": "$protected_private_net", "interfaceRequest": "eth1" }, + { "name": "$onap_private_net", "interfaceRequest": "eth2" } ]' kubernetes.io/target-runtime: virtlet.cloud spec: @@ -805,7 +842,7 @@ spec: "type": "bridge", "ipam": { "type": "host-local", - "subnet": "10.10.0.0/16" + "subnet": "$onap_private_net_cidr" } }' NET @@ -972,3 +1009,88 @@ SERVICE popd } +# populate_CSAR_ovn4nfv() - Create content used for OVN4NFV functional test +function populate_CSAR_ovn4nfv { + local csar_id=$1 + + _checks_args $csar_id + pushd ${CSAR_DIR}/${csar_id} + + cat << META > metadata.yaml +resources: + onap_network: + - ovn-port-net.yaml + - ovn-priv-net.yaml + network: + - onap-ovn4nfvk8s-network.yaml + deployment: + - $ovn4nfv_deployment_name.yaml +META + + 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-port-net.yaml +apiVersion: v1 +kind: onapNetwork +metadata: + name: ovn-port-net + cnitype : ovn4nfvk8s +spec: + name: ovn-port-net + subnet: 172.16.33.0/24 + gateway: 172.16.33.1/24 +NETWORK + + cat << NETWORK > ovn-priv-net.yaml +apiVersion: v1 +kind: onapNetwork +metadata: + name: ovn-priv-net + cnitype : ovn4nfvk8s +spec: + name: ovn-priv-net + subnet: 172.16.44.0/24 + gateway: 172.16.44.1/24 +NETWORK + + cat << DEPLOYMENT > $ovn4nfv_deployment_name.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: $ovn4nfv_deployment_name + 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-port-net", "interface": "net0" , "defaultGateway": "false"}, + { "name": "ovn-priv-net", "interface": "net1" , "defaultGateway": "false"}]' + spec: + containers: + - name: $ovn4nfv_deployment_name + image: "busybox" + command: ["top"] + stdin: true + tty: true +DEPLOYMENT + popd +} diff --git a/vagrant/tests/_functions.sh b/vagrant/tests/_functions.sh index c359e729..fe69b07b 100755 --- a/vagrant/tests/_functions.sh +++ b/vagrant/tests/_functions.sh @@ -12,6 +12,66 @@ set -o errexit set -o nounset set -o pipefail +function _get_ovn_central_address { + ansible_ifconfig=$(ansible ovn-central[0] -i $test_folder/../inventory/hosts.ini -m shell -a "ifconfig eth1 |grep \"inet addr\" |awk '{print \$2}' |awk -F: '{print \$2}'") + if [[ $ansible_ifconfig != *CHANGED* ]]; then + echo "Fail to get the OVN central IP address from eth1 nic" + exit + fi + echo "$(echo ${ansible_ifconfig#*>>} | tr '\n' ':')6641" +} + +# install_ovn_deps() - Install dependencies required for tests that require OVN +function install_ovn_deps { + if ! $(yq --version &>/dev/null); then + sudo -E pip install yq + fi + if ! $(ovn-nbctl --version &>/dev/null); then + source /etc/os-release || source /usr/lib/os-release + case ${ID,,} in + *suse) + ;; + ubuntu|debian) + sudo apt-get install -y apt-transport-https + echo "deb https://packages.wand.net.nz $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/wand.list + sudo curl https://packages.wand.net.nz/keyring.gpg -o /etc/apt/trusted.gpg.d/wand.gpg + sudo apt-get update + sudo apt install -y ovn-common + ;; + rhel|centos|fedora) + ;; + esac + fi +} + +# init_network() - This function creates the OVN resouces required by the test +function init_network { + local fname=$1 + local router_name="ovn4nfv-master" + + name=$(cat $fname | yq '.spec.name' | xargs) + subnet=$(cat $fname | yq '.spec.subnet' | xargs) + gateway=$(cat $fname | yq '.spec.gateway' | xargs) + ovn_central_address=$(_get_ovn_central_address) + + router_mac=$(printf '00:00:00:%02X:%02X:%02X' $((RANDOM%256)) $((RANDOM%256)) $((RANDOM%256))) + ovn-nbctl --may-exist --db tcp:$ovn_central_address ls-add $name -- set logical_switch $name other-config:subnet=$subnet external-ids:gateway_ip=$gateway + ovn-nbctl --may-exist --db tcp:$ovn_central_address lrp-add $router_name rtos-$name $router_mac $gateway + ovn-nbctl --may-exist --db tcp:$ovn_central_address lsp-add $name stor-$name -- set logical_switch_port stor-$name type=router options:router-port=rtos-$name addresses=\"$router_mac\" +} + +# cleanup_network() - This function removes the OVN resources created for the test +function cleanup_network { + local fname=$1 + + name=$(cat $fname | yq '.spec.name' | xargs) + ovn_central_address=$(_get_ovn_central_address) + + for cmd in "ls-del $name" "lrp-del rtos-$name" "lsp-del stor-$name"; do + ovn-nbctl --if-exist --db tcp:$ovn_central_address $cmd + done +} + function _checks_args { if [[ -z $1 ]]; then echo "Missing CSAR ID argument" @@ -67,7 +127,7 @@ function setup { for deployment_name in $@; do recreate_deployment $deployment_name done - + sleep 5 for deployment_name in $@; do wait_deployment $deployment_name done diff --git a/vagrant/tests/integration_cFW.sh b/vagrant/tests/integration_cFW.sh index 0077c73d..92c280b9 100755 --- a/vagrant/tests/integration_cFW.sh +++ b/vagrant/tests/integration_cFW.sh @@ -21,8 +21,8 @@ csar_id=4f726e2a-b74a-11e8-ad7c-525400feed2 populate_CSAR_containers_vFW $csar_id pushd ${CSAR_DIR}/${csar_id} -for network in unprotected-private-net-cidr-network protected-private-net-cidr-network onap-private-net-cidr-network; do - kubectl apply -f $network.yaml +for resource in $unprotected_private_net $protected_private_net $onap_private_net; do + kubectl apply -f $resource.yaml done setup $packetgen_deployment_name $firewall_deployment_name $sink_deployment_name diff --git a/vagrant/tests/integration_vFW.sh b/vagrant/tests/integration_vFW.sh index e0f7075e..962f9f75 100755 --- a/vagrant/tests/integration_vFW.sh +++ b/vagrant/tests/integration_vFW.sh @@ -24,7 +24,7 @@ fi populate_CSAR_vms_vFW $csar_id pushd ${CSAR_DIR}/${csar_id} -for resource in unprotected-private-net-cidr-network protected-private-net-cidr-network onap-private-net-cidr-network; do +for resource in $unprotected_private_net $protected_private_net $onap_private_net; do kubectl apply -f $resource.yaml done setup $packetgen_deployment_name $firewall_deployment_name $sink_deployment_name diff --git a/vagrant/tests/integration_vcFW.sh b/vagrant/tests/integration_vcFW.sh index 4fadfa23..15cffcb8 100755 --- a/vagrant/tests/integration_vcFW.sh +++ b/vagrant/tests/integration_vcFW.sh @@ -18,13 +18,19 @@ source _functions.sh csar_id=aa443e7e-c8ba-11e8-8877-525400b164ff # Setup +install_ovn_deps if [[ ! -f $HOME/.ssh/id_rsa.pub ]]; then echo -e "\n\n\n" | ssh-keygen -t rsa -N "" fi populate_CSAR_vms_containers_vFW $csar_id pushd ${CSAR_DIR}/${csar_id} -for resource in unprotected-private-net-cidr-network protected-private-net-cidr-network onap-private-net-cidr-network sink-service sink-ingress; do +for net in $unprotected_private_net $protected_private_net $onap_private_net; do + cleanup_network $net.yaml + echo "Create OVN Network $net network" + init_network $net.yaml +done +for resource in onap-ovn4nfvk8s-network sink-service; do kubectl apply -f $resource.yaml done setup $packetgen_deployment_name $firewall_deployment_name $sink_deployment_name @@ -40,7 +46,10 @@ for deployment_name in $packetgen_deployment_name $firewall_deployment_name; do echo "=== Virtlet details ====" echo "$(kubectl plugin virt virsh dumpxml $vm | grep VIRTLET_)\n" done -popd # Teardown #teardown $packetgen_deployment_name $firewall_deployment_name $sink_deployment_name +#for net in $unprotected_private_net $protected_private_net $onap_private_net; do +# cleanup_network $net.yaml +#done +popd diff --git a/vagrant/tests/ovn4nfv.sh b/vagrant/tests/ovn4nfv.sh new file mode 100755 index 00000000..37fddfd8 --- /dev/null +++ b/vagrant/tests/ovn4nfv.sh @@ -0,0 +1,46 @@ +#!/bin/bash +############################################################################## +# 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 + +csar_id=a1c5b53e-d7ab-11e8-85b7-525400e8c29a + +# Setup +install_ovn_deps +populate_CSAR_ovn4nfv $csar_id + +pushd ${CSAR_DIR}/${csar_id} +for net in ovn-priv-net ovn-port-net; do + cleanup_network $net.yaml + echo "Create OVN Network $net network" + init_network $net.yaml +done +kubectl apply -f onap-ovn4nfvk8s-network.yaml +setup $ovn4nfv_deployment_name + +# Test +deployment_pod=$(kubectl get pods | grep $ovn4nfv_deployment_name | 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 $ovn4nfv_deployment_name +cleanup_network ovn-priv-net.yaml +cleanup_network ovn-port-net.yaml +popd diff --git a/vagrant/tests/plugin.sh b/vagrant/tests/plugin.sh index 16d8d306..55be1686 100755 --- a/vagrant/tests/plugin.sh +++ b/vagrant/tests/plugin.sh @@ -88,7 +88,7 @@ echo "VNF details $vnf_details" echo "Deleting $vnf_id VNF Instance" curl -X DELETE "${base_url}${cloud_region_id}/${namespace}/${vnf_id}" -if [[ -n $(curl -s -X GET "${base_url}${cloud_region_id}/${namespace}/${vnf_id}") ]]; then +if [[ 200 -eq $(curl -o /dev/null -w %{http_code} -s -X GET "${base_url}${cloud_region_id}/${namespace}/${vnf_id}") ]]; then echo "VNF Instance not deleted" exit 1 fi |