storagenode/console: added estimated payout for current month and estimated pay stub for previous month (until there's real data in satellite's table) + heldback percentage rate for previous month.
Change-Id: I9346f6d22ed6fbb7e5346b102fc898467678f384
This commit is contained in:
parent
ee7de0424b
commit
8db848791f
@ -30,3 +30,25 @@ func PeriodToTime(period string) (_ time.Time, err error) {
|
|||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MonthsCountSince calculates the months between now and the createdAtTime time.Time value passed.
|
||||||
|
func MonthsCountSince(from time.Time) int {
|
||||||
|
now := time.Now().UTC()
|
||||||
|
return MonthsBetweenDates(from, now)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MonthsBetweenDates calculates amount of months between two dates
|
||||||
|
func MonthsBetweenDates(from time.Time, to time.Time) int {
|
||||||
|
months := 0
|
||||||
|
month := from.Month()
|
||||||
|
for from.Before(to) {
|
||||||
|
from = from.Add(time.Hour * 24)
|
||||||
|
nextMonth := from.Month()
|
||||||
|
if nextMonth != month {
|
||||||
|
months++
|
||||||
|
}
|
||||||
|
month = nextMonth
|
||||||
|
}
|
||||||
|
|
||||||
|
return months
|
||||||
|
}
|
||||||
|
@ -47,3 +47,23 @@ func TestPeriodToTime(t *testing.T) {
|
|||||||
require.Equal(t, periodTime.String(), tc.periodTime.String())
|
require.Equal(t, periodTime.String(), tc.periodTime.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMonthsBetweenDates(t *testing.T) {
|
||||||
|
testCases := [...]struct {
|
||||||
|
from time.Time
|
||||||
|
to time.Time
|
||||||
|
monthsAmount int
|
||||||
|
}{
|
||||||
|
{time.Date(2020, 2, 13, 0, 0, 0, 0, &time.Location{}), time.Date(2020, 05, 13, 0, 0, 0, 0, &time.Location{}), 3},
|
||||||
|
{time.Date(2015, 7, 30, 0, 0, 0, 0, &time.Location{}), time.Date(2020, 05, 13, 0, 0, 0, 0, &time.Location{}), 58},
|
||||||
|
{time.Date(2017, 1, 28, 0, 0, 0, 0, &time.Location{}), time.Date(2020, 05, 13, 0, 0, 0, 0, &time.Location{}), 40},
|
||||||
|
{time.Date(2016, 11, 1, 0, 0, 0, 0, &time.Location{}), time.Date(2020, 05, 13, 0, 0, 0, 0, &time.Location{}), 42},
|
||||||
|
{time.Date(2019, 4, 17, 0, 0, 0, 0, &time.Location{}), time.Date(2020, 05, 13, 0, 0, 0, 0, &time.Location{}), 13},
|
||||||
|
{time.Date(2018, 9, 11, 0, 0, 0, 0, &time.Location{}), time.Date(2020, 05, 13, 0, 0, 0, 0, &time.Location{}), 20},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
monthDiff := date.MonthsBetweenDates(tc.from, tc.to)
|
||||||
|
require.Equal(t, monthDiff, tc.monthsAmount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -111,6 +111,47 @@ func (dashboard *StorageNode) Satellite(w http.ResponseWriter, r *http.Request)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EstimatedPayout returns estimated payout from specific satellite or all satellites if current traffic level remains same.
|
||||||
|
func (dashboard *StorageNode) EstimatedPayout(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
var err error
|
||||||
|
defer mon.Task()(&ctx)(&err)
|
||||||
|
|
||||||
|
w.Header().Set(contentType, applicationJSON)
|
||||||
|
|
||||||
|
queryParams := r.URL.Query()
|
||||||
|
id := queryParams.Get("id")
|
||||||
|
if id == "" {
|
||||||
|
data, err := dashboard.service.GetAllSatellitesEstimatedPayout(ctx)
|
||||||
|
if err != nil {
|
||||||
|
dashboard.serveJSONError(w, http.StatusInternalServerError, ErrStorageNodeAPI.Wrap(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.NewEncoder(w).Encode(data); err != nil {
|
||||||
|
dashboard.log.Error("failed to encode json response", zap.Error(ErrHeldAmountAPI.Wrap(err)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
satelliteID, err := storj.NodeIDFromString(id)
|
||||||
|
if err != nil {
|
||||||
|
dashboard.serveJSONError(w, http.StatusBadRequest, ErrHeldAmountAPI.Wrap(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := dashboard.service.GetSatelliteEstimatedPayout(ctx, satelliteID)
|
||||||
|
if err != nil {
|
||||||
|
dashboard.serveJSONError(w, http.StatusInternalServerError, ErrStorageNodeAPI.Wrap(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.NewEncoder(w).Encode(data); err != nil {
|
||||||
|
dashboard.log.Error("failed to encode json response", zap.Error(ErrHeldAmountAPI.Wrap(err)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// serveJSONError writes JSON error to response output stream.
|
// serveJSONError writes JSON error to response output stream.
|
||||||
func (dashboard *StorageNode) serveJSONError(w http.ResponseWriter, status int, err error) {
|
func (dashboard *StorageNode) serveJSONError(w http.ResponseWriter, status int, err error) {
|
||||||
w.WriteHeader(status)
|
w.WriteHeader(status)
|
||||||
|
219
storagenode/console/consoleapi/storagenode_test.go
Normal file
219
storagenode/console/consoleapi/storagenode_test.go
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
// Copyright (C) 2020 Storj Labs, Inc.
|
||||||
|
// See LICENSE for copying information.
|
||||||
|
|
||||||
|
package consoleapi_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"math"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"storj.io/common/pb"
|
||||||
|
"storj.io/common/storj"
|
||||||
|
"storj.io/common/testcontext"
|
||||||
|
"storj.io/storj/private/date"
|
||||||
|
"storj.io/storj/private/testplanet"
|
||||||
|
"storj.io/storj/storagenode/heldamount"
|
||||||
|
"storj.io/storj/storagenode/pricing"
|
||||||
|
"storj.io/storj/storagenode/reputation"
|
||||||
|
"storj.io/storj/storagenode/storageusage"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
actions = []pb.PieceAction{
|
||||||
|
pb.PieceAction_INVALID,
|
||||||
|
|
||||||
|
pb.PieceAction_PUT,
|
||||||
|
pb.PieceAction_GET,
|
||||||
|
pb.PieceAction_GET_AUDIT,
|
||||||
|
pb.PieceAction_GET_REPAIR,
|
||||||
|
pb.PieceAction_PUT_REPAIR,
|
||||||
|
pb.PieceAction_DELETE,
|
||||||
|
|
||||||
|
pb.PieceAction_PUT,
|
||||||
|
pb.PieceAction_GET,
|
||||||
|
pb.PieceAction_GET_AUDIT,
|
||||||
|
pb.PieceAction_GET_REPAIR,
|
||||||
|
pb.PieceAction_PUT_REPAIR,
|
||||||
|
pb.PieceAction_DELETE,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStorageNodeApi(t *testing.T) {
|
||||||
|
testplanet.Run(t,
|
||||||
|
testplanet.Config{
|
||||||
|
SatelliteCount: 2,
|
||||||
|
StorageNodeCount: 1,
|
||||||
|
},
|
||||||
|
func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
||||||
|
satellite := planet.Satellites[0]
|
||||||
|
satellite2 := planet.Satellites[1]
|
||||||
|
sno := planet.StorageNodes[0]
|
||||||
|
console := sno.Console
|
||||||
|
bandwidthdb := sno.DB.Bandwidth()
|
||||||
|
pricingdb := sno.DB.Pricing()
|
||||||
|
storageusagedb := sno.DB.StorageUsage()
|
||||||
|
reputationdb := sno.DB.Reputation()
|
||||||
|
baseURL := fmt.Sprintf("http://%s/api/sno", console.Listener.Addr())
|
||||||
|
|
||||||
|
now := time.Now().UTC().Add(-2 * time.Hour)
|
||||||
|
|
||||||
|
randAmount1 := int64(120000000000)
|
||||||
|
randAmount2 := int64(450000000000)
|
||||||
|
|
||||||
|
for _, action := range actions {
|
||||||
|
err := bandwidthdb.Add(ctx, satellite.ID(), action, randAmount1, now)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = bandwidthdb.Add(ctx, satellite2.ID(), action, randAmount2, now.Add(2*time.Hour))
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
var satellites []storj.NodeID
|
||||||
|
|
||||||
|
satellites = append(satellites, satellite.ID(), satellite2.ID())
|
||||||
|
stamps, _ := makeStorageUsageStamps(satellites)
|
||||||
|
|
||||||
|
err := storageusagedb.Store(ctx, stamps)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
egressPrice, repairPrice, auditPrice, diskPrice := int64(2000), int64(1000), int64(1000), int64(150)
|
||||||
|
|
||||||
|
err = pricingdb.Store(ctx, pricing.Pricing{
|
||||||
|
SatelliteID: satellite.ID(),
|
||||||
|
EgressBandwidth: egressPrice,
|
||||||
|
RepairBandwidth: repairPrice,
|
||||||
|
AuditBandwidth: auditPrice,
|
||||||
|
DiskSpace: diskPrice,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = pricingdb.Store(ctx, pricing.Pricing{
|
||||||
|
SatelliteID: satellite2.ID(),
|
||||||
|
EgressBandwidth: egressPrice,
|
||||||
|
RepairBandwidth: repairPrice,
|
||||||
|
AuditBandwidth: auditPrice,
|
||||||
|
DiskSpace: diskPrice,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = reputationdb.Store(ctx, reputation.Stats{
|
||||||
|
SatelliteID: satellite.ID(),
|
||||||
|
JoinedAt: time.Now().UTC(),
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = reputationdb.Store(ctx, reputation.Stats{
|
||||||
|
SatelliteID: satellite2.ID(),
|
||||||
|
JoinedAt: time.Now().UTC(),
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
t.Run("test EstimatedPayout", func(t *testing.T) {
|
||||||
|
// should return estimated payout for both satellites in current month and empty for previous
|
||||||
|
url := fmt.Sprintf("%s/estimatedPayout", baseURL)
|
||||||
|
res, err := http.Get(url)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, res)
|
||||||
|
require.Equal(t, http.StatusOK, res.StatusCode)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
err = res.Body.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
}()
|
||||||
|
body, err := ioutil.ReadAll(res.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
expectedAuditRepairSatellite1 := 4 * (float64(randAmount1*auditPrice) / math.Pow10(12))
|
||||||
|
expectedAuditRepairSatellite2 := 4 * float64(randAmount2*repairPrice) / math.Pow10(12)
|
||||||
|
expectedUsageSatellite1 := 2 * float64(randAmount1*egressPrice) / math.Pow10(12)
|
||||||
|
expectedUsageSatellite2 := 2 * float64(randAmount2*egressPrice) / math.Pow10(12)
|
||||||
|
expectedDisk := int64(float64(30000000000000*diskPrice/730)/math.Pow10(12)) * int64(time.Now().UTC().Day())
|
||||||
|
|
||||||
|
day := int64(time.Now().Day())
|
||||||
|
|
||||||
|
month := time.Now().UTC()
|
||||||
|
_, to := date.MonthBoundary(month)
|
||||||
|
|
||||||
|
sum1 := expectedAuditRepairSatellite1 + expectedUsageSatellite1 + float64(expectedDisk)
|
||||||
|
sum1AfterHeld := math.Round(sum1 / 4)
|
||||||
|
estimated1 := int64(sum1AfterHeld) * int64(to.Day()) / day
|
||||||
|
sum2 := expectedAuditRepairSatellite2 + expectedUsageSatellite2 + float64(expectedDisk)
|
||||||
|
sum2AfterHeld := math.Round(sum2 / 4)
|
||||||
|
estimated2 := int64(sum2AfterHeld) * int64(to.Day()) / day
|
||||||
|
|
||||||
|
expected, err := json.Marshal(heldamount.EstimatedPayout{
|
||||||
|
CurrentMonthEstimatedAmount: estimated1 + estimated2,
|
||||||
|
PreviousMonthPayout: heldamount.PayoutMonthly{
|
||||||
|
EgressBandwidth: 0,
|
||||||
|
EgressPayout: 0,
|
||||||
|
EgressRepairAudit: 0,
|
||||||
|
RepairAuditPayout: 0,
|
||||||
|
DiskSpace: 0,
|
||||||
|
DiskSpaceAmount: 0,
|
||||||
|
HeldPercentRate: 0,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, string(expected)+"\n", string(body))
|
||||||
|
|
||||||
|
// should return estimated payout for first satellite in current month and empty for previous
|
||||||
|
url = fmt.Sprintf("%s/estimatedPayout?id=%s", baseURL, satellite.ID().String())
|
||||||
|
res2, err := http.Get(url)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, res)
|
||||||
|
require.Equal(t, http.StatusOK, res.StatusCode)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
err = res2.Body.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
}()
|
||||||
|
body2, err := ioutil.ReadAll(res2.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
expected2, err := json.Marshal(heldamount.EstimatedPayout{
|
||||||
|
CurrentMonthEstimatedAmount: estimated1,
|
||||||
|
PreviousMonthPayout: heldamount.PayoutMonthly{
|
||||||
|
EgressBandwidth: 0,
|
||||||
|
EgressPayout: 0,
|
||||||
|
EgressRepairAudit: 0,
|
||||||
|
RepairAuditPayout: 0,
|
||||||
|
DiskSpace: 0,
|
||||||
|
DiskSpaceAmount: 0,
|
||||||
|
HeldPercentRate: 75,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, string(expected2)+"\n", string(body2))
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeStorageUsageStamps creates storage usage stamps and expected summaries for provided satellites.
|
||||||
|
// Creates one entry per day for 30 days with last date as beginning of provided endDate.
|
||||||
|
func makeStorageUsageStamps(satellites []storj.NodeID) ([]storageusage.Stamp, map[storj.NodeID]float64) {
|
||||||
|
var stamps []storageusage.Stamp
|
||||||
|
summary := make(map[storj.NodeID]float64)
|
||||||
|
|
||||||
|
now := time.Now().UTC().Day()
|
||||||
|
|
||||||
|
for _, satellite := range satellites {
|
||||||
|
for i := 0; i < now; i++ {
|
||||||
|
stamp := storageusage.Stamp{
|
||||||
|
SatelliteID: satellite,
|
||||||
|
AtRestTotal: 30000000000000,
|
||||||
|
IntervalStart: time.Now().UTC().Add(time.Hour * -24 * time.Duration(i)),
|
||||||
|
}
|
||||||
|
|
||||||
|
summary[satellite] += stamp.AtRestTotal
|
||||||
|
stamps = append(stamps, stamp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stamps, summary
|
||||||
|
}
|
@ -68,6 +68,7 @@ func NewServer(logger *zap.Logger, assets http.FileSystem, notifications *notifi
|
|||||||
storageNodeRouter.HandleFunc("/", storageNodeController.StorageNode).Methods(http.MethodGet)
|
storageNodeRouter.HandleFunc("/", storageNodeController.StorageNode).Methods(http.MethodGet)
|
||||||
storageNodeRouter.HandleFunc("/satellites", storageNodeController.Satellites).Methods(http.MethodGet)
|
storageNodeRouter.HandleFunc("/satellites", storageNodeController.Satellites).Methods(http.MethodGet)
|
||||||
storageNodeRouter.HandleFunc("/satellite/{id}", storageNodeController.Satellite).Methods(http.MethodGet)
|
storageNodeRouter.HandleFunc("/satellite/{id}", storageNodeController.Satellite).Methods(http.MethodGet)
|
||||||
|
storageNodeRouter.HandleFunc("/estimatedPayout", storageNodeController.EstimatedPayout).Methods(http.MethodGet)
|
||||||
|
|
||||||
notificationController := consoleapi.NewNotifications(server.log, server.notifications)
|
notificationController := consoleapi.NewNotifications(server.log, server.notifications)
|
||||||
notificationRouter := router.PathPrefix("/api/notifications").Subrouter()
|
notificationRouter := router.PathPrefix("/api/notifications").Subrouter()
|
||||||
|
@ -5,6 +5,7 @@ package console
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"math"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/spacemonkeygo/monkit/v3"
|
"github.com/spacemonkeygo/monkit/v3"
|
||||||
@ -18,6 +19,7 @@ import (
|
|||||||
"storj.io/storj/private/version/checker"
|
"storj.io/storj/private/version/checker"
|
||||||
"storj.io/storj/storagenode/bandwidth"
|
"storj.io/storj/storagenode/bandwidth"
|
||||||
"storj.io/storj/storagenode/contact"
|
"storj.io/storj/storagenode/contact"
|
||||||
|
"storj.io/storj/storagenode/heldamount"
|
||||||
"storj.io/storj/storagenode/pieces"
|
"storj.io/storj/storagenode/pieces"
|
||||||
"storj.io/storj/storagenode/pricing"
|
"storj.io/storj/storagenode/pricing"
|
||||||
"storj.io/storj/storagenode/reputation"
|
"storj.io/storj/storagenode/reputation"
|
||||||
@ -365,3 +367,163 @@ func (s *Service) VerifySatelliteID(ctx context.Context, satelliteID storj.NodeI
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetSatelliteEstimatedPayout returns estimated payout for current and previous months from specific satellite with current level of load.
|
||||||
|
func (s *Service) GetSatelliteEstimatedPayout(ctx context.Context, satelliteID storj.NodeID) (payout heldamount.EstimatedPayout, err error) {
|
||||||
|
defer mon.Task()(&ctx)(&err)
|
||||||
|
|
||||||
|
currentMonthPayout, err := s.estimatedPayoutCurrentMonth(ctx, satelliteID)
|
||||||
|
if err != nil {
|
||||||
|
return heldamount.EstimatedPayout{}, SNOServiceErr.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
previousMonthPayout, err := s.estimatedPayoutPreviousMonth(ctx, satelliteID)
|
||||||
|
if err != nil {
|
||||||
|
return heldamount.EstimatedPayout{}, SNOServiceErr.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
payout.CurrentMonthEstimatedAmount = currentMonthPayout
|
||||||
|
payout.PreviousMonthPayout = previousMonthPayout
|
||||||
|
|
||||||
|
return payout, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllSatellitesEstimatedPayout returns estimated payout for current and previous months from all satellites with current level of load.
|
||||||
|
func (s *Service) GetAllSatellitesEstimatedPayout(ctx context.Context) (payout heldamount.EstimatedPayout, err error) {
|
||||||
|
defer mon.Task()(&ctx)(&err)
|
||||||
|
|
||||||
|
satelliteIDs := s.trust.GetSatellites(ctx)
|
||||||
|
for i := 0; i < len(satelliteIDs); i++ {
|
||||||
|
current, err := s.estimatedPayoutCurrentMonth(ctx, satelliteIDs[i])
|
||||||
|
if err != nil {
|
||||||
|
return heldamount.EstimatedPayout{}, SNOServiceErr.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
previous, err := s.estimatedPayoutPreviousMonth(ctx, satelliteIDs[i])
|
||||||
|
if err != nil {
|
||||||
|
return heldamount.EstimatedPayout{}, SNOServiceErr.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
payout.CurrentMonthEstimatedAmount += current
|
||||||
|
payout.PreviousMonthPayout.DiskSpaceAmount += previous.DiskSpaceAmount
|
||||||
|
payout.PreviousMonthPayout.DiskSpace += previous.DiskSpace
|
||||||
|
payout.PreviousMonthPayout.EgressBandwidth += previous.EgressBandwidth
|
||||||
|
payout.PreviousMonthPayout.EgressPayout += previous.EgressPayout
|
||||||
|
payout.PreviousMonthPayout.RepairAuditPayout += previous.RepairAuditPayout
|
||||||
|
payout.PreviousMonthPayout.EgressRepairAudit += previous.EgressRepairAudit
|
||||||
|
}
|
||||||
|
|
||||||
|
return payout, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// estimatedPayoutCurrentMonth returns estimated payout for current month from specific satellite with current level of load and previous month.
|
||||||
|
func (s *Service) estimatedPayoutCurrentMonth(ctx context.Context, satelliteID storj.NodeID) (_ int64, err error) {
|
||||||
|
defer mon.Task()(&ctx)(&err)
|
||||||
|
|
||||||
|
var totalSum int64
|
||||||
|
|
||||||
|
stats, err := s.reputationDB.Get(ctx, satelliteID)
|
||||||
|
if err != nil {
|
||||||
|
return 0, SNOServiceErr.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
heldRate := s.getHeldRate(stats.JoinedAt)
|
||||||
|
|
||||||
|
month := time.Now().UTC()
|
||||||
|
from, to := date.MonthBoundary(month)
|
||||||
|
|
||||||
|
priceModel, err := s.pricingDB.Get(ctx, satelliteID)
|
||||||
|
if err != nil {
|
||||||
|
return 0, SNOServiceErr.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bandwidthDaily, err := s.bandwidthDB.GetDailySatelliteRollups(ctx, satelliteID, from, to)
|
||||||
|
if err != nil {
|
||||||
|
return 0, SNOServiceErr.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(bandwidthDaily); i++ {
|
||||||
|
auditDaily := float64(bandwidthDaily[i].Egress.Audit*priceModel.AuditBandwidth) / math.Pow10(12)
|
||||||
|
repairDaily := float64(bandwidthDaily[i].Egress.Repair*priceModel.RepairBandwidth) / math.Pow10(12)
|
||||||
|
usageDaily := float64(bandwidthDaily[i].Egress.Usage*priceModel.EgressBandwidth) / math.Pow10(12)
|
||||||
|
totalSum += int64(auditDaily + repairDaily + usageDaily)
|
||||||
|
}
|
||||||
|
|
||||||
|
storageDaily, err := s.storageUsageDB.GetDaily(ctx, satelliteID, from, to)
|
||||||
|
if err != nil {
|
||||||
|
return 0, SNOServiceErr.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for j := 0; j < len(storageDaily); j++ {
|
||||||
|
diskSpace := (storageDaily[j].AtRestTotal * float64(priceModel.DiskSpace) / 730) / math.Pow10(12)
|
||||||
|
totalSum += int64(diskSpace)
|
||||||
|
}
|
||||||
|
|
||||||
|
day := int64(time.Now().UTC().Day())
|
||||||
|
amount := totalSum - (totalSum*heldRate)/100
|
||||||
|
|
||||||
|
return amount * int64(to.Day()) / day, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// estimatedPayoutPreviousMonth returns estimated payout data for previous month from specific satellite.
|
||||||
|
func (s *Service) estimatedPayoutPreviousMonth(ctx context.Context, satelliteID storj.NodeID) (payoutData heldamount.PayoutMonthly, err error) {
|
||||||
|
defer mon.Task()(&ctx)(&err)
|
||||||
|
|
||||||
|
month := time.Now().UTC().AddDate(0, -1, 0).UTC()
|
||||||
|
from, to := date.MonthBoundary(month)
|
||||||
|
|
||||||
|
priceModel, err := s.pricingDB.Get(ctx, satelliteID)
|
||||||
|
if err != nil {
|
||||||
|
return heldamount.PayoutMonthly{}, SNOServiceErr.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stats, err := s.reputationDB.Get(ctx, satelliteID)
|
||||||
|
if err != nil {
|
||||||
|
return heldamount.PayoutMonthly{}, SNOServiceErr.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
heldRate := s.getHeldRate(stats.JoinedAt)
|
||||||
|
payoutData.HeldPercentRate = heldRate
|
||||||
|
|
||||||
|
bandwidthDaily, err := s.bandwidthDB.GetDailySatelliteRollups(ctx, satelliteID, from, to)
|
||||||
|
if err != nil {
|
||||||
|
return heldamount.PayoutMonthly{}, SNOServiceErr.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(bandwidthDaily); i++ {
|
||||||
|
payoutData.EgressBandwidth += bandwidthDaily[i].Egress.Usage
|
||||||
|
usagePayout := float64(bandwidthDaily[i].Egress.Usage*priceModel.EgressBandwidth*heldRate/100) / math.Pow10(12)
|
||||||
|
payoutData.EgressPayout += int64(usagePayout)
|
||||||
|
payoutData.EgressRepairAudit += bandwidthDaily[i].Egress.Audit + bandwidthDaily[i].Egress.Repair
|
||||||
|
repairAuditPayout := float64((bandwidthDaily[i].Egress.Audit*priceModel.AuditBandwidth+bandwidthDaily[i].Egress.Repair*priceModel.RepairBandwidth)*heldRate/100) / math.Pow10(12)
|
||||||
|
payoutData.RepairAuditPayout += int64(repairAuditPayout)
|
||||||
|
}
|
||||||
|
|
||||||
|
storageDaily, err := s.storageUsageDB.GetDaily(ctx, satelliteID, from, to)
|
||||||
|
if err != nil {
|
||||||
|
return heldamount.PayoutMonthly{}, SNOServiceErr.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for j := 0; j < len(storageDaily); j++ {
|
||||||
|
payoutData.DiskSpace += storageDaily[j].AtRestTotal
|
||||||
|
payoutData.DiskSpaceAmount += int64(storageDaily[j].AtRestTotal / 730 / math.Pow10(12) * float64(priceModel.DiskSpace*heldRate/100))
|
||||||
|
}
|
||||||
|
|
||||||
|
return payoutData, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) getHeldRate(joinTime time.Time) (heldRate int64) {
|
||||||
|
monthsSinceJoin := date.MonthsCountSince(joinTime)
|
||||||
|
switch monthsSinceJoin {
|
||||||
|
case 0, 1, 2:
|
||||||
|
heldRate = 75
|
||||||
|
case 3, 4, 5:
|
||||||
|
heldRate = 50
|
||||||
|
case 6, 7, 8:
|
||||||
|
heldRate = 25
|
||||||
|
default:
|
||||||
|
heldRate = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return heldRate
|
||||||
|
}
|
||||||
|
@ -63,3 +63,20 @@ type Heldback struct {
|
|||||||
Period string `json:"period"`
|
Period string `json:"period"`
|
||||||
Held int64 `json:"held"`
|
Held int64 `json:"held"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EstimatedPayout contains amount in cents of estimated payout for current and previous months.
|
||||||
|
type EstimatedPayout struct {
|
||||||
|
CurrentMonthEstimatedAmount int64 `json:"currentAmount"`
|
||||||
|
PreviousMonthPayout PayoutMonthly `json:"previousPayout"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PayoutMonthly contains bandwidth and payout amount for month.
|
||||||
|
type PayoutMonthly struct {
|
||||||
|
EgressBandwidth int64 `json:"egressBandwidth"`
|
||||||
|
EgressPayout int64 `json:"egressPayout"`
|
||||||
|
EgressRepairAudit int64 `json:"egressRepairAudit"`
|
||||||
|
RepairAuditPayout int64 `json:"repairAuditPayout"`
|
||||||
|
DiskSpace float64 `json:"diskSpace"`
|
||||||
|
DiskSpaceAmount int64 `json:"diskSpaceAmount"`
|
||||||
|
HeldPercentRate int64 `json:"heldRate"`
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user