2021-11-09 08:07:49 +00:00
|
|
|
package client
|
|
|
|
|
|
|
|
import (
|
2022-10-03 10:13:48 +00:00
|
|
|
"context"
|
2022-03-07 11:39:49 +00:00
|
|
|
"crypto/ecdsa"
|
|
|
|
"crypto/tls"
|
2022-10-03 10:13:48 +00:00
|
|
|
"errors"
|
2022-03-07 11:39:49 +00:00
|
|
|
"time"
|
|
|
|
|
2023-03-07 11:20:03 +00:00
|
|
|
v2accounting "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/accounting"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
|
2023-05-30 13:48:51 +00:00
|
|
|
"google.golang.org/grpc"
|
2021-11-09 08:07:49 +00:00
|
|
|
)
|
|
|
|
|
2022-12-29 10:46:18 +00:00
|
|
|
// Client represents virtual connection to the FrostFS network to communicate
|
|
|
|
// with FrostFS server using FrostFS API protocol. It is designed to provide
|
2022-03-07 11:39:49 +00:00
|
|
|
// an abstraction interface from the protocol details of data transfer over
|
2022-12-29 10:46:18 +00:00
|
|
|
// a network in FrostFS.
|
2022-01-10 09:19:46 +00:00
|
|
|
//
|
2022-03-07 11:39:49 +00:00
|
|
|
// Client can be created using simple Go variable declaration. Before starting
|
|
|
|
// work with the Client, it SHOULD BE correctly initialized (see Init method).
|
2022-12-29 10:46:18 +00:00
|
|
|
// Before executing the FrostFS operations using the Client, connection to the
|
2022-03-07 11:39:49 +00:00
|
|
|
// server MUST BE correctly established (see Dial method and pay attention
|
|
|
|
// to the mandatory parameters). Using the Client before connecting have
|
|
|
|
// been established can lead to a panic. After the work, the Client SHOULD BE
|
|
|
|
// closed (see Close method): it frees internal and system resources which were
|
|
|
|
// allocated for the period of work of the Client. Calling Init/Dial/Close method
|
|
|
|
// during the communication process step strongly discouraged as it leads to
|
|
|
|
// undefined behavior.
|
2022-01-24 10:33:43 +00:00
|
|
|
//
|
2022-12-29 10:46:18 +00:00
|
|
|
// Each method which produces a FrostFS API call may return a server response.
|
2022-01-24 10:33:43 +00:00
|
|
|
// Status responses are returned in the result structure, and can be cast
|
|
|
|
// to built-in error instance (or in the returned error if the client is
|
|
|
|
// configured accordingly). Certain statuses can be checked using `apistatus`
|
2022-02-28 10:56:46 +00:00
|
|
|
// and standard `errors` packages. Note that package provides some helper
|
|
|
|
// functions to work with status returns (e.g. IsErrContainerNotFound).
|
2022-01-24 10:33:43 +00:00
|
|
|
// All possible responses are documented in methods, however, some may be
|
|
|
|
// returned from all of them (pay attention to the presence of the pointer sign):
|
2022-08-24 14:17:40 +00:00
|
|
|
// - *apistatus.ServerInternal on internal server error;
|
2022-09-15 05:35:27 +00:00
|
|
|
// - *apistatus.NodeUnderMaintenance if a server is under maintenance;
|
2022-08-24 14:17:40 +00:00
|
|
|
// - *apistatus.SuccessDefaultV2 on default success.
|
2022-03-07 11:39:49 +00:00
|
|
|
//
|
|
|
|
// Client MUST NOT be copied by value: use pointer to Client instead.
|
|
|
|
//
|
|
|
|
// See client package overview to get some examples.
|
2021-12-29 13:18:09 +00:00
|
|
|
type Client struct {
|
2022-03-07 11:39:49 +00:00
|
|
|
prm PrmInit
|
|
|
|
|
|
|
|
c client.Client
|
2022-09-20 13:06:14 +00:00
|
|
|
|
2022-12-29 10:46:18 +00:00
|
|
|
server frostFSAPIServer
|
2022-03-07 11:39:49 +00:00
|
|
|
}
|
2021-11-09 08:07:49 +00:00
|
|
|
|
2022-03-07 11:39:49 +00:00
|
|
|
// Init brings the Client instance to its initial state.
|
|
|
|
//
|
|
|
|
// One-time method call during application init stage (before Dial) is expected.
|
|
|
|
// Calling multiple times leads to undefined behavior.
|
|
|
|
//
|
|
|
|
// See docs of PrmInit methods for details. See also Dial / Close.
|
|
|
|
func (c *Client) Init(prm PrmInit) {
|
|
|
|
c.prm = prm
|
2021-11-09 08:07:49 +00:00
|
|
|
}
|
|
|
|
|
2022-12-29 10:46:18 +00:00
|
|
|
// Dial establishes a connection to the server from the FrostFS network.
|
2022-03-07 11:39:49 +00:00
|
|
|
// Returns an error describing failure reason. If failed, the Client
|
|
|
|
// SHOULD NOT be used.
|
|
|
|
//
|
2022-10-03 10:13:48 +00:00
|
|
|
// Uses the context specified by SetContext if it was called with non-nil
|
|
|
|
// argument, otherwise context.Background() is used. Dial returns context
|
|
|
|
// errors, see context package docs for details.
|
|
|
|
//
|
2023-02-27 07:53:27 +00:00
|
|
|
// Returns an error if required parameters are set incorrectly, look carefully
|
2022-03-07 11:39:49 +00:00
|
|
|
// at the method documentation.
|
2022-01-10 09:19:46 +00:00
|
|
|
//
|
2022-03-07 11:39:49 +00:00
|
|
|
// One-time method call during application start-up stage (after Init ) is expected.
|
|
|
|
// Calling multiple times leads to undefined behavior.
|
|
|
|
//
|
|
|
|
// See also Init / Close.
|
2023-04-07 05:55:12 +00:00
|
|
|
func (c *Client) Dial(ctx context.Context, prm PrmDial) error {
|
2022-03-07 11:39:49 +00:00
|
|
|
if prm.endpoint == "" {
|
2023-02-27 07:53:27 +00:00
|
|
|
return errorServerAddrUnset
|
2022-03-07 11:39:49 +00:00
|
|
|
}
|
2021-11-09 08:07:49 +00:00
|
|
|
|
2022-03-07 11:39:49 +00:00
|
|
|
if prm.timeoutDialSet {
|
|
|
|
if prm.timeoutDial <= 0 {
|
2023-02-27 07:53:27 +00:00
|
|
|
return errorNonPositiveTimeout
|
2022-03-07 11:39:49 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
prm.timeoutDial = 5 * time.Second
|
2021-11-09 08:07:49 +00:00
|
|
|
}
|
|
|
|
|
2022-09-06 15:17:54 +00:00
|
|
|
if prm.streamTimeoutSet {
|
|
|
|
if prm.streamTimeout <= 0 {
|
2023-02-27 07:53:27 +00:00
|
|
|
return errorNonPositiveTimeout
|
2022-09-06 15:17:54 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
prm.streamTimeout = 10 * time.Second
|
|
|
|
}
|
|
|
|
|
2022-03-07 11:39:49 +00:00
|
|
|
c.c = *client.New(append(
|
|
|
|
client.WithNetworkURIAddress(prm.endpoint, prm.tlsConfig),
|
|
|
|
client.WithDialTimeout(prm.timeoutDial),
|
2022-09-06 15:17:54 +00:00
|
|
|
client.WithRWTimeout(prm.streamTimeout),
|
2023-05-30 13:48:51 +00:00
|
|
|
client.WithGRPCDialOptions(prm.dialOptions),
|
2022-03-07 11:39:49 +00:00
|
|
|
)...)
|
|
|
|
|
2022-12-29 10:46:18 +00:00
|
|
|
c.setFrostFSAPIServer((*coreServer)(&c.c))
|
2022-09-20 13:06:14 +00:00
|
|
|
|
2022-03-07 11:39:49 +00:00
|
|
|
// TODO: (neofs-api-go#382) perform generic dial stage of the client.Client
|
2022-10-03 10:13:48 +00:00
|
|
|
_, err := rpc.Balance(&c.c, new(v2accounting.BalanceRequest),
|
2023-04-07 05:55:12 +00:00
|
|
|
client.WithContext(ctx),
|
2022-10-03 10:13:48 +00:00
|
|
|
)
|
|
|
|
// return context errors since they signal about dial problem
|
|
|
|
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
|
|
|
|
return err
|
|
|
|
}
|
2022-03-07 11:39:49 +00:00
|
|
|
|
2022-03-14 09:53:32 +00:00
|
|
|
return nil
|
2022-03-07 11:39:49 +00:00
|
|
|
}
|
|
|
|
|
2022-12-29 10:46:18 +00:00
|
|
|
// sets underlying provider of frostFSAPIServer. The method is used for testing as an approach
|
|
|
|
// to skip Dial stage and override FrostFS API server. MUST NOT be used outside test code.
|
2023-03-07 11:20:03 +00:00
|
|
|
// In real applications wrapper over git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client
|
2022-09-20 13:06:14 +00:00
|
|
|
// is statically used.
|
2022-12-29 10:46:18 +00:00
|
|
|
func (c *Client) setFrostFSAPIServer(server frostFSAPIServer) {
|
2022-09-20 13:06:14 +00:00
|
|
|
c.server = server
|
|
|
|
}
|
|
|
|
|
2022-12-29 10:46:18 +00:00
|
|
|
// Close closes underlying connection to the FrostFS server. Implements io.Closer.
|
2022-03-07 11:39:49 +00:00
|
|
|
// MUST NOT be called before successful Dial. Can be called concurrently
|
|
|
|
// with server operations processing on running goroutines: in this case
|
|
|
|
// they are likely to fail due to a connection error.
|
|
|
|
//
|
|
|
|
// One-time method call during application shutdown stage (after Init and Dial)
|
|
|
|
// is expected. Calling multiple times leads to undefined behavior.
|
|
|
|
//
|
|
|
|
// See also Init / Dial.
|
|
|
|
func (c *Client) Close() error {
|
|
|
|
return c.c.Conn().Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
// PrmInit groups initialization parameters of Client instances.
|
|
|
|
//
|
|
|
|
// See also Init.
|
|
|
|
type PrmInit struct {
|
2022-12-29 10:46:18 +00:00
|
|
|
resolveFrostFSErrors bool
|
2022-03-07 11:39:49 +00:00
|
|
|
|
|
|
|
key ecdsa.PrivateKey
|
|
|
|
|
|
|
|
cbRespInfo func(ResponseMetaInfo) error
|
|
|
|
|
|
|
|
netMagic uint64
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetDefaultPrivateKey sets Client private key to be used for the protocol
|
|
|
|
// communication by default.
|
|
|
|
//
|
|
|
|
// Required for operations without custom key parametrization (see corresponding Prm* docs).
|
|
|
|
func (x *PrmInit) SetDefaultPrivateKey(key ecdsa.PrivateKey) {
|
|
|
|
x.key = key
|
|
|
|
}
|
|
|
|
|
2022-12-29 10:46:18 +00:00
|
|
|
// ResolveFrostFSFailures makes the Client to resolve failure statuses of the
|
|
|
|
// FrostFS protocol into Go built-in errors. These errors are returned from
|
2022-03-07 11:39:49 +00:00
|
|
|
// each protocol operation. By default, statuses aren't resolved and written
|
|
|
|
// to the resulting structure (see corresponding Res* docs).
|
2022-12-29 10:46:18 +00:00
|
|
|
func (x *PrmInit) ResolveFrostFSFailures() {
|
|
|
|
x.resolveFrostFSErrors = true
|
2022-03-07 11:39:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// SetResponseInfoCallback makes the Client to pass ResponseMetaInfo from each
|
2022-12-29 10:46:18 +00:00
|
|
|
// FrostFS server response to f. Nil (default) means ignore response meta info.
|
2022-03-07 11:39:49 +00:00
|
|
|
func (x *PrmInit) SetResponseInfoCallback(f func(ResponseMetaInfo) error) {
|
|
|
|
x.cbRespInfo = f
|
|
|
|
}
|
|
|
|
|
|
|
|
// PrmDial groups connection parameters for the Client.
|
|
|
|
//
|
|
|
|
// See also Dial.
|
|
|
|
type PrmDial struct {
|
|
|
|
endpoint string
|
|
|
|
|
|
|
|
tlsConfig *tls.Config
|
|
|
|
|
|
|
|
timeoutDialSet bool
|
|
|
|
timeoutDial time.Duration
|
2022-09-06 15:17:54 +00:00
|
|
|
|
|
|
|
streamTimeoutSet bool
|
|
|
|
streamTimeout time.Duration
|
2023-05-30 13:48:51 +00:00
|
|
|
|
|
|
|
dialOptions []grpc.DialOption
|
2022-03-07 11:39:49 +00:00
|
|
|
}
|
|
|
|
|
2022-12-29 10:46:18 +00:00
|
|
|
// SetServerURI sets server URI in the FrostFS network.
|
2022-03-07 11:39:49 +00:00
|
|
|
// Required parameter.
|
|
|
|
//
|
|
|
|
// Format of the URI:
|
2022-08-24 14:17:40 +00:00
|
|
|
//
|
|
|
|
// [scheme://]host:port
|
2022-03-07 11:39:49 +00:00
|
|
|
//
|
|
|
|
// Supported schemes:
|
2022-08-24 14:17:40 +00:00
|
|
|
//
|
|
|
|
// grpc
|
|
|
|
// grpcs
|
2022-03-07 11:39:49 +00:00
|
|
|
//
|
|
|
|
// See also SetTLSConfig.
|
|
|
|
func (x *PrmDial) SetServerURI(endpoint string) {
|
|
|
|
x.endpoint = endpoint
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetTLSConfig sets tls.Config to open TLS client connection
|
2022-12-29 10:46:18 +00:00
|
|
|
// to the FrostFS server. Nil (default) means insecure connection.
|
2022-03-07 11:39:49 +00:00
|
|
|
//
|
|
|
|
// See also SetServerURI.
|
|
|
|
func (x *PrmDial) SetTLSConfig(tlsConfig *tls.Config) {
|
|
|
|
x.tlsConfig = tlsConfig
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetTimeout sets the timeout for connection to be established.
|
|
|
|
// MUST BE positive. If not called, 5s timeout will be used by default.
|
|
|
|
func (x *PrmDial) SetTimeout(timeout time.Duration) {
|
|
|
|
x.timeoutDialSet = true
|
|
|
|
x.timeoutDial = timeout
|
2021-11-09 08:07:49 +00:00
|
|
|
}
|
2022-09-06 15:17:54 +00:00
|
|
|
|
|
|
|
// SetStreamTimeout sets the timeout for individual operations in streaming RPC.
|
|
|
|
// MUST BE positive. If not called, 10s timeout will be used by default.
|
|
|
|
func (x *PrmDial) SetStreamTimeout(timeout time.Duration) {
|
|
|
|
x.streamTimeoutSet = true
|
|
|
|
x.streamTimeout = timeout
|
|
|
|
}
|
2023-05-30 13:48:51 +00:00
|
|
|
|
|
|
|
// SetGRPCDialOptions sets the gRPC dial options for new gRPC client connection.
|
|
|
|
func (x *PrmDial) SetGRPCDialOptions(opts ...grpc.DialOption) {
|
|
|
|
x.dialOptions = opts
|
|
|
|
}
|