forked from TrueCloudLab/frostfs-node
[#733] frostfs-cli: Add control ir remove-container
Signed-off-by: Dmitrii Stepanov <d.stepanov@yadro.com>
This commit is contained in:
parent
dc81b4b50c
commit
eac61fe9b2
14 changed files with 220 additions and 22 deletions
|
@ -12,8 +12,10 @@ func initControlIRCmd() {
|
||||||
irCmd.AddCommand(tickEpochCmd)
|
irCmd.AddCommand(tickEpochCmd)
|
||||||
irCmd.AddCommand(removeNodeCmd)
|
irCmd.AddCommand(removeNodeCmd)
|
||||||
irCmd.AddCommand(irHealthCheckCmd)
|
irCmd.AddCommand(irHealthCheckCmd)
|
||||||
|
irCmd.AddCommand(removeContainerCmd)
|
||||||
|
|
||||||
initControlIRTickEpochCmd()
|
initControlIRTickEpochCmd()
|
||||||
initControlIRRemoveNodeCmd()
|
initControlIRRemoveNodeCmd()
|
||||||
initControlIRHealthCheckCmd()
|
initControlIRHealthCheckCmd()
|
||||||
|
initControlIRRemoveContainerCmd()
|
||||||
}
|
}
|
||||||
|
|
94
cmd/frostfs-cli/modules/control/ir_remove_container.go
Normal file
94
cmd/frostfs-cli/modules/control/ir_remove_container.go
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
package control
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
||||||
|
rawclient "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
|
||||||
|
"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"
|
||||||
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ownerFlag = "owner"
|
||||||
|
)
|
||||||
|
|
||||||
|
var removeContainerCmd = &cobra.Command{
|
||||||
|
Use: "remove-container",
|
||||||
|
Short: "Schedules a container removal",
|
||||||
|
Long: `Schedules a container removal via a notary request.
|
||||||
|
Container data will be deleted asynchronously by policer.
|
||||||
|
To check removal status "frostfs-cli container list" command can be used.`,
|
||||||
|
Run: removeContainer,
|
||||||
|
}
|
||||||
|
|
||||||
|
func initControlIRRemoveContainerCmd() {
|
||||||
|
initControlFlags(removeContainerCmd)
|
||||||
|
|
||||||
|
flags := removeContainerCmd.Flags()
|
||||||
|
flags.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
|
||||||
|
flags.String(ownerFlag, "", "Container owner's wallet address.")
|
||||||
|
removeContainerCmd.MarkFlagsMutuallyExclusive(commonflags.CIDFlag, ownerFlag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeContainer(cmd *cobra.Command, _ []string) {
|
||||||
|
req := prepareRemoveContainerRequest(cmd)
|
||||||
|
|
||||||
|
pk := key.Get(cmd)
|
||||||
|
c := getClient(cmd, pk)
|
||||||
|
|
||||||
|
commonCmd.ExitOnErr(cmd, "could not sign request: %w", ircontrolsrv.SignMessage(pk, req))
|
||||||
|
|
||||||
|
var resp *ircontrol.RemoveContainerResponse
|
||||||
|
err := c.ExecRaw(func(client *rawclient.Client) error {
|
||||||
|
var err error
|
||||||
|
resp, err = ircontrol.RemoveContainer(client, req)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
commonCmd.ExitOnErr(cmd, "failed to execute request: %w", err)
|
||||||
|
|
||||||
|
verifyResponse(cmd, resp.GetSignature(), resp.GetBody())
|
||||||
|
|
||||||
|
if len(req.GetBody().GetContainerId()) > 0 {
|
||||||
|
cmd.Println("Container scheduled to removal")
|
||||||
|
} else {
|
||||||
|
cmd.Println("User containers sheduled to removal")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareRemoveContainerRequest(cmd *cobra.Command) *ircontrol.RemoveContainerRequest {
|
||||||
|
req := &ircontrol.RemoveContainerRequest{
|
||||||
|
Body: &ircontrol.RemoveContainerRequest_Body{},
|
||||||
|
}
|
||||||
|
|
||||||
|
cidStr, err := cmd.Flags().GetString(commonflags.CIDFlag)
|
||||||
|
commonCmd.ExitOnErr(cmd, "failed to get cid: ", err)
|
||||||
|
|
||||||
|
ownerStr, err := cmd.Flags().GetString(ownerFlag)
|
||||||
|
commonCmd.ExitOnErr(cmd, "failed to get owner: ", err)
|
||||||
|
|
||||||
|
if len(ownerStr) == 0 && len(cidStr) == 0 {
|
||||||
|
commonCmd.ExitOnErr(cmd, "invalid usage: %w", errors.New("neither owner's wallet address nor container ID are specified"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ownerStr) > 0 {
|
||||||
|
var owner user.ID
|
||||||
|
commonCmd.ExitOnErr(cmd, "invalid owner ID: %w", owner.DecodeString(ownerStr))
|
||||||
|
var ownerID refs.OwnerID
|
||||||
|
owner.WriteToV2(&ownerID)
|
||||||
|
req.Body.Owner = ownerID.StableMarshal(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cidStr) > 0 {
|
||||||
|
var containerID cid.ID
|
||||||
|
commonCmd.ExitOnErr(cmd, "invalid container ID: %w", containerID.DecodeString(cidStr))
|
||||||
|
req.Body.ContainerId = containerID[:]
|
||||||
|
}
|
||||||
|
return req
|
||||||
|
}
|
|
@ -343,7 +343,7 @@ func (s *Server) initGRPCServer(cfg *viper.Viper) error {
|
||||||
p.SetPrivateKey(*s.key)
|
p.SetPrivateKey(*s.key)
|
||||||
p.SetHealthChecker(s)
|
p.SetHealthChecker(s)
|
||||||
|
|
||||||
controlSvc := controlsrv.New(p, s.netmapClient,
|
controlSvc := controlsrv.New(p, s.netmapClient, s.containerClient,
|
||||||
controlsrv.WithAllowedKeys(authKeys),
|
controlsrv.WithAllowedKeys(authKeys),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -389,6 +389,7 @@ func (s *Server) initClientsFromMorph() (*serverMorphClients, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
s.containerClient = result.CnrClient
|
||||||
|
|
||||||
s.netmapClient, err = nmClient.NewFromMorph(s.morphClient, s.contracts.netmap, fee, nmClient.TryNotary(), nmClient.AsAlphabet())
|
s.netmapClient, err = nmClient.NewFromMorph(s.morphClient, s.contracts.netmap, fee, nmClient.TryNotary(), nmClient.AsAlphabet())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/metrics"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/metrics"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client"
|
||||||
balanceClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/balance"
|
balanceClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/balance"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/container"
|
||||||
nmClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
|
nmClient "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/subscriber"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/subscriber"
|
||||||
|
@ -56,6 +57,7 @@ type (
|
||||||
balanceClient *balanceClient.Client
|
balanceClient *balanceClient.Client
|
||||||
netmapClient *nmClient.Client
|
netmapClient *nmClient.Client
|
||||||
persistate *state.PersistentStorage
|
persistate *state.PersistentStorage
|
||||||
|
containerClient *container.Client
|
||||||
|
|
||||||
// metrics
|
// metrics
|
||||||
irMetrics *metrics.InnerRingServiceMetrics
|
irMetrics *metrics.InnerRingServiceMetrics
|
||||||
|
|
|
@ -67,7 +67,7 @@ func (d *DeletePrm) SetKey(key []byte) {
|
||||||
//
|
//
|
||||||
// If TryNotary is provided, calls notary contract.
|
// If TryNotary is provided, calls notary contract.
|
||||||
func (c *Client) Delete(p DeletePrm) error {
|
func (c *Client) Delete(p DeletePrm) error {
|
||||||
if len(p.signature) == 0 {
|
if len(p.signature) == 0 && !p.IsControl() {
|
||||||
return errNilArgument
|
return errNilArgument
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -115,6 +115,11 @@ func (i *InvokePrmOptional) SetControlTX(b bool) {
|
||||||
i.controlTX = b
|
i.controlTX = b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsControl gets whether a control transaction will be used.
|
||||||
|
func (i *InvokePrmOptional) IsControl() bool {
|
||||||
|
return i.controlTX
|
||||||
|
}
|
||||||
|
|
||||||
// Invoke calls Invoke method of Client with static internal script hash and fee.
|
// Invoke calls Invoke method of Client with static internal script hash and fee.
|
||||||
// Supported args types are the same as in Client.
|
// Supported args types are the same as in Client.
|
||||||
//
|
//
|
||||||
|
|
|
@ -12,6 +12,7 @@ const (
|
||||||
rpcHealthCheck = "HealthCheck"
|
rpcHealthCheck = "HealthCheck"
|
||||||
rpcTickEpoch = "TickEpoch"
|
rpcTickEpoch = "TickEpoch"
|
||||||
rpcRemoveNode = "RemoveNode"
|
rpcRemoveNode = "RemoveNode"
|
||||||
|
rpcRemoveContainer = "RemoveContainer"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HealthCheck executes ControlService.HealthCheck RPC.
|
// HealthCheck executes ControlService.HealthCheck RPC.
|
||||||
|
@ -40,6 +41,14 @@ func RemoveNode(
|
||||||
return sendUnary[RemoveNodeRequest, RemoveNodeResponse](cli, rpcRemoveNode, req, opts...)
|
return sendUnary[RemoveNodeRequest, RemoveNodeResponse](cli, rpcRemoveNode, req, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RemoveContainer(
|
||||||
|
cli *client.Client,
|
||||||
|
req *RemoveContainerRequest,
|
||||||
|
opts ...client.CallOption,
|
||||||
|
) (*RemoveContainerResponse, error) {
|
||||||
|
return sendUnary[RemoveContainerRequest, RemoveContainerResponse](cli, rpcRemoveContainer, req, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
func sendUnary[I, O grpc.Message](cli *client.Client, rpcName string, req *I, opts ...client.CallOption) (*O, error) {
|
func sendUnary[I, O grpc.Message](cli *client.Client, rpcName string, req *I, opts ...client.CallOption) (*O, error) {
|
||||||
var resp O
|
var resp O
|
||||||
wResp := &responseWrapper[*O]{
|
wResp := &responseWrapper[*O]{
|
||||||
|
|
|
@ -5,8 +5,12 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/container"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
|
||||||
control "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control/ir"
|
control "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control/ir"
|
||||||
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
)
|
)
|
||||||
|
@ -99,3 +103,63 @@ func (s *Server) RemoveNode(_ context.Context, req *control.RemoveNodeRequest) (
|
||||||
|
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemoveContainer forces a container removal.
|
||||||
|
func (s *Server) RemoveContainer(_ context.Context, req *control.RemoveContainerRequest) (*control.RemoveContainerResponse, error) {
|
||||||
|
if err := s.isValidRequest(req); err != nil {
|
||||||
|
return nil, status.Error(codes.PermissionDenied, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.Body.GetContainerId()) > 0 && len(req.Body.GetOwner()) > 0 {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, "specify the owner and container at the same time is not allowed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(req.Body.GetContainerId()) > 0 {
|
||||||
|
var containerID cid.ID
|
||||||
|
if err := containerID.Decode(req.Body.GetContainerId()); err != nil {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("failed to parse container ID: %s", err.Error()))
|
||||||
|
}
|
||||||
|
if err := s.removeContainer(containerID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var ownerID refs.OwnerID
|
||||||
|
if err := ownerID.Unmarshal(req.GetBody().GetOwner()); err != nil {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("failed to parse ownerID: %s", err.Error()))
|
||||||
|
}
|
||||||
|
var owner user.ID
|
||||||
|
if err := owner.ReadFromV2(ownerID); err != nil {
|
||||||
|
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("failed to read owner: %s", err.Error()))
|
||||||
|
}
|
||||||
|
|
||||||
|
cids, err := s.containerClient.ContainersOf(&owner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get owner's containers: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, containerID := range cids {
|
||||||
|
if err := s.removeContainer(containerID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := &control.RemoveContainerResponse{
|
||||||
|
Body: &control.RemoveContainerResponse_Body{},
|
||||||
|
}
|
||||||
|
if err := SignMessage(&s.prm.key.PrivateKey, resp); err != nil {
|
||||||
|
return nil, status.Error(codes.Internal, err.Error())
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) removeContainer(containerID cid.ID) error {
|
||||||
|
var prm container.DeletePrm
|
||||||
|
prm.SetCID(containerID[:])
|
||||||
|
prm.SetControlTX(true)
|
||||||
|
|
||||||
|
if err := s.containerClient.Delete(prm); err != nil {
|
||||||
|
return fmt.Errorf("forcing container removal: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package control
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/container"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -14,7 +15,7 @@ import (
|
||||||
type Server struct {
|
type Server struct {
|
||||||
prm Prm
|
prm Prm
|
||||||
netmapClient *netmap.Client
|
netmapClient *netmap.Client
|
||||||
|
containerClient *container.Client
|
||||||
allowedKeys [][]byte
|
allowedKeys [][]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +33,7 @@ func panicOnPrmValue(n string, v any) {
|
||||||
// Forms white list from all keys specified via
|
// Forms white list from all keys specified via
|
||||||
// WithAllowedKeys option and a public key of
|
// WithAllowedKeys option and a public key of
|
||||||
// the parameterized private key.
|
// the parameterized private key.
|
||||||
func New(prm Prm, netmapClient *netmap.Client, opts ...Option) *Server {
|
func New(prm Prm, netmapClient *netmap.Client, containerClient *container.Client, opts ...Option) *Server {
|
||||||
// verify required parameters
|
// verify required parameters
|
||||||
switch {
|
switch {
|
||||||
case prm.healthChecker == nil:
|
case prm.healthChecker == nil:
|
||||||
|
@ -49,6 +50,7 @@ func New(prm Prm, netmapClient *netmap.Client, opts ...Option) *Server {
|
||||||
return &Server{
|
return &Server{
|
||||||
prm: prm,
|
prm: prm,
|
||||||
netmapClient: netmapClient,
|
netmapClient: netmapClient,
|
||||||
|
containerClient: containerClient,
|
||||||
|
|
||||||
allowedKeys: append(o.allowedKeys, prm.key.PublicKey().Bytes()),
|
allowedKeys: append(o.allowedKeys, prm.key.PublicKey().Bytes()),
|
||||||
}
|
}
|
||||||
|
|
BIN
pkg/services/control/ir/service.pb.go
generated
BIN
pkg/services/control/ir/service.pb.go
generated
Binary file not shown.
|
@ -14,6 +14,8 @@ service ControlService {
|
||||||
rpc TickEpoch (TickEpochRequest) returns (TickEpochResponse);
|
rpc TickEpoch (TickEpochRequest) returns (TickEpochResponse);
|
||||||
// Forces a node removal to be signaled by the IR node with high probability.
|
// Forces a node removal to be signaled by the IR node with high probability.
|
||||||
rpc RemoveNode (RemoveNodeRequest) returns (RemoveNodeResponse);
|
rpc RemoveNode (RemoveNodeRequest) returns (RemoveNodeResponse);
|
||||||
|
// Forces a container removal to be signaled by the IR node with high probability.
|
||||||
|
rpc RemoveContainer(RemoveContainerRequest) returns (RemoveContainerResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Health check request.
|
// Health check request.
|
||||||
|
@ -75,3 +77,20 @@ message RemoveNodeResponse {
|
||||||
Body body = 1;
|
Body body = 1;
|
||||||
Signature signature = 2;
|
Signature signature = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message RemoveContainerRequest {
|
||||||
|
message Body{
|
||||||
|
bytes container_id = 1;
|
||||||
|
bytes owner = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
Body body = 1;
|
||||||
|
Signature signature = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RemoveContainerResponse {
|
||||||
|
message Body{}
|
||||||
|
|
||||||
|
Body body = 1;
|
||||||
|
Signature signature = 2;
|
||||||
|
}
|
BIN
pkg/services/control/ir/service_frostfs.pb.go
generated
BIN
pkg/services/control/ir/service_frostfs.pb.go
generated
Binary file not shown.
BIN
pkg/services/control/ir/service_grpc.pb.go
generated
BIN
pkg/services/control/ir/service_grpc.pb.go
generated
Binary file not shown.
BIN
pkg/services/control/ir/types.pb.go
generated
BIN
pkg/services/control/ir/types.pb.go
generated
Binary file not shown.
Loading…
Reference in a new issue