summaryrefslogtreecommitdiffstats
path: root/src/k8splugin/api
diff options
context:
space:
mode:
Diffstat (limited to 'src/k8splugin/api')
-rw-r--r--src/k8splugin/api/api.go10
-rw-r--r--src/k8splugin/api/defhandler.go1
-rw-r--r--src/k8splugin/api/profilehandler.go161
-rw-r--r--src/k8splugin/api/profilehandler_test.go422
4 files changed, 593 insertions, 1 deletions
diff --git a/src/k8splugin/api/api.go b/src/k8splugin/api/api.go
index 06f5009f..593e2b0b 100644
--- a/src/k8splugin/api/api.go
+++ b/src/k8splugin/api/api.go
@@ -106,6 +106,7 @@ func NewRouter(kubeconfig string) *mux.Router {
vnfInstanceHandler.HandleFunc("/{cloudRegionID}/{namespace}/{externalVNFID}", DeleteHandler).Methods("DELETE")
vnfInstanceHandler.HandleFunc("/{cloudRegionID}/{namespace}/{externalVNFID}", GetHandler).Methods("GET")
+ //rbd is resource bundle definition
resRouter := router.PathPrefix("/v1/rb").Subrouter()
rbdef := rbDefinitionHandler{client: rb.NewDefinitionClient()}
resRouter.HandleFunc("/definition", rbdef.createHandler).Methods("POST")
@@ -114,6 +115,15 @@ func NewRouter(kubeconfig string) *mux.Router {
resRouter.HandleFunc("/definition/{rbdID}", rbdef.getHandler).Methods("GET")
resRouter.HandleFunc("/definition/{rbdID}", rbdef.deleteHandler).Methods("DELETE")
+ //rbp is resource bundle profile
+ rbprofile := rbProfileHandler{client: rb.NewProfileClient()}
+ resRouter.HandleFunc("/profile", rbprofile.createHandler).Methods("POST")
+ resRouter.HandleFunc("/profile/{rbpID}/content", rbprofile.uploadHandler).Methods("POST")
+ resRouter.HandleFunc("/profile/help", rbprofile.helpHandler).Methods("GET")
+ resRouter.HandleFunc("/profile", rbprofile.listHandler).Methods("GET")
+ resRouter.HandleFunc("/profile/{rbpID}", rbprofile.getHandler).Methods("GET")
+ resRouter.HandleFunc("/profile/{rbpID}", rbprofile.deleteHandler).Methods("DELETE")
+
// (TODO): Fix update method
// vnfInstanceHandler.HandleFunc("/{vnfInstanceId}", UpdateHandler).Methods("PUT")
diff --git a/src/k8splugin/api/defhandler.go b/src/k8splugin/api/defhandler.go
index 222baaee..31b0f38f 100644
--- a/src/k8splugin/api/defhandler.go
+++ b/src/k8splugin/api/defhandler.go
@@ -70,7 +70,6 @@ func (h rbDefinitionHandler) createHandler(w http.ResponseWriter, r *http.Reques
}
// uploadHandler handles upload of the bundle tar file into the database
-// Note: This will be implemented in a different patch
func (h rbDefinitionHandler) uploadHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
uuid := vars["rbdID"]
diff --git a/src/k8splugin/api/profilehandler.go b/src/k8splugin/api/profilehandler.go
new file mode 100644
index 00000000..1090efe5
--- /dev/null
+++ b/src/k8splugin/api/profilehandler.go
@@ -0,0 +1,161 @@
+/*
+ * 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 api
+
+import (
+ "encoding/json"
+ "io/ioutil"
+ "k8splugin/rb"
+ "net/http"
+
+ "github.com/gorilla/mux"
+)
+
+// Used to store backend implementations objects
+// Also simplifies mocking for unit testing purposes
+type rbProfileHandler struct {
+ // Interface that implements bundle Definition operations
+ // We will set this variable with a mock interface for testing
+ client rb.ProfileManager
+}
+
+// createHandler handles creation of the definition entry in the database
+func (h rbProfileHandler) createHandler(w http.ResponseWriter, r *http.Request) {
+ var v rb.Profile
+
+ if r.Body == nil {
+ http.Error(w, "Empty body", http.StatusBadRequest)
+ return
+ }
+
+ err := json.NewDecoder(r.Body).Decode(&v)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusUnprocessableEntity)
+ return
+ }
+
+ // Name is required.
+ if v.Name == "" {
+ http.Error(w, "Missing name in POST request", http.StatusBadRequest)
+ return
+ }
+
+ // Definition ID is required
+ if v.RBDID == "" {
+ http.Error(w, "Missing Resource Bundle Definition ID in POST request", http.StatusBadRequest)
+ return
+ }
+
+ ret, err := h.client.Create(v)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusCreated)
+ err = json.NewEncoder(w).Encode(ret)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+// uploadHandler handles upload of the bundle tar file into the database
+func (h rbProfileHandler) uploadHandler(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ uuid := vars["rbpID"]
+
+ 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)
+}
+
+// listHandler handles GET (list) operations on the endpoint
+// Returns a list of rb.Definitions
+func (h rbProfileHandler) listHandler(w http.ResponseWriter, r *http.Request) {
+ ret, err := h.client.List()
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ err = json.NewEncoder(w).Encode(ret)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+// helpHandler handles GET (list) operations on the endpoint
+// Returns a list of rb.Definitions
+func (h rbProfileHandler) helpHandler(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "text/html")
+ w.WriteHeader(http.StatusOK)
+}
+
+// getHandler handles GET operations on a particular ids
+// Returns a rb.Definition
+func (h rbProfileHandler) getHandler(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ id := vars["rbpID"]
+
+ ret, err := h.client.Get(id)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ err = json.NewEncoder(w).Encode(ret)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
+// deleteHandler handles DELETE operations on a particular bundle definition id
+func (h rbProfileHandler) deleteHandler(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ id := vars["rbpID"]
+
+ err := h.client.Delete(id)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.WriteHeader(http.StatusNoContent)
+}
diff --git a/src/k8splugin/api/profilehandler_test.go b/src/k8splugin/api/profilehandler_test.go
new file mode 100644
index 00000000..87725882
--- /dev/null
+++ b/src/k8splugin/api/profilehandler_test.go
@@ -0,0 +1,422 @@
+/*
+ * 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 api
+
+import (
+ "bytes"
+ "encoding/json"
+ "io"
+ "k8splugin/rb"
+ "net/http"
+ "net/http/httptest"
+ "reflect"
+ "sort"
+ "testing"
+
+ pkgerrors "github.com/pkg/errors"
+)
+
+//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 mockRBProfile struct {
+ rb.ProfileManager
+ // Items and err will be used to customize each test
+ // via a localized instantiation of mockRBProfile
+ Items []rb.Profile
+ Err error
+}
+
+func (m *mockRBProfile) Create(inp rb.Profile) (rb.Profile, error) {
+ if m.Err != nil {
+ return rb.Profile{}, m.Err
+ }
+
+ return m.Items[0], nil
+}
+
+func (m *mockRBProfile) List() ([]rb.Profile, error) {
+ if m.Err != nil {
+ return []rb.Profile{}, m.Err
+ }
+
+ return m.Items, nil
+}
+
+func (m *mockRBProfile) Get(id string) (rb.Profile, error) {
+ if m.Err != nil {
+ return rb.Profile{}, m.Err
+ }
+
+ return m.Items[0], nil
+}
+
+func (m *mockRBProfile) Delete(id string) error {
+ return m.Err
+}
+
+func (m *mockRBProfile) Upload(id string, inp []byte) error {
+ return m.Err
+}
+
+func TestRBProfileCreateHandler(t *testing.T) {
+ testCases := []struct {
+ label string
+ reader io.Reader
+ expected rb.Profile
+ expectedCode int
+ rbDefClient *mockRBProfile
+ }{
+ {
+ label: "Missing Body Failure",
+ expectedCode: http.StatusBadRequest,
+ rbDefClient: &mockRBProfile{},
+ },
+ {
+ label: "Create without UUID",
+ expectedCode: http.StatusCreated,
+ reader: bytes.NewBuffer([]byte(`{
+ "rbdid":"abcde123-e89b-8888-a456-986655447236",
+ "name":"testdomain",
+ "namespace":"default",
+ "kubernetesversion":"1.12.3"
+ }`)),
+ expected: rb.Profile{
+ UUID: "123e4567-e89b-12d3-a456-426655440000",
+ RBDID: "abcde123-e89b-8888-a456-986655447236",
+ Name: "testresourcebundle",
+ Namespace: "default",
+ KubernetesVersion: "1.12.3",
+ },
+ rbDefClient: &mockRBProfile{
+ //Items that will be returned by the mocked Client
+ Items: []rb.Profile{
+ {
+ UUID: "123e4567-e89b-12d3-a456-426655440000",
+ RBDID: "abcde123-e89b-8888-a456-986655447236",
+ Name: "testresourcebundle",
+ Namespace: "default",
+ KubernetesVersion: "1.12.3",
+ },
+ },
+ },
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ vh := rbProfileHandler{client: testCase.rbDefClient}
+ req, err := http.NewRequest("POST", "/v1/rb/profile", testCase.reader)
+
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ rr := httptest.NewRecorder()
+ hr := http.HandlerFunc(vh.createHandler)
+ hr.ServeHTTP(rr, req)
+
+ //Check returned code
+ if rr.Code != testCase.expectedCode {
+ t.Fatalf("Expected %d; Got: %d", testCase.expectedCode, rr.Code)
+ }
+
+ //Check returned body only if statusCreated
+ if rr.Code == http.StatusCreated {
+ got := rb.Profile{}
+ json.NewDecoder(rr.Body).Decode(&got)
+
+ if reflect.DeepEqual(testCase.expected, got) == false {
+ t.Errorf("createHandler returned unexpected body: got %v;"+
+ " expected %v", got, testCase.expected)
+ }
+ }
+ })
+ }
+}
+
+func TestRBProfileListHandler(t *testing.T) {
+
+ testCases := []struct {
+ label string
+ expected []rb.Profile
+ expectedCode int
+ rbDefClient *mockRBProfile
+ }{
+ {
+ label: "List Bundle Profiles",
+ expectedCode: http.StatusOK,
+ expected: []rb.Profile{
+ {
+ UUID: "123e4567-e89b-12d3-a456-426655440000",
+ RBDID: "abcde123-e89b-8888-a456-986655447236",
+ Name: "testresourcebundle",
+ Namespace: "default",
+ KubernetesVersion: "1.12.3",
+ },
+ {
+ UUID: "123e4567-e89b-12d3-a456-426655441111",
+ RBDID: "abcde123-e89b-8888-a456-986655441111",
+ Name: "testresourcebundle2",
+ Namespace: "default",
+ KubernetesVersion: "1.12.3",
+ },
+ },
+ rbDefClient: &mockRBProfile{
+ // list of Profiles that will be returned by the mockclient
+ Items: []rb.Profile{
+ {
+ UUID: "123e4567-e89b-12d3-a456-426655440000",
+ RBDID: "abcde123-e89b-8888-a456-986655447236",
+ Name: "testresourcebundle",
+ Namespace: "default",
+ KubernetesVersion: "1.12.3",
+ },
+ {
+ UUID: "123e4567-e89b-12d3-a456-426655441111",
+ RBDID: "abcde123-e89b-8888-a456-986655441111",
+ Name: "testresourcebundle2",
+ Namespace: "default",
+ KubernetesVersion: "1.12.3",
+ },
+ },
+ },
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ vh := rbProfileHandler{client: testCase.rbDefClient}
+ req, err := http.NewRequest("GET", "/v1/rb/profile", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ rr := httptest.NewRecorder()
+ hr := http.HandlerFunc(vh.listHandler)
+
+ hr.ServeHTTP(rr, req)
+ //Check returned code
+ if rr.Code != testCase.expectedCode {
+ t.Fatalf("Expected %d; Got: %d", testCase.expectedCode, rr.Code)
+ }
+
+ //Check returned body only if statusOK
+ if rr.Code == http.StatusOK {
+ got := []rb.Profile{}
+ json.NewDecoder(rr.Body).Decode(&got)
+
+ // Since the order of returned slice is not guaranteed
+ // Check both and return error if both don't match
+ sort.Slice(got, func(i, j int) bool {
+ return got[i].UUID < got[i].UUID
+ })
+ // Sort both as it is not expected that testCase.expected
+ // is sorted
+ sort.Slice(testCase.expected, func(i, j int) bool {
+ return testCase.expected[i].UUID < testCase.expected[i].UUID
+ })
+
+ if reflect.DeepEqual(testCase.expected, got) == false {
+ t.Errorf("listHandler returned unexpected body: got %v;"+
+ " expected %v", got, testCase.expected)
+ }
+ }
+ })
+ }
+}
+
+func TestRBProfileGetHandler(t *testing.T) {
+
+ testCases := []struct {
+ label string
+ expected rb.Profile
+ inpUUID string
+ expectedCode int
+ rbDefClient *mockRBProfile
+ }{
+ {
+ label: "Get Bundle Profile",
+ expectedCode: http.StatusOK,
+ expected: rb.Profile{
+ UUID: "123e4567-e89b-12d3-a456-426655441111",
+ RBDID: "abcde123-e89b-8888-a456-986655447236",
+ Name: "testresourcebundle2",
+ Namespace: "default",
+ KubernetesVersion: "1.12.3",
+ },
+ inpUUID: "123e4567-e89b-12d3-a456-426655441111",
+ rbDefClient: &mockRBProfile{
+ // list of Profiles that will be returned by the mockclient
+ Items: []rb.Profile{
+ {
+ UUID: "123e4567-e89b-12d3-a456-426655441111",
+ RBDID: "abcde123-e89b-8888-a456-986655447236",
+ Name: "testresourcebundle2",
+ Namespace: "default",
+ KubernetesVersion: "1.12.3",
+ },
+ },
+ },
+ },
+ {
+ label: "Get Non-Exiting Bundle Profile",
+ expectedCode: http.StatusInternalServerError,
+ inpUUID: "123e4567-e89b-12d3-a456-426655440000",
+ rbDefClient: &mockRBProfile{
+ // list of Profiles that will be returned by the mockclient
+ Items: []rb.Profile{},
+ Err: pkgerrors.New("Internal Error"),
+ },
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ vh := rbProfileHandler{client: testCase.rbDefClient}
+ req, err := http.NewRequest("GET", "/v1/rb/profile/"+testCase.inpUUID, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ rr := httptest.NewRecorder()
+ hr := http.HandlerFunc(vh.getHandler)
+
+ hr.ServeHTTP(rr, req)
+ //Check returned code
+ if rr.Code != testCase.expectedCode {
+ t.Fatalf("Expected %d; Got: %d", testCase.expectedCode, rr.Code)
+ }
+
+ //Check returned body only if statusOK
+ if rr.Code == http.StatusOK {
+ got := rb.Profile{}
+ json.NewDecoder(rr.Body).Decode(&got)
+
+ if reflect.DeepEqual(testCase.expected, got) == false {
+ t.Errorf("listHandler returned unexpected body: got %v;"+
+ " expected %v", got, testCase.expected)
+ }
+ }
+ })
+ }
+}
+
+func TestRBProfileDeleteHandler(t *testing.T) {
+
+ testCases := []struct {
+ label string
+ inpUUID string
+ expectedCode int
+ rbDefClient *mockRBProfile
+ }{
+ {
+ label: "Delete Bundle Profile",
+ expectedCode: http.StatusNoContent,
+ inpUUID: "123e4567-e89b-12d3-a456-426655441111",
+ rbDefClient: &mockRBProfile{},
+ },
+ {
+ label: "Delete Non-Exiting Bundle Profile",
+ expectedCode: http.StatusInternalServerError,
+ inpUUID: "123e4567-e89b-12d3-a456-426655440000",
+ rbDefClient: &mockRBProfile{
+ Err: pkgerrors.New("Internal Error"),
+ },
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ vh := rbProfileHandler{client: testCase.rbDefClient}
+ req, err := http.NewRequest("GET", "/v1/rb/profile/"+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 TestRBProfileUploadHandler(t *testing.T) {
+
+ testCases := []struct {
+ label string
+ inpUUID string
+ body io.Reader
+ expectedCode int
+ rbDefClient *mockRBProfile
+ }{
+ {
+ label: "Upload Bundle Profile 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: &mockRBProfile{},
+ },
+ {
+ label: "Upload Invalid Bundle Profile 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: &mockRBProfile{
+ Err: pkgerrors.New("Internal Error"),
+ },
+ },
+ {
+ label: "Upload Empty Body Content",
+ expectedCode: http.StatusBadRequest,
+ inpUUID: "123e4567-e89b-12d3-a456-426655440000",
+ rbDefClient: &mockRBProfile{},
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ vh := rbProfileHandler{client: testCase.rbDefClient}
+ req, err := http.NewRequest("POST",
+ "/v1/rb/profile/"+testCase.inpUUID+"/content", testCase.body)
+
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ rr := httptest.NewRecorder()
+ hr := http.HandlerFunc(vh.uploadHandler)
+
+ hr.ServeHTTP(rr, req)
+ //Check returned code
+ if rr.Code != testCase.expectedCode {
+ t.Fatalf("Expected %d; Got: %d", testCase.expectedCode, rr.Code)
+ }
+ })
+ }
+}