diff --git a/cmd/neofs-cli/modules/control.go b/cmd/neofs-cli/modules/control.go deleted file mode 100644 index 5735044119..0000000000 --- a/cmd/neofs-cli/modules/control.go +++ /dev/null @@ -1,533 +0,0 @@ -package cmd - -import ( - "crypto/ecdsa" - "errors" - "fmt" - - "github.com/mr-tron/base58" - "github.com/nspcc-dev/neofs-api-go/v2/refs" - rawclient "github.com/nspcc-dev/neofs-api-go/v2/rpc/client" - internalclient "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/client" - "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/common" - "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/commonflags" - "github.com/nspcc-dev/neofs-node/pkg/services/control" - ircontrol "github.com/nspcc-dev/neofs-node/pkg/services/control/ir" - ircontrolsrv "github.com/nspcc-dev/neofs-node/pkg/services/control/ir/server" - controlSvc "github.com/nspcc-dev/neofs-node/pkg/services/control/server" - "github.com/nspcc-dev/neofs-sdk-go/client" - neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" - addressSDK "github.com/nspcc-dev/neofs-sdk-go/object/address" - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -var controlCmd = &cobra.Command{ - Use: "control", - Short: "Operations with storage node", - Long: `Operations with storage node`, - PersistentPreRun: func(cmd *cobra.Command, args []string) { - ff := cmd.Flags() - - _ = viper.BindPFlag(commonflags.WalletPath, ff.Lookup(commonflags.WalletPath)) - _ = viper.BindPFlag(commonflags.Account, ff.Lookup(commonflags.Account)) - _ = viper.BindPFlag(controlRPC, ff.Lookup(controlRPC)) - }, -} - -var healthCheckCmd = &cobra.Command{ - Use: "healthcheck", - Short: "Health check of the NeoFS node", - Long: "Health check of the NeoFS node. Checks storage node by default, use --ir flag to work with Inner Ring.", - Run: healthCheck, -} - -var setNetmapStatusCmd = &cobra.Command{ - Use: "set-status", - Short: "Set status of the storage node in NeoFS network map", - Long: "Set status of the storage node in NeoFS network map", - Run: setNetmapStatus, -} - -var setShardModeCmd = &cobra.Command{ - Use: "set-mode", - Short: "Set work mode of the shard", - Long: "Set work mode of the shard", - Run: setShardMode, -} - -var listShardsCmd = &cobra.Command{ - Use: "list", - Short: "List shards of the storage node", - Long: "List shards of the storage node", - Run: listShards, -} - -var shardsCmd = &cobra.Command{ - Use: "shards", - Short: "Operations with storage node's shards", - Long: "Operations with storage node's shards", -} - -const ( - netmapStatusFlag = "status" - - netmapStatusOnline = "online" - netmapStatusOffline = "offline" - netmapStatusMaintenance = "maintenance" - - shardModeFlag = "mode" - shardIDFlag = "id" - shardClearErrorsFlag = "clear-errors" - - shardModeReadOnly = "read-only" - shardModeReadWrite = "read-write" - shardModeDegraded = "degraded" -) - -const ( - controlRPC = "endpoint" - controlRPCDefault = "" - controlRPCUsage = "remote node control address (as 'multiaddr' or ':')" -) - -// control healthcheck flags -const ( - healthcheckIRFlag = "ir" -) - -// control healthcheck vars -var ( - healthCheckIRVar bool -) - -// control netmap vars -var ( - netmapStatus string -) - -// control shard vars -var ( - shardMode string - shardID string -) - -func initControlHealthCheckCmd() { - commonflags.InitWithoutRPC(healthCheckCmd) - - flags := healthCheckCmd.Flags() - - flags.String(controlRPC, controlRPCDefault, controlRPCUsage) - flags.BoolVar(&healthCheckIRVar, healthcheckIRFlag, false, "Communicate with IR node") -} - -func initControlSetShardModeCmd() { - commonflags.InitWithoutRPC(setShardModeCmd) - - flags := setShardModeCmd.Flags() - - flags.String(controlRPC, controlRPCDefault, controlRPCUsage) - flags.StringVarP(&shardID, shardIDFlag, "", "", "ID of the shard in base58 encoding") - flags.StringVarP(&shardMode, shardModeFlag, "", "", - fmt.Sprintf("new shard mode keyword ('%s', '%s', '%s')", - shardModeReadWrite, - shardModeReadOnly, - shardModeDegraded, - ), - ) - flags.Bool(shardClearErrorsFlag, false, "Set shard error count to 0") -} - -func initControlShardsListCmd() { - commonflags.InitWithoutRPC(listShardsCmd) - - flags := listShardsCmd.Flags() - - flags.String(controlRPC, controlRPCDefault, controlRPCUsage) -} - -func initControlSetNetmapStatusCmd() { - commonflags.InitWithoutRPC(setNetmapStatusCmd) - - flags := setNetmapStatusCmd.Flags() - - flags.String(controlRPC, controlRPCDefault, controlRPCUsage) - flags.StringVarP(&netmapStatus, netmapStatusFlag, "", "", - fmt.Sprintf("new netmap status keyword ('%s', '%s', '%s')", - netmapStatusOnline, - netmapStatusOffline, - netmapStatusMaintenance, - ), - ) - - _ = setNetmapStatusCmd.MarkFlagRequired(netmapStatusFlag) -} - -func initControlDropObjectsCmd() { - commonflags.InitWithoutRPC(dropObjectsCmd) - - flags := dropObjectsCmd.Flags() - - flags.String(controlRPC, controlRPCDefault, controlRPCUsage) - flags.StringSliceVarP(&dropObjectsList, dropObjectsFlag, "o", nil, - "List of object addresses to be removed in string format") - - _ = dropObjectsCmd.MarkFlagRequired(dropObjectsFlag) -} - -func initControlSnapshotCmd() { - commonflags.InitWithoutRPC(snapshotCmd) - - flags := snapshotCmd.Flags() - - flags.String(controlRPC, controlRPCDefault, controlRPCUsage) - flags.BoolVar(&netmapSnapshotJSON, "json", false, - "print netmap structure in JSON format") -} - -func init() { - rootCmd.AddCommand(controlCmd) - - shardsCmd.AddCommand(listShardsCmd) - shardsCmd.AddCommand(setShardModeCmd) - shardsCmd.AddCommand(dumpShardCmd) - shardsCmd.AddCommand(restoreShardCmd) - - controlCmd.AddCommand( - healthCheckCmd, - setNetmapStatusCmd, - dropObjectsCmd, - snapshotCmd, - shardsCmd, - ) - - initControlHealthCheckCmd() - initControlSetNetmapStatusCmd() - initControlDropObjectsCmd() - initControlSnapshotCmd() - initControlShardsListCmd() - initControlSetShardModeCmd() - initControlDumpShardCmd() - initControlRestoreShardCmd() -} - -func verifyResponseControl(cmd *cobra.Command, - sigControl interface { - GetKey() []byte - GetSign() []byte - }, - body interface { - StableMarshal([]byte) ([]byte, error) - }, -) { - if sigControl == nil { - common.ExitOnErr(cmd, "", errors.New("missing response signature")) - } - - bodyData, err := body.StableMarshal(nil) - common.ExitOnErr(cmd, "marshal response body: %w", err) - - // TODO(@cthulhu-rider): #1387 use Signature message from NeoFS API to avoid conversion - var sigV2 refs.Signature - sigV2.SetScheme(refs.ECDSA_SHA512) - sigV2.SetKey(sigControl.GetKey()) - sigV2.SetSign(sigControl.GetSign()) - - var sig neofscrypto.Signature - sig.ReadFromV2(sigV2) - - if !sig.Verify(bodyData) { - common.ExitOnErr(cmd, "", errors.New("invalid response signature")) - } -} - -func healthCheck(cmd *cobra.Command, _ []string) { - key, err := getKeyNoGenerate() - common.ExitOnErr(cmd, "", err) - - cli, err := getControlSDKClient(key) - common.ExitOnErr(cmd, "", err) - - if healthCheckIRVar { - healthCheckIR(cmd, key, cli) - return - } - - req := new(control.HealthCheckRequest) - - req.SetBody(new(control.HealthCheckRequest_Body)) - - err = controlSvc.SignMessage(key, req) - common.ExitOnErr(cmd, "could not sign message: %w", err) - - var resp *control.HealthCheckResponse - err = cli.ExecRaw(func(client *rawclient.Client) error { - resp, err = control.HealthCheck(client, req) - return err - }) - common.ExitOnErr(cmd, "rpc error: %w", err) - - verifyResponseControl(cmd, resp.GetSignature(), resp.GetBody()) - - cmd.Printf("Network status: %s\n", resp.GetBody().GetNetmapStatus()) - cmd.Printf("Health status: %s\n", resp.GetBody().GetHealthStatus()) -} - -func healthCheckIR(cmd *cobra.Command, key *ecdsa.PrivateKey, c *client.Client) { - req := new(ircontrol.HealthCheckRequest) - - req.SetBody(new(ircontrol.HealthCheckRequest_Body)) - - err := ircontrolsrv.SignMessage(key, req) - common.ExitOnErr(cmd, "could not sign request: %w", err) - - var resp *ircontrol.HealthCheckResponse - err = c.ExecRaw(func(client *rawclient.Client) error { - resp, err = ircontrol.HealthCheck(client, req) - return err - }) - common.ExitOnErr(cmd, "rpc error: %w", err) - - verifyResponseControl(cmd, resp.GetSignature(), resp.GetBody()) - - cmd.Printf("Health status: %s\n", resp.GetBody().GetHealthStatus()) -} - -func setNetmapStatus(cmd *cobra.Command, _ []string) { - key, err := getKeyNoGenerate() - common.ExitOnErr(cmd, "", err) - - var status control.NetmapStatus - - switch netmapStatus { - default: - common.ExitOnErr(cmd, "", fmt.Errorf("unsupported status %s", netmapStatus)) - case netmapStatusOnline: - status = control.NetmapStatus_ONLINE - case netmapStatusOffline: - status = control.NetmapStatus_OFFLINE - case netmapStatusMaintenance: - status = control.NetmapStatus_MAINTENANCE - } - - req := new(control.SetNetmapStatusRequest) - - body := new(control.SetNetmapStatusRequest_Body) - req.SetBody(body) - - body.SetStatus(status) - - err = controlSvc.SignMessage(key, req) - common.ExitOnErr(cmd, "could not sign request: %w", err) - - cli, err := getControlSDKClient(key) - common.ExitOnErr(cmd, "", err) - - var resp *control.SetNetmapStatusResponse - err = cli.ExecRaw(func(client *rawclient.Client) error { - resp, err = control.SetNetmapStatus(client, req) - return err - }) - common.ExitOnErr(cmd, "rpc error: %w", err) - - verifyResponseControl(cmd, resp.GetSignature(), resp.GetBody()) - - cmd.Println("Network status update request successfully sent.") -} - -const dropObjectsFlag = "objects" - -var dropObjectsList []string - -var dropObjectsCmd = &cobra.Command{ - Use: "drop-objects", - Short: "Drop objects from the node's local storage", - Long: "Drop objects from the node's local storage", - Run: func(cmd *cobra.Command, args []string) { - key, err := getKeyNoGenerate() - common.ExitOnErr(cmd, "", err) - - binAddrList := make([][]byte, 0, len(dropObjectsList)) - - for i := range dropObjectsList { - a := addressSDK.NewAddress() - - err := a.Parse(dropObjectsList[i]) - if err != nil { - common.ExitOnErr(cmd, "", fmt.Errorf("could not parse address #%d: %w", i, err)) - } - - binAddr, err := a.Marshal() - common.ExitOnErr(cmd, "could not marshal the address: %w", err) - - binAddrList = append(binAddrList, binAddr) - } - - req := new(control.DropObjectsRequest) - - body := new(control.DropObjectsRequest_Body) - req.SetBody(body) - - body.SetAddressList(binAddrList) - - err = controlSvc.SignMessage(key, req) - common.ExitOnErr(cmd, "could not sign request: %w", err) - - cli, err := getControlSDKClient(key) - common.ExitOnErr(cmd, "", err) - - var resp *control.DropObjectsResponse - err = cli.ExecRaw(func(client *rawclient.Client) error { - resp, err = control.DropObjects(client, req) - return err - }) - common.ExitOnErr(cmd, "rpc error: %w", err) - - verifyResponseControl(cmd, resp.GetSignature(), resp.GetBody()) - - cmd.Println("Objects were successfully marked to be removed.") - }, -} - -var snapshotCmd = &cobra.Command{ - Use: "netmap-snapshot", - Short: "Get network map snapshot", - Long: "Get network map snapshot", - Run: func(cmd *cobra.Command, args []string) { - key, err := getKeyNoGenerate() - common.ExitOnErr(cmd, "", err) - - req := new(control.NetmapSnapshotRequest) - req.SetBody(new(control.NetmapSnapshotRequest_Body)) - - err = controlSvc.SignMessage(key, req) - common.ExitOnErr(cmd, "could not sign request: %w", err) - - cli, err := getControlSDKClient(key) - common.ExitOnErr(cmd, "", err) - - var resp *control.NetmapSnapshotResponse - err = cli.ExecRaw(func(client *rawclient.Client) error { - resp, err = control.NetmapSnapshot(client, req) - return err - }) - common.ExitOnErr(cmd, "rpc error: %w", err) - - verifyResponseControl(cmd, resp.GetSignature(), resp.GetBody()) - - prettyPrintNetmap(cmd, resp.GetBody().GetNetmap(), netmapSnapshotJSON) - }, -} - -func listShards(cmd *cobra.Command, _ []string) { - key, err := getKeyNoGenerate() - common.ExitOnErr(cmd, "", err) - - req := new(control.ListShardsRequest) - req.SetBody(new(control.ListShardsRequest_Body)) - - err = controlSvc.SignMessage(key, req) - common.ExitOnErr(cmd, "could not sign request: %w", err) - - cli, err := getControlSDKClient(key) - common.ExitOnErr(cmd, "", err) - - var resp *control.ListShardsResponse - err = cli.ExecRaw(func(client *rawclient.Client) error { - resp, err = control.ListShards(client, req) - return err - }) - common.ExitOnErr(cmd, "rpc error: %w", err) - - verifyResponseControl(cmd, resp.GetSignature(), resp.GetBody()) - - prettyPrintShards(cmd, resp.GetBody().GetShards()) -} - -// getControlSDKClient calls getSDKClientFlag with "endpoint" flag. -func getControlSDKClient(key *ecdsa.PrivateKey) (*client.Client, error) { - return internalclient.GetSDKClientByFlag(key, controlRPC) -} - -func prettyPrintShards(cmd *cobra.Command, ii []*control.ShardInfo) { - for _, i := range ii { - var mode string - - switch i.GetMode() { - case control.ShardMode_READ_WRITE: - mode = "read-write" - case control.ShardMode_READ_ONLY: - mode = "read-only" - case control.ShardMode_DEGRADED: - mode = "degraded" - default: - mode = "unknown" - } - - pathPrinter := func(name, path string) string { - if path == "" { - return "" - } - - return fmt.Sprintf("%s: %s\n", name, path) - } - - cmd.Printf("Shard %s:\nMode: %s\n"+ - pathPrinter("Metabase", i.GetMetabasePath())+ - pathPrinter("Blobstor", i.GetBlobstorPath())+ - pathPrinter("Write-cache", i.GetWritecachePath())+ - fmt.Sprintf("Error count: %d\n", i.GetErrorCount()), - base58.Encode(i.Shard_ID), - mode, - ) - } -} - -func setShardMode(cmd *cobra.Command, _ []string) { - key, err := getKeyNoGenerate() - common.ExitOnErr(cmd, "", err) - - var mode control.ShardMode - - switch shardMode { - default: - common.ExitOnErr(cmd, "", fmt.Errorf("unsupported mode %s", mode)) - case shardModeReadWrite: - mode = control.ShardMode_READ_WRITE - case shardModeReadOnly: - mode = control.ShardMode_READ_ONLY - case shardModeDegraded: - mode = control.ShardMode_DEGRADED - } - - req := new(control.SetShardModeRequest) - - body := new(control.SetShardModeRequest_Body) - req.SetBody(body) - - rawID, err := base58.Decode(shardID) - common.ExitOnErr(cmd, "incorrect shard ID encoding: %w", err) - - body.SetMode(mode) - body.SetShardID(rawID) - - reset, _ := cmd.Flags().GetBool(shardClearErrorsFlag) - body.ClearErrorCounter(reset) - - err = controlSvc.SignMessage(key, req) - common.ExitOnErr(cmd, "could not sign request: %w", err) - - cli, err := getControlSDKClient(key) - common.ExitOnErr(cmd, "", err) - - var resp *control.SetShardModeResponse - err = cli.ExecRaw(func(client *rawclient.Client) error { - resp, err = control.SetShardMode(client, req) - return err - }) - common.ExitOnErr(cmd, "rpc error: %w", err) - - verifyResponseControl(cmd, resp.GetSignature(), resp.GetBody()) - - cmd.Println("Shard mode update request successfully sent.") -} diff --git a/cmd/neofs-cli/modules/control/drop_objects.go b/cmd/neofs-cli/modules/control/drop_objects.go new file mode 100644 index 0000000000..d00b509ef6 --- /dev/null +++ b/cmd/neofs-cli/modules/control/drop_objects.go @@ -0,0 +1,74 @@ +package control + +import ( + "fmt" + + rawclient "github.com/nspcc-dev/neofs-api-go/v2/rpc/client" + "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/common" + "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/commonflags" + "github.com/nspcc-dev/neofs-node/pkg/services/control" + addressSDK "github.com/nspcc-dev/neofs-sdk-go/object/address" + "github.com/spf13/cobra" +) + +const dropObjectsFlag = "objects" + +var dropObjectsCmd = &cobra.Command{ + Use: "drop-objects", + Short: "Drop objects from the node's local storage", + Long: "Drop objects from the node's local storage", + Run: func(cmd *cobra.Command, args []string) { + pk := getKey(cmd) + + dropObjectsList, _ := cmd.Flags().GetStringSlice(dropObjectsFlag) + binAddrList := make([][]byte, 0, len(dropObjectsList)) + + for i := range dropObjectsList { + a := addressSDK.NewAddress() + + err := a.Parse(dropObjectsList[i]) + if err != nil { + common.ExitOnErr(cmd, "", fmt.Errorf("could not parse address #%d: %w", i, err)) + } + + binAddr, err := a.Marshal() + common.ExitOnErr(cmd, "could not marshal the address: %w", err) + + binAddrList = append(binAddrList, binAddr) + } + + body := new(control.DropObjectsRequest_Body) + body.SetAddressList(binAddrList) + + req := new(control.DropObjectsRequest) + req.SetBody(body) + + signRequest(cmd, pk, req) + + cli := getClient(cmd, pk) + + var resp *control.DropObjectsResponse + var err error + err = cli.ExecRaw(func(client *rawclient.Client) error { + resp, err = control.DropObjects(client, req) + return err + }) + common.ExitOnErr(cmd, "rpc error: %w", err) + + verifyResponse(cmd, resp.GetSignature(), resp.GetBody()) + + cmd.Println("Objects were successfully marked to be removed.") + }, +} + +func initControlDropObjectsCmd() { + commonflags.InitWithoutRPC(dropObjectsCmd) + + flags := dropObjectsCmd.Flags() + + flags.String(controlRPC, controlRPCDefault, controlRPCUsage) + flags.StringSliceP(dropObjectsFlag, "o", nil, + "List of object addresses to be removed in string format") + + _ = dropObjectsCmd.MarkFlagRequired(dropObjectsFlag) +} diff --git a/cmd/neofs-cli/modules/control/healthcheck.go b/cmd/neofs-cli/modules/control/healthcheck.go new file mode 100644 index 0000000000..8b8afba7bc --- /dev/null +++ b/cmd/neofs-cli/modules/control/healthcheck.go @@ -0,0 +1,83 @@ +package control + +import ( + "crypto/ecdsa" + + rawclient "github.com/nspcc-dev/neofs-api-go/v2/rpc/client" + "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/common" + "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/commonflags" + "github.com/nspcc-dev/neofs-node/pkg/services/control" + ircontrol "github.com/nspcc-dev/neofs-node/pkg/services/control/ir" + ircontrolsrv "github.com/nspcc-dev/neofs-node/pkg/services/control/ir/server" + "github.com/nspcc-dev/neofs-sdk-go/client" + "github.com/spf13/cobra" +) + +const ( + healthcheckIRFlag = "ir" +) + +var healthCheckCmd = &cobra.Command{ + Use: "healthcheck", + Short: "Health check of the NeoFS node", + Long: "Health check of the NeoFS node. Checks storage node by default, use --ir flag to work with Inner Ring.", + Run: healthCheck, +} + +func initControlHealthCheckCmd() { + commonflags.InitWithoutRPC(healthCheckCmd) + + flags := healthCheckCmd.Flags() + + flags.String(controlRPC, controlRPCDefault, controlRPCUsage) + flags.Bool(healthcheckIRFlag, false, "Communicate with IR node") +} + +func healthCheck(cmd *cobra.Command, _ []string) { + pk := getKey(cmd) + + cli := getClient(cmd, pk) + + if isIR, _ := cmd.Flags().GetBool(healthcheckIRFlag); isIR { + healthCheckIR(cmd, pk, cli) + return + } + + req := new(control.HealthCheckRequest) + req.SetBody(new(control.HealthCheckRequest_Body)) + + signRequest(cmd, pk, req) + + var resp *control.HealthCheckResponse + var err error + err = cli.ExecRaw(func(client *rawclient.Client) error { + resp, err = control.HealthCheck(client, req) + return err + }) + common.ExitOnErr(cmd, "rpc error: %w", err) + + verifyResponse(cmd, resp.GetSignature(), resp.GetBody()) + + cmd.Printf("Network status: %s\n", resp.GetBody().GetNetmapStatus()) + cmd.Printf("Health status: %s\n", resp.GetBody().GetHealthStatus()) +} + +func healthCheckIR(cmd *cobra.Command, key *ecdsa.PrivateKey, c *client.Client) { + req := new(ircontrol.HealthCheckRequest) + + req.SetBody(new(ircontrol.HealthCheckRequest_Body)) + + err := ircontrolsrv.SignMessage(key, req) + common.ExitOnErr(cmd, "could not sign request: %w", err) + + var resp *ircontrol.HealthCheckResponse + err = c.ExecRaw(func(client *rawclient.Client) error { + resp, err = ircontrol.HealthCheck(client, req) + return err + }) + common.ExitOnErr(cmd, "rpc error: %w", err) + + verifyResponse(cmd, resp.GetSignature(), resp.GetBody()) + + cmd.Printf("Health status: %s\n", resp.GetBody().GetHealthStatus()) +} diff --git a/cmd/neofs-cli/modules/control/root.go b/cmd/neofs-cli/modules/control/root.go new file mode 100644 index 0000000000..07041a1ab7 --- /dev/null +++ b/cmd/neofs-cli/modules/control/root.go @@ -0,0 +1,42 @@ +package control + +import ( + "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/commonflags" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var Cmd = &cobra.Command{ + Use: "control", + Short: "Operations with storage node", + Long: `Operations with storage node`, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + ff := cmd.Flags() + + _ = viper.BindPFlag(commonflags.WalletPath, ff.Lookup(commonflags.WalletPath)) + _ = viper.BindPFlag(commonflags.Account, ff.Lookup(commonflags.Account)) + _ = viper.BindPFlag(controlRPC, ff.Lookup(controlRPC)) + }, +} + +const ( + controlRPC = "endpoint" + controlRPCDefault = "" + controlRPCUsage = "remote node control address (as 'multiaddr' or ':')" +) + +func init() { + Cmd.AddCommand( + healthCheckCmd, + setNetmapStatusCmd, + dropObjectsCmd, + snapshotCmd, + shardsCmd, + ) + + initControlHealthCheckCmd() + initControlSetNetmapStatusCmd() + initControlDropObjectsCmd() + initControlSnapshotCmd() + initControlShardsCmd() +} diff --git a/cmd/neofs-cli/modules/control/set_netmap_status.go b/cmd/neofs-cli/modules/control/set_netmap_status.go new file mode 100644 index 0000000000..97c7cc228f --- /dev/null +++ b/cmd/neofs-cli/modules/control/set_netmap_status.go @@ -0,0 +1,82 @@ +package control + +import ( + "fmt" + + rawclient "github.com/nspcc-dev/neofs-api-go/v2/rpc/client" + "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/common" + "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/commonflags" + "github.com/nspcc-dev/neofs-node/pkg/services/control" + "github.com/spf13/cobra" +) + +const ( + netmapStatusFlag = "status" + + netmapStatusOnline = "online" + netmapStatusOffline = "offline" + netmapStatusMaintenance = "maintenance" +) + +var setNetmapStatusCmd = &cobra.Command{ + Use: "set-status", + Short: "Set status of the storage node in NeoFS network map", + Long: "Set status of the storage node in NeoFS network map", + Run: setNetmapStatus, +} + +func initControlSetNetmapStatusCmd() { + commonflags.InitWithoutRPC(setNetmapStatusCmd) + + flags := setNetmapStatusCmd.Flags() + + flags.String(controlRPC, controlRPCDefault, controlRPCUsage) + flags.String(netmapStatusFlag, "", + fmt.Sprintf("new netmap status keyword ('%s', '%s', '%s')", + netmapStatusOnline, + netmapStatusOffline, + netmapStatusMaintenance, + ), + ) + + _ = setNetmapStatusCmd.MarkFlagRequired(netmapStatusFlag) +} + +func setNetmapStatus(cmd *cobra.Command, _ []string) { + pk := getKey(cmd) + + var status control.NetmapStatus + + switch st, _ := cmd.Flags().GetString(netmapStatusFlag); st { + default: + common.ExitOnErr(cmd, "", fmt.Errorf("unsupported status %s", st)) + case netmapStatusOnline: + status = control.NetmapStatus_ONLINE + case netmapStatusOffline: + status = control.NetmapStatus_OFFLINE + case netmapStatusMaintenance: + status = control.NetmapStatus_MAINTENANCE + } + + body := new(control.SetNetmapStatusRequest_Body) + body.SetStatus(status) + + req := new(control.SetNetmapStatusRequest) + req.SetBody(body) + + signRequest(cmd, pk, req) + + cli := getClient(cmd, pk) + + var resp *control.SetNetmapStatusResponse + var err error + err = cli.ExecRaw(func(client *rawclient.Client) error { + resp, err = control.SetNetmapStatus(client, req) + return err + }) + common.ExitOnErr(cmd, "rpc error: %w", err) + + verifyResponse(cmd, resp.GetSignature(), resp.GetBody()) + + cmd.Println("Network status update request successfully sent.") +} diff --git a/cmd/neofs-cli/modules/control/shards.go b/cmd/neofs-cli/modules/control/shards.go new file mode 100644 index 0000000000..91543412c9 --- /dev/null +++ b/cmd/neofs-cli/modules/control/shards.go @@ -0,0 +1,23 @@ +package control + +import ( + "github.com/spf13/cobra" +) + +var shardsCmd = &cobra.Command{ + Use: "shards", + Short: "Operations with storage node's shards", + Long: "Operations with storage node's shards", +} + +func initControlShardsCmd() { + shardsCmd.AddCommand(listShardsCmd) + shardsCmd.AddCommand(setShardModeCmd) + shardsCmd.AddCommand(dumpShardCmd) + shardsCmd.AddCommand(restoreShardCmd) + + initControlShardsListCmd() + initControlSetShardModeCmd() + initControlDumpShardCmd() + initControlRestoreShardCmd() +} diff --git a/cmd/neofs-cli/modules/dump.go b/cmd/neofs-cli/modules/control/shards_dump.go similarity index 71% rename from cmd/neofs-cli/modules/dump.go rename to cmd/neofs-cli/modules/control/shards_dump.go index 907011a44a..7549d53f3c 100644 --- a/cmd/neofs-cli/modules/dump.go +++ b/cmd/neofs-cli/modules/control/shards_dump.go @@ -1,12 +1,10 @@ -package cmd +package control import ( - "github.com/mr-tron/base58" "github.com/nspcc-dev/neofs-api-go/v2/rpc/client" "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/common" "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/commonflags" "github.com/nspcc-dev/neofs-node/pkg/services/control" - controlSvc "github.com/nspcc-dev/neofs-node/pkg/services/control/server" "github.com/spf13/cobra" ) @@ -23,14 +21,10 @@ var dumpShardCmd = &cobra.Command{ } func dumpShard(cmd *cobra.Command, _ []string) { - key, err := getKeyNoGenerate() - common.ExitOnErr(cmd, "", err) + pk := getKey(cmd) body := new(control.DumpShardRequest_Body) - - rawID, err := base58.Decode(shardID) - common.ExitOnErr(cmd, "incorrect shard ID encoding: %w", err) - body.SetShardID(rawID) + body.SetShardID(getShardID(cmd)) p, _ := cmd.Flags().GetString(dumpFilepathFlag) body.SetFilepath(p) @@ -41,20 +35,19 @@ func dumpShard(cmd *cobra.Command, _ []string) { req := new(control.DumpShardRequest) req.SetBody(body) - err = controlSvc.SignMessage(key, req) - common.ExitOnErr(cmd, "could not sign request: %w", err) + signRequest(cmd, pk, req) - cli, err := getControlSDKClient(key) - common.ExitOnErr(cmd, "", err) + cli := getClient(cmd, pk) var resp *control.DumpShardResponse + var err error err = cli.ExecRaw(func(client *client.Client) error { resp, err = control.DumpShard(client, req) return err }) common.ExitOnErr(cmd, "rpc error: %w", err) - verifyResponseControl(cmd, resp.GetSignature(), resp.GetBody()) + verifyResponse(cmd, resp.GetSignature(), resp.GetBody()) cmd.Println("Shard has been dumped successfully.") } @@ -64,7 +57,7 @@ func initControlDumpShardCmd() { flags := dumpShardCmd.Flags() flags.String(controlRPC, controlRPCDefault, controlRPCUsage) - flags.StringVarP(&shardID, shardIDFlag, "", "", "Shard ID in base58 encoding") + flags.String(shardIDFlag, "", "Shard ID in base58 encoding") flags.String(dumpFilepathFlag, "", "File to write objects to") flags.Bool(dumpIgnoreErrorsFlag, false, "Skip invalid/unreadable objects") diff --git a/cmd/neofs-cli/modules/control/shards_list.go b/cmd/neofs-cli/modules/control/shards_list.go new file mode 100644 index 0000000000..0fa3f9f5bb --- /dev/null +++ b/cmd/neofs-cli/modules/control/shards_list.go @@ -0,0 +1,84 @@ +package control + +import ( + "fmt" + + "github.com/mr-tron/base58" + rawclient "github.com/nspcc-dev/neofs-api-go/v2/rpc/client" + "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/common" + "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/commonflags" + "github.com/nspcc-dev/neofs-node/pkg/services/control" + "github.com/spf13/cobra" +) + +var listShardsCmd = &cobra.Command{ + Use: "list", + Short: "List shards of the storage node", + Long: "List shards of the storage node", + Run: listShards, +} + +func initControlShardsListCmd() { + commonflags.InitWithoutRPC(listShardsCmd) + + flags := listShardsCmd.Flags() + + flags.String(controlRPC, controlRPCDefault, controlRPCUsage) +} + +func listShards(cmd *cobra.Command, _ []string) { + pk := getKey(cmd) + + req := new(control.ListShardsRequest) + req.SetBody(new(control.ListShardsRequest_Body)) + + signRequest(cmd, pk, req) + + cli := getClient(cmd, pk) + + var resp *control.ListShardsResponse + var err error + err = cli.ExecRaw(func(client *rawclient.Client) error { + resp, err = control.ListShards(client, req) + return err + }) + common.ExitOnErr(cmd, "rpc error: %w", err) + + verifyResponse(cmd, resp.GetSignature(), resp.GetBody()) + + prettyPrintShards(cmd, resp.GetBody().GetShards()) +} + +func prettyPrintShards(cmd *cobra.Command, ii []*control.ShardInfo) { + for _, i := range ii { + var mode string + + switch i.GetMode() { + case control.ShardMode_READ_WRITE: + mode = "read-write" + case control.ShardMode_READ_ONLY: + mode = "read-only" + case control.ShardMode_DEGRADED: + mode = "degraded" + default: + mode = "unknown" + } + + pathPrinter := func(name, path string) string { + if path == "" { + return "" + } + + return fmt.Sprintf("%s: %s\n", name, path) + } + + cmd.Printf("Shard %s:\nMode: %s\n"+ + pathPrinter("Metabase", i.GetMetabasePath())+ + pathPrinter("Blobstor", i.GetBlobstorPath())+ + pathPrinter("Write-cache", i.GetWritecachePath())+ + fmt.Sprintf("Error count: %d\n", i.GetErrorCount()), + base58.Encode(i.Shard_ID), + mode, + ) + } +} diff --git a/cmd/neofs-cli/modules/restore.go b/cmd/neofs-cli/modules/control/shards_restore.go similarity index 72% rename from cmd/neofs-cli/modules/restore.go rename to cmd/neofs-cli/modules/control/shards_restore.go index fb2c44f5f1..5605becb50 100644 --- a/cmd/neofs-cli/modules/restore.go +++ b/cmd/neofs-cli/modules/control/shards_restore.go @@ -1,12 +1,10 @@ -package cmd +package control import ( - "github.com/mr-tron/base58" "github.com/nspcc-dev/neofs-api-go/v2/rpc/client" "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/common" "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/commonflags" "github.com/nspcc-dev/neofs-node/pkg/services/control" - controlSvc "github.com/nspcc-dev/neofs-node/pkg/services/control/server" "github.com/spf13/cobra" ) @@ -23,14 +21,10 @@ var restoreShardCmd = &cobra.Command{ } func restoreShard(cmd *cobra.Command, _ []string) { - key, err := getKeyNoGenerate() - common.ExitOnErr(cmd, "", err) + pk := getKey(cmd) body := new(control.RestoreShardRequest_Body) - - rawID, err := base58.Decode(shardID) - common.ExitOnErr(cmd, "incorrect shard ID encoding: %w", err) - body.SetShardID(rawID) + body.SetShardID(getShardID(cmd)) p, _ := cmd.Flags().GetString(restoreFilepathFlag) body.SetFilepath(p) @@ -41,20 +35,19 @@ func restoreShard(cmd *cobra.Command, _ []string) { req := new(control.RestoreShardRequest) req.SetBody(body) - err = controlSvc.SignMessage(key, req) - common.ExitOnErr(cmd, "could not sign request: %w", err) + signRequest(cmd, pk, req) - cli, err := getControlSDKClient(key) - common.ExitOnErr(cmd, "", err) + cli := getClient(cmd, pk) var resp *control.RestoreShardResponse + var err error err = cli.ExecRaw(func(client *client.Client) error { resp, err = control.RestoreShard(client, req) return err }) common.ExitOnErr(cmd, "rpc error: %w", err) - verifyResponseControl(cmd, resp.GetSignature(), resp.GetBody()) + verifyResponse(cmd, resp.GetSignature(), resp.GetBody()) cmd.Println("Shard has been restored successfully.") } @@ -64,7 +57,7 @@ func initControlRestoreShardCmd() { flags := restoreShardCmd.Flags() flags.String(controlRPC, controlRPCDefault, controlRPCUsage) - flags.StringVarP(&shardID, shardIDFlag, "", "", "Shard ID in base58 encoding") + flags.String(shardIDFlag, "", "Shard ID in base58 encoding") flags.String(restoreFilepathFlag, "", "File to read objects from") flags.Bool(restoreIgnoreErrorsFlag, false, "Skip invalid/unreadable objects") diff --git a/cmd/neofs-cli/modules/control/shards_set_mode.go b/cmd/neofs-cli/modules/control/shards_set_mode.go new file mode 100644 index 0000000000..90ce0a7505 --- /dev/null +++ b/cmd/neofs-cli/modules/control/shards_set_mode.go @@ -0,0 +1,97 @@ +package control + +import ( + "fmt" + + "github.com/mr-tron/base58" + rawclient "github.com/nspcc-dev/neofs-api-go/v2/rpc/client" + "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/common" + "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/commonflags" + "github.com/nspcc-dev/neofs-node/pkg/services/control" + "github.com/spf13/cobra" +) + +const ( + shardModeFlag = "mode" + shardIDFlag = "id" + shardClearErrorsFlag = "clear-errors" + + shardModeReadOnly = "read-only" + shardModeReadWrite = "read-write" + shardModeDegraded = "degraded" +) + +var setShardModeCmd = &cobra.Command{ + Use: "set-mode", + Short: "Set work mode of the shard", + Long: "Set work mode of the shard", + Run: setShardMode, +} + +func initControlSetShardModeCmd() { + commonflags.InitWithoutRPC(setShardModeCmd) + + flags := setShardModeCmd.Flags() + + flags.String(controlRPC, controlRPCDefault, controlRPCUsage) + flags.String(shardIDFlag, "", "ID of the shard in base58 encoding") + flags.String(shardModeFlag, "", + fmt.Sprintf("new shard mode keyword ('%s', '%s', '%s')", + shardModeReadWrite, + shardModeReadOnly, + shardModeDegraded, + ), + ) + flags.Bool(shardClearErrorsFlag, false, "Set shard error count to 0") +} + +func setShardMode(cmd *cobra.Command, _ []string) { + pk := getKey(cmd) + + var mode control.ShardMode + + switch shardMode, _ := cmd.Flags().GetString(shardModeFlag); shardMode { + default: + common.ExitOnErr(cmd, "", fmt.Errorf("unsupported mode %s", shardMode)) + case shardModeReadWrite: + mode = control.ShardMode_READ_WRITE + case shardModeReadOnly: + mode = control.ShardMode_READ_ONLY + case shardModeDegraded: + mode = control.ShardMode_DEGRADED + } + + req := new(control.SetShardModeRequest) + + body := new(control.SetShardModeRequest_Body) + req.SetBody(body) + + body.SetMode(mode) + body.SetShardID(getShardID(cmd)) + + reset, _ := cmd.Flags().GetBool(shardClearErrorsFlag) + body.ClearErrorCounter(reset) + + signRequest(cmd, pk, req) + + cli := getClient(cmd, pk) + + var resp *control.SetShardModeResponse + var err error + err = cli.ExecRaw(func(client *rawclient.Client) error { + resp, err = control.SetShardMode(client, req) + return err + }) + common.ExitOnErr(cmd, "rpc error: %w", err) + + verifyResponse(cmd, resp.GetSignature(), resp.GetBody()) + + cmd.Println("Shard mode update request successfully sent.") +} + +func getShardID(cmd *cobra.Command) []byte { + sid, _ := cmd.Flags().GetString(shardIDFlag) + raw, err := base58.Decode(sid) + common.ExitOnErr(cmd, "incorrect shard ID encoding: %w", err) + return raw +} diff --git a/cmd/neofs-cli/modules/control/snapshot.go b/cmd/neofs-cli/modules/control/snapshot.go new file mode 100644 index 0000000000..7f2852ed03 --- /dev/null +++ b/cmd/neofs-cli/modules/control/snapshot.go @@ -0,0 +1,73 @@ +package control + +import ( + "github.com/mr-tron/base58" + rawclient "github.com/nspcc-dev/neofs-api-go/v2/rpc/client" + "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/common" + "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/commonflags" + "github.com/nspcc-dev/neofs-node/pkg/services/control" + "github.com/spf13/cobra" +) + +const ( + netmapSnapshotJSONFlag = "json" +) + +var snapshotCmd = &cobra.Command{ + Use: "netmap-snapshot", + Short: "Get network map snapshot", + Long: "Get network map snapshot", + Run: func(cmd *cobra.Command, args []string) { + pk := getKey(cmd) + + req := new(control.NetmapSnapshotRequest) + req.SetBody(new(control.NetmapSnapshotRequest_Body)) + + signRequest(cmd, pk, req) + + cli := getClient(cmd, pk) + + var resp *control.NetmapSnapshotResponse + var err error + err = cli.ExecRaw(func(client *rawclient.Client) error { + resp, err = control.NetmapSnapshot(client, req) + return err + }) + common.ExitOnErr(cmd, "rpc error: %w", err) + + verifyResponse(cmd, resp.GetSignature(), resp.GetBody()) + + isJSON, _ := cmd.Flags().GetBool(netmapSnapshotJSONFlag) + prettyPrintNetmap(cmd, resp.GetBody().GetNetmap(), isJSON) + }, +} + +func initControlSnapshotCmd() { + commonflags.InitWithoutRPC(snapshotCmd) + + flags := snapshotCmd.Flags() + + flags.String(controlRPC, controlRPCDefault, controlRPCUsage) + flags.Bool(netmapSnapshotJSONFlag, false, "print netmap structure in JSON format") +} + +func prettyPrintNetmap(cmd *cobra.Command, nm *control.Netmap, jsonEncoding bool) { + if jsonEncoding { + common.PrettyPrintJSON(cmd, nm, "netmap") + return + } + + cmd.Println("Epoch:", nm.GetEpoch()) + + for i, node := range nm.GetNodes() { + cmd.Printf("Node %d: %s %s %v\n", i+1, + base58.Encode(node.GetPublicKey()), + node.GetState(), + node.GetAddresses(), + ) + + for _, attr := range node.GetAttributes() { + cmd.Printf("\t%s: %s\n", attr.GetKey(), attr.GetValue()) + } + } +} diff --git a/cmd/neofs-cli/modules/control/util.go b/cmd/neofs-cli/modules/control/util.go new file mode 100644 index 0000000000..3ad38e1471 --- /dev/null +++ b/cmd/neofs-cli/modules/control/util.go @@ -0,0 +1,62 @@ +package control + +import ( + "crypto/ecdsa" + "errors" + + "github.com/nspcc-dev/neofs-api-go/v2/refs" + internalclient "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/client" + "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/common" + "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/key" + controlSvc "github.com/nspcc-dev/neofs-node/pkg/services/control/server" + "github.com/nspcc-dev/neofs-sdk-go/client" + neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" + "github.com/spf13/cobra" +) + +func signRequest(cmd *cobra.Command, pk *ecdsa.PrivateKey, req controlSvc.SignedMessage) { + err := controlSvc.SignMessage(pk, req) + common.ExitOnErr(cmd, "could not sign request: %w", err) +} + +func verifyResponse(cmd *cobra.Command, + sigControl interface { + GetKey() []byte + GetSign() []byte + }, + body interface { + StableMarshal([]byte) ([]byte, error) + }, +) { + if sigControl == nil { + common.ExitOnErr(cmd, "", errors.New("missing response signature")) + } + + bodyData, err := body.StableMarshal(nil) + common.ExitOnErr(cmd, "marshal response body: %w", err) + + // TODO(@cthulhu-rider): #1387 use Signature message from NeoFS API to avoid conversion + var sigV2 refs.Signature + sigV2.SetScheme(refs.ECDSA_SHA512) + sigV2.SetKey(sigControl.GetKey()) + sigV2.SetSign(sigControl.GetSign()) + + var sig neofscrypto.Signature + sig.ReadFromV2(sigV2) + + if !sig.Verify(bodyData) { + common.ExitOnErr(cmd, "", errors.New("invalid response signature")) + } +} + +func getClient(cmd *cobra.Command, pk *ecdsa.PrivateKey) *client.Client { + cli, err := internalclient.GetSDKClientByFlag(pk, controlRPC) + common.ExitOnErr(cmd, "", err) + return cli +} + +func getKey(cmd *cobra.Command) *ecdsa.PrivateKey { + pk, err := key.Get() + common.ExitOnErr(cmd, "", err) + return pk +} diff --git a/cmd/neofs-cli/modules/netmap.go b/cmd/neofs-cli/modules/netmap.go index 52478f25e4..736a09f096 100644 --- a/cmd/neofs-cli/modules/netmap.go +++ b/cmd/neofs-cli/modules/netmap.go @@ -4,13 +4,11 @@ import ( "encoding/hex" "time" - "github.com/mr-tron/base58" "github.com/nspcc-dev/neo-go/pkg/config/netmode" internalclient "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/client" "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/common" "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/commonflags" nmClient "github.com/nspcc-dev/neofs-node/pkg/morph/client/netmap" - "github.com/nspcc-dev/neofs-node/pkg/services/control" "github.com/nspcc-dev/neofs-sdk-go/netmap" "github.com/spf13/cobra" ) @@ -203,24 +201,3 @@ func prettyPrintNodeInfo(cmd *cobra.Command, i *netmap.NodeInfo, jsonEncoding bo cmd.Printf("attribute: %s=%s\n", attribute.Key(), attribute.Value()) } } - -func prettyPrintNetmap(cmd *cobra.Command, nm *control.Netmap, jsonEncoding bool) { - if jsonEncoding { - common.PrettyPrintJSON(cmd, nm, "netmap") - return - } - - cmd.Println("Epoch:", nm.GetEpoch()) - - for i, node := range nm.GetNodes() { - cmd.Printf("Node %d: %s %s %v\n", i+1, - base58.Encode(node.GetPublicKey()), - node.GetState(), - node.GetAddresses(), - ) - - for _, attr := range node.GetAttributes() { - cmd.Printf("\t%s: %s\n", attr.GetKey(), attr.GetValue()) - } - } -} diff --git a/cmd/neofs-cli/modules/root.go b/cmd/neofs-cli/modules/root.go index 14b265fbc8..e46b127076 100644 --- a/cmd/neofs-cli/modules/root.go +++ b/cmd/neofs-cli/modules/root.go @@ -15,6 +15,7 @@ import ( accountingCli "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/modules/accounting" "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/modules/acl" bearerCli "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/modules/bearer" + controlCli "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/modules/control" sessionCli "github.com/nspcc-dev/neofs-node/cmd/neofs-cli/modules/session" "github.com/nspcc-dev/neofs-node/misc" "github.com/nspcc-dev/neofs-node/pkg/util/gendoc" @@ -93,6 +94,7 @@ func init() { rootCmd.AddCommand(bearerCli.Cmd) rootCmd.AddCommand(sessionCli.Cmd) rootCmd.AddCommand(accountingCli.Cmd) + rootCmd.AddCommand(controlCli.Cmd) rootCmd.AddCommand(gendoc.Command(rootCmd)) } @@ -142,10 +144,6 @@ func getKey() (*ecdsa.PrivateKey, error) { return key.GetOrGenerate() } -func getKeyNoGenerate() (*ecdsa.PrivateKey, error) { - return key.Get() -} - type clientWithKey interface { SetClient(*client.Client) }