payments accesses database directly (#1135)

* move payments command into satellite/main.go

* flag for db connection string in paymentsCmd

* refactor payments to satellite subcommand

* reports command, add payments arg descriptions

* report data prints to stdout unless --out is set

* fix small error in csv columns
This commit is contained in:
Cameron 2019-01-30 16:44:50 -05:00 committed by GitHub
parent 6025f9f19e
commit 1403b15cc7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 146 additions and 0 deletions

View File

@ -10,6 +10,7 @@ import (
"path/filepath"
"sort"
"text/tabwriter"
"time"
"github.com/spf13/cobra"
"github.com/zeebo/errs"
@ -57,6 +58,17 @@ var (
Short: "Repair Queue Diagnostic Tool support",
RunE: cmdQDiag,
}
reportsCmd = &cobra.Command{
Use: "reports",
Short: "Generate a report",
}
paymentsCmd = &cobra.Command{
Use: "payments [start] [end]",
Short: "Generate a payment report for a given period",
Long: "Generate a payment report for a given period. Format dates using YYYY-MM-DD",
Args: cobra.MinimumNArgs(2),
RunE: cmdPayments,
}
runCfg Satellite
setupCfg Satellite
@ -68,6 +80,10 @@ var (
Database string `help:"satellite database connection string" default:"sqlite3://$CONFDIR/master.db"`
QListLimit int `help:"maximum segments that can be requested" default:"1000"`
}
paymentsCfg struct {
Database string `help:"satellite database connection string" default:"sqlite3://$CONFDIR/master.db"`
Output string `help:"destination of report output" default:""`
}
defaultConfDir = fpath.ApplicationDir("storj", "satellite")
// TODO: this path should be defined somewhere else
@ -101,10 +117,13 @@ func init() {
rootCmd.AddCommand(setupCmd)
rootCmd.AddCommand(diagCmd)
rootCmd.AddCommand(qdiagCmd)
rootCmd.AddCommand(reportsCmd)
reportsCmd.AddCommand(paymentsCmd)
cfgstruct.Bind(runCmd.Flags(), &runCfg, cfgstruct.ConfDir(defaultConfDir), cfgstruct.IdentityDir(defaultIdentityDir))
cfgstruct.BindSetup(setupCmd.Flags(), &setupCfg, cfgstruct.ConfDir(defaultConfDir), cfgstruct.IdentityDir(defaultIdentityDir))
cfgstruct.Bind(diagCmd.Flags(), &diagCfg, cfgstruct.ConfDir(defaultConfDir), cfgstruct.IdentityDir(defaultIdentityDir))
cfgstruct.Bind(qdiagCmd.Flags(), &qdiagCfg, cfgstruct.ConfDir(defaultConfDir), cfgstruct.IdentityDir(defaultIdentityDir))
cfgstruct.Bind(paymentsCmd.Flags(), &paymentsCfg, cfgstruct.ConfDir(defaultConfDir), cfgstruct.IdentityDir(defaultIdentityDir))
}
func cmdRun(cmd *cobra.Command, args []string) (err error) {
@ -268,6 +287,42 @@ func cmdQDiag(cmd *cobra.Command, args []string) (err error) {
return w.Flush()
}
func cmdPayments(cmd *cobra.Command, args []string) (err error) {
ctx := process.Ctx(cmd)
layout := "2006-01-02"
start, err := time.Parse(layout, args[0])
if err != nil {
return errs.New("Invalid date format. Please use YYYY-MM-DD")
}
end, err := time.Parse(layout, args[1])
if err != nil {
return errs.New("Invalid date format. Please use YYYY-MM-DD")
}
// Ensure that start date is not after end date
if start.After(end) {
return errs.New("Invalid time period (%v) - (%v)", start, end)
}
// send output to stdout
if paymentsCfg.Output == "" {
return generateCSV(ctx, start, end, os.Stdout)
}
// send output to file
file, err := os.Create(paymentsCfg.Output)
if err != nil {
return err
}
defer func() {
err = errs.Combine(err, file.Close())
}()
return generateCSV(ctx, start, end, file)
}
func main() {
process.Exec(rootCmd)
}

91
cmd/satellite/payments.go Normal file
View File

@ -0,0 +1,91 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
package main
import (
"context"
"encoding/csv"
"fmt"
"io"
"os"
"strconv"
"time"
"github.com/zeebo/errs"
"storj.io/storj/pkg/accounting"
"storj.io/storj/satellite/satellitedb"
)
// generateCSV generates a payment report for all nodes for a given period
func generateCSV(ctx context.Context, start time.Time, end time.Time, output io.Writer) error {
db, err := satellitedb.New(paymentsCfg.Database)
if err != nil {
return errs.New("error connecting to master database on satellite: %+v", err)
}
defer func() {
err = errs.Combine(err, db.Close())
}()
rows, err := db.Accounting().QueryPaymentInfo(ctx, start, end)
if err != nil {
return err
}
w := csv.NewWriter(output)
headers := []string{
"nodeID",
"nodeCreationDate",
"auditSuccessRatio",
"byte-hours:AtRest",
"bytes:BWRepair-GET",
"bytes:BWRepair-PUT",
"bytes:BWAudit",
"bytes:BWGet",
"bytes:BWPut",
"date",
"walletAddress",
}
if err := w.Write(headers); err != nil {
return err
}
for _, row := range rows {
nid := row.NodeID
wallet, err := db.OverlayCache().GetWalletAddress(ctx, nid)
if err != nil {
return err
}
row.Wallet = wallet
record := structToStringSlice(row)
if err := w.Write(record); err != nil {
return err
}
}
if err := w.Error(); err != nil {
return err
}
w.Flush()
if output != os.Stdout {
fmt.Println("Generated payment report")
}
return err
}
func structToStringSlice(s *accounting.CSVRow) []string {
record := []string{
s.NodeID.String(),
s.NodeCreationDate.Format("2006-01-02"),
strconv.FormatFloat(s.AuditSuccessRatio, 'f', 5, 64),
strconv.FormatFloat(s.AtRestTotal, 'f', 5, 64),
strconv.FormatInt(s.GetRepairTotal, 10),
strconv.FormatInt(s.PutRepairTotal, 10),
strconv.FormatInt(s.GetAuditTotal, 10),
strconv.FormatInt(s.GetTotal, 10),
strconv.FormatInt(s.PutTotal, 10),
s.Date.Format("2006-01-02"),
s.Wallet,
}
return record
}