2018-07-27 22:11:44 +01:00
|
|
|
// Copyright (C) 2018 Storj Labs, Inc.
|
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
|
|
|
package statdb
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2018-10-31 16:18:51 +00:00
|
|
|
"database/sql"
|
2018-07-27 22:11:44 +01:00
|
|
|
"strings"
|
|
|
|
|
2018-11-15 00:03:19 +00:00
|
|
|
"github.com/zeebo/errs"
|
2018-07-27 22:11:44 +01:00
|
|
|
"go.uber.org/zap"
|
|
|
|
"google.golang.org/grpc/codes"
|
|
|
|
"google.golang.org/grpc/status"
|
2018-10-08 23:15:54 +01:00
|
|
|
monkit "gopkg.in/spacemonkeygo/monkit.v2"
|
2018-07-27 22:11:44 +01:00
|
|
|
|
2018-11-09 22:15:35 +00:00
|
|
|
"storj.io/storj/internal/migrate"
|
2018-09-10 15:23:08 +01:00
|
|
|
"storj.io/storj/pkg/pointerdb/auth"
|
2018-07-27 22:11:44 +01:00
|
|
|
dbx "storj.io/storj/pkg/statdb/dbx"
|
|
|
|
pb "storj.io/storj/pkg/statdb/proto"
|
|
|
|
)
|
|
|
|
|
2018-10-08 23:15:54 +01:00
|
|
|
var (
|
2018-11-15 00:03:19 +00:00
|
|
|
mon = monkit.Package()
|
|
|
|
errAuditSuccess = errs.Class("statdb audit success error")
|
|
|
|
errUptime = errs.Class("statdb uptime error")
|
2018-10-08 23:15:54 +01:00
|
|
|
)
|
|
|
|
|
2018-07-27 22:11:44 +01:00
|
|
|
// Server implements the statdb RPC service
|
|
|
|
type Server struct {
|
|
|
|
DB *dbx.DB
|
|
|
|
logger *zap.Logger
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewServer creates instance of Server
|
|
|
|
func NewServer(driver, source string, logger *zap.Logger) (*Server, error) {
|
|
|
|
db, err := dbx.Open(driver, source)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-11-09 22:15:35 +00:00
|
|
|
err = migrate.Create("statdb", db)
|
|
|
|
if err != nil {
|
2018-07-27 22:11:44 +01:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &Server{
|
|
|
|
DB: db,
|
|
|
|
logger: logger,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Server) validateAuth(APIKeyBytes []byte) error {
|
|
|
|
if !auth.ValidateAPIKey(string(APIKeyBytes)) {
|
2018-08-27 18:28:16 +01:00
|
|
|
s.logger.Error("unauthorized request: ", zap.Error(status.Errorf(codes.Unauthenticated, "Invalid API credential")))
|
|
|
|
return status.Errorf(codes.Unauthenticated, "Invalid API credential")
|
2018-07-27 22:11:44 +01:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-08-25 02:52:58 +01:00
|
|
|
// Create a db entry for the provided storagenode
|
2018-07-27 22:11:44 +01:00
|
|
|
func (s *Server) Create(ctx context.Context, createReq *pb.CreateRequest) (resp *pb.CreateResponse, err error) {
|
2018-10-08 23:15:54 +01:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
2018-07-27 22:11:44 +01:00
|
|
|
s.logger.Debug("entering statdb Create")
|
|
|
|
|
2018-08-27 18:28:16 +01:00
|
|
|
APIKeyBytes := createReq.APIKey
|
2018-07-27 22:11:44 +01:00
|
|
|
if err := s.validateAuth(APIKeyBytes); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-11-15 00:03:19 +00:00
|
|
|
var (
|
|
|
|
totalAuditCount int64
|
|
|
|
auditSuccessCount int64
|
|
|
|
auditSuccessRatio float64
|
|
|
|
totalUptimeCount int64
|
|
|
|
uptimeSuccessCount int64
|
|
|
|
uptimeRatio float64
|
|
|
|
)
|
2018-07-27 22:11:44 +01:00
|
|
|
|
2018-11-15 00:03:19 +00:00
|
|
|
stats := createReq.Stats
|
|
|
|
if stats != nil {
|
|
|
|
totalAuditCount = stats.AuditCount
|
|
|
|
auditSuccessCount = stats.AuditSuccessCount
|
|
|
|
auditSuccessRatio, err = checkRatioVars(auditSuccessCount, totalAuditCount)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errAuditSuccess.Wrap(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
totalUptimeCount = stats.UptimeCount
|
|
|
|
uptimeSuccessCount = stats.UptimeSuccessCount
|
|
|
|
uptimeRatio, err = checkRatioVars(uptimeSuccessCount, totalUptimeCount)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errUptime.Wrap(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
node := createReq.Node
|
2018-07-27 22:11:44 +01:00
|
|
|
|
|
|
|
dbNode, err := s.DB.Create_Node(
|
|
|
|
ctx,
|
2018-10-31 16:18:51 +00:00
|
|
|
dbx.Node_Id(node.NodeId),
|
2018-07-27 22:11:44 +01:00
|
|
|
dbx.Node_AuditSuccessCount(auditSuccessCount),
|
|
|
|
dbx.Node_TotalAuditCount(totalAuditCount),
|
|
|
|
dbx.Node_AuditSuccessRatio(auditSuccessRatio),
|
|
|
|
dbx.Node_UptimeSuccessCount(uptimeSuccessCount),
|
|
|
|
dbx.Node_TotalUptimeCount(totalUptimeCount),
|
|
|
|
dbx.Node_UptimeRatio(uptimeRatio),
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, status.Errorf(codes.Internal, err.Error())
|
|
|
|
}
|
|
|
|
s.logger.Debug("created in the db: " + string(node.NodeId))
|
|
|
|
|
|
|
|
nodeStats := &pb.NodeStats{
|
2018-10-31 16:18:51 +00:00
|
|
|
NodeId: dbNode.Id,
|
2018-11-15 00:03:19 +00:00
|
|
|
AuditCount: dbNode.TotalAuditCount,
|
2018-07-27 22:11:44 +01:00
|
|
|
AuditSuccessRatio: dbNode.AuditSuccessRatio,
|
|
|
|
UptimeRatio: dbNode.UptimeRatio,
|
|
|
|
}
|
|
|
|
return &pb.CreateResponse{
|
|
|
|
Stats: nodeStats,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2018-08-25 02:52:58 +01:00
|
|
|
// Get a storagenode's stats from the db
|
2018-07-27 22:11:44 +01:00
|
|
|
func (s *Server) Get(ctx context.Context, getReq *pb.GetRequest) (resp *pb.GetResponse, err error) {
|
2018-10-08 23:15:54 +01:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
2018-07-27 22:11:44 +01:00
|
|
|
s.logger.Debug("entering statdb Get")
|
|
|
|
|
2018-08-27 18:28:16 +01:00
|
|
|
APIKeyBytes := getReq.APIKey
|
2018-07-27 22:11:44 +01:00
|
|
|
err = s.validateAuth(APIKeyBytes)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-10-31 16:18:51 +00:00
|
|
|
dbNode, err := s.DB.Get_Node_By_Id(ctx, dbx.Node_Id(getReq.NodeId))
|
2018-07-27 22:11:44 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, status.Errorf(codes.Internal, err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
nodeStats := &pb.NodeStats{
|
2018-10-31 16:18:51 +00:00
|
|
|
NodeId: dbNode.Id,
|
2018-11-15 00:03:19 +00:00
|
|
|
AuditCount: dbNode.TotalAuditCount,
|
2018-07-27 22:11:44 +01:00
|
|
|
AuditSuccessRatio: dbNode.AuditSuccessRatio,
|
|
|
|
UptimeRatio: dbNode.UptimeRatio,
|
|
|
|
}
|
|
|
|
return &pb.GetResponse{
|
|
|
|
Stats: nodeStats,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2018-10-30 17:11:22 +00:00
|
|
|
// FindValidNodes finds a subset of storagenodes that meet reputation requirements
|
|
|
|
func (s *Server) FindValidNodes(ctx context.Context, getReq *pb.FindValidNodesRequest) (resp *pb.FindValidNodesResponse, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
s.logger.Debug("entering statdb FindValidNodes")
|
|
|
|
|
|
|
|
passedIds := [][]byte{}
|
|
|
|
|
|
|
|
nodeIds := getReq.NodeIds
|
|
|
|
minAuditCount := getReq.MinStats.AuditCount
|
|
|
|
minAuditSuccess := getReq.MinStats.AuditSuccessRatio
|
|
|
|
minUptime := getReq.MinStats.UptimeRatio
|
|
|
|
|
2018-10-31 16:18:51 +00:00
|
|
|
rows, err := s.findValidNodesQuery(nodeIds, minAuditCount, minAuditSuccess, minUptime)
|
2018-10-30 17:11:22 +00:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
err = rows.Close()
|
|
|
|
if err != nil {
|
|
|
|
s.logger.Error(err.Error())
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
for rows.Next() {
|
|
|
|
node := &dbx.Node{}
|
|
|
|
err = rows.Scan(&node.Id, &node.TotalAuditCount, &node.AuditSuccessRatio, &node.UptimeRatio, &node.CreatedAt)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-10-31 16:18:51 +00:00
|
|
|
passedIds = append(passedIds, node.Id)
|
2018-10-30 17:11:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return &pb.FindValidNodesResponse{
|
|
|
|
PassedIds: passedIds,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2018-10-31 16:18:51 +00:00
|
|
|
func (s *Server) findValidNodesQuery(nodeIds [][]byte, auditCount int64, auditSuccess, uptime float64) (*sql.Rows, error) {
|
|
|
|
args := make([]interface{}, len(nodeIds))
|
2018-10-30 17:11:22 +00:00
|
|
|
for i, id := range nodeIds {
|
2018-10-31 16:18:51 +00:00
|
|
|
args[i] = id
|
2018-10-30 17:11:22 +00:00
|
|
|
}
|
2018-10-31 16:18:51 +00:00
|
|
|
args = append(args, auditCount, auditSuccess, uptime)
|
|
|
|
|
|
|
|
rows, err := s.DB.Query(`SELECT nodes.id, nodes.total_audit_count,
|
|
|
|
nodes.audit_success_ratio, nodes.uptime_ratio, nodes.created_at
|
2018-10-30 17:11:22 +00:00
|
|
|
FROM nodes
|
2018-10-31 16:18:51 +00:00
|
|
|
WHERE nodes.id IN (?`+strings.Repeat(", ?", len(nodeIds)-1)+`)
|
|
|
|
AND nodes.total_audit_count >= ?
|
|
|
|
AND nodes.audit_success_ratio >= ?
|
|
|
|
AND nodes.uptime_ratio >= ?`, args...)
|
2018-10-30 17:11:22 +00:00
|
|
|
|
2018-10-31 16:18:51 +00:00
|
|
|
return rows, err
|
2018-10-30 17:11:22 +00:00
|
|
|
}
|
|
|
|
|
2018-08-25 02:52:58 +01:00
|
|
|
// Update a single storagenode's stats in the db
|
2018-07-27 22:11:44 +01:00
|
|
|
func (s *Server) Update(ctx context.Context, updateReq *pb.UpdateRequest) (resp *pb.UpdateResponse, err error) {
|
2018-10-08 23:15:54 +01:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
2018-07-27 22:11:44 +01:00
|
|
|
s.logger.Debug("entering statdb Update")
|
|
|
|
|
2018-08-27 18:28:16 +01:00
|
|
|
APIKeyBytes := updateReq.APIKey
|
2018-07-27 22:11:44 +01:00
|
|
|
err = s.validateAuth(APIKeyBytes)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-10-16 18:40:34 +01:00
|
|
|
node := updateReq.GetNode()
|
|
|
|
|
|
|
|
createIfReq := &pb.CreateEntryIfNotExistsRequest{
|
|
|
|
Node: updateReq.GetNode(),
|
|
|
|
APIKey: APIKeyBytes,
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = s.CreateEntryIfNotExists(ctx, createIfReq)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-07-27 22:11:44 +01:00
|
|
|
|
2018-10-31 16:18:51 +00:00
|
|
|
dbNode, err := s.DB.Get_Node_By_Id(ctx, dbx.Node_Id(node.NodeId))
|
2018-07-27 22:11:44 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, status.Errorf(codes.Internal, err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
auditSuccessCount := dbNode.AuditSuccessCount
|
|
|
|
totalAuditCount := dbNode.TotalAuditCount
|
2018-08-27 18:28:16 +01:00
|
|
|
var auditSuccessRatio float64
|
2018-10-08 23:15:54 +01:00
|
|
|
uptimeSuccessCount := dbNode.UptimeSuccessCount
|
2018-07-27 22:11:44 +01:00
|
|
|
totalUptimeCount := dbNode.TotalUptimeCount
|
2018-08-27 18:28:16 +01:00
|
|
|
var uptimeRatio float64
|
2018-07-27 22:11:44 +01:00
|
|
|
|
|
|
|
updateFields := dbx.Node_Update_Fields{}
|
|
|
|
|
|
|
|
if node.UpdateAuditSuccess {
|
|
|
|
auditSuccessCount, totalAuditCount, auditSuccessRatio = updateRatioVars(
|
|
|
|
node.AuditSuccess,
|
|
|
|
auditSuccessCount,
|
|
|
|
totalAuditCount,
|
|
|
|
)
|
|
|
|
|
|
|
|
updateFields.AuditSuccessCount = dbx.Node_AuditSuccessCount(auditSuccessCount)
|
|
|
|
updateFields.TotalAuditCount = dbx.Node_TotalAuditCount(totalAuditCount)
|
|
|
|
updateFields.AuditSuccessRatio = dbx.Node_AuditSuccessRatio(auditSuccessRatio)
|
|
|
|
}
|
|
|
|
if node.UpdateUptime {
|
|
|
|
uptimeSuccessCount, totalUptimeCount, uptimeRatio = updateRatioVars(
|
|
|
|
node.IsUp,
|
|
|
|
uptimeSuccessCount,
|
|
|
|
totalUptimeCount,
|
|
|
|
)
|
|
|
|
|
|
|
|
updateFields.UptimeSuccessCount = dbx.Node_UptimeSuccessCount(uptimeSuccessCount)
|
|
|
|
updateFields.TotalUptimeCount = dbx.Node_TotalUptimeCount(totalUptimeCount)
|
|
|
|
updateFields.UptimeRatio = dbx.Node_UptimeRatio(uptimeRatio)
|
|
|
|
}
|
|
|
|
|
2018-10-31 16:18:51 +00:00
|
|
|
dbNode, err = s.DB.Update_Node_By_Id(ctx, dbx.Node_Id(node.NodeId), updateFields)
|
2018-07-27 22:11:44 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, status.Errorf(codes.Internal, err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
nodeStats := &pb.NodeStats{
|
2018-10-31 16:18:51 +00:00
|
|
|
NodeId: dbNode.Id,
|
2018-07-27 22:11:44 +01:00
|
|
|
AuditSuccessRatio: dbNode.AuditSuccessRatio,
|
|
|
|
UptimeRatio: dbNode.UptimeRatio,
|
|
|
|
}
|
|
|
|
return &pb.UpdateResponse{
|
|
|
|
Stats: nodeStats,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2018-10-16 18:40:34 +01:00
|
|
|
// UpdateBatch for updating multiple farmers' stats in the db
|
2018-07-27 22:11:44 +01:00
|
|
|
func (s *Server) UpdateBatch(ctx context.Context, updateBatchReq *pb.UpdateBatchRequest) (resp *pb.UpdateBatchResponse, err error) {
|
2018-10-08 23:15:54 +01:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
2018-07-27 22:11:44 +01:00
|
|
|
s.logger.Debug("entering statdb UpdateBatch")
|
|
|
|
|
2018-08-27 18:28:16 +01:00
|
|
|
APIKeyBytes := updateBatchReq.APIKey
|
2018-10-16 18:40:34 +01:00
|
|
|
var nodeStatsList []*pb.NodeStats
|
|
|
|
var failedNodes []*pb.Node
|
|
|
|
for _, node := range updateBatchReq.NodeList {
|
2018-07-27 22:11:44 +01:00
|
|
|
updateReq := &pb.UpdateRequest{
|
|
|
|
Node: node,
|
|
|
|
APIKey: APIKeyBytes,
|
|
|
|
}
|
|
|
|
|
|
|
|
updateRes, err := s.Update(ctx, updateReq)
|
|
|
|
if err != nil {
|
2018-10-16 18:40:34 +01:00
|
|
|
s.logger.Error(err.Error())
|
|
|
|
failedNodes = append(failedNodes, node)
|
|
|
|
} else {
|
|
|
|
nodeStatsList = append(nodeStatsList, updateRes.Stats)
|
2018-07-27 22:11:44 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
updateBatchRes := &pb.UpdateBatchResponse{
|
2018-10-16 18:40:34 +01:00
|
|
|
FailedNodes: failedNodes,
|
|
|
|
StatsList: nodeStatsList,
|
2018-07-27 22:11:44 +01:00
|
|
|
}
|
|
|
|
return updateBatchRes, nil
|
|
|
|
}
|
|
|
|
|
2018-10-16 18:40:34 +01:00
|
|
|
// CreateEntryIfNotExists creates a statdb node entry and saves to statdb if it didn't already exist
|
|
|
|
func (s *Server) CreateEntryIfNotExists(ctx context.Context, createIfReq *pb.CreateEntryIfNotExistsRequest) (resp *pb.CreateEntryIfNotExistsResponse, err error) {
|
|
|
|
APIKeyBytes := createIfReq.APIKey
|
2018-11-15 00:03:19 +00:00
|
|
|
|
2018-10-16 18:40:34 +01:00
|
|
|
getReq := &pb.GetRequest{
|
|
|
|
NodeId: createIfReq.Node.NodeId,
|
|
|
|
APIKey: APIKeyBytes,
|
|
|
|
}
|
|
|
|
getRes, err := s.Get(ctx, getReq)
|
2018-11-15 00:03:19 +00:00
|
|
|
// TODO: figure out better way to confirm error is type dbx.ErrorCode_NoRows
|
|
|
|
if err != nil && strings.Contains(err.Error(), "no rows in result set") {
|
|
|
|
createReq := &pb.CreateRequest{
|
|
|
|
Node: createIfReq.Node,
|
|
|
|
APIKey: APIKeyBytes,
|
|
|
|
}
|
|
|
|
res, err := s.Create(ctx, createReq)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2018-10-16 18:40:34 +01:00
|
|
|
}
|
2018-11-15 00:03:19 +00:00
|
|
|
createEntryIfNotExistsRes := &pb.CreateEntryIfNotExistsResponse{
|
|
|
|
Stats: res.Stats,
|
|
|
|
}
|
|
|
|
return createEntryIfNotExistsRes, nil
|
|
|
|
}
|
|
|
|
if err != nil {
|
2018-10-16 18:40:34 +01:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
createEntryIfNotExistsRes := &pb.CreateEntryIfNotExistsResponse{
|
|
|
|
Stats: getRes.Stats,
|
|
|
|
}
|
|
|
|
return createEntryIfNotExistsRes, nil
|
|
|
|
}
|
|
|
|
|
2018-07-27 22:11:44 +01:00
|
|
|
func updateRatioVars(newStatus bool, successCount, totalCount int64) (int64, int64, float64) {
|
|
|
|
totalCount++
|
|
|
|
if newStatus {
|
|
|
|
successCount++
|
|
|
|
}
|
|
|
|
newRatio := float64(successCount) / float64(totalCount)
|
|
|
|
return successCount, totalCount, newRatio
|
|
|
|
}
|
2018-11-15 00:03:19 +00:00
|
|
|
|
|
|
|
func checkRatioVars(successCount, totalCount int64) (ratio float64, err error) {
|
|
|
|
if successCount < 0 {
|
|
|
|
return 0, errs.New("success count less than 0")
|
|
|
|
}
|
|
|
|
if totalCount < 0 {
|
|
|
|
return 0, errs.New("total count less than 0")
|
|
|
|
}
|
|
|
|
if successCount > totalCount {
|
|
|
|
return 0, errs.New("success count greater than total count")
|
|
|
|
}
|
|
|
|
|
|
|
|
ratio = float64(successCount) / float64(totalCount)
|
|
|
|
return ratio, nil
|
|
|
|
}
|