satellite/compensation: add offline status tracking

Change-Id: I52e615d3db186416ee95029dc72df626f0e69ad7
This commit is contained in:
Jeff Wendling 2020-04-08 14:44:27 -06:00 committed by Egon Elbre
parent cafa7a5f0b
commit 42f63c6538
4 changed files with 115 additions and 64 deletions

View File

@ -81,18 +81,19 @@ func generateInvoicesCSV(ctx context.Context, period compensation.Period, out io
}
nodeInfo := compensation.NodeInfo{
ID: usage.NodeID,
CreatedAt: node.CreatedAt,
Disqualified: node.Disqualified,
GracefulExit: gracefulExit,
UsageAtRest: usage.AtRestTotal,
UsageGet: usage.GetTotal,
UsagePut: usage.PutTotal,
UsageGetRepair: usage.GetRepairTotal,
UsagePutRepair: usage.PutRepairTotal,
UsageGetAudit: usage.GetAuditTotal,
TotalHeld: withheldAmounts.TotalHeld,
TotalDisposed: withheldAmounts.TotalDisposed,
ID: usage.NodeID,
CreatedAt: node.CreatedAt,
LastContactSuccess: node.Reputation.LastContactSuccess,
Disqualified: node.Disqualified,
GracefulExit: gracefulExit,
UsageAtRest: usage.AtRestTotal,
UsageGet: usage.GetTotal,
UsagePut: usage.PutTotal,
UsageGetRepair: usage.GetRepairTotal,
UsagePutRepair: usage.PutRepairTotal,
UsageGetAudit: usage.GetAuditTotal,
TotalHeld: withheldAmounts.TotalHeld,
TotalDisposed: withheldAmounts.TotalDisposed,
}
invoice := compensation.Invoice{

View File

@ -26,13 +26,17 @@ const (
// GracefulExit is included if the node has gracefully exited.
GracefulExit Code = "X"
// Offline is included if the node's last contact success is before the starting
// period.
Offline Code = "O"
)
// CodeFromString parses the string into a Code.
func CodeFromString(s string) (Code, error) {
code := Code(s)
switch code {
case Disqualified, Sanctioned, No1099, InWithholding, GracefulExit:
case Disqualified, Sanctioned, No1099, InWithholding, GracefulExit, Offline:
return code, nil
default:
return "", Error.New("no such code %q", code)

View File

@ -36,18 +36,19 @@ var (
// NodeInfo contains all of the information about a node and the operations
// it performed in some period.
type NodeInfo struct {
ID storj.NodeID
CreatedAt time.Time
Disqualified *time.Time
GracefulExit *time.Time
UsageAtRest float64
UsageGet int64
UsagePut int64
UsageGetRepair int64
UsagePutRepair int64
UsageGetAudit int64
TotalHeld currency.MicroUnit
TotalDisposed currency.MicroUnit
ID storj.NodeID
CreatedAt time.Time
LastContactSuccess time.Time
Disqualified *time.Time
GracefulExit *time.Time
UsageAtRest float64
UsageGet int64
UsagePut int64
UsageGetRepair int64
UsagePutRepair int64
UsageGetAudit int64
TotalHeld currency.MicroUnit
TotalDisposed currency.MicroUnit
}
// Statement is the computed amounts and codes from a node.
@ -98,6 +99,7 @@ type PeriodInfo struct {
// GenerateStatements generates all of the Statements for the given PeriodInfo.
func GenerateStatements(info PeriodInfo) ([]Statement, error) {
startDate := info.Period.StartDate()
endDate := info.Period.EndDateExclusive()
rates := info.Rates
@ -150,6 +152,11 @@ func GenerateStatements(info PeriodInfo) ([]Statement, error) {
codes = append(codes, GracefulExit)
}
offline := node.LastContactSuccess.Before(startDate)
if offline {
codes = append(codes, Offline)
}
withheldPercent, inWithholding := NodeWithheldPercent(withheldPercents, node.CreatedAt, endDate)
held := PercentOf(total, decimal.NewFromInt(int64(withheldPercent)))
owed := total.Sub(held)
@ -177,7 +184,7 @@ func GenerateStatements(info PeriodInfo) ([]Statement, error) {
owed = owed.Add(disposed)
}
// If the node is disqualified nothing is owed/held/disposed.
// If the node is disqualified but not gracefully exited, nothing is owed/held/disposed.
if node.Disqualified != nil && node.Disqualified.Before(endDate) && !gracefullyExited {
codes = append(codes, Disqualified)
disposed = decimal.Zero
@ -185,6 +192,13 @@ func GenerateStatements(info PeriodInfo) ([]Statement, error) {
owed = decimal.Zero
}
// If the node is offline, nothing is owed/held/disposed.
if offline {
disposed = decimal.Zero
held = decimal.Zero
owed = decimal.Zero
}
var overflowErrs errs.Group
toMicroUnit := func(v decimal.Decimal) currency.MicroUnit {
m, err := currency.MicroUnitFromDecimal(v)

View File

@ -52,14 +52,15 @@ func TestGenerateStatements(t *testing.T) {
name: "within withholding",
surgePercent: 0,
node: compensation.NodeInfo{
ID: nodeID,
CreatedAt: time.Date(2019, 11, 2, 0, 0, 0, 0, time.UTC),
UsageAtRest: 1 * GB,
UsageGet: 2 * TB,
UsagePut: 3 * TB,
UsageGetRepair: 4 * TB,
UsagePutRepair: 5 * TB,
UsageGetAudit: 6 * TB,
ID: nodeID,
LastContactSuccess: time.Date(2019, 11, 2, 0, 0, 0, 0, time.UTC),
CreatedAt: time.Date(2019, 11, 2, 0, 0, 0, 0, time.UTC),
UsageAtRest: 1 * GB,
UsageGet: 2 * TB,
UsagePut: 3 * TB,
UsageGetRepair: 4 * TB,
UsagePutRepair: 5 * TB,
UsageGetAudit: 6 * TB,
},
statement: compensation.Statement{
NodeID: nodeID,
@ -79,15 +80,16 @@ func TestGenerateStatements(t *testing.T) {
name: "just out of withheld",
surgePercent: 0,
node: compensation.NodeInfo{
ID: nodeID,
CreatedAt: time.Date(2019, 11, 1, 0, 0, 0, 0, time.UTC),
UsageAtRest: 1 * GB,
UsageGet: 2 * TB,
UsagePut: 3 * TB,
UsageGetRepair: 4 * TB,
UsagePutRepair: 5 * TB,
UsageGetAudit: 6 * TB,
TotalHeld: D(40),
ID: nodeID,
LastContactSuccess: time.Date(2019, 11, 2, 0, 0, 0, 0, time.UTC),
CreatedAt: time.Date(2019, 11, 1, 0, 0, 0, 0, time.UTC),
UsageAtRest: 1 * GB,
UsageGet: 2 * TB,
UsagePut: 3 * TB,
UsageGetRepair: 4 * TB,
UsagePutRepair: 5 * TB,
UsageGetAudit: 6 * TB,
TotalHeld: D(40),
},
statement: compensation.Statement{
NodeID: nodeID,
@ -106,16 +108,17 @@ func TestGenerateStatements(t *testing.T) {
name: "out of withheld and already disposed",
surgePercent: 0,
node: compensation.NodeInfo{
ID: nodeID,
CreatedAt: time.Date(2019, 6, 12, 0, 0, 0, 0, time.UTC),
UsageAtRest: 1 * GB,
UsageGet: 2 * TB,
UsagePut: 3 * TB,
UsageGetRepair: 4 * TB,
UsagePutRepair: 5 * TB,
UsageGetAudit: 6 * TB,
TotalHeld: D(40),
TotalDisposed: D(24),
ID: nodeID,
LastContactSuccess: time.Date(2019, 11, 2, 0, 0, 0, 0, time.UTC),
CreatedAt: time.Date(2019, 6, 12, 0, 0, 0, 0, time.UTC),
UsageAtRest: 1 * GB,
UsageGet: 2 * TB,
UsagePut: 3 * TB,
UsageGetRepair: 4 * TB,
UsagePutRepair: 5 * TB,
UsageGetAudit: 6 * TB,
TotalHeld: D(40),
TotalDisposed: D(24),
},
statement: compensation.Statement{
NodeID: nodeID,
@ -134,17 +137,18 @@ func TestGenerateStatements(t *testing.T) {
name: "graceful exit within period",
surgePercent: 0,
node: compensation.NodeInfo{
ID: nodeID,
CreatedAt: time.Date(2018, 6, 12, 0, 0, 0, 0, time.UTC),
GracefulExit: timePtr(time.Date(2019, 11, 30, 23, 59, 59, 0, time.UTC)),
UsageAtRest: 1 * GB,
UsageGet: 2 * TB,
UsagePut: 3 * TB,
UsageGetRepair: 4 * TB,
UsagePutRepair: 5 * TB,
UsageGetAudit: 6 * TB,
TotalHeld: D(40),
TotalDisposed: D(24),
ID: nodeID,
LastContactSuccess: time.Date(2019, 11, 2, 0, 0, 0, 0, time.UTC),
CreatedAt: time.Date(2018, 6, 12, 0, 0, 0, 0, time.UTC),
GracefulExit: timePtr(time.Date(2019, 11, 30, 23, 59, 59, 0, time.UTC)),
UsageAtRest: 1 * GB,
UsageGet: 2 * TB,
UsagePut: 3 * TB,
UsageGetRepair: 4 * TB,
UsagePutRepair: 5 * TB,
UsageGetAudit: 6 * TB,
TotalHeld: D(40),
TotalDisposed: D(24),
},
statement: compensation.Statement{
NodeID: nodeID,
@ -160,6 +164,34 @@ func TestGenerateStatements(t *testing.T) {
Disposed: D(16),
},
},
{
name: "offline",
surgePercent: 0,
node: compensation.NodeInfo{
ID: nodeID,
LastContactSuccess: time.Date(2019, 10, 2, 0, 0, 0, 0, time.UTC),
CreatedAt: time.Date(2019, 11, 2, 0, 0, 0, 0, time.UTC),
UsageAtRest: 1 * GB,
UsageGet: 2 * TB,
UsagePut: 3 * TB,
UsageGetRepair: 4 * TB,
UsagePutRepair: 5 * TB,
UsageGetAudit: 6 * TB,
},
statement: compensation.Statement{
NodeID: nodeID,
Codes: compensation.Codes{compensation.Offline, compensation.InWithholding},
AtRest: D(2),
Get: D(6),
Put: D(15),
GetRepair: D(28),
PutRepair: D(55),
GetAudit: D(78),
Owed: D(0),
Held: D(0),
Disposed: D(0),
},
},
} {
tt := tt
t.Run(tt.name, func(t *testing.T) {