From 8e5b7cb98e9a18e12215fd224cb48fd865b99e3a Mon Sep 17 00:00:00 2001 From: Alejandro Lopez Date: Wed, 3 May 2023 16:19:46 +0300 Subject: [PATCH] [#114] Add remove-node IR control command Signed-off-by: Alejandro Lopez --- cmd/frostfs-cli/modules/control/ir.go | 2 + .../modules/control/ir_remove_node.go | 58 ++++++++++++++++++ .../client/netmap/{add_peer.go => peer.go} | 16 +++++ pkg/services/control/ir/rpc.go | 9 +++ pkg/services/control/ir/server/calls.go | 42 +++++++++++++ pkg/services/control/ir/service.go | 12 ++++ pkg/services/control/ir/service.pb.go | Bin 23887 -> 33564 bytes pkg/services/control/ir/service.proto | 18 ++++++ pkg/services/control/ir/service_frostfs.pb.go | Bin 10125 -> 15200 bytes pkg/services/control/ir/service_grpc.pb.go | Bin 5434 -> 7044 bytes 10 files changed, 157 insertions(+) create mode 100644 cmd/frostfs-cli/modules/control/ir_remove_node.go rename pkg/morph/client/netmap/{add_peer.go => peer.go} (70%) diff --git a/cmd/frostfs-cli/modules/control/ir.go b/cmd/frostfs-cli/modules/control/ir.go index e89dda076..ae3f8502c 100644 --- a/cmd/frostfs-cli/modules/control/ir.go +++ b/cmd/frostfs-cli/modules/control/ir.go @@ -10,6 +10,8 @@ var irCmd = &cobra.Command{ func initControlIRCmd() { irCmd.AddCommand(tickEpochCmd) + irCmd.AddCommand(removeNodeCmd) initControlIRTickEpochCmd() + initControlIRRemoveNodeCmd() } diff --git a/cmd/frostfs-cli/modules/control/ir_remove_node.go b/cmd/frostfs-cli/modules/control/ir_remove_node.go new file mode 100644 index 000000000..f5b968b7f --- /dev/null +++ b/cmd/frostfs-cli/modules/control/ir_remove_node.go @@ -0,0 +1,58 @@ +package control + +import ( + "encoding/hex" + "errors" + + rawclient "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client" + "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key" + commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common" + ircontrol "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control/ir" + ircontrolsrv "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control/ir/server" + "github.com/spf13/cobra" +) + +var removeNodeCmd = &cobra.Command{ + Use: "remove-node", + Short: "Forces a node removal from netmap", + Long: "Forces a node removal from netmap via a notary request. It should be executed on other IR nodes as well.", + Run: removeNode, +} + +func initControlIRRemoveNodeCmd() { + initControlFlags(removeNodeCmd) + + flags := removeNodeCmd.Flags() + flags.String("node", "", "Node public key as a hex string") + _ = removeNodeCmd.MarkFlagRequired("node") +} + +func removeNode(cmd *cobra.Command, _ []string) { + pk := key.Get(cmd) + c := getClient(cmd, pk) + + nodeKeyStr, _ := cmd.Flags().GetString("node") + if len(nodeKeyStr) == 0 { + commonCmd.ExitOnErr(cmd, "parsing node public key: ", errors.New("key cannot be empty")) + } + nodeKey, err := hex.DecodeString(nodeKeyStr) + commonCmd.ExitOnErr(cmd, "can't decode node public key: %w", err) + + req := new(ircontrol.RemoveNodeRequest) + req.SetBody(&ircontrol.RemoveNodeRequest_Body{ + Key: nodeKey, + }) + + commonCmd.ExitOnErr(cmd, "could not sign request: %w", ircontrolsrv.SignMessage(pk, req)) + + var resp *ircontrol.RemoveNodeResponse + err = c.ExecRaw(func(client *rawclient.Client) error { + resp, err = ircontrol.RemoveNode(client, req) + return err + }) + commonCmd.ExitOnErr(cmd, "rpc error: %w", err) + + verifyResponse(cmd, resp.GetSignature(), resp.GetBody()) + + cmd.Println("Node removed") +} diff --git a/pkg/morph/client/netmap/add_peer.go b/pkg/morph/client/netmap/peer.go similarity index 70% rename from pkg/morph/client/netmap/add_peer.go rename to pkg/morph/client/netmap/peer.go index dc6c25540..7ceaa0250 100644 --- a/pkg/morph/client/netmap/add_peer.go +++ b/pkg/morph/client/netmap/peer.go @@ -41,3 +41,19 @@ func (c *Client) AddPeer(p AddPeerPrm) error { } return nil } + +// ForceRemovePeer marks the given peer as offline via a notary control transaction. +func (c *Client) ForceRemovePeer(nodeInfo netmap.NodeInfo) error { + if !c.client.WithNotary() { + return fmt.Errorf("peer can be forcefully removed only in notary environment") + } + + prm := UpdatePeerPrm{} + prm.SetKey(nodeInfo.PublicKey()) + prm.SetControlTX(true) + + if err := c.UpdatePeerState(prm); err != nil { + return fmt.Errorf("updating peer state: %v", err) + } + return nil +} diff --git a/pkg/services/control/ir/rpc.go b/pkg/services/control/ir/rpc.go index 6b2234954..1b635c149 100644 --- a/pkg/services/control/ir/rpc.go +++ b/pkg/services/control/ir/rpc.go @@ -11,6 +11,7 @@ const serviceName = "ircontrol.ControlService" const ( rpcHealthCheck = "HealthCheck" rpcTickEpoch = "TickEpoch" + rpcRemoveNode = "RemoveNode" ) // HealthCheck executes ControlService.HealthCheck RPC. @@ -31,6 +32,14 @@ func TickEpoch( return sendUnary[TickEpochRequest, TickEpochResponse](cli, rpcTickEpoch, req, opts...) } +func RemoveNode( + cli *client.Client, + req *RemoveNodeRequest, + opts ...client.CallOption, +) (*RemoveNodeResponse, error) { + return sendUnary[RemoveNodeRequest, RemoveNodeResponse](cli, rpcRemoveNode, req, opts...) +} + func sendUnary[I, O grpc.Message](cli *client.Client, rpcName string, req *I, opts ...client.CallOption) (*O, error) { var resp O wResp := &responseWrapper[*O]{ diff --git a/pkg/services/control/ir/server/calls.go b/pkg/services/control/ir/server/calls.go index 56e2e3f79..680d1e606 100644 --- a/pkg/services/control/ir/server/calls.go +++ b/pkg/services/control/ir/server/calls.go @@ -1,9 +1,11 @@ package control import ( + "bytes" "context" "fmt" + "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap" control "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control/ir" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -57,3 +59,43 @@ func (s *Server) TickEpoch(_ context.Context, req *control.TickEpochRequest) (*c return resp, nil } + +// RemoveNode forces a node removal. +// +// If request is not signed with a key from white list, permission error returns. +func (s *Server) RemoveNode(_ context.Context, req *control.RemoveNodeRequest) (*control.RemoveNodeResponse, error) { + if err := s.isValidRequest(req); err != nil { + return nil, status.Error(codes.PermissionDenied, err.Error()) + } + + resp := new(control.RemoveNodeResponse) + resp.SetBody(new(control.RemoveNodeResponse_Body)) + + nm, err := s.netmapClient.NetMap() + if err != nil { + return nil, fmt.Errorf("getting netmap: %w", err) + } + var nodeInfo netmap.NodeInfo + for _, info := range nm.Nodes() { + if bytes.Equal(info.PublicKey(), req.GetBody().GetKey()) { + nodeInfo = info + break + } + } + if len(nodeInfo.PublicKey()) == 0 { + return nil, status.Error(codes.NotFound, "no such node") + } + if nodeInfo.IsOffline() { + return nil, status.Error(codes.FailedPrecondition, "node is already offline") + } + + if err := s.netmapClient.ForceRemovePeer(nodeInfo); err != nil { + return nil, fmt.Errorf("forcing node removal: %w", err) + } + + if err := SignMessage(&s.prm.key.PrivateKey, resp); err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + return resp, nil +} diff --git a/pkg/services/control/ir/service.go b/pkg/services/control/ir/service.go index 1aaec2c87..b2db2b43a 100644 --- a/pkg/services/control/ir/service.go +++ b/pkg/services/control/ir/service.go @@ -32,3 +32,15 @@ func (x *TickEpochResponse) SetBody(v *TickEpochResponse_Body) { x.Body = v } } + +func (x *RemoveNodeRequest) SetBody(v *RemoveNodeRequest_Body) { + if x != nil { + x.Body = v + } +} + +func (x *RemoveNodeResponse) SetBody(v *RemoveNodeResponse_Body) { + if x != nil { + x.Body = v + } +} diff --git a/pkg/services/control/ir/service.pb.go b/pkg/services/control/ir/service.pb.go index 84acdfc82dba1a3a8fb147772098614fea461ea8..bec74a3beaecfdcc8792ed3dc368d640e5f72eac 100644 GIT binary patch delta 3014 zcma)8YfM{Z7|tnBdKpp{N}=3N%L)_GjW46?oIoPy$zmiiG0Pz8KR5io(=tw>sDDoTzVG)w z@B2L8d%p86TxZ_Dpt|yfIdgJ)Tw*(=(a1@uBQhxAb=7%%kg3-xTy-fOcwn4Ty3QK% zl;&Hh5=;e>($$+5Qkr+OY*}lNozk%r(!?bC%Legg;i7V3Ae~d1r_#r3-7~rwoo?vF z*Z^BT#a3*9v9rz6WK(2ty48`>~oYsDYN{ng4+6XTJw z3CT{hnvf<*rBga>#S~ko)b2~&5PB^MGF;NPD|`6RRH`%=Oe1(;P2g|OJS^P-)|!Ea6dj%vyoYbf7Nes@ z%PM?QK^L0wv*jUbgOi4lKh#OUV`+y8-+7EGofbcF`Ka@o zQS7at=6Sz>GhVB06^7#f7fwDZn=;d!5@`mn2zGEj5g&;)c-QBokoOwu(IZ+gBSW#dV`S~n!KCcDheHEgGW z*Q!e~(BP$iYXYo>mv@OD#7bQ)^*w7S!E8en!huX`vP~97BC%%N3gpiT+YkyEP~_Io zn5D6VVX?bWhCSCp);>h&YsoQ_93goQ zF1FM#BIaA#m|8q(E{46;f!|s)@OMa$(N>-u^Gl&3+-TLjyoMRS1;mcN^0DC&yMtwE zV?39b8VQzY@#DBhio1teC+y= zh|h1PX1XYDq8-{H`bA(!JG8dpyA=?=5v9!73x@?DJplC33ecJrc- z$JMY2z2S7!gflYY{dbcw;6=2A?YcD&GQ)Y83mezFZiS0r2gp73kiP0AY70=;xXTpUw aut72VUl!u$gY%|xKb2+e z5w@G|;g@Y)9d|zB@TES1jM0GWav|oW96Idlu$p;PyNhw!VOO8ZZwYKo%*5lnhb84O z*UU^teDXe5f1a=#jc!S>x<=y)J6)nstb;Am3R}BHOM&8H;3ft7XpK&ksB(>py?E1A zhs9G0-UY%q9Mcpen2lU$M`=eDN3YF68@2)lY;`*d6jn<`Tk$)j!&0L7uC8q(> diff --git a/pkg/services/control/ir/service.proto b/pkg/services/control/ir/service.proto index 5862e8fbd..d647db0df 100644 --- a/pkg/services/control/ir/service.proto +++ b/pkg/services/control/ir/service.proto @@ -12,6 +12,8 @@ service ControlService { rpc HealthCheck (HealthCheckRequest) returns (HealthCheckResponse); // Forces a new epoch to be signaled by the IR node with high probability. rpc TickEpoch (TickEpochRequest) returns (TickEpochResponse); + // Forces a node removal to be signaled by the IR node with high probability. + rpc RemoveNode (RemoveNodeRequest) returns (RemoveNodeResponse); } // Health check request. @@ -57,3 +59,19 @@ message TickEpochResponse { Body body = 1; Signature signature = 2; } + +message RemoveNodeRequest { + message Body{ + bytes key = 1; + } + + Body body = 1; + Signature signature = 2; +} + +message RemoveNodeResponse { + message Body{} + + Body body = 1; + Signature signature = 2; +} diff --git a/pkg/services/control/ir/service_frostfs.pb.go b/pkg/services/control/ir/service_frostfs.pb.go index d480f0b50dd02c8435a4ad406cc546da526d4696..c93253105317c9a7bb476e9a2da0266d8e6b4895 100644 GIT binary patch delta 294 zcmeD6e^9oeSABAyNGDfNYHog6s$YIe>SP5`&B+0Ld_qo@C8@>1nN_J8hB^urdfus( zo7eG8VMJ9bGWida+$5=5R

HJyO!lK!uY(N{LL?W^P4NyLmUW7!xx84(k+T{^SCg zyMhQ;1f>?1rWTh>zOR;!q-pY5*<;Au1O?H}=8Vfw+?1ej5=qHsZ9Z{iYc~4|Ohx8T T{-AmfN%`dKYWc|A01-O?(F$y8 delta 7 OcmaD**6Y8aR~-NlIRlpf diff --git a/pkg/services/control/ir/service_grpc.pb.go b/pkg/services/control/ir/service_grpc.pb.go index 700d340ca8705a58a9a71d5a146e192633a02397..6ba214da09c0a82a003442b5b4097c03ebd6c8bc 100644 GIT binary patch delta 480 zcmdm`)ndM(lSPtKUths3zbHAiSRqj%FFz$!p(r&szbr9lvNWeTR}hGu>IW2_oX;wS z%)P*-g2dfi!Q#w3*_BluNesV^$$!~U6u)J)L(;oBhuwe)zuRmOZqrE4&nro-DA99< z&^iiQ2+5$-!qU{@5={jSB*D$yoSsaRIk`0{GG+2SE^lN<@8|x*HrYf#6iICILEh=e z++@BQWbP}zOk{2xe}w|VK)6*|D^DAYr7dc9B*)8sA@VPuWIB1*{Iiz1l-rY)*R delta 36 scmZoM-=(#olV!6Ts}b|&&72PzHyiR;Fm1Nrf6lgfjf5K0WNqn40P?5|LjV8(