Add a list api for instances 02/91702/1
authorKiran Kamineni <kiran.k.kamineni@intel.com>
Thu, 18 Jul 2019 21:11:11 +0000 (14:11 -0700)
committerKiran Kamineni <kiran.k.kamineni@intel.com>
Thu, 18 Jul 2019 21:11:16 +0000 (14:11 -0700)
curl -X GET /v1/instance
returns all the instances created.
It returns abbreviated instances for improved readability.
For details on what resources were created
for each instance, use the ID with GET.

Issue-ID: MULTICLOUD-715
Change-Id: I05afe0fd2c254acbca4329289c81545f95c9fac5
Signed-off-by: Kiran Kamineni <kiran.k.kamineni@intel.com>
src/k8splugin/api/api.go
src/k8splugin/api/instancehandler.go
src/k8splugin/api/instancehandler_test.go
src/k8splugin/internal/app/instance.go

index 353972a..1bbe14a 100644 (file)
@@ -38,6 +38,7 @@ func NewRouter(defClient rb.DefinitionManager,
        instHandler := instanceHandler{client: instClient}
        instRouter := router.PathPrefix("/v1").Subrouter()
        instRouter.HandleFunc("/instance", instHandler.createHandler).Methods("POST")
+       instRouter.HandleFunc("/instance", instHandler.listHandler).Methods("GET")
        instRouter.HandleFunc("/instance/{instID}", instHandler.getHandler).Methods("GET")
        instRouter.HandleFunc("/instance/{instID}", instHandler.deleteHandler).Methods("DELETE")
        // (TODO): Fix update method
index 42f3b21..3ec055b 100644 (file)
@@ -106,6 +106,24 @@ func (i instanceHandler) getHandler(w http.ResponseWriter, r *http.Request) {
        }
 }
 
+// getHandler retrieves information about an instance via the ID
+func (i instanceHandler) listHandler(w http.ResponseWriter, r *http.Request) {
+
+       resp, err := i.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(resp)
+       if err != nil {
+               http.Error(w, err.Error(), http.StatusInternalServerError)
+               return
+       }
+}
+
 // deleteHandler method terminates an instance via the ID
 func (i instanceHandler) deleteHandler(w http.ResponseWriter, r *http.Request) {
        vars := mux.Vars(r)
index 35cef53..83fa3d2 100644 (file)
@@ -21,6 +21,7 @@ import (
        "net/http"
        "net/http/httptest"
        "reflect"
+       "sort"
        "testing"
 
        "github.com/onap/multicloud-k8s/src/k8splugin/internal/app"
@@ -38,8 +39,9 @@ type mockInstanceClient struct {
        app.InstanceManager
        // Items and err will be used to customize each test
        // via a localized instantiation of mockInstanceClient
-       items []app.InstanceResponse
-       err   error
+       items     []app.InstanceResponse
+       miniitems []app.InstanceMiniResponse
+       err       error
 }
 
 func (m *mockInstanceClient) Create(inp app.InstanceRequest) (app.InstanceResponse, error) {
@@ -58,6 +60,14 @@ func (m *mockInstanceClient) Get(id string) (app.InstanceResponse, error) {
        return m.items[0], nil
 }
 
+func (m *mockInstanceClient) List() ([]app.InstanceMiniResponse, error) {
+       if m.err != nil {
+               return []app.InstanceMiniResponse{}, m.err
+       }
+
+       return m.miniitems, nil
+}
+
 func (m *mockInstanceClient) Find(rbName string, ver string, profile string, labelKeys map[string]string) ([]app.InstanceResponse, error) {
        if m.err != nil {
                return nil, m.err
@@ -297,6 +307,110 @@ func TestInstanceGetHandler(t *testing.T) {
        }
 }
 
+func TestInstanceListHandler(t *testing.T) {
+       testCases := []struct {
+               label            string
+               input            string
+               expectedCode     int
+               expectedResponse []app.InstanceMiniResponse
+               instClient       *mockInstanceClient
+       }{
+               {
+                       label:        "Fail to List Instance",
+                       input:        "HaKpys8e",
+                       expectedCode: http.StatusInternalServerError,
+                       instClient: &mockInstanceClient{
+                               err: pkgerrors.New("Internal error"),
+                       },
+               },
+               {
+                       label:        "Succesful List Instances",
+                       expectedCode: http.StatusOK,
+                       expectedResponse: []app.InstanceMiniResponse{
+                               {
+                                       ID: "HaKpys8e",
+                                       Request: app.InstanceRequest{
+                                               RBName:      "test-rbdef",
+                                               RBVersion:   "v1",
+                                               ProfileName: "profile1",
+                                               CloudRegion: "region1",
+                                       },
+                                       Namespace: "testnamespace",
+                               },
+                               {
+                                       ID: "HaKpys8f",
+                                       Request: app.InstanceRequest{
+                                               RBName:      "test-rbdef-two",
+                                               RBVersion:   "versionsomething",
+                                               ProfileName: "profile3",
+                                               CloudRegion: "region1",
+                                       },
+                                       Namespace: "testnamespace-two",
+                               },
+                       },
+                       instClient: &mockInstanceClient{
+                               miniitems: []app.InstanceMiniResponse{
+                                       {
+                                               ID: "HaKpys8e",
+                                               Request: app.InstanceRequest{
+                                                       RBName:      "test-rbdef",
+                                                       RBVersion:   "v1",
+                                                       ProfileName: "profile1",
+                                                       CloudRegion: "region1",
+                                               },
+                                               Namespace: "testnamespace",
+                                       },
+                                       {
+                                               ID: "HaKpys8f",
+                                               Request: app.InstanceRequest{
+                                                       RBName:      "test-rbdef-two",
+                                                       RBVersion:   "versionsomething",
+                                                       ProfileName: "profile3",
+                                                       CloudRegion: "region1",
+                                               },
+                                               Namespace: "testnamespace-two",
+                                       },
+                               },
+                       },
+               },
+       }
+
+       for _, testCase := range testCases {
+               t.Run(testCase.label, func(t *testing.T) {
+                       request := httptest.NewRequest("GET", "/v1/instance", nil)
+                       resp := executeRequest(request, NewRouter(nil, nil, testCase.instClient, nil, nil, nil))
+
+                       if testCase.expectedCode != resp.StatusCode {
+                               t.Fatalf("Request method returned: %v and it was expected: %v",
+                                       resp.StatusCode, testCase.expectedCode)
+                       }
+                       if resp.StatusCode == http.StatusOK {
+                               var response []app.InstanceMiniResponse
+                               err := json.NewDecoder(resp.Body).Decode(&response)
+                               if err != nil {
+                                       t.Fatalf("Parsing the returned response got an error (%s)", err)
+                               }
+
+                               // Since the order of returned slice is not guaranteed
+                               // Sort them first and then do deepequal
+                               // Check both and return error if both don't match
+                               sort.Slice(response, func(i, j int) bool {
+                                       return response[i].ID < response[j].ID
+                               })
+
+                               sort.Slice(testCase.expectedResponse, func(i, j int) bool {
+                                       return testCase.expectedResponse[i].ID < testCase.expectedResponse[j].ID
+                               })
+
+                               if reflect.DeepEqual(testCase.expectedResponse, response) == false {
+                                       t.Fatalf("TestGetHandler returned:\n result=%v\n expected=%v",
+                                               &response, testCase.expectedResponse)
+                               }
+                       }
+               })
+       }
+}
+
 func TestDeleteHandler(t *testing.T) {
        testCases := []struct {
                label        string
index 5272d60..d28fe79 100644 (file)
@@ -19,6 +19,7 @@ package app
 import (
        "encoding/base64"
        "encoding/json"
+       "log"
        "math/rand"
 
        "github.com/onap/multicloud-k8s/src/k8splugin/internal/db"
@@ -46,10 +47,20 @@ type InstanceResponse struct {
        Resources []helm.KubernetesResource `json:"resources"`
 }
 
+// InstanceMiniResponse contains the response from instantiation
+// It does NOT include the created resources.
+// Use the regular GET to get the created resources for a particular instance
+type InstanceMiniResponse struct {
+       ID        string          `json:"id"`
+       Request   InstanceRequest `json:"request"`
+       Namespace string          `json:"namespace"`
+}
+
 // InstanceManager is an interface exposes the instantiation functionality
 type InstanceManager interface {
        Create(i InstanceRequest) (InstanceResponse, error)
        Get(id string) (InstanceResponse, error)
+       List() ([]InstanceMiniResponse, error)
        Find(rbName string, ver string, profile string, labelKeys map[string]string) ([]InstanceResponse, error)
        Delete(id string) error
 }
@@ -171,6 +182,37 @@ func (v *InstanceClient) Get(id string) (InstanceResponse, error) {
        return InstanceResponse{}, pkgerrors.New("Error getting Instance")
 }
 
+// List returns the instance for corresponding ID
+// Empty string returns all
+func (v *InstanceClient) List() ([]InstanceMiniResponse, error) {
+
+       dbres, err := db.DBconn.ReadAll(v.storeName, v.tagInst)
+       if err != nil || len(dbres) == 0 {
+               return []InstanceMiniResponse{}, pkgerrors.Wrap(err, "Listing Instances")
+       }
+
+       var results []InstanceMiniResponse
+       for key, value := range dbres {
+               //value is a byte array
+               if value != nil {
+                       resp := InstanceResponse{}
+                       err = db.DBconn.Unmarshal(value, &resp)
+                       if err != nil {
+                               log.Printf("[Instance] Error: %s Unmarshaling Instance: %s", err.Error(), key)
+                       }
+
+                       miniresp := InstanceMiniResponse{
+                               ID:        resp.ID,
+                               Request:   resp.Request,
+                               Namespace: resp.Namespace,
+                       }
+                       results = append(results, miniresp)
+               }
+       }
+
+       return results, nil
+}
+
 // Find returns the instances that match the given criteria
 // If version is empty, it will return all instances for a given rbName
 // If profile is empty, it will return all instances for a given rbName+version