From b2ad1f3b3e4f2d82ea6d36b810f944de2ed283ee Mon Sep 17 00:00:00 2001 From: Airat Arifullin Date: Mon, 6 May 2024 16:33:20 +0300 Subject: [PATCH] [#215] client: Introduce apemanager rpc interface * Introduce `APEManagerAddChain`, `APEManagerRemoveChain`, `APEManagerListChains`. * Introduce reqeuest/response types for these handlers (Prm*, Res*). * Inroduce status type for apemanager `APEManagerAccessDenied`; add unit-tests. Signed-off-by: Airat Arifullin --- client/apemanager_add_chain.go | 79 ++++++++++++++++++++++++++++++ client/apemanager_list_chains.go | 80 +++++++++++++++++++++++++++++++ client/apemanager_remove_chain.go | 73 ++++++++++++++++++++++++++++ client/errors.go | 6 +++ client/status/apemanager.go | 53 ++++++++++++++++++++ client/status/v2.go | 7 +++ client/status/v2_test.go | 12 +++++ 7 files changed, 310 insertions(+) create mode 100644 client/apemanager_add_chain.go create mode 100644 client/apemanager_list_chains.go create mode 100644 client/apemanager_remove_chain.go create mode 100644 client/status/apemanager.go diff --git a/client/apemanager_add_chain.go b/client/apemanager_add_chain.go new file mode 100644 index 0000000..00fd66d --- /dev/null +++ b/client/apemanager_add_chain.go @@ -0,0 +1,79 @@ +package client + +import ( + "context" + "fmt" + + apemanager_v2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/apemanager" + rpcapi "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc" + "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client" + session_v2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session" + "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/signature" + apemanager_sdk "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/apemanager" + apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status" +) + +// PrmAPEManagerAddChain groups parameters of APEManagerAddChain operation. +type PrmAPEManagerAddChain struct { + XHeaders []string + + ChainTarget apemanager_sdk.ChainTarget + + Chain apemanager_sdk.Chain +} + +func (prm *PrmAPEManagerAddChain) buildRequest(c *Client) (*apemanager_v2.AddChainRequest, error) { + if len(prm.XHeaders)%2 != 0 { + return nil, errorInvalidXHeaders + } + + req := new(apemanager_v2.AddChainRequest) + reqBody := new(apemanager_v2.AddChainRequestBody) + + reqBody.SetTarget(prm.ChainTarget.ToV2()) + reqBody.SetChain(prm.Chain.ToV2()) + + req.SetBody(reqBody) + + var meta session_v2.RequestMetaHeader + writeXHeadersToMeta(prm.XHeaders, &meta) + + c.prepareRequest(req, &meta) + + return req, nil +} + +type ResAPEManagerAddChain struct { + statusRes + + // ChainID of set Chain. If Chain does not contain chainID before request, then + // ChainID is generated. + ChainID apemanager_sdk.ChainID +} + +// APEManagerAddChain sets Chain for ChainTarget. +func (c *Client) APEManagerAddChain(ctx context.Context, prm PrmAPEManagerAddChain) (*ResAPEManagerAddChain, error) { + req, err := prm.buildRequest(c) + if err != nil { + return nil, err + } + + if err := signature.SignServiceMessage(&c.prm.Key, req); err != nil { + return nil, fmt.Errorf("sign request: %w", err) + } + + resp, err := rpcapi.AddChain(&c.c, req, client.WithContext(ctx)) + if err != nil { + return nil, err + } + + var res ResAPEManagerAddChain + res.st, err = c.processResponse(resp) + if err != nil || !apistatus.IsSuccessful(res.st) { + return &res, err + } + + res.ChainID = resp.GetBody().GetChainID() + + return &res, nil +} diff --git a/client/apemanager_list_chains.go b/client/apemanager_list_chains.go new file mode 100644 index 0000000..dd5c4dd --- /dev/null +++ b/client/apemanager_list_chains.go @@ -0,0 +1,80 @@ +package client + +import ( + "context" + "fmt" + + apemanager_v2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/apemanager" + rpcapi "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc" + "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client" + session_v2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session" + "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/signature" + apemanager_sdk "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/apemanager" + apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status" +) + +// PrmAPEManagerListChains groups parameters of APEManagerListChains operation. +type PrmAPEManagerListChains struct { + XHeaders []string + + ChainTarget apemanager_sdk.ChainTarget +} + +func (prm *PrmAPEManagerListChains) buildRequest(c *Client) (*apemanager_v2.ListChainsRequest, error) { + if len(prm.XHeaders)%2 != 0 { + return nil, errorInvalidXHeaders + } + + req := new(apemanager_v2.ListChainsRequest) + reqBody := new(apemanager_v2.ListChainsRequestBody) + + reqBody.SetTarget(prm.ChainTarget.ToV2()) + + req.SetBody(reqBody) + + var meta session_v2.RequestMetaHeader + writeXHeadersToMeta(prm.XHeaders, &meta) + + c.prepareRequest(req, &meta) + + return req, nil +} + +type ResAPEManagerListChains struct { + statusRes + + Chains []apemanager_sdk.Chain +} + +// APEManagerListChains lists Chains for ChainTarget. +func (c *Client) APEManagerListChains(ctx context.Context, prm PrmAPEManagerListChains) (*ResAPEManagerListChains, error) { + req, err := prm.buildRequest(c) + if err != nil { + return nil, err + } + + if err := signature.SignServiceMessage(&c.prm.Key, req); err != nil { + return nil, fmt.Errorf("sign request: %w", err) + } + + resp, err := rpcapi.ListChains(&c.c, req, client.WithContext(ctx)) + if err != nil { + return nil, err + } + + var res ResAPEManagerListChains + res.st, err = c.processResponse(resp) + if err != nil || !apistatus.IsSuccessful(res.st) { + return nil, err + } + + for _, ch := range resp.GetBody().GetChains() { + var chSDK apemanager_sdk.Chain + if err := chSDK.ReadFromV2(ch); err != nil { + return nil, err + } + res.Chains = append(res.Chains, chSDK) + } + + return &res, nil +} diff --git a/client/apemanager_remove_chain.go b/client/apemanager_remove_chain.go new file mode 100644 index 0000000..dc10113 --- /dev/null +++ b/client/apemanager_remove_chain.go @@ -0,0 +1,73 @@ +package client + +import ( + "context" + "fmt" + + apemanager_v2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/apemanager" + rpcapi "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc" + "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client" + session_v2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session" + "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/signature" + apemanager_sdk "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/apemanager" + apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status" +) + +// PrmAPEManagerRemoveChain groups parameters of APEManagerRemoveChain operation. +type PrmAPEManagerRemoveChain struct { + XHeaders []string + + ChainTarget apemanager_sdk.ChainTarget + + ChainID apemanager_sdk.ChainID +} + +func (prm *PrmAPEManagerRemoveChain) buildRequest(c *Client) (*apemanager_v2.RemoveChainRequest, error) { + if len(prm.XHeaders)%2 != 0 { + return nil, errorInvalidXHeaders + } + + req := new(apemanager_v2.RemoveChainRequest) + reqBody := new(apemanager_v2.RemoveChainRequestBody) + + reqBody.SetTarget(prm.ChainTarget.ToV2()) + reqBody.SetChainID(prm.ChainID) + + req.SetBody(reqBody) + + var meta session_v2.RequestMetaHeader + writeXHeadersToMeta(prm.XHeaders, &meta) + + c.prepareRequest(req, &meta) + + return req, nil +} + +type ResAPEManagerRemoveChain struct { + statusRes +} + +// APEManagerRemoveChain removes Chain with ChainID defined for ChainTarget. +func (c *Client) APEManagerRemoveChain(ctx context.Context, prm PrmAPEManagerRemoveChain) (*ResAPEManagerRemoveChain, error) { + req, err := prm.buildRequest(c) + if err != nil { + return nil, err + } + + if err := signature.SignServiceMessage(&c.prm.Key, req); err != nil { + return nil, fmt.Errorf("sign request: %w", err) + } + + resp, err := rpcapi.RemoveChain(&c.c, req, client.WithContext(ctx)) + if err != nil { + return nil, err + } + + var res ResAPEManagerRemoveChain + res.st, err = c.processResponse(resp) + if err != nil || !apistatus.IsSuccessful(res.st) { + return &res, err + } + + return &res, nil +} diff --git a/client/errors.go b/client/errors.go index 64144b6..be87c62 100644 --- a/client/errors.go +++ b/client/errors.go @@ -61,6 +61,12 @@ func IsErrSessionNotFound(err error) bool { return wrapsErrType[*apistatus.SessionTokenNotFound](err) } +// IsErrAPEManagerAccessDenied checks if err corresponds to FrostFS status return +// corresponding to apemanager access deny. Supports wrapped errors. +func IsErrAPEManagerAccessDenied(err error) bool { + return wrapsErrType[*apistatus.APEManagerAccessDenied](err) +} + // returns error describing missing field with the given name. func newErrMissingResponseField(name string) error { return fmt.Errorf("missing %s field in the response", name) diff --git a/client/status/apemanager.go b/client/status/apemanager.go new file mode 100644 index 0000000..f68fd48 --- /dev/null +++ b/client/status/apemanager.go @@ -0,0 +1,53 @@ +package apistatus + +import ( + "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/apemanager" + "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/status" +) + +// APEManagerAccessDenied describes status of the failure because of the access control violation. +// Instances provide Status and StatusV2 interfaces. +type APEManagerAccessDenied struct { + v2 status.Status +} + +const defaultAPEManagerAccessDeniedMsg = "apemanager access denied" + +func (x *APEManagerAccessDenied) Error() string { + msg := x.v2.Message() + if msg == "" { + msg = defaultAPEManagerAccessDeniedMsg + } + + return errMessageStatusV2( + globalizeCodeV2(apemanager.StatusAPEManagerAccessDenied, apemanager.GlobalizeFail), + msg, + ) +} + +func (x *APEManagerAccessDenied) fromStatusV2(st *status.Status) { + x.v2 = *st +} + +// ToStatusV2 converts APEManagerAccessDenied to v2's Status. +// If the value was returned by FromStatusV2, returns the source message. +// Otherwise, returns message with +// - code: APE_MANAGER_ACCESS_DENIED; +// - string message: "apemanager access denied"; +// - details: empty. +func (x APEManagerAccessDenied) ToStatusV2() *status.Status { + x.v2.SetCode(globalizeCodeV2(apemanager.StatusAPEManagerAccessDenied, apemanager.GlobalizeFail)) + x.v2.SetMessage(defaultAPEManagerAccessDeniedMsg) + return &x.v2 +} + +// WriteReason writes human-readable access rejection reason. +func (x *APEManagerAccessDenied) WriteReason(reason string) { + apemanager.WriteAccessDeniedDesc(&x.v2, reason) +} + +// Reason returns human-readable access rejection reason returned by the server. +// Returns empty value is reason is not presented. +func (x APEManagerAccessDenied) Reason() string { + return apemanager.ReadAccessDeniedDesc(x.v2) +} diff --git a/client/status/v2.go b/client/status/v2.go index ff6ccec..f5bef6a 100644 --- a/client/status/v2.go +++ b/client/status/v2.go @@ -3,6 +3,7 @@ package apistatus import ( "fmt" + "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/apemanager" "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container" "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object" "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session" @@ -92,6 +93,12 @@ func FromStatusV2(st *status.Status) Status { case session.StatusTokenExpired: decoder = new(SessionTokenExpired) } + case apemanager.LocalizeFailStatus(&code): + //nolint:exhaustive + switch code { + case apemanager.StatusAPEManagerAccessDenied: + decoder = new(APEManagerAccessDenied) + } } if decoder == nil { diff --git a/client/status/v2_test.go b/client/status/v2_test.go index 14d4c30..2295c47 100644 --- a/client/status/v2_test.go +++ b/client/status/v2_test.go @@ -125,6 +125,12 @@ func TestToStatusV2(t *testing.T) { }), codeV2: 4097, }, + { + status: (statusConstructor)(func() apistatus.Status { + return new(apistatus.APEManagerAccessDenied) + }), + codeV2: 5120, + }, { status: (statusConstructor)(func() apistatus.Status { return new(apistatus.NodeUnderMaintenance) @@ -278,6 +284,12 @@ func TestFromStatusV2(t *testing.T) { }), codeV2: 4097, }, + { + status: (statusConstructor)(func() apistatus.Status { + return new(apistatus.APEManagerAccessDenied) + }), + codeV2: 5120, + }, { status: (statusConstructor)(func() apistatus.Status { return new(apistatus.NodeUnderMaintenance)