mnd/payouts: estimations replaced with expectations

Added expectations endpoint (estimations and distributed), added
coalesce to db query, so in case of empty payouts db 0 will be returned instead of error.

Change-Id: I535f14ef097876448d8949bc302895b25da2b6e7
This commit is contained in:
Qweder93 2021-05-24 20:13:47 +03:00 committed by Yaroslav Vorobiov
parent 83e82eb473
commit 79172777bd
6 changed files with 84 additions and 63 deletions

View File

@ -53,8 +53,8 @@ func (controller *Payouts) GetAllNodesTotalEarned(w http.ResponseWriter, r *http
} }
} }
// NodeEstimations handles node's estimated. // NodeExpectations handles node's estimated and undistributed.
func (controller *Payouts) NodeEstimations(w http.ResponseWriter, r *http.Request) { func (controller *Payouts) NodeExpectations(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
var err error var err error
defer mon.Task()(&ctx)(&err) defer mon.Task()(&ctx)(&err)
@ -74,33 +74,33 @@ func (controller *Payouts) NodeEstimations(w http.ResponseWriter, r *http.Reques
return return
} }
estimations, err := controller.service.NodeEstimations(ctx, nodeID) expectations, err := controller.service.NodeExpectations(ctx, nodeID)
if err != nil { if err != nil {
controller.serveError(w, http.StatusInternalServerError, ErrPayouts.Wrap(err)) controller.serveError(w, http.StatusInternalServerError, ErrPayouts.Wrap(err))
return return
} }
if err = json.NewEncoder(w).Encode(estimations); err != nil { if err = json.NewEncoder(w).Encode(expectations); err != nil {
controller.log.Error("failed to write json response", zap.Error(err)) controller.log.Error("failed to write json response", zap.Error(err))
return return
} }
} }
// Estimations handles nodes estimated earnings. // Expectations handles nodes estimated and undistributed earnings.
func (controller *Payouts) Estimations(w http.ResponseWriter, r *http.Request) { func (controller *Payouts) Expectations(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
var err error var err error
defer mon.Task()(&ctx)(&err) defer mon.Task()(&ctx)(&err)
w.Header().Add("Content-Type", "application/json") w.Header().Add("Content-Type", "application/json")
estimations, err := controller.service.Estimations(ctx) expectations, err := controller.service.Expectations(ctx)
if err != nil { if err != nil {
controller.serveError(w, http.StatusInternalServerError, ErrPayouts.Wrap(err)) controller.serveError(w, http.StatusInternalServerError, ErrPayouts.Wrap(err))
return return
} }
if err = json.NewEncoder(w).Encode(estimations); err != nil { if err = json.NewEncoder(w).Encode(expectations); err != nil {
controller.log.Error("failed to write json response", zap.Error(err)) controller.log.Error("failed to write json response", zap.Error(err))
return return
} }

View File

@ -80,8 +80,8 @@ func NewServer(log *zap.Logger, config Config, nodes *nodes.Service, payouts *pa
payoutsRouter.HandleFunc("/summary/{period}", payoutsController.PeriodSummary).Methods(http.MethodGet) payoutsRouter.HandleFunc("/summary/{period}", payoutsController.PeriodSummary).Methods(http.MethodGet)
payoutsRouter.HandleFunc("/summary", payoutsController.Summary).Methods(http.MethodGet) payoutsRouter.HandleFunc("/summary", payoutsController.Summary).Methods(http.MethodGet)
payoutsRouter.HandleFunc("/total-earned", payoutsController.GetAllNodesTotalEarned).Methods(http.MethodGet) payoutsRouter.HandleFunc("/total-earned", payoutsController.GetAllNodesTotalEarned).Methods(http.MethodGet)
payoutsRouter.HandleFunc("/estimations/{nodeID}", payoutsController.NodeEstimations).Methods(http.MethodGet) payoutsRouter.HandleFunc("/expectations/{nodeID}", payoutsController.NodeExpectations).Methods(http.MethodGet)
payoutsRouter.HandleFunc("/estimations", payoutsController.Estimations).Methods(http.MethodGet) payoutsRouter.HandleFunc("/expectations", payoutsController.Expectations).Methods(http.MethodGet)
if server.config.StaticDir != "" { if server.config.StaticDir != "" {
router.PathPrefix("/static/").Handler(http.StripPrefix("/static", fs)) router.PathPrefix("/static/").Handler(http.StripPrefix("/static", fs))

View File

@ -41,3 +41,9 @@ func (summary *Summary) Add(held, paid int64, id storj.NodeID, name string) {
NodeName: name, NodeName: name,
}) })
} }
// Expectations contains estimated and undistributed payouts.
type Expectations struct {
CurrentMonthEstimation int64 `json:"currentMonthEstimation"`
Undistributed int64 `json:"undistributed"`
}

View File

@ -308,54 +308,55 @@ func (service *Service) getAllSatellitesAllTime(ctx context.Context, node nodes.
return response.PayoutInfo, nil return response.PayoutInfo, nil
} }
// NodeEstimations returns node's estimated earnings. // NodeExpectations returns node's estimated and undistributed earnings.
func (service *Service) NodeEstimations(ctx context.Context, nodeID storj.NodeID) (_ int64, err error) { func (service *Service) NodeExpectations(ctx context.Context, nodeID storj.NodeID) (_ Expectations, err error) {
defer mon.Task()(&ctx)(&err) defer mon.Task()(&ctx)(&err)
node, err := service.nodes.Get(ctx, nodeID) node, err := service.nodes.Get(ctx, nodeID)
if err != nil { if err != nil {
return 0, Error.Wrap(err) return Expectations{}, Error.Wrap(err)
} }
est, err := service.nodeEstimations(ctx, node) expectation, err := service.nodeExpectations(ctx, node)
if err != nil { if err != nil {
return 0, Error.Wrap(err) return Expectations{}, Error.Wrap(err)
} }
return est, nil return expectation, nil
} }
// Estimations returns all nodes estimated earnings. // Expectations returns all nodes estimated and undistributed earnings.
func (service *Service) Estimations(ctx context.Context) (_ int64, err error) { func (service *Service) Expectations(ctx context.Context) (_ Expectations, err error) {
defer mon.Task()(&ctx)(&err) defer mon.Task()(&ctx)(&err)
var estimations int64 var expectations Expectations
list, err := service.nodes.List(ctx) list, err := service.nodes.List(ctx)
if err != nil { if err != nil {
return 0, Error.Wrap(err) return Expectations{}, Error.Wrap(err)
} }
for _, node := range list { for _, node := range list {
est, err := service.nodeEstimations(ctx, node) expectation, err := service.nodeExpectations(ctx, node)
if err != nil { if err != nil {
return 0, Error.Wrap(err) return Expectations{}, Error.Wrap(err)
} }
estimations += est expectations.Undistributed += expectation.Undistributed
expectations.CurrentMonthEstimation += expectation.CurrentMonthEstimation
} }
return estimations, nil return expectations, nil
} }
// nodeEstimations retrieves data from a single node. // nodeExpectations retrieves data from a single node.
func (service *Service) nodeEstimations(ctx context.Context, node nodes.Node) (_ int64, err error) { func (service *Service) nodeExpectations(ctx context.Context, node nodes.Node) (_ Expectations, err error) {
conn, err := service.dialer.DialNodeURL(ctx, storj.NodeURL{ conn, err := service.dialer.DialNodeURL(ctx, storj.NodeURL{
ID: node.ID, ID: node.ID,
Address: node.PublicAddress, Address: node.PublicAddress,
}) })
if err != nil { if err != nil {
return 0, Error.Wrap(err) return Expectations{}, Error.Wrap(err)
} }
defer func() { defer func() {
@ -369,10 +370,15 @@ func (service *Service) nodeEstimations(ctx context.Context, node nodes.Node) (_
estimated, err := payoutClient.EstimatedPayoutTotal(ctx, &multinodepb.EstimatedPayoutTotalRequest{Header: header}) estimated, err := payoutClient.EstimatedPayoutTotal(ctx, &multinodepb.EstimatedPayoutTotalRequest{Header: header})
if err != nil { if err != nil {
return 0, Error.Wrap(err) return Expectations{}, Error.Wrap(err)
} }
return estimated.EstimatedEarnings, nil undistributed, err := payoutClient.Undistributed(ctx, &multinodepb.UndistributedRequest{Header: header})
if err != nil {
return Expectations{}, Error.Wrap(err)
}
return Expectations{Undistributed: undistributed.Total, CurrentMonthEstimation: estimated.EstimatedEarnings}, nil
} }
// getAmount returns earned from node. // getAmount returns earned from node.

View File

@ -456,46 +456,54 @@ func TestPayouts(t *testing.T) {
}) })
} }
func TestPayoutsUndistributedEndpoint(t *testing.T) { func TestUndistributed(t *testing.T) {
storagenodedbtest.Run(t, func(ctx *testcontext.Context, t *testing.T, db storagenode.DB) { storagenodedbtest.Run(t, func(ctx *testcontext.Context, t *testing.T, db storagenode.DB) {
payoutdb := db.Payout() payoutdb := db.Payout()
satelliteID1 := testrand.NodeID() satelliteID1 := testrand.NodeID()
satelliteID2 := testrand.NodeID() satelliteID2 := testrand.NodeID()
err := payoutdb.StorePayStub(ctx, payouts.PayStub{ t.Run("empty db no error", func(t *testing.T) {
SatelliteID: satelliteID2, undistributed, err := payoutdb.GetUndistributed(ctx)
Period: "2020-01", require.NoError(t, err)
Distributed: 150, require.EqualValues(t, undistributed, 0)
Paid: 250,
}) })
require.NoError(t, err)
err = payoutdb.StorePayStub(ctx, payouts.PayStub{ t.Run("few paystubs with different satellites", func(t *testing.T) {
SatelliteID: satelliteID2, err := payoutdb.StorePayStub(ctx, payouts.PayStub{
Period: "2020-02", SatelliteID: satelliteID2,
Distributed: 250, Period: "2020-01",
Paid: 350, Distributed: 150,
Paid: 250,
})
require.NoError(t, err)
err = payoutdb.StorePayStub(ctx, payouts.PayStub{
SatelliteID: satelliteID2,
Period: "2020-02",
Distributed: 250,
Paid: 350,
})
require.NoError(t, err)
err = payoutdb.StorePayStub(ctx, payouts.PayStub{
SatelliteID: satelliteID1,
Period: "2020-01",
Distributed: 100,
Paid: 300,
})
require.NoError(t, err)
err = payoutdb.StorePayStub(ctx, payouts.PayStub{
SatelliteID: satelliteID1,
Period: "2020-02",
Distributed: 400,
Paid: 500,
})
require.NoError(t, err)
undistributed, err := payoutdb.GetUndistributed(ctx)
require.NoError(t, err)
require.EqualValues(t, undistributed, 500)
}) })
require.NoError(t, err)
err = payoutdb.StorePayStub(ctx, payouts.PayStub{
SatelliteID: satelliteID1,
Period: "2020-01",
Distributed: 100,
Paid: 300,
})
require.NoError(t, err)
err = payoutdb.StorePayStub(ctx, payouts.PayStub{
SatelliteID: satelliteID1,
Period: "2020-02",
Distributed: 400,
Paid: 500,
})
require.NoError(t, err)
undistributed, err := payoutdb.GetUndistributed(ctx)
require.NoError(t, err)
require.EqualValues(t, undistributed, 500)
}) })
} }

View File

@ -572,13 +572,14 @@ func (db *payoutDB) GetUndistributed(ctx context.Context) (_ int64, err error) {
var distributed, paid int64 var distributed, paid int64
rowPayment := db.QueryRowContext(ctx, rowPayment := db.QueryRowContext(ctx,
`SELECT SUM(distributed), SUM(paid) FROM paystubs`) `SELECT COALESCE(SUM(distributed),0), COALESCE(SUM(paid), 0) FROM paystubs`)
err = rowPayment.Scan(&distributed, &paid) err = rowPayment.Scan(&distributed, &paid)
if err != nil { if err != nil {
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
return 0, payouts.ErrNoPayStubForPeriod.Wrap(err) return 0, payouts.ErrNoPayStubForPeriod.Wrap(err)
} }
return 0, ErrPayout.Wrap(err) return 0, ErrPayout.Wrap(err)
} }