forked from TrueCloudLab/frostfs-sdk-go
[#48] client: Refactor accounting.Balance()
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
This commit is contained in:
parent
d48788c7a9
commit
548a81d3e6
2 changed files with 46 additions and 275 deletions
|
@ -2,12 +2,16 @@ package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
v2accounting "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/accounting"
|
v2accounting "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/accounting"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
|
||||||
rpcapi "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc"
|
rpcapi "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
|
"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"
|
"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"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -26,6 +30,24 @@ func (x *PrmBalanceGet) SetAccount(id user.ID) {
|
||||||
x.accountSet = true
|
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.
|
// ResBalanceGet groups resulting values of BalanceGet operation.
|
||||||
type ResBalanceGet struct {
|
type ResBalanceGet struct {
|
||||||
statusRes
|
statusRes
|
||||||
|
@ -52,57 +74,35 @@ func (x ResBalanceGet) Amount() accounting.Decimal {
|
||||||
// Return statuses:
|
// Return statuses:
|
||||||
// - global (see Client docs).
|
// - global (see Client docs).
|
||||||
func (c *Client) BalanceGet(ctx context.Context, prm PrmBalanceGet) (*ResBalanceGet, error) {
|
func (c *Client) BalanceGet(ctx context.Context, prm PrmBalanceGet) (*ResBalanceGet, error) {
|
||||||
if !prm.accountSet {
|
req, err := prm.buildRequest(c)
|
||||||
return nil, errorAccountNotSet
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// form request body
|
if err := signature.SignServiceMessage(&c.prm.key, req); err != nil {
|
||||||
var accountV2 refs.OwnerID
|
return nil, fmt.Errorf("sign request: %w", err)
|
||||||
prm.account.WriteToV2(&accountV2)
|
}
|
||||||
|
|
||||||
var body v2accounting.BalanceRequestBody
|
resp, err := rpcapi.Balance(&c.c, req, client.WithContext(ctx))
|
||||||
body.SetOwnerID(&accountV2)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
// form request
|
}
|
||||||
var req v2accounting.BalanceRequest
|
|
||||||
|
var res ResBalanceGet
|
||||||
req.SetBody(&body)
|
res.st, err = c.processResponse(resp)
|
||||||
|
if err != nil || !apistatus.IsSuccessful(res.st) {
|
||||||
// init call context
|
return &res, err
|
||||||
|
|
||||||
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"
|
const fieldBalance = "balance"
|
||||||
|
|
||||||
bal := resp.GetBody().GetBalance()
|
bal := resp.GetBody().GetBalance()
|
||||||
if bal == nil {
|
if bal == nil {
|
||||||
cc.err = newErrMissingResponseField(fieldBalance)
|
return &res, newErrMissingResponseField(fieldBalance)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cc.err = res.amount.ReadFromV2(*bal)
|
if err := res.amount.ReadFromV2(*bal); err != nil {
|
||||||
if cc.err != nil {
|
return &res, newErrInvalidResponseField(fieldBalance, err)
|
||||||
cc.err = newErrInvalidResponseField(fieldBalance, cc.err)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// process call
|
|
||||||
if !cc.processCall() {
|
|
||||||
return nil, cc.err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &res, nil
|
return &res, nil
|
||||||
}
|
}
|
||||||
|
|
229
client/common.go
229
client/common.go
|
@ -1,7 +1,6 @@
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/ecdsa"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
@ -13,21 +12,11 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/version"
|
"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.
|
// structure is embedded to all resulting types in order to inherit status-related methods.
|
||||||
type statusRes struct {
|
type statusRes struct {
|
||||||
st apistatus.Status
|
st apistatus.Status
|
||||||
}
|
}
|
||||||
|
|
||||||
// setStatus implements resCommon interface method.
|
|
||||||
func (x *statusRes) setStatus(st apistatus.Status) {
|
|
||||||
x.st = st
|
|
||||||
}
|
|
||||||
|
|
||||||
// Status returns server's status return.
|
// Status returns server's status return.
|
||||||
//
|
//
|
||||||
// Use apistatus package functionality to handle the status.
|
// 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")
|
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 {
|
type request interface {
|
||||||
GetMetaHeader() *v2session.RequestMetaHeader
|
GetMetaHeader() *v2session.RequestMetaHeader
|
||||||
SetMetaHeader(*v2session.RequestMetaHeader)
|
SetMetaHeader(*v2session.RequestMetaHeader)
|
||||||
SetVerificationHeader(*v2session.RequestVerificationHeader)
|
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) {
|
func (c *Client) prepareRequest(req request, meta *v2session.RequestMetaHeader) {
|
||||||
ttl := meta.GetTTL()
|
ttl := meta.GetTTL()
|
||||||
if ttl == 0 {
|
if ttl == 0 {
|
||||||
|
@ -187,75 +99,6 @@ func (c *Client) prepareRequest(req request, meta *v2session.RequestMetaHeader)
|
||||||
req.SetMetaHeader(meta)
|
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.
|
// processResponse verifies response signature and converts status to an error if needed.
|
||||||
func (c *Client) processResponse(resp responseV2) (apistatus.Status, error) {
|
func (c *Client) processResponse(resp responseV2) (apistatus.Status, error) {
|
||||||
if c.prm.cbRespInfo != nil {
|
if c.prm.cbRespInfo != nil {
|
||||||
|
@ -280,78 +123,6 @@ func (c *Client) processResponse(resp responseV2) (apistatus.Status, error) {
|
||||||
return st, nil
|
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
|
// 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:
|
// 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
|
// most often used to transmit data over a fixed version of the FrostFS protocol, as well
|
||||||
|
|
Loading…
Reference in a new issue