From d426d4f00da416d65edc558f156f06682cdabcf2 Mon Sep 17 00:00:00 2001 From: crawter Date: Mon, 14 Jun 2021 15:03:21 +0300 Subject: [PATCH] multinode/storage: disk space of concrete node and total disk space implemented Change-Id: I50a96d3e9a77615e79b826a592c0a07c247e0520 --- multinode/console/controllers/storage.go | 59 ++++++++++++++++++ multinode/console/server/server.go | 2 + multinode/storage/service.go | 67 +++++++++++++++++++++ multinode/storage/storage.go | 20 ++++++ multinode/storage/storage_test.go | 77 ++++++++++++++++++++++++ 5 files changed, 225 insertions(+) diff --git a/multinode/console/controllers/storage.go b/multinode/console/controllers/storage.go index 01ee7a7b0..3115b50b6 100644 --- a/multinode/console/controllers/storage.go +++ b/multinode/console/controllers/storage.go @@ -244,6 +244,65 @@ func (storage *Storage) TotalUsageSatellite(w http.ResponseWriter, r *http.Reque } } +// TotalDiskSpace returns all info about all storagenodes disk space usage. +func (storage *Storage) TotalDiskSpace(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + var err error + defer mon.Task()(&ctx)(&err) + + w.Header().Add("Content-Type", "application/json") + + totalDiskSpace, err := storage.service.TotalDiskSpace(ctx) + if err != nil { + storage.log.Error("could not get total disk space", zap.Error(err)) + storage.serveError(w, http.StatusInternalServerError, ErrStorage.Wrap(err)) + return + } + + if err = json.NewEncoder(w).Encode(totalDiskSpace); err != nil { + storage.log.Error("failed to write json response", zap.Error(err)) + return + } +} + +// DiskSpace returns all info about concrete storagenode disk space usage. +func (storage *Storage) DiskSpace(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + var err error + defer mon.Task()(&ctx)(&err) + + w.Header().Add("Content-Type", "application/json") + segments := mux.Vars(r) + + nodeIDparam, ok := segments["nodeID"] + if !ok { + storage.serveError(w, http.StatusBadRequest, ErrStorage.New("node id is missing")) + return + } + nodeID, err := storj.NodeIDFromString(nodeIDparam) + if err != nil { + storage.serveError(w, http.StatusBadRequest, ErrStorage.Wrap(err)) + return + } + + diskSpace, err := storage.service.DiskSpace(ctx, nodeID) + if err != nil { + if nodes.ErrNoNode.Has(err) { + storage.serveError(w, http.StatusNotFound, ErrStorage.Wrap(err)) + return + } + + storage.log.Error("could not get disk space", zap.Error(err)) + storage.serveError(w, http.StatusInternalServerError, ErrStorage.Wrap(err)) + return + } + + if err = json.NewEncoder(w).Encode(diskSpace); err != nil { + storage.log.Error("failed to write json response", zap.Error(err)) + return + } +} + // serveError set http statuses and send json error. func (storage *Storage) serveError(w http.ResponseWriter, status int, err error) { w.WriteHeader(status) diff --git a/multinode/console/server/server.go b/multinode/console/server/server.go index 86fba9a4d..fc541bb5a 100644 --- a/multinode/console/server/server.go +++ b/multinode/console/server/server.go @@ -122,6 +122,8 @@ func NewServer(log *zap.Logger, listener net.Listener, config Config, services S storageRouter.HandleFunc("/usage/{nodeID}", storageController.Usage).Methods(http.MethodGet) storageRouter.HandleFunc("/satellites/{satelliteID}/usage", storageController.TotalUsageSatellite).Methods(http.MethodGet) storageRouter.HandleFunc("/satellites/{satelliteID}/usage/{nodeID}", storageController.UsageSatellite).Methods(http.MethodGet) + storageRouter.HandleFunc("/disk-space", storageController.TotalDiskSpace).Methods(http.MethodGet) + storageRouter.HandleFunc("/disk-space/{nodeID}", storageController.DiskSpace).Methods(http.MethodGet) if server.config.StaticDir != "" { router.PathPrefix("/static/").Handler(http.StripPrefix("/static", fs)) diff --git a/multinode/storage/service.go b/multinode/storage/service.go index f6ad8ddda..a8ebb2cec 100644 --- a/multinode/storage/service.go +++ b/multinode/storage/service.go @@ -125,6 +125,73 @@ func (service *Service) TotalUsageSatellite(ctx context.Context, satelliteID sto return cache.Sorted(), nil } +// TotalDiskSpace returns all info about all storagenodes disk space usage. +func (service *Service) TotalDiskSpace(ctx context.Context) (totalDiskSpace DiskSpace, err error) { + defer mon.Task()(&ctx)(&err) + + nodes, err := service.nodes.List(ctx) + if err != nil { + return DiskSpace{}, Error.Wrap(err) + } + + for _, node := range nodes { + diskSpace, err := service.dialDiskSpace(ctx, node) + if err != nil { + // TODO: how should we handle offline node? + continue + } + totalDiskSpace.Add(diskSpace) + } + + return totalDiskSpace, nil +} + +// DiskSpace returns all info about concrete storagenode disk space usage. +func (service *Service) DiskSpace(ctx context.Context, nodeID storj.NodeID) (_ DiskSpace, err error) { + defer mon.Task()(&ctx)(&err) + + node, err := service.nodes.Get(ctx, nodeID) + if err != nil { + return DiskSpace{}, Error.Wrap(err) + } + + return service.dialDiskSpace(ctx, node) +} + +// dialDiskSpace dials node and retrieves all info about concrete storagenode disk space usage. +func (service *Service) dialDiskSpace(ctx context.Context, node nodes.Node) (diskSpace DiskSpace, err error) { + conn, err := service.dialer.DialNodeURL(ctx, storj.NodeURL{ + ID: node.ID, + Address: node.PublicAddress, + }) + if err != nil { + return DiskSpace{}, Error.Wrap(err) + } + defer func() { + err = errs.Combine(err, conn.Close()) + }() + + storageClient := multinodepb.NewDRPCStorageClient(conn) + + diskSpaceResponse, err := storageClient.DiskSpace(ctx, &multinodepb.DiskSpaceRequest{ + Header: &multinodepb.RequestHeader{ + ApiKey: node.APISecret, + }, + }) + if err != nil { + return DiskSpace{}, Error.Wrap(err) + } + + return DiskSpace{ + Allocated: diskSpaceResponse.Allocated, + Used: diskSpaceResponse.UsedPieces, + Trash: diskSpaceResponse.UsedTrash, + Free: diskSpaceResponse.Free, + Available: diskSpaceResponse.Available, + Overused: diskSpaceResponse.Overused, + }, nil +} + // dialUsage dials node and retrieves it's storage usage for provided interval. func (service *Service) dialUsage(ctx context.Context, node nodes.Node, from, to time.Time) (_ []UsageStamp, err error) { defer mon.Task()(&ctx)(&err) diff --git a/multinode/storage/storage.go b/multinode/storage/storage.go index 3af125ec6..76c24f5d8 100644 --- a/multinode/storage/storage.go +++ b/multinode/storage/storage.go @@ -53,3 +53,23 @@ func (cache *UsageStampDailyCache) Sorted() []UsageStamp { return usage } + +// DiskSpace stores all info about storagenode disk space usage. +type DiskSpace struct { + Allocated int64 `json:"allocated"` + Used int64 `json:"usedPieces"` + Trash int64 `json:"usedTrash"` + Free int64 `json:"free"` + Available int64 `json:"available"` + Overused int64 `json:"overused"` +} + +// Add combines disk space with another one. +func (diskSpace *DiskSpace) Add(space DiskSpace) { + diskSpace.Allocated += space.Allocated + diskSpace.Used += space.Used + diskSpace.Trash += space.Trash + diskSpace.Free += space.Free + diskSpace.Available += space.Available + diskSpace.Overused += space.Overused +} diff --git a/multinode/storage/storage_test.go b/multinode/storage/storage_test.go index 9b1cea838..d212ecaef 100644 --- a/multinode/storage/storage_test.go +++ b/multinode/storage/storage_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "storj.io/storj/multinode/storage" @@ -150,3 +151,79 @@ func TestUsageStampDailyCache(t *testing.T) { stamps := cache.Sorted() require.Equal(t, expected, stamps) } + +func TestDiskSpaceAdd(t *testing.T) { + totalDiskSpace := storage.DiskSpace{ + Allocated: 0, + Used: 0, + Trash: 0, + Free: 0, + Available: 0, + Overused: 0, + } + + testData := []struct { + diskSpace storage.DiskSpace + expected storage.DiskSpace + }{ + { + diskSpace: storage.DiskSpace{ + Allocated: 1, + Used: 1, + Trash: 1, + Free: 1, + Available: 1, + Overused: 1, + }, + expected: storage.DiskSpace{ + Allocated: 1, + Used: 1, + Trash: 1, + Free: 1, + Available: 1, + Overused: 1, + }, + }, + { + diskSpace: storage.DiskSpace{ + Allocated: 2, + Used: 2, + Trash: 2, + Free: 2, + Available: 2, + Overused: 2, + }, + expected: storage.DiskSpace{ + Allocated: 3, + Used: 3, + Trash: 3, + Free: 3, + Available: 3, + Overused: 3, + }, + }, + { + diskSpace: storage.DiskSpace{ + Allocated: 3, + Used: 3, + Trash: 3, + Free: 3, + Available: 3, + Overused: 3, + }, + expected: storage.DiskSpace{ + Allocated: 6, + Used: 6, + Trash: 6, + Free: 6, + Available: 6, + Overused: 6, + }, + }, + } + + for _, test := range testData { + totalDiskSpace.Add(test.diskSpace) + assert.Equal(t, totalDiskSpace, test.expected) + } +}