Controller API support 46/106146/5
authorEric Multanen <eric.w.multanen@intel.com>
Thu, 16 Apr 2020 17:44:06 +0000 (10:44 -0700)
committerEric Multanen <eric.w.multanen@intel.com>
Thu, 23 Apr 2020 06:30:24 +0000 (23:30 -0700)
Add controller API support as baseline for adding
gRPC framework.

Issue-ID: MULTICLOUD-1019
Signed-off-by: Eric Multanen <eric.w.multanen@intel.com>
Change-Id: Ifd522a0eefbb8e54be45cc62003d3809283c9bfe

src/orchestrator/api/api.go
src/orchestrator/api/controllerhandler.go
src/orchestrator/api/controllerhandler_test.go
src/orchestrator/pkg/infra/validation/validation.go
src/orchestrator/pkg/module/controller.go
src/orchestrator/pkg/module/controller_test.go
src/orchestrator/pkg/module/module.go

index 6277d99..8b4b91a 100644 (file)
@@ -111,7 +111,8 @@ func NewRouter(projectClient moduleLib.ProjectManager,
        router.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{composite-app-version}/composite-profiles/{composite-profile-name}/profiles/{app-profile}", appProfileHandler.deleteAppProfileHandler).Methods("DELETE")
 
        router.HandleFunc("/controllers", controlHandler.createHandler).Methods("POST")
-       router.HandleFunc("/controllers", controlHandler.createHandler).Methods("PUT")
+       router.HandleFunc("/controllers", controlHandler.getHandler).Methods("GET")
+       router.HandleFunc("/controllers/{controller-name}", controlHandler.putHandler).Methods("PUT")
        router.HandleFunc("/controllers/{controller-name}", controlHandler.getHandler).Methods("GET")
        router.HandleFunc("/controllers/{controller-name}", controlHandler.deleteHandler).Methods("DELETE")
        //setting routes for genericPlacementIntent
index 4f98c02..1dad2bf 100644 (file)
@@ -22,7 +22,9 @@ import (
        "net/http"
 
        "github.com/gorilla/mux"
+       "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/validation"
        moduleLib "github.com/onap/multicloud-k8s/src/orchestrator/pkg/module"
+       pkgerrors "github.com/pkg/errors"
 )
 
 // Used to store backend implementations objects
@@ -33,6 +35,43 @@ type controllerHandler struct {
        client moduleLib.ControllerManager
 }
 
+// Check for valid format of input parameters
+func validateControllerInputs(c moduleLib.Controller) error {
+       // validate metadata
+       err := moduleLib.IsValidMetadata(c.Metadata)
+       if err != nil {
+               return pkgerrors.Wrap(err, "Invalid controller metadata")
+       }
+
+       errs := validation.IsValidName(c.Spec.Host)
+       if len(errs) > 0 {
+               return pkgerrors.Errorf("Invalid host name for controller %v, errors: %v", c.Spec.Host, errs)
+       }
+
+       errs = validation.IsValidNumber(c.Spec.Port, 0, 65535)
+       if len(errs) > 0 {
+               return pkgerrors.Errorf("Invalid controller port [%v], errors: %v", c.Spec.Port, errs)
+       }
+
+       found := false
+       for _, val := range moduleLib.CONTROLLER_TYPES {
+               if c.Spec.Type == val {
+                       found = true
+                       break
+               }
+       }
+       if !found {
+               return pkgerrors.Errorf("Invalid controller type: %v", c.Spec.Type)
+       }
+
+       errs = validation.IsValidNumber(c.Spec.Priority, moduleLib.MinControllerPriority, moduleLib.MaxControllerPriority)
+       if len(errs) > 0 {
+               return pkgerrors.Errorf("Invalid controller priority = [%v], errors: %v", c.Spec.Priority, errs)
+       }
+
+       return nil
+}
+
 // Create handles creation of the controller entry in the database
 func (h controllerHandler) createHandler(w http.ResponseWriter, r *http.Request) {
        var m moduleLib.Controller
@@ -48,12 +87,12 @@ func (h controllerHandler) createHandler(w http.ResponseWriter, r *http.Request)
        }
 
        // Name is required.
-       if m.Name == "" {
+       if m.Metadata.Name == "" {
                http.Error(w, "Missing name in POST request", http.StatusBadRequest)
                return
        }
 
-       ret, err := h.client.CreateController(m)
+       ret, err := h.client.CreateController(m, false)
        if err != nil {
                http.Error(w, err.Error(), http.StatusInternalServerError)
                return
@@ -68,18 +107,73 @@ func (h controllerHandler) createHandler(w http.ResponseWriter, r *http.Request)
        }
 }
 
-// Get handles GET operations on a particular controller Name
-// Returns a controller
-func (h controllerHandler) getHandler(w http.ResponseWriter, r *http.Request) {
+// Put handles creation or update of the controller entry in the database
+func (h controllerHandler) putHandler(w http.ResponseWriter, r *http.Request) {
+       var m moduleLib.Controller
        vars := mux.Vars(r)
        name := vars["controller-name"]
 
-       ret, err := h.client.GetController(name)
+       err := json.NewDecoder(r.Body).Decode(&m)
+       switch {
+       case err == io.EOF:
+               http.Error(w, "Empty body", http.StatusBadRequest)
+               return
+       case err != nil:
+               http.Error(w, err.Error(), http.StatusUnprocessableEntity)
+               return
+       }
+
+       // Name is required.
+       if m.Metadata.Name == "" {
+               http.Error(w, "Missing name in POST request", http.StatusBadRequest)
+               return
+       }
+
+       // name in URL should match name in body
+       if m.Metadata.Name != name {
+               http.Error(w, "Mismatched name in PUT request", http.StatusBadRequest)
+               return
+       }
+
+       ret, err := h.client.CreateController(m, true)
        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
+       }
+}
+
+// Get handles GET operations on a particular controller Name
+// Returns a controller
+func (h controllerHandler) getHandler(w http.ResponseWriter, r *http.Request) {
+       vars := mux.Vars(r)
+       name := vars["controller-name"]
+       var ret interface{}
+       var err error
+
+       // handle the get all controllers case
+       if len(name) == 0 {
+               ret, err = h.client.GetControllers()
+               if err != nil {
+                       http.Error(w, err.Error(), http.StatusInternalServerError)
+                       return
+               }
+       } else {
+
+               ret, err = h.client.GetController(name)
+               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)
index 3c543cb..a2f93ea 100644 (file)
@@ -40,7 +40,7 @@ type mockControllerManager struct {
        Err   error
 }
 
-func (m *mockControllerManager) CreateController(inp moduleLib.Controller) (moduleLib.Controller, error) {
+func (m *mockControllerManager) CreateController(inp moduleLib.Controller, mayExist bool) (moduleLib.Controller, error) {
        if m.Err != nil {
                return moduleLib.Controller{}, m.Err
        }
@@ -56,6 +56,14 @@ func (m *mockControllerManager) GetController(name string) (moduleLib.Controller
        return m.Items[0], nil
 }
 
+func (m *mockControllerManager) GetControllers() ([]moduleLib.Controller, error) {
+       if m.Err != nil {
+               return []moduleLib.Controller{}, m.Err
+       }
+
+       return m.Items, nil
+}
+
 func (m *mockControllerManager) DeleteController(name string) error {
        return m.Err
 }
@@ -77,22 +85,33 @@ func TestControllerCreateHandler(t *testing.T) {
                        label:        "Create Controller",
                        expectedCode: http.StatusCreated,
                        reader: bytes.NewBuffer([]byte(`{
-                               "name":"testController",
+                "metadata": {
+                               "name":"testController"
+                               },
+                               "spec": {
                                "ip-address":"10.188.234.1",
-                               "port":8080
+                               "port":8080 }
                                }`)),
                        expected: moduleLib.Controller{
-                               Name: "testController",
-                               Host: "10.188.234.1",
-                               Port: 8080,
+                               Metadata: moduleLib.Metadata{
+                                       Name: "testController",
+                               },
+                               Spec: moduleLib.ControllerSpec{
+                                       Host: "10.188.234.1",
+                                       Port: 8080,
+                               },
                        },
                        controllerClient: &mockControllerManager{
                                //Items that will be returned by the mocked Client
                                Items: []moduleLib.Controller{
                                        {
-                                               Name: "testController",
-                                               Host: "10.188.234.1",
-                                               Port: 8080,
+                                               Metadata: moduleLib.Metadata{
+                                                       Name: "testController",
+                                               },
+                                               Spec: moduleLib.ControllerSpec{
+                                                       Host: "10.188.234.1",
+                                                       Port: 8080,
+                                               },
                                        },
                                },
                        },
@@ -144,17 +163,25 @@ func TestControllerGetHandler(t *testing.T) {
                        label:        "Get Controller",
                        expectedCode: http.StatusOK,
                        expected: moduleLib.Controller{
-                               Name: "testController",
-                               Host: "10.188.234.1",
-                               Port: 8080,
+                               Metadata: moduleLib.Metadata{
+                                       Name: "testController",
+                               },
+                               Spec: moduleLib.ControllerSpec{
+                                       Host: "10.188.234.1",
+                                       Port: 8080,
+                               },
                        },
                        name: "testController",
                        controllerClient: &mockControllerManager{
                                Items: []moduleLib.Controller{
                                        {
-                                               Name: "testController",
-                                               Host: "10.188.234.1",
-                                               Port: 8080,
+                                               Metadata: moduleLib.Metadata{
+                                                       Name: "testController",
+                                               },
+                                               Spec: moduleLib.ControllerSpec{
+                                                       Host: "10.188.234.1",
+                                                       Port: 8080,
+                                               },
                                        },
                                },
                        },
index 448ea5d..c43d29e 100644 (file)
@@ -23,6 +23,7 @@ import (
        "io"
        "net"
        "regexp"
+       "strconv"
        "strings"
 
        pkgerrors "github.com/pkg/errors"
@@ -280,6 +281,28 @@ func IsValidNumber(value, min, max int) []string {
        return errs
 }
 
+func IsValidNumberStr(value string, min, max int) []string {
+       var errs []string
+
+       if min > max {
+               errs = append(errs, "invalid constraints")
+               return errs
+       }
+
+       n, err := strconv.Atoi(value)
+       if err != nil {
+               errs = append(errs, err.Error())
+               return errs
+       }
+       if n < min {
+               errs = append(errs, "value less than minimum")
+       }
+       if n > max {
+               errs = append(errs, "value greater than maximum")
+       }
+       return errs
+}
+
 /*
 IsValidParameterPresent method takes in a vars map and a array of string parameters
 that you expect to be present in the GET request.
index 35d6f89..976b898 100644 (file)
@@ -27,13 +27,24 @@ import (
 // Controller contains the parameters needed for Controllers
 // It implements the interface for managing the Controllers
 type Controller struct {
-       Name string `json:"name"`
-
-       Host string `json:"host"`
+       Metadata Metadata       `json:"metadata"`
+       Spec     ControllerSpec `json:"spec"`
+}
 
-       Port int64 `json:"port"`
+type ControllerSpec struct {
+       Host     string `json:"host"`
+       Port     int    `json:"port"`
+       Type     string `json:"type"`
+       Priority int    `json:"priority"`
 }
 
+const MinControllerPriority = 1
+const MaxControllerPriority = 1000000
+const CONTROLLER_TYPE_ACTION string = "action"
+const CONTROLLER_TYPE_PLACEMENT string = "placement"
+
+var CONTROLLER_TYPES = [...]string{CONTROLLER_TYPE_ACTION, CONTROLLER_TYPE_PLACEMENT}
+
 // ControllerKey is the key structure that is used in the database
 type ControllerKey struct {
        ControllerName string `json:"controller-name"`
@@ -52,8 +63,9 @@ func (mk ControllerKey) String() string {
 
 // ControllerManager is an interface exposes the Controller functionality
 type ControllerManager interface {
-       CreateController(ms Controller) (Controller, error)
+       CreateController(ms Controller, mayExist bool) (Controller, error)
        GetController(name string) (Controller, error)
+       GetControllers() ([]Controller, error)
        DeleteController(name string) error
 }
 
@@ -74,20 +86,20 @@ func NewControllerClient() *ControllerClient {
 }
 
 // CreateController a new collection based on the Controller
-func (mc *ControllerClient) CreateController(m Controller) (Controller, error) {
+func (mc *ControllerClient) CreateController(m Controller, mayExist bool) (Controller, error) {
 
        //Construct the composite key to select the entry
        key := ControllerKey{
-               ControllerName: m.Name,
+               ControllerName: m.Metadata.Name,
        }
 
        //Check if this Controller already exists
-       _, err := mc.GetController(m.Name)
-       if err == nil {
+       _, err := mc.GetController(m.Metadata.Name)
+       if err == nil && !mayExist {
                return Controller{}, pkgerrors.New("Controller already exists")
        }
 
-       err = db.DBconn.Create(mc.collectionName, key, mc.tagMeta, m)
+       err = db.DBconn.Insert(mc.collectionName, key, nil, mc.tagMeta, m)
        if err != nil {
                return Controller{}, pkgerrors.Wrap(err, "Creating DB Entry")
        }
@@ -102,15 +114,14 @@ func (mc *ControllerClient) GetController(name string) (Controller, error) {
        key := ControllerKey{
                ControllerName: name,
        }
-       value, err := db.DBconn.Read(mc.collectionName, key, mc.tagMeta)
+       value, err := db.DBconn.Find(mc.collectionName, key, mc.tagMeta)
        if err != nil {
                return Controller{}, pkgerrors.Wrap(err, "Get Controller")
        }
 
-       //value is a byte array
        if value != nil {
                microserv := Controller{}
-               err = db.DBconn.Unmarshal(value, &microserv)
+               err = db.DBconn.Unmarshal(value[0], &microserv)
                if err != nil {
                        return Controller{}, pkgerrors.Wrap(err, "Unmarshaling Value")
                }
@@ -120,6 +131,42 @@ func (mc *ControllerClient) GetController(name string) (Controller, error) {
        return Controller{}, pkgerrors.New("Error getting Controller")
 }
 
+// GetControllers returns all the  Controllers that are registered
+func (mc *ControllerClient) GetControllers() ([]Controller, error) {
+
+       //Construct the composite key to select the entry
+       key := ControllerKey{
+               ControllerName: "",
+       }
+
+       var resp []Controller
+       values, err := db.DBconn.Find(mc.collectionName, key, mc.tagMeta)
+       if err != nil {
+               return []Controller{}, pkgerrors.Wrap(err, "Get Controller")
+       }
+
+       for _, value := range values {
+               microserv := Controller{}
+               err = db.DBconn.Unmarshal(value, &microserv)
+               if err != nil {
+                       return []Controller{}, pkgerrors.Wrap(err, "Unmarshaling Value")
+               }
+
+               // run healthcheck
+               /*
+                       err = mc.HealthCheck(microserv.Name)
+                       if err != nil {
+                               log.Warn("HealthCheck Failed", log.Fields{
+                                       "Controller": microserv.Name,
+                               })
+                       }
+               */
+               resp = append(resp, microserv)
+       }
+
+       return resp, nil
+}
+
 // DeleteController the  Controller from database
 func (mc *ControllerClient) DeleteController(name string) error {
 
@@ -127,7 +174,7 @@ func (mc *ControllerClient) DeleteController(name string) error {
        key := ControllerKey{
                ControllerName: name,
        }
-       err := db.DBconn.Delete(name, key, mc.tagMeta)
+       err := db.DBconn.Remove(mc.collectionName, key)
        if err != nil {
                return pkgerrors.Wrap(err, "Delete Controller Entry;")
        }
index 2e783c1..a13f43a 100644 (file)
@@ -37,14 +37,22 @@ func TestCreateController(t *testing.T) {
                {
                        label: "Create Controller",
                        inp: Controller{
-                               Name: "testController",
-                               Host: "132.156.0.10",
-                               Port: 8080,
+                               Metadata: Metadata{
+                                       Name: "testController",
+                               },
+                               Spec: ControllerSpec{
+                                       Host: "132.156.0.10",
+                                       Port: 8080,
+                               },
                        },
                        expected: Controller{
-                               Name: "testController",
-                               Host: "132.156.0.10",
-                               Port: 8080,
+                               Metadata: Metadata{
+                                       Name: "testController",
+                               },
+                               Spec: ControllerSpec{
+                                       Host: "132.156.0.10",
+                                       Port: 8080,
+                               },
                        },
                        expectedError: "",
                        mockdb:        &db.MockDB{},
@@ -62,7 +70,7 @@ func TestCreateController(t *testing.T) {
                t.Run(testCase.label, func(t *testing.T) {
                        db.DBconn = testCase.mockdb
                        impl := NewControllerClient()
-                       got, err := impl.CreateController(testCase.inp)
+                       got, err := impl.CreateController(testCase.inp, false)
                        if err != nil {
                                if testCase.expectedError == "" {
                                        t.Fatalf("Create returned an unexpected error %s", err)
@@ -94,18 +102,25 @@ func TestGetController(t *testing.T) {
                        label: "Get Controller",
                        name:  "testController",
                        expected: Controller{
-                               Name: "testController",
-                               Host: "132.156.0.10",
-                               Port: 8080,
+                               Metadata: Metadata{
+                                       Name: "testController",
+                               },
+                               Spec: ControllerSpec{
+                                       Host: "132.156.0.10",
+                                       Port: 8080,
+                               },
                        },
                        expectedError: "",
                        mockdb: &db.MockDB{
                                Items: map[string]map[string][]byte{
                                        ControllerKey{ControllerName: "testController"}.String(): {
                                                "controllermetadata": []byte(
-                                                       "{\"name\":\"testController\"," +
+                                                       "{\"metadata\":{" +
+                                                               "\"name\":\"testController\"" +
+                                                               "}," +
+                                                               "\"spec\":{" +
                                                                "\"host\":\"132.156.0.10\"," +
-                                                               "\"port\":8080}"),
+                                                               "\"port\": 8080 }}"),
                                        },
                                },
                        },
index e05b875..463a55b 100644 (file)
 
 package module
 
+import (
+       "github.com/onap/multicloud-k8s/src/orchestrator/pkg/infra/validation"
+       pkgerrors "github.com/pkg/errors"
+)
+
 // Client for using the services in the orchestrator
 type Client struct {
        Project                *ProjectClient
@@ -49,3 +54,39 @@ func NewClient() *Client {
        c.Instantiation = NewInstantiationClient()
        return c
 }
+
+// It implements the interface for managing the ClusterProviders
+const MAX_DESCRIPTION_LEN int = 1024
+const MAX_USERDATA_LEN int = 4096
+
+type Metadata struct {
+       Name        string `json:"name" yaml:"name"`
+       Description string `json:"description" yaml:"-"`
+       UserData1   string `json:"userData1" yaml:"-"`
+       UserData2   string `json:"userData2" yaml:"-"`
+}
+
+// Check for valid format Metadata
+func IsValidMetadata(metadata Metadata) error {
+       errs := validation.IsValidName(metadata.Name)
+       if len(errs) > 0 {
+               return pkgerrors.Errorf("Invalid Metadata name=[%v], errors: %v", metadata.Name, errs)
+       }
+
+       errs = validation.IsValidString(metadata.Description, 0, MAX_DESCRIPTION_LEN, validation.VALID_ANY_STR)
+       if len(errs) > 0 {
+               return pkgerrors.Errorf("Invalid Metadata description=[%v], errors: %v", metadata.Description, errs)
+       }
+
+       errs = validation.IsValidString(metadata.UserData1, 0, MAX_DESCRIPTION_LEN, validation.VALID_ANY_STR)
+       if len(errs) > 0 {
+               return pkgerrors.Errorf("Invalid Metadata description=[%v], errors: %v", metadata.UserData1, errs)
+       }
+
+       errs = validation.IsValidString(metadata.UserData2, 0, MAX_DESCRIPTION_LEN, validation.VALID_ANY_STR)
+       if len(errs) > 0 {
+               return pkgerrors.Errorf("Invalid Metadata description=[%v], errors: %v", metadata.UserData2, errs)
+       }
+
+       return nil
+}