2020-03-13 14:01:12 +00:00
|
|
|
// Copyright (C) 2020 Storj Labs, Inc.
|
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
|
|
|
package heldamount
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2020-03-16 04:28:03 +00:00
|
|
|
"fmt"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
2020-03-13 14:01:12 +00:00
|
|
|
|
|
|
|
"github.com/spacemonkeygo/monkit/v3"
|
|
|
|
"github.com/zeebo/errs"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
|
|
|
|
"storj.io/common/pb"
|
|
|
|
"storj.io/common/rpc"
|
2020-03-24 12:39:13 +00:00
|
|
|
"storj.io/common/rpc/rpcstatus"
|
2020-03-13 14:01:12 +00:00
|
|
|
"storj.io/storj/pkg/storj"
|
2020-03-18 11:27:42 +00:00
|
|
|
"storj.io/storj/private/date"
|
2020-03-13 14:01:12 +00:00
|
|
|
"storj.io/storj/storagenode/trust"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2020-03-26 14:53:15 +00:00
|
|
|
// ErrHeldAmountService defines held amount service error.
|
2020-03-15 23:24:04 +00:00
|
|
|
ErrHeldAmountService = errs.Class("heldamount service error")
|
2020-03-13 14:01:12 +00:00
|
|
|
|
2020-03-26 14:53:15 +00:00
|
|
|
// ErrBadPeriod defines that period has wrong format.
|
|
|
|
ErrBadPeriod = errs.Class("wrong period format")
|
|
|
|
|
2020-03-13 14:01:12 +00:00
|
|
|
mon = monkit.Package()
|
|
|
|
)
|
|
|
|
|
|
|
|
// Client encapsulates HeldAmountClient with underlying connection
|
|
|
|
//
|
|
|
|
// architecture: Client
|
|
|
|
type Client struct {
|
|
|
|
conn *rpc.Conn
|
|
|
|
pb.DRPCHeldAmountClient
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close closes underlying client connection
|
|
|
|
func (c *Client) Close() error {
|
|
|
|
return c.conn.Close()
|
|
|
|
}
|
|
|
|
|
2020-03-15 23:24:04 +00:00
|
|
|
// TODO: separate service on service and endpoint.
|
|
|
|
|
2020-03-13 14:01:12 +00:00
|
|
|
// Service retrieves info from satellites using an rpc client
|
|
|
|
//
|
|
|
|
// architecture: Service
|
|
|
|
type Service struct {
|
|
|
|
log *zap.Logger
|
|
|
|
|
2020-03-15 23:24:04 +00:00
|
|
|
db DB
|
|
|
|
|
2020-03-13 14:01:12 +00:00
|
|
|
dialer rpc.Dialer
|
|
|
|
trust *trust.Pool
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewService creates new instance of service
|
2020-03-15 23:24:04 +00:00
|
|
|
func NewService(log *zap.Logger, db DB, dialer rpc.Dialer, trust *trust.Pool) *Service {
|
2020-03-13 14:01:12 +00:00
|
|
|
return &Service{
|
|
|
|
log: log,
|
2020-03-15 23:24:04 +00:00
|
|
|
db: db,
|
2020-03-13 14:01:12 +00:00
|
|
|
dialer: dialer,
|
|
|
|
trust: trust,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-15 23:24:04 +00:00
|
|
|
// GetPaystubStats retrieves held amount for particular satellite from satellite using grpc.
|
2020-03-13 14:01:12 +00:00
|
|
|
func (service *Service) GetPaystubStats(ctx context.Context, satelliteID storj.NodeID, period string) (_ *PayStub, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
|
|
|
client, err := service.dial(ctx, satelliteID)
|
|
|
|
if err != nil {
|
2020-03-15 23:24:04 +00:00
|
|
|
return nil, ErrHeldAmountService.Wrap(err)
|
2020-03-13 14:01:12 +00:00
|
|
|
}
|
|
|
|
defer func() { err = errs.Combine(err, client.Close()) }()
|
|
|
|
|
2020-03-18 11:27:42 +00:00
|
|
|
requestedPeriod, err := date.PeriodToTime(period)
|
2020-03-13 14:01:12 +00:00
|
|
|
if err != nil {
|
2020-03-15 23:24:04 +00:00
|
|
|
return nil, ErrHeldAmountService.Wrap(err)
|
2020-03-13 14:01:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
resp, err := client.GetPayStub(ctx, &pb.GetHeldAmountRequest{Period: requestedPeriod})
|
|
|
|
if err != nil {
|
2020-03-24 12:39:13 +00:00
|
|
|
if rpcstatus.Code(err) == rpcstatus.OutOfRange {
|
|
|
|
return nil, ErrNoPayStubForPeriod.Wrap(err)
|
|
|
|
}
|
|
|
|
|
2020-03-15 23:24:04 +00:00
|
|
|
return nil, ErrHeldAmountService.Wrap(err)
|
2020-03-13 14:01:12 +00:00
|
|
|
}
|
2020-03-24 12:39:13 +00:00
|
|
|
|
2020-03-13 14:01:12 +00:00
|
|
|
return &PayStub{
|
2020-03-24 12:39:13 +00:00
|
|
|
Period: period[0:7],
|
2020-03-13 14:01:12 +00:00
|
|
|
SatelliteID: satelliteID,
|
|
|
|
Created: resp.CreatedAt,
|
|
|
|
Codes: resp.Codes,
|
|
|
|
UsageAtRest: float64(resp.UsageAtRest),
|
|
|
|
UsageGet: resp.UsageGet,
|
|
|
|
UsagePut: resp.UsagePut,
|
|
|
|
UsageGetRepair: resp.CompGetRepair,
|
|
|
|
UsagePutRepair: resp.CompPutRepair,
|
|
|
|
UsageGetAudit: resp.UsageGetAudit,
|
|
|
|
CompAtRest: resp.CompAtRest,
|
|
|
|
CompGet: resp.CompGet,
|
|
|
|
CompPut: resp.CompPut,
|
|
|
|
CompGetRepair: resp.CompGetRepair,
|
|
|
|
CompPutRepair: resp.CompPutRepair,
|
|
|
|
CompGetAudit: resp.CompGetAudit,
|
|
|
|
SurgePercent: resp.SurgePercent,
|
|
|
|
Held: resp.Held,
|
|
|
|
Owed: resp.Owed,
|
|
|
|
Disposed: resp.Disposed,
|
|
|
|
Paid: resp.Paid,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2020-03-15 23:24:04 +00:00
|
|
|
// GetPayment retrieves payment data from particular satellite using grpc.
|
2020-03-13 14:37:47 +00:00
|
|
|
func (service *Service) GetPayment(ctx context.Context, satelliteID storj.NodeID, period string) (_ *Payment, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
|
|
|
client, err := service.dial(ctx, satelliteID)
|
|
|
|
if err != nil {
|
2020-03-15 23:24:04 +00:00
|
|
|
return nil, ErrHeldAmountService.Wrap(err)
|
2020-03-13 14:37:47 +00:00
|
|
|
}
|
|
|
|
defer func() { err = errs.Combine(err, client.Close()) }()
|
|
|
|
|
2020-03-18 11:27:42 +00:00
|
|
|
requestedPeriod, err := date.PeriodToTime(period)
|
2020-03-13 14:37:47 +00:00
|
|
|
if err != nil {
|
2020-03-15 23:24:04 +00:00
|
|
|
return nil, ErrHeldAmountService.Wrap(err)
|
2020-03-13 14:37:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
resp, err := client.GetPayment(ctx, &pb.GetPaymentRequest{Period: requestedPeriod})
|
|
|
|
if err != nil {
|
2020-03-24 12:39:13 +00:00
|
|
|
if rpcstatus.Code(err) == rpcstatus.OutOfRange {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2020-03-15 23:24:04 +00:00
|
|
|
return nil, ErrHeldAmountService.Wrap(err)
|
2020-03-13 14:37:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return &Payment{
|
|
|
|
ID: resp.Id,
|
|
|
|
Created: resp.CreatedAt,
|
|
|
|
SatelliteID: satelliteID,
|
2020-03-24 12:39:13 +00:00
|
|
|
Period: period[0:7],
|
2020-03-13 14:37:47 +00:00
|
|
|
Amount: resp.Amount,
|
|
|
|
Receipt: resp.Receipt,
|
|
|
|
Notes: resp.Notes,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2020-03-16 01:32:52 +00:00
|
|
|
// SatellitePayStubMonthlyCached retrieves held amount for particular satellite for selected month from storagenode database.
|
|
|
|
func (service *Service) SatellitePayStubMonthlyCached(ctx context.Context, satelliteID storj.NodeID, period string) (payStub *PayStub, err error) {
|
2020-03-15 23:24:04 +00:00
|
|
|
defer mon.Task()(&ctx, &satelliteID, &period)(&err)
|
|
|
|
|
2020-03-16 01:32:52 +00:00
|
|
|
payStub, err = service.db.GetPayStub(ctx, satelliteID, period)
|
|
|
|
if err != nil {
|
|
|
|
return nil, ErrHeldAmountService.Wrap(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return payStub, nil
|
|
|
|
}
|
|
|
|
|
2020-03-17 10:06:20 +00:00
|
|
|
// AllPayStubsMonthlyCached retrieves held amount for all satellites per selected period from storagenode database.
|
2020-03-16 01:32:52 +00:00
|
|
|
func (service *Service) AllPayStubsMonthlyCached(ctx context.Context, period string) (payStubs []PayStub, err error) {
|
|
|
|
defer mon.Task()(&ctx, &period)(&err)
|
|
|
|
|
|
|
|
payStubs, err = service.db.AllPayStubs(ctx, period)
|
|
|
|
if err != nil {
|
2020-03-26 14:53:15 +00:00
|
|
|
return payStubs, ErrHeldAmountService.Wrap(err)
|
2020-03-16 01:32:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return payStubs, nil
|
2020-03-15 23:24:04 +00:00
|
|
|
}
|
|
|
|
|
2020-03-16 04:28:03 +00:00
|
|
|
// SatellitePayStubPeriodCached retrieves held amount for all satellites for selected months from storagenode database.
|
2020-03-26 14:53:15 +00:00
|
|
|
func (service *Service) SatellitePayStubPeriodCached(ctx context.Context, satelliteID storj.NodeID, periodStart, periodEnd string) (payStubs []PayStub, err error) {
|
2020-03-16 04:28:03 +00:00
|
|
|
defer mon.Task()(&ctx, &satelliteID, &periodStart, &periodEnd)(&err)
|
|
|
|
|
|
|
|
periods, err := parsePeriodRange(periodStart, periodEnd)
|
|
|
|
if err != nil {
|
2020-03-26 14:53:15 +00:00
|
|
|
return []PayStub{}, err
|
2020-03-16 04:28:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, period := range periods {
|
|
|
|
payStub, err := service.db.GetPayStub(ctx, satelliteID, period)
|
|
|
|
if err != nil {
|
|
|
|
if ErrNoPayStubForPeriod.Has(err) {
|
|
|
|
continue
|
|
|
|
}
|
2020-03-26 14:53:15 +00:00
|
|
|
|
|
|
|
return []PayStub{}, ErrHeldAmountService.Wrap(err)
|
2020-03-16 04:28:03 +00:00
|
|
|
}
|
|
|
|
|
2020-03-26 14:53:15 +00:00
|
|
|
payStubs = append(payStubs, *payStub)
|
2020-03-16 04:28:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return payStubs, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// AllPayStubsPeriodCached retrieves held amount for all satellites for selected range of months from storagenode database.
|
|
|
|
func (service *Service) AllPayStubsPeriodCached(ctx context.Context, periodStart, periodEnd string) (payStubs []PayStub, err error) {
|
|
|
|
defer mon.Task()(&ctx, &periodStart, &periodEnd)(&err)
|
|
|
|
|
|
|
|
periods, err := parsePeriodRange(periodStart, periodEnd)
|
|
|
|
if err != nil {
|
2020-03-26 14:53:15 +00:00
|
|
|
return []PayStub{}, err
|
2020-03-16 04:28:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, period := range periods {
|
|
|
|
payStub, err := service.db.AllPayStubs(ctx, period)
|
|
|
|
if err != nil {
|
2020-03-26 14:53:15 +00:00
|
|
|
if ErrNoPayStubForPeriod.Has(err) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
return []PayStub{}, ErrHeldAmountService.Wrap(err)
|
2020-03-16 04:28:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
payStubs = append(payStubs, payStub...)
|
|
|
|
}
|
|
|
|
|
|
|
|
return payStubs, nil
|
|
|
|
}
|
|
|
|
|
2020-03-17 10:06:20 +00:00
|
|
|
// SatellitePaymentMonthlyCached retrieves payment data from particular satellite from storagenode database.
|
|
|
|
func (service *Service) SatellitePaymentMonthlyCached(ctx context.Context, satelliteID storj.NodeID, period string) (_ *Payment, err error) {
|
2020-03-15 23:24:04 +00:00
|
|
|
defer mon.Task()(&ctx, &satelliteID, &period)(&err)
|
|
|
|
|
2020-03-17 10:06:20 +00:00
|
|
|
payment, err := service.db.GetPayment(ctx, satelliteID, period)
|
|
|
|
if err != nil {
|
|
|
|
return nil, ErrHeldAmountService.Wrap(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return payment, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// AllPaymentsMonthlyCached retrieves payments for all satellites per selected period from storagenode database.
|
|
|
|
func (service *Service) AllPaymentsMonthlyCached(ctx context.Context, period string) (payments []Payment, err error) {
|
|
|
|
defer mon.Task()(&ctx, &period)(&err)
|
|
|
|
|
|
|
|
payments, err = service.db.AllPayments(ctx, period)
|
|
|
|
if err != nil {
|
|
|
|
return nil, ErrHeldAmountService.Wrap(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return payments, nil
|
2020-03-15 23:24:04 +00:00
|
|
|
}
|
|
|
|
|
2020-03-18 11:27:42 +00:00
|
|
|
// SatellitePaymentPeriodCached retrieves payment for all satellites for selected months from storagenode database.
|
|
|
|
func (service *Service) SatellitePaymentPeriodCached(ctx context.Context, satelliteID storj.NodeID, periodStart, periodEnd string) (payments []*Payment, err error) {
|
|
|
|
defer mon.Task()(&ctx, &satelliteID, &periodStart, &periodEnd)(&err)
|
|
|
|
|
|
|
|
periods, err := parsePeriodRange(periodStart, periodEnd)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, period := range periods {
|
|
|
|
payment, err := service.db.GetPayment(ctx, satelliteID, period)
|
|
|
|
if err != nil {
|
|
|
|
if ErrNoPayStubForPeriod.Has(err) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
return nil, ErrHeldAmountService.Wrap(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
payments = append(payments, payment)
|
|
|
|
}
|
|
|
|
|
|
|
|
return payments, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// AllPaymentsPeriodCached retrieves payment for all satellites for selected range of months from storagenode database.
|
|
|
|
func (service *Service) AllPaymentsPeriodCached(ctx context.Context, periodStart, periodEnd string) (payments []Payment, err error) {
|
|
|
|
defer mon.Task()(&ctx, &periodStart, &periodEnd)(&err)
|
|
|
|
|
|
|
|
periods, err := parsePeriodRange(periodStart, periodEnd)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, period := range periods {
|
|
|
|
payment, err := service.db.AllPayments(ctx, period)
|
|
|
|
if err != nil {
|
|
|
|
return nil, ErrHeldAmountService.Wrap(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
payments = append(payments, payment...)
|
|
|
|
}
|
|
|
|
|
|
|
|
return payments, nil
|
|
|
|
}
|
|
|
|
|
2020-03-13 14:01:12 +00:00
|
|
|
// dial dials the HeldAmount client for the satellite by id
|
|
|
|
func (service *Service) dial(ctx context.Context, satelliteID storj.NodeID) (_ *Client, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
|
|
|
address, err := service.trust.GetAddress(ctx, satelliteID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errs.New("unable to find satellite %s: %w", satelliteID, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
conn, err := service.dialer.DialAddressID(ctx, address, satelliteID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errs.New("unable to connect to the satellite %s: %w", satelliteID, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &Client{
|
|
|
|
conn: conn,
|
2020-03-25 12:15:27 +00:00
|
|
|
DRPCHeldAmountClient: pb.NewDRPCHeldAmountClient(conn),
|
2020-03-13 14:01:12 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2020-03-26 14:53:15 +00:00
|
|
|
// TODO: move to separate struct.
|
2020-03-16 04:28:03 +00:00
|
|
|
func parsePeriodRange(periodStart, periodEnd string) (periods []string, err error) {
|
|
|
|
var yearStart, yearEnd, monthStart, monthEnd int
|
|
|
|
|
|
|
|
start := strings.Split(periodStart, "-")
|
|
|
|
if len(start) != 2 {
|
2020-03-26 14:53:15 +00:00
|
|
|
return nil, ErrBadPeriod.New("period start has wrong format")
|
2020-03-16 04:28:03 +00:00
|
|
|
}
|
|
|
|
end := strings.Split(periodEnd, "-")
|
|
|
|
if len(start) != 2 {
|
2020-03-26 14:53:15 +00:00
|
|
|
return nil, ErrBadPeriod.New("period end has wrong format")
|
2020-03-16 04:28:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
yearStart, err = strconv.Atoi(start[0])
|
|
|
|
if err != nil {
|
2020-03-26 14:53:15 +00:00
|
|
|
return nil, ErrBadPeriod.New("period start has wrong format")
|
2020-03-16 04:28:03 +00:00
|
|
|
}
|
|
|
|
monthStart, err = strconv.Atoi(start[1])
|
|
|
|
if err != nil || monthStart > 12 || monthStart < 1 {
|
2020-03-26 14:53:15 +00:00
|
|
|
return nil, ErrBadPeriod.New("period start has wrong format")
|
2020-03-16 04:28:03 +00:00
|
|
|
}
|
|
|
|
yearEnd, err = strconv.Atoi(end[0])
|
|
|
|
if err != nil {
|
2020-03-26 14:53:15 +00:00
|
|
|
return nil, ErrBadPeriod.New("period end has wrong format")
|
2020-03-16 04:28:03 +00:00
|
|
|
}
|
|
|
|
monthEnd, err = strconv.Atoi(end[1])
|
|
|
|
if err != nil || monthEnd > 12 || monthEnd < 1 {
|
2020-03-26 14:53:15 +00:00
|
|
|
return nil, ErrBadPeriod.New("period end has wrong format")
|
2020-03-16 04:28:03 +00:00
|
|
|
}
|
|
|
|
if yearEnd < yearStart {
|
2020-03-26 14:53:15 +00:00
|
|
|
return nil, ErrBadPeriod.New("period has wrong format")
|
2020-03-16 04:28:03 +00:00
|
|
|
}
|
|
|
|
if yearEnd == yearStart && monthEnd < monthStart {
|
2020-03-26 14:53:15 +00:00
|
|
|
return nil, ErrBadPeriod.New("period has wrong format")
|
2020-03-16 04:28:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for ; yearStart <= yearEnd; yearStart++ {
|
|
|
|
lastMonth := 12
|
|
|
|
if yearStart == yearEnd {
|
|
|
|
lastMonth = monthEnd
|
|
|
|
}
|
|
|
|
for ; monthStart <= lastMonth; monthStart++ {
|
|
|
|
format := "%d-%d"
|
|
|
|
if monthStart < 10 {
|
|
|
|
format = "%d-0%d"
|
|
|
|
}
|
|
|
|
periods = append(periods, fmt.Sprintf(format, yearStart, monthStart))
|
|
|
|
}
|
|
|
|
|
|
|
|
monthStart = 1
|
|
|
|
}
|
|
|
|
|
|
|
|
return periods, nil
|
|
|
|
}
|