From 548a81d3e6aaa4d607b9dc236c3a2bbaa7fec440 Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Wed, 9 Aug 2023 09:48:05 +0300 Subject: [PATCH] [#48] client: Refactor accounting.Balance() Signed-off-by: Evgenii Stratonikov --- client/accounting.go | 92 ++++++++--------- client/common.go | 229 ------------------------------------------- 2 files changed, 46 insertions(+), 275 deletions(-) diff --git a/client/accounting.go b/client/accounting.go index 71dd030..021b26a 100644 --- a/client/accounting.go +++ b/client/accounting.go @@ -2,12 +2,16 @@ package client import ( "context" + "fmt" v2accounting "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/accounting" "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs" rpcapi "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc" "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client" + v2session "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session" + "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/signature" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/accounting" + apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" ) @@ -26,6 +30,24 @@ func (x *PrmBalanceGet) SetAccount(id user.ID) { x.accountSet = true } +func (x *PrmBalanceGet) buildRequest(c *Client) (*v2accounting.BalanceRequest, error) { + if !x.accountSet { + return nil, errorAccountNotSet + } + + var accountV2 refs.OwnerID + x.account.WriteToV2(&accountV2) + + var body v2accounting.BalanceRequestBody + body.SetOwnerID(&accountV2) + + var req v2accounting.BalanceRequest + req.SetBody(&body) + + c.prepareRequest(&req, new(v2session.RequestMetaHeader)) + return &req, nil +} + // ResBalanceGet groups resulting values of BalanceGet operation. type ResBalanceGet struct { statusRes @@ -52,57 +74,35 @@ func (x ResBalanceGet) Amount() accounting.Decimal { // Return statuses: // - global (see Client docs). func (c *Client) BalanceGet(ctx context.Context, prm PrmBalanceGet) (*ResBalanceGet, error) { - if !prm.accountSet { - return nil, errorAccountNotSet + req, err := prm.buildRequest(c) + if err != nil { + return nil, err } - // form request body - var accountV2 refs.OwnerID - prm.account.WriteToV2(&accountV2) - - var body v2accounting.BalanceRequestBody - body.SetOwnerID(&accountV2) - - // form request - var req v2accounting.BalanceRequest - - req.SetBody(&body) - - // init call context - - var ( - cc contextCall - res ResBalanceGet - ) - - c.initCallContext(&cc) - cc.meta = prm.prmCommonMeta - cc.req = &req - cc.statusRes = &res - cc.call = func() (responseV2, error) { - return rpcapi.Balance(&c.c, &req, client.WithContext(ctx)) - } - cc.result = func(r responseV2) { - resp := r.(*v2accounting.BalanceResponse) - - const fieldBalance = "balance" - - bal := resp.GetBody().GetBalance() - if bal == nil { - cc.err = newErrMissingResponseField(fieldBalance) - return - } - - cc.err = res.amount.ReadFromV2(*bal) - if cc.err != nil { - cc.err = newErrInvalidResponseField(fieldBalance, cc.err) - } + if err := signature.SignServiceMessage(&c.prm.key, req); err != nil { + return nil, fmt.Errorf("sign request: %w", err) } - // process call - if !cc.processCall() { - return nil, cc.err + resp, err := rpcapi.Balance(&c.c, req, client.WithContext(ctx)) + if err != nil { + return nil, err } + var res ResBalanceGet + res.st, err = c.processResponse(resp) + if err != nil || !apistatus.IsSuccessful(res.st) { + return &res, err + } + + const fieldBalance = "balance" + + bal := resp.GetBody().GetBalance() + if bal == nil { + return &res, newErrMissingResponseField(fieldBalance) + } + + if err := res.amount.ReadFromV2(*bal); err != nil { + return &res, newErrInvalidResponseField(fieldBalance, err) + } return &res, nil } diff --git a/client/common.go b/client/common.go index 7449288..907f6be 100644 --- a/client/common.go +++ b/client/common.go @@ -1,7 +1,6 @@ package client import ( - "crypto/ecdsa" "errors" "fmt" @@ -13,21 +12,11 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/version" ) -// common interface of resulting structures with API status. -type resCommon interface { - setStatus(apistatus.Status) -} - // structure is embedded to all resulting types in order to inherit status-related methods. type statusRes struct { st apistatus.Status } -// setStatus implements resCommon interface method. -func (x *statusRes) setStatus(st apistatus.Status) { - x.st = st -} - // Status returns server's status return. // // Use apistatus package functionality to handle the status. @@ -85,89 +74,12 @@ var ( errorInvalidXHeaders = errors.New("xheaders must be presented only as key-value pairs") ) -// groups all the details required to send a single request and process a response to it. -type contextCall struct { - // ================================================== - // state vars that do not require explicit initialization - - // final error to be returned from client method - err error - - // received response - resp responseV2 - - // ================================================== - // shared parameters which are set uniformly on all calls - - // request signing key - key ecdsa.PrivateKey - - // callback prior to processing the response by the client - callbackResp func(ResponseMetaInfo) error - - // if set, protocol errors will be expanded into a final error - resolveAPIFailures bool - - // FrostFS network magic - netMagic uint64 - - // Meta parameters - meta prmCommonMeta - - // ================================================== - // custom call parameters - - // structure of the call result - statusRes resCommon - - // request to be signed with a key and sent - req request - - // function to send a request (unary) and receive a response - call func() (responseV2, error) - - // function to send the request (req field) - wReq func() error - - // function to recv the response (resp field) - rResp func() error - - // function to close the message stream - closer func() error - - // function of writing response fields to the resulting structure (optional) - result func(v2 responseV2) -} - type request interface { GetMetaHeader() *v2session.RequestMetaHeader SetMetaHeader(*v2session.RequestMetaHeader) SetVerificationHeader(*v2session.RequestVerificationHeader) } -// sets needed fields of the request meta header. -func (x contextCall) prepareRequest() { - meta := x.req.GetMetaHeader() - if meta == nil { - meta = new(v2session.RequestMetaHeader) - x.req.SetMetaHeader(meta) - } - - if meta.GetTTL() == 0 { - meta.SetTTL(2) - } - - if meta.GetVersion() == nil { - var verV2 refs.Version - version.Current().WriteToV2(&verV2) - meta.SetVersion(&verV2) - } - - meta.SetNetworkMagic(x.netMagic) - - writeXHeadersToMeta(x.meta.xHeaders, meta) -} - func (c *Client) prepareRequest(req request, meta *v2session.RequestMetaHeader) { ttl := meta.GetTTL() if ttl == 0 { @@ -187,75 +99,6 @@ func (c *Client) prepareRequest(req request, meta *v2session.RequestMetaHeader) req.SetMetaHeader(meta) } -// prepares, signs and writes the request. Result means success. -// If failed, contextCall.err contains the reason. -func (x *contextCall) writeRequest() bool { - x.prepareRequest() - - x.req.SetVerificationHeader(nil) - - // sign the request - x.err = signature.SignServiceMessage(&x.key, x.req) - if x.err != nil { - x.err = fmt.Errorf("sign request: %w", x.err) - return false - } - - x.err = x.wReq() - if x.err != nil { - x.err = fmt.Errorf("write request: %w", x.err) - return false - } - - return true -} - -// performs common actions of response processing and writes any problem as a result status or client error -// (in both cases returns false). -// -// Actions: -// - verify signature (internal); -// - call response callback (internal); -// - unwrap status error (optional). -func (x *contextCall) processResponse() bool { - // call response callback if set - if x.callbackResp != nil { - x.err = x.callbackResp(ResponseMetaInfo{ - key: x.resp.GetVerificationHeader().GetBodySignature().GetKey(), - epoch: x.resp.GetMetaHeader().GetEpoch(), - }) - if x.err != nil { - x.err = fmt.Errorf("response callback error: %w", x.err) - return false - } - } - - // note that we call response callback before signature check since it is expected more lightweight - // while verification needs marshaling - - // verify response signature - x.err = signature.VerifyServiceMessage(x.resp) - if x.err != nil { - x.err = fmt.Errorf("invalid response signature: %w", x.err) - return false - } - - // get result status - st := apistatus.FromStatusV2(x.resp.GetMetaHeader().GetStatus()) - - // unwrap unsuccessful status and return it - // as error if client has been configured so - successfulStatus := apistatus.IsSuccessful(st) - - if x.resolveAPIFailures { - x.err = apistatus.ErrFromStatus(st) - } else { - x.statusRes.setStatus(st) - } - - return successfulStatus -} - // processResponse verifies response signature and converts status to an error if needed. func (c *Client) processResponse(resp responseV2) (apistatus.Status, error) { if c.prm.cbRespInfo != nil { @@ -280,78 +123,6 @@ func (c *Client) processResponse(resp responseV2) (apistatus.Status, error) { return st, nil } -// reads response (if rResp is set) and processes it. Result means success. -// If failed, contextCall.err (or statusRes if resolveAPIFailures is set) contains the reason. -func (x *contextCall) readResponse() bool { - if x.rResp != nil { - x.err = x.rResp() - if x.err != nil { - x.err = fmt.Errorf("read response: %w", x.err) - return false - } - } - - return x.processResponse() -} - -// closes the message stream (if closer is set) and writes the results (if result is set). -// Return means success. If failed, contextCall.err contains the reason. -func (x *contextCall) close() bool { - if x.closer != nil { - x.err = x.closer() - if x.err != nil { - x.err = fmt.Errorf("close RPC: %w", x.err) - return false - } - } - - // write response to resulting structure - if x.result != nil { - x.result(x.resp) - } - - return x.err == nil -} - -// goes through all stages of sending a request and processing a response. Returns true if successful. -// If failed, contextCall.err contains the reason. -func (x *contextCall) processCall() bool { - // set request writer - x.wReq = func() error { - var err error - x.resp, err = x.call() - return err - } - - // write request - ok := x.writeRequest() - if !ok { - return false - } - - // read response - ok = x.readResponse() - if !ok { - return x.err == nil - } - - // close and write response to resulting structure - ok = x.close() - if !ok { - return false - } - - return x.err == nil -} - -// initializes static cross-call parameters inherited from client. -func (c *Client) initCallContext(ctx *contextCall) { - ctx.key = c.prm.key - ctx.resolveAPIFailures = c.prm.resolveFrostFSErrors - ctx.callbackResp = c.prm.cbRespInfo - ctx.netMagic = c.prm.netMagic -} - // ExecRaw executes f with underlying git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client.Client // instance. Communicate over the Protocol Buffers protocol in a more flexible way: // most often used to transmit data over a fixed version of the FrostFS protocol, as well