apigen: new endpoint to get project's single bucket rollup

Added new endpoint to get project's single bucket usage rollup.
Extended generation code to handle service method args.

Change-Id: Ief768632a801c047c66e0617056fbd7b30427b33
This commit is contained in:
Vitalii Shpital 2022-02-17 15:49:07 +02:00
parent 9eaeebe115
commit 1245283637
6 changed files with 165 additions and 32 deletions

View File

@ -9,9 +9,11 @@ import (
"os"
"reflect"
"strings"
"time"
"github.com/zeebo/errs"
"storj.io/common/uuid"
"storj.io/storj/private/api"
)
@ -74,12 +76,15 @@ func (a *API) generateGo() ([]byte, error) {
p(`"context"`)
p(`"encoding/json"`)
p(`"net/http"`)
p(`"strconv"`)
p(`"time"`)
p("")
p(`"github.com/gorilla/mux"`)
p(`"github.com/zeebo/errs"`)
p(`"go.uber.org/zap"`)
p("")
p(`"storj.io/common/uuid"`)
p(`"storj.io/storj/private/api"`)
for _, group := range a.EndpointGroups {
@ -110,9 +115,13 @@ func (a *API) generateGo() ([]byte, error) {
p("")
p("type %sService interface {", group.Name)
for _, method := range group.Endpoints {
responseType := reflect.TypeOf(method.Response)
p("%s(context.Context) (%s, api.HTTPError)", method.MethodName, a.handleTypesPackage(responseType))
for _, e := range group.Endpoints {
responseType := reflect.TypeOf(e.Response)
var params string
for _, param := range e.Params {
params += param.Type.String() + ", "
}
p("%s(context.Context, "+params+") (%s, api.HTTPError)", e.MethodName, a.handleTypesPackage(responseType))
}
p("}")
p("")
@ -166,19 +175,47 @@ func (a *API) generateGo() ([]byte, error) {
p("")
}
methodFormat := "retVal, httpErr := h.service.%s(ctx"
args := []string{endpoint.MethodName}
// TODO to be implemented
// if !endpoint.NoAPIAuth {}
methodFormat += ")"
interfaceArgs := make([]interface{}, len(args))
for i, v := range args {
interfaceArgs[i] = v
for _, param := range endpoint.Params {
switch param.Type {
case reflect.TypeOf(uuid.UUID{}):
p("%s, err := uuid.FromString(r.URL.Query().Get(\"%s\"))", param.Name, param.Name)
p("if err != nil {")
p("api.ServeError(h.log, w, http.StatusBadRequest, err)")
p("return")
p("}")
p("")
continue
case reflect.TypeOf(time.Time{}):
p("%sStamp, err := strconv.ParseInt(r.URL.Query().Get(\"%s\"), 10, 64)", param.Name, param.Name)
p("if err != nil {")
p("api.ServeError(h.log, w, http.StatusBadRequest, err)")
p("return")
p("}")
p("")
p("%s := time.Unix(%sStamp, 0).UTC()", param.Name, param.Name)
p("")
continue
case reflect.TypeOf(""):
p("%s := r.URL.Query().Get(\"%s\")", param.Name, param.Name)
p("if %s == \"\" {", param.Name)
p("api.ServeError(h.log, w, http.StatusBadRequest, errs.New(\"parameter '%s' can't be empty\"))", param.Name)
p("return")
p("}")
p("")
continue
}
}
p(methodFormat, interfaceArgs...)
p("if err != nil {")
methodFormat := "retVal, httpErr := h.service.%s(ctx, "
for _, methodParam := range endpoint.Params {
methodFormat += methodParam.Name + ", "
}
methodFormat += ")"
p(methodFormat, endpoint.MethodName)
p("if httpErr.Err != nil {")
p("api.ServeError(h.log, w, httpErr.Status, httpErr.Err)")
p("return")
p("}")

View File

@ -3,7 +3,10 @@
package apigen
import "net/http"
import (
"net/http"
"reflect"
)
// Endpoint represents endpoint's configuration.
type Endpoint struct {
@ -14,7 +17,7 @@ type Endpoint struct {
NoAPIAuth bool
Request interface{}
Response interface{}
Params []string
Params []Param
}
// CookieAuth returns endpoint's cookie auth status.
@ -54,3 +57,17 @@ func (eg *EndpointGroup) addEndpoint(path, method string, endpoint *Endpoint) {
eg.Endpoints[pathMethod] = endpoint
}
// Param represents string interpretation of param's name and type.
type Param struct {
Name string
Type reflect.Type
}
// NewParam constructor which creates new Param entity by given name and type.
func NewParam(name string, instance interface{}) Param {
return Param{
Name: name,
Type: reflect.TypeOf(instance),
}
}

View File

@ -142,21 +142,21 @@ type BucketUsagePage struct {
// BucketUsageRollup is total bucket usage info
// for certain period.
type BucketUsageRollup struct {
ProjectID uuid.UUID
BucketName string
ProjectID uuid.UUID `json:"projectID"`
BucketName string `json:"bucketName"`
TotalStoredData float64
TotalStoredData float64 `json:"totalStoredData"`
TotalSegments float64
ObjectCount float64
MetadataSize float64
TotalSegments float64 `json:"totalSegments"`
ObjectCount float64 `json:"objectCount"`
MetadataSize float64 `json:"metadataSize"`
RepairEgress float64
GetEgress float64
AuditEgress float64
RepairEgress float64 `json:"repairEgress"`
GetEgress float64 `json:"getEgress"`
AuditEgress float64 `json:"auditEgress"`
Since time.Time
Before time.Time
Since time.Time `json:"since"`
Before time.Time `json:"before"`
}
// StoragenodeAccounting stores information about bandwidth and storage usage for storage nodes.

View File

@ -7,19 +7,24 @@ import (
"context"
"encoding/json"
"net/http"
"strconv"
"time"
"github.com/gorilla/mux"
"github.com/zeebo/errs"
"go.uber.org/zap"
"storj.io/common/uuid"
"storj.io/storj/private/api"
"storj.io/storj/satellite/accounting"
"storj.io/storj/satellite/console"
)
var ErrProjectsAPI = errs.Class("consoleapi projects api")
type ProjectManagementService interface {
GetUserProjects(context.Context) ([]console.Project, api.HTTPError)
GenGetUsersProjects(context.Context) ([]console.Project, api.HTTPError)
GenGetSingleBucketUsageRollup(context.Context, uuid.UUID, string, time.Time, time.Time) (*accounting.BucketUsageRollup, api.HTTPError)
}
// Handler is an api handler that exposes all projects related functionality.
@ -37,12 +42,13 @@ func NewProjectManagement(log *zap.Logger, service ProjectManagementService, rou
}
projectsRouter := router.PathPrefix("/api/v0/projects").Subrouter()
projectsRouter.HandleFunc("/", handler.handleGetUserProjects).Methods("GET")
projectsRouter.HandleFunc("/", handler.handleGenGetUsersProjects).Methods("GET")
projectsRouter.HandleFunc("/bucket-rollup", handler.handleGenGetSingleBucketUsageRollup).Methods("GET")
return handler
}
func (h *Handler) handleGetUserProjects(w http.ResponseWriter, r *http.Request) {
func (h *Handler) handleGenGetUsersProjects(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
defer mon.Task()(&ctx)(&err)
@ -55,14 +61,67 @@ func (h *Handler) handleGetUserProjects(w http.ResponseWriter, r *http.Request)
return
}
retVal, httpErr := h.service.GetUserProjects(ctx)
if err != nil {
retVal, httpErr := h.service.GenGetUsersProjects(ctx)
if httpErr.Err != nil {
api.ServeError(h.log, w, httpErr.Status, httpErr.Err)
return
}
err = json.NewEncoder(w).Encode(retVal)
if err != nil {
h.log.Debug("failed to write json GetUserProjects response", zap.Error(ErrProjectsAPI.Wrap(err)))
h.log.Debug("failed to write json GenGetUsersProjects response", zap.Error(ErrProjectsAPI.Wrap(err)))
}
}
func (h *Handler) handleGenGetSingleBucketUsageRollup(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var err error
defer mon.Task()(&ctx)(&err)
w.Header().Set("Content-Type", "application/json")
ctx, err = h.auth.IsAuthenticated(ctx, r)
if err != nil {
api.ServeError(h.log, w, http.StatusUnauthorized, err)
return
}
projectID, err := uuid.FromString(r.URL.Query().Get("projectID"))
if err != nil {
api.ServeError(h.log, w, http.StatusBadRequest, err)
return
}
bucket := r.URL.Query().Get("bucket")
if bucket == "" {
api.ServeError(h.log, w, http.StatusBadRequest, errs.New("parameter 'bucket' can't be empty"))
return
}
sinceStamp, err := strconv.ParseInt(r.URL.Query().Get("since"), 10, 64)
if err != nil {
api.ServeError(h.log, w, http.StatusBadRequest, err)
return
}
since := time.Unix(sinceStamp, 0).UTC()
beforeStamp, err := strconv.ParseInt(r.URL.Query().Get("before"), 10, 64)
if err != nil {
api.ServeError(h.log, w, http.StatusBadRequest, err)
return
}
before := time.Unix(beforeStamp, 0).UTC()
retVal, httpErr := h.service.GenGetSingleBucketUsageRollup(ctx, projectID, bucket, since, before)
if httpErr.Err != nil {
api.ServeError(h.log, w, httpErr.Status, httpErr.Err)
return
}
err = json.NewEncoder(w).Encode(retVal)
if err != nil {
h.log.Debug("failed to write json GenGetSingleBucketUsageRollup response", zap.Error(ErrProjectsAPI.Wrap(err)))
}
}

View File

@ -6,7 +6,11 @@ package main
//go:generate go run ./
import (
"time"
"storj.io/common/uuid"
"storj.io/storj/private/apigen"
"storj.io/storj/satellite/accounting"
"storj.io/storj/satellite/console"
)
@ -23,10 +27,22 @@ func main() {
g.Get("/", &apigen.Endpoint{
Name: "Get Projects",
Description: "Gets all projects user has",
MethodName: "GetUserProjects",
MethodName: "GenGetUsersProjects",
Response: []console.Project{},
})
g.Get("/bucket-rollup", &apigen.Endpoint{
Name: "Get Project's Bucket Usage",
Description: "Gets project's bucket usage by bucket ID",
MethodName: "GenGetSingleBucketUsageRollup",
Response: &accounting.BucketUsageRollup{},
Params: []apigen.Param{
apigen.NewParam("projectID", uuid.UUID{}),
apigen.NewParam("bucket", ""),
apigen.NewParam("since", time.Time{}),
apigen.NewParam("before", time.Time{}),
},
})
}
a.MustWrite("satellite/console/consoleweb/consoleapi/api.gen.go")

View File

@ -212,6 +212,10 @@ func NewServer(logger *zap.Logger, config Config, service *console.Service, mail
router := mux.NewRouter()
fs := http.FileServer(http.Dir(server.config.StaticDir))
if server.config.GeneratedAPIEnabled {
consoleapi.NewProjectManagement(logger, server.service, router, server.service)
}
router.HandleFunc("/registrationToken/", server.createRegistrationTokenHandler)
router.HandleFunc("/robots.txt", server.seoHandler)