From 4af0037a676a293a25c48fa64da790cd1f586c53 Mon Sep 17 00:00:00 2001 From: crawter Date: Tue, 1 Jun 2021 10:50:18 +0300 Subject: [PATCH] multinode/console: operators controller added Change-Id: I170371baec3c6996bd2af3c332620bd6fee3ed63 --- multinode/console/controllers/operators.go | 95 ++++++++++++++++++++++ multinode/console/server/server.go | 25 ++++-- multinode/nodes/nodes.go | 2 + multinode/operators/service.go | 4 +- multinode/peer.go | 15 ++++ 5 files changed, 130 insertions(+), 11 deletions(-) create mode 100644 multinode/console/controllers/operators.go diff --git a/multinode/console/controllers/operators.go b/multinode/console/controllers/operators.go new file mode 100644 index 000000000..b5635d0d7 --- /dev/null +++ b/multinode/console/controllers/operators.go @@ -0,0 +1,95 @@ +// Copyright (C) 2021 Storj Labs, Inc. +// See LICENSE for copying information. + +package controllers + +import ( + "encoding/json" + "net/http" + "strconv" + + "github.com/zeebo/errs" + "go.uber.org/zap" + + "storj.io/storj/multinode/operators" +) + +var ( + // ErrOperators is an internal error type for operators web api controller. + ErrOperators = errs.Class("nodes web api controller") +) + +const ( + defaultLimit = 5 +) + +// Operators is a web api controller. +type Operators struct { + log *zap.Logger + service *operators.Service +} + +// NewOperators is a constructor for Operators. +func NewOperators(log *zap.Logger, service *operators.Service) *Operators { + return &Operators{ + log: log, + service: service, + } +} + +// ListPaginated handles retrieval of operators. +func (controller *Operators) ListPaginated(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + var err error + defer mon.Task()(&ctx)(&err) + + w.Header().Add("Content-Type", "application/json") + + limit := int64(defaultLimit) + if limitParam := r.URL.Query().Get("limit"); limitParam != "" { + limit, err = strconv.ParseInt(limitParam, 10, 64) + if err != nil { + controller.serveError(w, http.StatusBadRequest, ErrOperators.Wrap(err)) + } + } + + pageParam := r.URL.Query().Get("page") + if pageParam == "" { + controller.serveError(w, http.StatusBadRequest, ErrOperators.Wrap(errs.New("page is missing"))) + return + } + pageNumber, err := strconv.ParseInt(pageParam, 10, 64) + if err != nil { + controller.serveError(w, http.StatusBadRequest, ErrOperators.Wrap(err)) + return + } + + cursor := operators.Cursor{ + Limit: limit, + Page: pageNumber, + } + page, err := controller.service.ListPaginated(ctx, cursor) + if err != nil { + controller.log.Error("could not get operators page", zap.Error(ErrOperators.Wrap(err))) + controller.serveError(w, http.StatusInternalServerError, ErrOperators.Wrap(err)) + return + } + + if err = json.NewEncoder(w).Encode(page); err != nil { + controller.log.Error("failed to write json response", zap.Error(ErrOperators.Wrap(err))) + return + } +} + +// serveError set http statuses and send json error. +func (controller *Operators) serveError(w http.ResponseWriter, status int, err error) { + w.WriteHeader(status) + var response struct { + Error string `json:"error"` + } + response.Error = err.Error() + err = json.NewEncoder(w).Encode(response) + if err != nil { + controller.log.Error("failed to write json error response", zap.Error(ErrOperators.Wrap(err))) + } +} diff --git a/multinode/console/server/server.go b/multinode/console/server/server.go index 40ebb9c85..d302793b3 100644 --- a/multinode/console/server/server.go +++ b/multinode/console/server/server.go @@ -17,6 +17,7 @@ import ( "storj.io/storj/multinode/console/controllers" "storj.io/storj/multinode/nodes" + "storj.io/storj/multinode/operators" "storj.io/storj/multinode/payouts" ) @@ -37,9 +38,10 @@ type Config struct { type Server struct { log *zap.Logger - config Config - nodes *nodes.Service - payouts *payouts.Service + config Config + nodes *nodes.Service + payouts *payouts.Service + operators *operators.Service listener net.Listener http http.Server @@ -48,13 +50,14 @@ type Server struct { } // NewServer returns new instance of Multinode Dashboard http server. -func NewServer(log *zap.Logger, config Config, nodes *nodes.Service, payouts *payouts.Service, listener net.Listener) (*Server, error) { +func NewServer(log *zap.Logger, config Config, nodes *nodes.Service, payouts *payouts.Service, operators *operators.Service, listener net.Listener) (*Server, error) { server := Server{ - log: log, - config: config, - nodes: nodes, - listener: listener, - payouts: payouts, + log: log, + config: config, + nodes: nodes, + operators: operators, + payouts: payouts, + listener: listener, } router := mux.NewRouter() @@ -73,6 +76,10 @@ func NewServer(log *zap.Logger, config Config, nodes *nodes.Service, payouts *pa nodesRouter.HandleFunc("/{id}", nodesController.UpdateName).Methods(http.MethodPatch) nodesRouter.HandleFunc("/{id}", nodesController.Delete).Methods(http.MethodDelete) + operatorsController := controllers.NewOperators(server.log, server.operators) + operatorsRouter := apiRouter.PathPrefix("/operators").Subrouter() + operatorsRouter.HandleFunc("", operatorsController.ListPaginated).Methods(http.MethodGet) + payoutsController := controllers.NewPayouts(server.log, server.payouts) payoutsRouter := apiRouter.PathPrefix("/payouts").Subrouter() payoutsRouter.HandleFunc("/summaries", payoutsController.Summary).Methods(http.MethodGet) diff --git a/multinode/nodes/nodes.go b/multinode/nodes/nodes.go index 49d632b08..d42e5da28 100644 --- a/multinode/nodes/nodes.go +++ b/multinode/nodes/nodes.go @@ -21,6 +21,8 @@ type DB interface { // List returns all connected nodes. List(ctx context.Context) ([]Node, error) // ListPaged returns paginated nodes list. + // TODO: rename to ListPaginated, because pagination is to divide up copy into pages, + // because paging doesn't necessarily mean pagination in computing. ListPaged(ctx context.Context, cursor Cursor) (page Page, err error) // Add creates new node in NodesDB. // TODO: pass Node entity instead of set of a parameters. diff --git a/multinode/operators/service.go b/multinode/operators/service.go index 7b8bbe6e7..857bf2bb1 100644 --- a/multinode/operators/service.go +++ b/multinode/operators/service.go @@ -43,8 +43,8 @@ func NewService(log *zap.Logger, dialer rpc.Dialer, nodes nodes.DB) *Service { } } -// ListOperatorsPaginated returns paginated list of operators. -func (service *Service) ListOperatorsPaginated(ctx context.Context, cursor Cursor) (_ Page, err error) { +// ListPaginated returns paginated list of operators. +func (service *Service) ListPaginated(ctx context.Context, cursor Cursor) (_ Page, err error) { defer mon.Task()(&ctx)(&err) if cursor.Limit > MaxOperatorsOnPage { cursor.Limit = MaxOperatorsOnPage diff --git a/multinode/peer.go b/multinode/peer.go index 0bcdc5ee2..4ff24400a 100644 --- a/multinode/peer.go +++ b/multinode/peer.go @@ -17,6 +17,7 @@ import ( "storj.io/private/debug" "storj.io/storj/multinode/console/server" "storj.io/storj/multinode/nodes" + "storj.io/storj/multinode/operators" "storj.io/storj/multinode/payouts" "storj.io/storj/private/lifecycle" ) @@ -62,6 +63,11 @@ type Peer struct { Service *nodes.Service } + // exposes operators related logic. + Operators struct { + Service *operators.Service + } + // contains logic of payouts domain. Payouts struct { Service *payouts.Service @@ -105,6 +111,14 @@ func New(log *zap.Logger, full *identity.FullIdentity, config Config, db DB) (_ ) } + { // operators setup + peer.Operators.Service = operators.NewService( + peer.Log.Named("operators:service"), + peer.Dialer, + peer.DB.Nodes(), + ) + } + { // payouts setup peer.Payouts.Service = payouts.NewService( peer.Log.Named("payouts:service"), @@ -124,6 +138,7 @@ func New(log *zap.Logger, full *identity.FullIdentity, config Config, db DB) (_ config.Console, peer.Nodes.Service, peer.Payouts.Service, + peer.Operators.Service, peer.Console.Listener, ) if err != nil {