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{ nodeInfo := compensation.NodeInfo{
ID: usage.NodeID, ID: usage.NodeID,
CreatedAt: node.CreatedAt, CreatedAt: node.CreatedAt,
Disqualified: node.Disqualified, LastContactSuccess: node.Reputation.LastContactSuccess,
GracefulExit: gracefulExit, Disqualified: node.Disqualified,
UsageAtRest: usage.AtRestTotal, GracefulExit: gracefulExit,
UsageGet: usage.GetTotal, UsageAtRest: usage.AtRestTotal,
UsagePut: usage.PutTotal, UsageGet: usage.GetTotal,
UsageGetRepair: usage.GetRepairTotal, UsagePut: usage.PutTotal,
UsagePutRepair: usage.PutRepairTotal, UsageGetRepair: usage.GetRepairTotal,
UsageGetAudit: usage.GetAuditTotal, UsagePutRepair: usage.PutRepairTotal,
TotalHeld: withheldAmounts.TotalHeld, UsageGetAudit: usage.GetAuditTotal,
TotalDisposed: withheldAmounts.TotalDisposed, TotalHeld: withheldAmounts.TotalHeld,
TotalDisposed: withheldAmounts.TotalDisposed,
} }
invoice := compensation.Invoice{ invoice := compensation.Invoice{

View File

@ -26,13 +26,17 @@ const (
// GracefulExit is included if the node has gracefully exited. // GracefulExit is included if the node has gracefully exited.
GracefulExit Code = "X" 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. // CodeFromString parses the string into a Code.
func CodeFromString(s string) (Code, error) { func CodeFromString(s string) (Code, error) {
code := Code(s) code := Code(s)
switch code { switch code {
case Disqualified, Sanctioned, No1099, InWithholding, GracefulExit: case Disqualified, Sanctioned, No1099, InWithholding, GracefulExit, Offline:
return code, nil return code, nil
default: default:
return "", Error.New("no such code %q", code) 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 // NodeInfo contains all of the information about a node and the operations
// it performed in some period. // it performed in some period.
type NodeInfo struct { type NodeInfo struct {
ID storj.NodeID ID storj.NodeID
CreatedAt time.Time CreatedAt time.Time
Disqualified *time.Time LastContactSuccess time.Time
GracefulExit *time.Time Disqualified *time.Time
UsageAtRest float64 GracefulExit *time.Time
UsageGet int64 UsageAtRest float64
UsagePut int64 UsageGet int64
UsageGetRepair int64 UsagePut int64
UsagePutRepair int64 UsageGetRepair int64
UsageGetAudit int64 UsagePutRepair int64
TotalHeld currency.MicroUnit UsageGetAudit int64
TotalDisposed currency.MicroUnit TotalHeld currency.MicroUnit
TotalDisposed currency.MicroUnit
} }
// Statement is the computed amounts and codes from a node. // 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. // GenerateStatements generates all of the Statements for the given PeriodInfo.
func GenerateStatements(info PeriodInfo) ([]Statement, error) { func GenerateStatements(info PeriodInfo) ([]Statement, error) {
startDate := info.Period.StartDate()
endDate := info.Period.EndDateExclusive() endDate := info.Period.EndDateExclusive()
rates := info.Rates rates := info.Rates
@ -150,6 +152,11 @@ func GenerateStatements(info PeriodInfo) ([]Statement, error) {
codes = append(codes, GracefulExit) codes = append(codes, GracefulExit)
} }
offline := node.LastContactSuccess.Before(startDate)
if offline {
codes = append(codes, Offline)
}
withheldPercent, inWithholding := NodeWithheldPercent(withheldPercents, node.CreatedAt, endDate) withheldPercent, inWithholding := NodeWithheldPercent(withheldPercents, node.CreatedAt, endDate)
held := PercentOf(total, decimal.NewFromInt(int64(withheldPercent))) held := PercentOf(total, decimal.NewFromInt(int64(withheldPercent)))
owed := total.Sub(held) owed := total.Sub(held)
@ -177,7 +184,7 @@ func GenerateStatements(info PeriodInfo) ([]Statement, error) {
owed = owed.Add(disposed) 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 { if node.Disqualified != nil && node.Disqualified.Before(endDate) && !gracefullyExited {
codes = append(codes, Disqualified) codes = append(codes, Disqualified)
disposed = decimal.Zero disposed = decimal.Zero
@ -185,6 +192,13 @@ func GenerateStatements(info PeriodInfo) ([]Statement, error) {
owed = decimal.Zero 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 var overflowErrs errs.Group
toMicroUnit := func(v decimal.Decimal) currency.MicroUnit { toMicroUnit := func(v decimal.Decimal) currency.MicroUnit {
m, err := currency.MicroUnitFromDecimal(v) m, err := currency.MicroUnitFromDecimal(v)

View File

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