From 0b59486c82a9786c85438f6352636b19b83e1021 Mon Sep 17 00:00:00 2001 From: Eric Multanen Date: Thu, 16 Apr 2020 10:44:06 -0700 Subject: Controller API support Add controller API support as baseline for adding gRPC framework. Issue-ID: MULTICLOUD-1019 Signed-off-by: Eric Multanen Change-Id: Ifd522a0eefbb8e54be45cc62003d3809283c9bfe --- src/orchestrator/api/api.go | 3 +- src/orchestrator/api/controllerhandler.go | 106 +++++++++++++++++++-- src/orchestrator/api/controllerhandler_test.go | 57 ++++++++--- .../pkg/infra/validation/validation.go | 23 +++++ src/orchestrator/pkg/module/controller.go | 75 ++++++++++++--- src/orchestrator/pkg/module/controller_test.go | 39 +++++--- src/orchestrator/pkg/module/module.go | 41 ++++++++ 7 files changed, 296 insertions(+), 48 deletions(-) (limited to 'src') diff --git a/src/orchestrator/api/api.go b/src/orchestrator/api/api.go index 6277d994..8b4b91a1 100644 --- a/src/orchestrator/api/api.go +++ b/src/orchestrator/api/api.go @@ -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 diff --git a/src/orchestrator/api/controllerhandler.go b/src/orchestrator/api/controllerhandler.go index 4f98c023..1dad2bf8 100644 --- a/src/orchestrator/api/controllerhandler.go +++ b/src/orchestrator/api/controllerhandler.go @@ -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) diff --git a/src/orchestrator/api/controllerhandler_test.go b/src/orchestrator/api/controllerhandler_test.go index 3c543cb8..a2f93ea7 100644 --- a/src/orchestrator/api/controllerhandler_test.go +++ b/src/orchestrator/api/controllerhandler_test.go @@ -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, + }, }, }, }, diff --git a/src/orchestrator/pkg/infra/validation/validation.go b/src/orchestrator/pkg/infra/validation/validation.go index 448ea5de..c43d29ec 100644 --- a/src/orchestrator/pkg/infra/validation/validation.go +++ b/src/orchestrator/pkg/infra/validation/validation.go @@ -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. diff --git a/src/orchestrator/pkg/module/controller.go b/src/orchestrator/pkg/module/controller.go index 35d6f892..976b8988 100644 --- a/src/orchestrator/pkg/module/controller.go +++ b/src/orchestrator/pkg/module/controller.go @@ -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, µserv) + err = db.DBconn.Unmarshal(value[0], µserv) 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, µserv) + 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;") } diff --git a/src/orchestrator/pkg/module/controller_test.go b/src/orchestrator/pkg/module/controller_test.go index 2e783c13..a13f43a5 100644 --- a/src/orchestrator/pkg/module/controller_test.go +++ b/src/orchestrator/pkg/module/controller_test.go @@ -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 }}"), }, }, }, diff --git a/src/orchestrator/pkg/module/module.go b/src/orchestrator/pkg/module/module.go index e05b8753..463a55b3 100644 --- a/src/orchestrator/pkg/module/module.go +++ b/src/orchestrator/pkg/module/module.go @@ -16,6 +16,11 @@ 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 +} -- cgit 1.2.3-korg