2019-10-11 14:58:12 +01:00
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
package main
import (
"bufio"
"context"
"fmt"
2019-10-15 23:07:32 +01:00
"io"
2019-10-11 14:58:12 +01:00
"os"
"strings"
"text/tabwriter"
"github.com/spf13/cobra"
"github.com/zeebo/errs"
"go.uber.org/zap"
2019-12-27 11:48:47 +00:00
"storj.io/common/memory"
"storj.io/common/rpc"
"storj.io/common/storj"
2023-04-05 22:05:07 +01:00
"storj.io/private/cfgstruct"
2020-03-23 19:18:20 +00:00
"storj.io/private/process"
2020-06-05 21:11:46 +01:00
"storj.io/storj/private/date"
2020-02-25 11:00:31 +00:00
"storj.io/storj/private/prompt"
2023-04-05 22:05:07 +01:00
"storj.io/storj/storagenode"
2020-10-30 13:04:07 +00:00
"storj.io/storj/storagenode/internalpb"
2019-10-11 14:58:12 +01:00
)
2023-04-05 22:05:07 +01:00
type gracefulExitCfg struct {
storagenode . Config
}
func newGracefulExitInitCmd ( f * Factory ) * cobra . Command {
var cfg gracefulExitCfg
cmd := & cobra . Command {
Use : "exit-satellite" ,
Short : "Initiate graceful exit" ,
Long : "Initiate gracefule exit.\n" +
"The command shows the list of the available satellites that can be exited " +
"and ask for choosing one." ,
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
return cmdGracefulExitInit ( cmd , & cfg )
} ,
Annotations : map [ string ] string { "type" : "helper" } ,
}
process . Bind ( cmd , & cfg , f . Defaults , cfgstruct . ConfDir ( f . ConfDir ) )
return cmd
}
func newGracefulExitStatusCmd ( f * Factory ) * cobra . Command {
var cfg gracefulExitCfg
cmd := & cobra . Command {
Use : "exit-status" ,
Short : "Display graceful exit status" ,
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
return cmdGracefulExitStatus ( cmd , & cfg )
} ,
Annotations : map [ string ] string { "type" : "helper" } ,
}
process . Bind ( cmd , & cfg , f . Defaults , cfgstruct . ConfDir ( f . ConfDir ) )
return cmd
}
2019-10-11 14:58:12 +01:00
type gracefulExitClient struct {
conn * rpc . Conn
}
2020-06-05 21:11:46 +01:00
type unavailableSatellite struct {
id storj . NodeID
monthsLeft int
}
2019-10-11 14:58:12 +01:00
func dialGracefulExitClient ( ctx context . Context , address string ) ( * gracefulExitClient , error ) {
conn , err := rpc . NewDefaultDialer ( nil ) . DialAddressUnencrypted ( ctx , address )
if err != nil {
return nil , errs . Wrap ( err )
}
return & gracefulExitClient { conn : conn } , nil
}
2020-10-30 13:04:07 +00:00
func ( client * gracefulExitClient ) getNonExitingSatellites ( ctx context . Context ) ( * internalpb . GetNonExitingSatellitesResponse , error ) {
return internalpb . NewDRPCNodeGracefulExitClient ( client . conn ) . GetNonExitingSatellites ( ctx , & internalpb . GetNonExitingSatellitesRequest { } )
2019-10-11 14:58:12 +01:00
}
2020-10-30 13:04:07 +00:00
func ( client * gracefulExitClient ) initGracefulExit ( ctx context . Context , req * internalpb . InitiateGracefulExitRequest ) ( * internalpb . ExitProgress , error ) {
return internalpb . NewDRPCNodeGracefulExitClient ( client . conn ) . InitiateGracefulExit ( ctx , req )
2019-10-15 23:07:32 +01:00
}
2020-10-30 13:04:07 +00:00
func ( client * gracefulExitClient ) getExitProgress ( ctx context . Context ) ( * internalpb . GetExitProgressResponse , error ) {
return internalpb . NewDRPCNodeGracefulExitClient ( client . conn ) . GetExitProgress ( ctx , & internalpb . GetExitProgressRequest { } )
2019-10-11 14:58:12 +01:00
}
2020-10-30 13:04:07 +00:00
func ( client * gracefulExitClient ) gracefulExitFeasibility ( ctx context . Context , id storj . NodeID ) ( * internalpb . GracefulExitFeasibilityResponse , error ) {
return internalpb . NewDRPCNodeGracefulExitClient ( client . conn ) . GracefulExitFeasibility ( ctx , & internalpb . GracefulExitFeasibilityRequest { NodeId : id } )
2020-06-05 21:11:46 +01:00
}
2019-10-11 14:58:12 +01:00
func ( client * gracefulExitClient ) close ( ) error {
return client . conn . Close ( )
}
2023-04-05 22:05:07 +01:00
func cmdGracefulExitInit ( cmd * cobra . Command , cfg * gracefulExitCfg ) error {
2019-10-11 14:58:12 +01:00
ctx , _ := process . Ctx ( cmd )
2023-04-05 22:05:07 +01:00
ident , err := cfg . Identity . Load ( )
2019-10-11 14:58:12 +01:00
if err != nil {
2020-04-13 10:31:17 +01:00
zap . L ( ) . Fatal ( "Failed to load identity." , zap . Error ( err ) )
2019-10-11 14:58:12 +01:00
} else {
2020-04-13 10:31:17 +01:00
zap . L ( ) . Info ( "Identity loaded." , zap . Stringer ( "Node ID" , ident . ID ) )
2019-10-11 14:58:12 +01:00
}
// display warning message
2020-02-25 11:00:31 +00:00
confirmed , err := prompt . Confirm ( "By starting a graceful exit from a satellite, you will no longer receive new uploads from that satellite.\nThis action can not be undone.\nAre you sure you want to continue? [y/n]\n" )
if err != nil {
return err
}
if ! confirmed {
2019-10-11 14:58:12 +01:00
return nil
}
2023-04-05 22:05:07 +01:00
client , err := dialGracefulExitClient ( ctx , cfg . Server . PrivateAddress )
2019-10-11 14:58:12 +01:00
if err != nil {
return errs . Wrap ( err )
}
defer func ( ) {
if err := client . close ( ) ; err != nil {
2020-04-13 10:31:17 +01:00
zap . L ( ) . Debug ( "Closing graceful exit client failed." , zap . Error ( err ) )
2019-10-11 14:58:12 +01:00
}
} ( )
// get list of satellites
satelliteList , err := client . getNonExitingSatellites ( ctx )
if err != nil {
2019-10-15 23:07:32 +01:00
fmt . Println ( "Can't find any non-exiting satellites." )
2019-10-11 14:58:12 +01:00
return errs . Wrap ( err )
}
2019-10-15 23:07:32 +01:00
if len ( satelliteList . GetSatellites ( ) ) < 1 {
fmt . Println ( "Can't find any non-exiting satellites." )
return nil
}
2019-10-11 14:58:12 +01:00
// display satellite options
w := tabwriter . NewWriter ( os . Stdout , 0 , 0 , 2 , ' ' , 0 )
fmt . Fprintln ( w , "Domain Name\tNode ID\tSpace Used\t" )
for _ , satellite := range satelliteList . GetSatellites ( ) {
2019-10-15 23:07:32 +01:00
fmt . Fprintf ( w , "%s\t%s\t%s\t\n" , satellite . GetDomainName ( ) , satellite . NodeId . String ( ) , memory . Size ( satellite . GetSpaceUsed ( ) ) . Base10String ( ) )
2019-10-11 14:58:12 +01:00
}
2019-10-15 23:07:32 +01:00
fmt . Fprintln ( w , "Please enter a space delimited list of satellite domain names you would like to gracefully exit. Press enter to continue:" )
2019-10-11 14:58:12 +01:00
var selectedSatellite [ ] string
scanner := bufio . NewScanner ( os . Stdin )
for scanner . Scan ( ) {
input := scanner . Text ( )
// parse selected satellite from user input
inputs := strings . Split ( input , " " )
selectedSatellite = append ( selectedSatellite , inputs ... )
break
}
2020-07-16 18:00:23 +01:00
if scanErr := scanner . Err ( ) ; scanErr != nil {
return errs . Wrap ( scanErr )
2019-10-11 14:58:12 +01:00
}
// validate user input
satelliteIDs := make ( [ ] storj . NodeID , 0 , len ( satelliteList . GetSatellites ( ) ) )
for _ , selected := range selectedSatellite {
for _ , satellite := range satelliteList . GetSatellites ( ) {
if satellite . GetDomainName ( ) == selected {
satelliteIDs = append ( satelliteIDs , satellite . NodeId )
}
}
}
2020-06-05 21:11:46 +01:00
return gracefulExitInit ( ctx , satelliteIDs , w , client )
2019-10-15 23:07:32 +01:00
}
2023-04-05 22:05:07 +01:00
func cmdGracefulExitStatus ( cmd * cobra . Command , cfg * gracefulExitCfg ) ( err error ) {
2019-10-15 23:07:32 +01:00
ctx , _ := process . Ctx ( cmd )
2023-04-05 22:05:07 +01:00
ident , err := cfg . Identity . Load ( )
2019-10-15 23:07:32 +01:00
if err != nil {
2020-04-13 10:31:17 +01:00
zap . L ( ) . Fatal ( "Failed to load identity." , zap . Error ( err ) )
2019-10-15 23:07:32 +01:00
} else {
2020-04-13 10:31:17 +01:00
zap . L ( ) . Info ( "Identity loaded." , zap . Stringer ( "Node ID" , ident . ID ) )
2019-10-15 23:07:32 +01:00
}
2023-04-05 22:05:07 +01:00
client , err := dialGracefulExitClient ( ctx , cfg . Server . PrivateAddress )
2019-10-15 23:07:32 +01:00
if err != nil {
return errs . Wrap ( err )
}
defer func ( ) {
if err := client . close ( ) ; err != nil {
2020-04-13 10:31:17 +01:00
zap . L ( ) . Debug ( "Closing graceful exit client failed." , zap . Error ( err ) )
2019-10-11 14:58:12 +01:00
}
2019-10-15 23:07:32 +01:00
} ( )
// call get status to get status for all satellites' that are in exiting
progresses , err := client . getExitProgress ( ctx )
if err != nil {
return errs . Wrap ( err )
2019-10-11 14:58:12 +01:00
}
2019-10-15 23:07:32 +01:00
if len ( progresses . GetProgress ( ) ) < 1 {
fmt . Println ( "No graceful exit in progress." )
return nil
}
// display exit progress
w := tabwriter . NewWriter ( os . Stdout , 0 , 0 , 2 , ' ' , 0 )
defer func ( ) {
err = w . Flush ( )
if err != nil {
err = errs . Wrap ( err )
}
} ( )
displayExitProgress ( w , progresses . GetProgress ( ) )
2019-10-11 14:58:12 +01:00
return nil
}
2019-10-15 23:07:32 +01:00
2020-10-30 13:04:07 +00:00
func displayExitProgress ( w io . Writer , progresses [ ] * internalpb . ExitProgress ) {
2019-11-26 17:32:26 +00:00
fmt . Fprintln ( w , "\nDomain Name\tNode ID\tPercent Complete\tSuccessful\tCompletion Receipt" )
2019-10-15 23:07:32 +01:00
for _ , progress := range progresses {
isSuccessful := "N"
2019-11-26 17:32:26 +00:00
receipt := "N/A"
2019-10-15 23:07:32 +01:00
if progress . Successful {
isSuccessful = "Y"
}
2019-11-26 17:32:26 +00:00
if progress . GetCompletionReceipt ( ) != nil && len ( progress . GetCompletionReceipt ( ) ) > 0 {
receipt = fmt . Sprintf ( "%x" , progress . GetCompletionReceipt ( ) )
}
fmt . Fprintf ( w , "%s\t%s\t%.2f%%\t%s\t%s\t\n" , progress . GetDomainName ( ) , progress . NodeId . String ( ) , progress . GetPercentComplete ( ) , isSuccessful , receipt )
2019-10-15 23:07:32 +01:00
}
}
2020-06-05 21:11:46 +01:00
func gracefulExitInit ( ctx context . Context , satelliteIDs [ ] storj . NodeID , w * tabwriter . Writer , client * gracefulExitClient ) ( err error ) {
if len ( satelliteIDs ) < 1 {
fmt . Println ( "Invalid input. Please use valid satellite domian names." )
return errs . New ( "Invalid satellite domain names" )
}
var satellites [ ] unavailableSatellite
for i := 0 ; i < len ( satelliteIDs ) ; i ++ {
response , err := client . gracefulExitFeasibility ( ctx , satelliteIDs [ i ] )
if err != nil {
return err
}
if ! response . IsAllowed {
left := int ( response . MonthsRequired ) - date . MonthsCountSince ( response . JoinedAt )
satellites = append ( satellites , unavailableSatellite { id : satelliteIDs [ i ] , monthsLeft : left } )
}
}
if satellites != nil {
fmt . Println ( "You are not allowed to initiate graceful exit on satellite for next amount of months:" )
for _ , satellite := range satellites {
fmt . Fprintf ( w , "%s\t%d\n" , satellite . id . String ( ) , satellite . monthsLeft )
}
return errs . New ( "You are not allowed to graceful exit on some of provided satellites" )
}
// save satellites for graceful exit into the db
2020-10-30 13:04:07 +00:00
progresses := make ( [ ] * internalpb . ExitProgress , 0 , len ( satelliteIDs ) )
2020-06-05 21:11:46 +01:00
var errgroup errs . Group
for _ , id := range satelliteIDs {
2020-10-30 13:04:07 +00:00
req := & internalpb . InitiateGracefulExitRequest {
2020-06-05 21:11:46 +01:00
NodeId : id ,
}
resp , err := client . initGracefulExit ( ctx , req )
if err != nil {
zap . L ( ) . Debug ( "Initializing graceful exit failed." , zap . Stringer ( "Satellite ID" , id ) , zap . Error ( err ) )
errgroup . Add ( err )
continue
}
progresses = append ( progresses , resp )
}
if len ( progresses ) < 1 {
fmt . Println ( "Failed to initialize graceful exit. Please try again later." )
return errgroup . Err ( )
}
displayExitProgress ( w , progresses )
err = w . Flush ( )
if err != nil {
return errs . Wrap ( err )
}
return nil
}