package client import ( "context" "crypto/ecdsa" "crypto/tls" "errors" "time" 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" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) // Client represents virtual connection to the FrostFS network to communicate // with FrostFS server using FrostFS API protocol. It is designed to provide // an abstraction interface from the protocol details of data transfer over // a network in FrostFS. // // Client can be created using simple Go variable declaration. Before starting // work with the Client, it SHOULD BE correctly initialized (see Init method). // Before executing the FrostFS operations using the Client, connection to the // 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. // // Each method which produces a FrostFS API call may return a server response. // 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` // and standard `errors` packages. Note that package provides some helper // functions to work with status returns (e.g. IsErrContainerNotFound). // 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): // - *apistatus.ServerInternal on internal server error; // - *apistatus.NodeUnderMaintenance if a server is under maintenance; // - *apistatus.SuccessDefaultV2 on default success. // // Client MUST NOT be copied by value: use pointer to Client instead. // // See client package overview to get some examples. type Client struct { prm PrmInit c client.Client server frostFSAPIServer } // 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 } // Dial establishes a connection to the server from the FrostFS network. // Returns an error describing failure reason. If failed, the Client // SHOULD NOT be used. // // 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. // // Returns an error if required parameters are set incorrectly, look carefully // at the method documentation. // // One-time method call during application start-up stage (after Init ) is expected. // Calling multiple times leads to undefined behavior. // // See also Init / Close. func (c *Client) Dial(ctx context.Context, prm PrmDial) error { if prm.Endpoint == "" { return errorServerAddrUnset } if prm.DialTimeout <= 0 { prm.DialTimeout = defaultDialTimeout } if prm.StreamTimeout <= 0 { prm.StreamTimeout = defaultStreamTimeout } c.c = *client.New(append( client.WithNetworkURIAddress(prm.Endpoint, prm.TLSConfig), client.WithDialTimeout(prm.DialTimeout), client.WithRWTimeout(prm.StreamTimeout), client.WithGRPCDialOptions(prm.GRPCDialOptions), )...) c.setFrostFSAPIServer((*coreServer)(&c.c)) ctx, cancel := context.WithTimeout(ctx, prm.DialTimeout) defer cancel() _, err := rpc.Balance(&c.c, new(v2accounting.BalanceRequest), client.WithContext(ctx), ) if err != nil { var ctxErr error // return context errors since they signal about dial problem if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { ctxErr = err } else if st, ok := status.FromError(err); ok && st.Code() == codes.Canceled { ctxErr = context.Canceled } else if ok && st.Code() == codes.DeadlineExceeded { ctxErr = context.DeadlineExceeded } if ctxErr != nil { if conn := c.c.Conn(); conn != nil { _ = conn.Close() } return ctxErr } } return nil } // 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. // In real applications wrapper over git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client // is statically used. func (c *Client) setFrostFSAPIServer(server frostFSAPIServer) { c.server = server } // Close closes underlying connection to the FrostFS server. Implements io.Closer. // 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 { DisableFrostFSErrorResolution bool Key ecdsa.PrivateKey ResponseInfoCallback 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). // // Deprecated: Use PrmInit.Key instead. func (x *PrmInit) SetDefaultPrivateKey(key ecdsa.PrivateKey) { x.Key = key } // Deprecated: method is no-op. Option is default. func (x *PrmInit) ResolveFrostFSFailures() { } // DisableFrostFSFailuresResolution makes the Client to preserve failure statuses of the // FrostFS protocol only in resulting structure (see corresponding Res* docs). // These errors are returned from each protocol operation. By default, statuses // are resolved and returned as a Go built-in errors. // // Deprecated: Use PrmInit.DisableFrostFSErrorResolution instead. func (x *PrmInit) DisableFrostFSFailuresResolution() { x.DisableFrostFSErrorResolution = true } // SetResponseInfoCallback makes the Client to pass ResponseMetaInfo from each // FrostFS server response to f. Nil (default) means ignore response meta info. // // Deprecated: Use PrmInit.ResponseInfoCallback instead. func (x *PrmInit) SetResponseInfoCallback(f func(ResponseMetaInfo) error) { x.ResponseInfoCallback = f } const ( defaultDialTimeout = 5 * time.Second defaultStreamTimeout = 10 * time.Second ) // PrmDial groups connection parameters for the Client. // // See also Dial. type PrmDial struct { Endpoint string TLSConfig *tls.Config // If DialTimeout is non-positive, then it's set to defaultDialTimeout. DialTimeout time.Duration // If StreamTimeout is non-positive, then it's set to defaultStreamTimeout. StreamTimeout time.Duration GRPCDialOptions []grpc.DialOption } // SetServerURI sets server URI in the FrostFS network. // Required parameter. // // Format of the URI: // // [scheme://]host:port // // Supported schemes: // // grpc // grpcs // // See also SetTLSConfig. // // Deprecated: Use PrmDial.Endpoint instead. func (x *PrmDial) SetServerURI(endpoint string) { x.Endpoint = endpoint } // SetTLSConfig sets tls.Config to open TLS client connection // to the FrostFS server. Nil (default) means insecure connection. // // See also SetServerURI. // // Depreacted: Use PrmDial.TLSConfig instead. 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. // // Deprecated: Use PrmDial.DialTimeout instead. func (x *PrmDial) SetTimeout(timeout time.Duration) { x.DialTimeout = timeout } // SetStreamTimeout sets the timeout for individual operations in streaming RPC. // MUST BE positive. If not called, 10s timeout will be used by default. // // Deprecated: Use PrmDial.StreamTimeout instead. func (x *PrmDial) SetStreamTimeout(timeout time.Duration) { x.StreamTimeout = timeout } // SetGRPCDialOptions sets the gRPC dial options for new gRPC client connection. // // Deprecated: Use PrmDial.GRPCDialOptions instead. func (x *PrmDial) SetGRPCDialOptions(opts ...grpc.DialOption) { x.GRPCDialOptions = opts }