git.frostfs.info/TrueCloudLab/frostfs-sdk-go@v0.0.0-20241022124111-5361f0ecebd3/client/client.go (about)

     1  package client
     2  
     3  import (
     4  	"context"
     5  	"crypto/ecdsa"
     6  	"crypto/tls"
     7  	"errors"
     8  	"time"
     9  
    10  	v2accounting "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/accounting"
    11  	"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc"
    12  	"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
    13  	"google.golang.org/grpc"
    14  	"google.golang.org/grpc/codes"
    15  	"google.golang.org/grpc/status"
    16  )
    17  
    18  // Client represents virtual connection to the FrostFS network to communicate
    19  // with FrostFS server using FrostFS API protocol. It is designed to provide
    20  // an abstraction interface from the protocol details of data transfer over
    21  // a network in FrostFS.
    22  //
    23  // Client can be created using simple Go variable declaration. Before starting
    24  // work with the Client, it SHOULD BE correctly initialized (see Init method).
    25  // Before executing the FrostFS operations using the Client, connection to the
    26  // server MUST BE correctly established (see Dial method and pay attention
    27  // to the mandatory parameters). Using the Client before connecting have
    28  // been established can lead to a panic. After the work, the Client SHOULD BE
    29  // closed (see Close method): it frees internal and system resources which were
    30  // allocated for the period of work of the Client. Calling Init/Dial/Close method
    31  // during the communication process step strongly discouraged as it leads to
    32  // undefined behavior.
    33  //
    34  // Each method which produces a FrostFS API call may return a server response.
    35  // Status responses are returned in the result structure, and can be cast
    36  // to built-in error instance (or in the returned error if the client is
    37  // configured accordingly). Certain statuses can be checked using `apistatus`
    38  // and standard `errors` packages. Note that package provides some helper
    39  // functions to work with status returns (e.g. IsErrContainerNotFound).
    40  // All possible responses are documented in methods, however, some may be
    41  // returned from all of them (pay attention to the presence of the pointer sign):
    42  //   - *apistatus.ServerInternal on internal server error;
    43  //   - *apistatus.NodeUnderMaintenance if a server is under maintenance;
    44  //   - *apistatus.SuccessDefaultV2 on default success.
    45  //
    46  // Client MUST NOT be copied by value: use pointer to Client instead.
    47  //
    48  // See client package overview to get some examples.
    49  type Client struct {
    50  	prm PrmInit
    51  
    52  	c client.Client
    53  
    54  	server frostFSAPIServer
    55  }
    56  
    57  // Init brings the Client instance to its initial state.
    58  //
    59  // One-time method call during application init stage (before Dial) is expected.
    60  // Calling multiple times leads to undefined behavior.
    61  //
    62  // See docs of PrmInit methods for details. See also Dial / Close.
    63  func (c *Client) Init(prm PrmInit) {
    64  	c.prm = prm
    65  }
    66  
    67  // Dial establishes a connection to the server from the FrostFS network.
    68  // Returns an error describing failure reason. If failed, the Client
    69  // SHOULD NOT be used.
    70  //
    71  // Uses the context specified by SetContext if it was called with non-nil
    72  // argument, otherwise context.Background() is used. Dial returns context
    73  // errors, see context package docs for details.
    74  //
    75  // Returns an error if required parameters are set incorrectly, look carefully
    76  // at the method documentation.
    77  //
    78  // One-time method call during application start-up stage (after Init ) is expected.
    79  // Calling multiple times leads to undefined behavior.
    80  //
    81  // See also Init / Close.
    82  func (c *Client) Dial(ctx context.Context, prm PrmDial) error {
    83  	if prm.Endpoint == "" {
    84  		return errorServerAddrUnset
    85  	}
    86  
    87  	if prm.DialTimeout <= 0 {
    88  		prm.DialTimeout = defaultDialTimeout
    89  	}
    90  	if prm.StreamTimeout <= 0 {
    91  		prm.StreamTimeout = defaultStreamTimeout
    92  	}
    93  
    94  	c.c = *client.New(append(
    95  		client.WithNetworkURIAddress(prm.Endpoint, prm.TLSConfig),
    96  		client.WithDialTimeout(prm.DialTimeout),
    97  		client.WithRWTimeout(prm.StreamTimeout),
    98  		client.WithGRPCDialOptions(prm.GRPCDialOptions),
    99  	)...)
   100  
   101  	c.setFrostFSAPIServer((*coreServer)(&c.c))
   102  
   103  	ctx, cancel := context.WithTimeout(ctx, prm.DialTimeout)
   104  	defer cancel()
   105  	_, err := rpc.Balance(&c.c, new(v2accounting.BalanceRequest),
   106  		client.WithContext(ctx),
   107  	)
   108  	if err != nil {
   109  		var ctxErr error
   110  
   111  		// return context errors since they signal about dial problem
   112  		if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
   113  			ctxErr = err
   114  		} else if st, ok := status.FromError(err); ok && st.Code() == codes.Canceled {
   115  			ctxErr = context.Canceled
   116  		} else if ok && st.Code() == codes.DeadlineExceeded {
   117  			ctxErr = context.DeadlineExceeded
   118  		}
   119  		if ctxErr != nil {
   120  			if conn := c.c.Conn(); conn != nil {
   121  				_ = conn.Close()
   122  			}
   123  			return ctxErr
   124  		}
   125  	}
   126  
   127  	return nil
   128  }
   129  
   130  // sets underlying provider of frostFSAPIServer. The method is used for testing as an approach
   131  // to skip Dial stage and override FrostFS API server. MUST NOT be used outside test code.
   132  // In real applications wrapper over git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client
   133  // is statically used.
   134  func (c *Client) setFrostFSAPIServer(server frostFSAPIServer) {
   135  	c.server = server
   136  }
   137  
   138  // Close closes underlying connection to the FrostFS server. Implements io.Closer.
   139  // MUST NOT be called before successful Dial. Can be called concurrently
   140  // with server operations processing on running goroutines: in this case
   141  // they are likely to fail due to a connection error.
   142  //
   143  // One-time method call during application shutdown stage (after Init and Dial)
   144  // is expected. Calling multiple times leads to undefined behavior.
   145  //
   146  // See also Init / Dial.
   147  func (c *Client) Close() error {
   148  	return c.c.Conn().Close()
   149  }
   150  
   151  // PrmInit groups initialization parameters of Client instances.
   152  //
   153  // See also Init.
   154  type PrmInit struct {
   155  	DisableFrostFSErrorResolution bool
   156  
   157  	Key ecdsa.PrivateKey
   158  
   159  	ResponseInfoCallback func(ResponseMetaInfo) error
   160  
   161  	NetMagic uint64
   162  }
   163  
   164  // SetDefaultPrivateKey sets Client private key to be used for the protocol
   165  // communication by default.
   166  //
   167  // Required for operations without custom key parametrization (see corresponding Prm* docs).
   168  //
   169  // Deprecated: Use PrmInit.Key instead.
   170  func (x *PrmInit) SetDefaultPrivateKey(key ecdsa.PrivateKey) {
   171  	x.Key = key
   172  }
   173  
   174  // Deprecated: method is no-op. Option is default.
   175  func (x *PrmInit) ResolveFrostFSFailures() {
   176  }
   177  
   178  // DisableFrostFSFailuresResolution makes the Client to preserve failure statuses of the
   179  // FrostFS protocol only in resulting structure (see corresponding Res* docs).
   180  // These errors are returned from each protocol operation. By default, statuses
   181  // are resolved and returned as a Go built-in errors.
   182  //
   183  // Deprecated: Use PrmInit.DisableFrostFSErrorResolution instead.
   184  func (x *PrmInit) DisableFrostFSFailuresResolution() {
   185  	x.DisableFrostFSErrorResolution = true
   186  }
   187  
   188  // SetResponseInfoCallback makes the Client to pass ResponseMetaInfo from each
   189  // FrostFS server response to f. Nil (default) means ignore response meta info.
   190  //
   191  // Deprecated: Use PrmInit.ResponseInfoCallback instead.
   192  func (x *PrmInit) SetResponseInfoCallback(f func(ResponseMetaInfo) error) {
   193  	x.ResponseInfoCallback = f
   194  }
   195  
   196  const (
   197  	defaultDialTimeout   = 5 * time.Second
   198  	defaultStreamTimeout = 10 * time.Second
   199  )
   200  
   201  // PrmDial groups connection parameters for the Client.
   202  //
   203  // See also Dial.
   204  type PrmDial struct {
   205  	Endpoint string
   206  
   207  	TLSConfig *tls.Config
   208  
   209  	// If DialTimeout is non-positive, then it's set to defaultDialTimeout.
   210  	DialTimeout time.Duration
   211  
   212  	// If StreamTimeout is non-positive, then it's set to defaultStreamTimeout.
   213  	StreamTimeout time.Duration
   214  
   215  	GRPCDialOptions []grpc.DialOption
   216  }
   217  
   218  // SetServerURI sets server URI in the FrostFS network.
   219  // Required parameter.
   220  //
   221  // Format of the URI:
   222  //
   223  //	[scheme://]host:port
   224  //
   225  // Supported schemes:
   226  //
   227  //	grpc
   228  //	grpcs
   229  //
   230  // See also SetTLSConfig.
   231  //
   232  // Deprecated: Use PrmDial.Endpoint instead.
   233  func (x *PrmDial) SetServerURI(endpoint string) {
   234  	x.Endpoint = endpoint
   235  }
   236  
   237  // SetTLSConfig sets tls.Config to open TLS client connection
   238  // to the FrostFS server. Nil (default) means insecure connection.
   239  //
   240  // See also SetServerURI.
   241  //
   242  // Depreacted: Use PrmDial.TLSConfig instead.
   243  func (x *PrmDial) SetTLSConfig(tlsConfig *tls.Config) {
   244  	x.TLSConfig = tlsConfig
   245  }
   246  
   247  // SetTimeout sets the timeout for connection to be established.
   248  // MUST BE positive. If not called, 5s timeout will be used by default.
   249  //
   250  // Deprecated: Use PrmDial.DialTimeout instead.
   251  func (x *PrmDial) SetTimeout(timeout time.Duration) {
   252  	x.DialTimeout = timeout
   253  }
   254  
   255  // SetStreamTimeout sets the timeout for individual operations in streaming RPC.
   256  // MUST BE positive. If not called, 10s timeout will be used by default.
   257  //
   258  // Deprecated: Use PrmDial.StreamTimeout instead.
   259  func (x *PrmDial) SetStreamTimeout(timeout time.Duration) {
   260  	x.StreamTimeout = timeout
   261  }
   262  
   263  // SetGRPCDialOptions sets the gRPC dial options for new gRPC client connection.
   264  //
   265  // Deprecated: Use PrmDial.GRPCDialOptions instead.
   266  func (x *PrmDial) SetGRPCDialOptions(opts ...grpc.DialOption) {
   267  	x.GRPCDialOptions = opts
   268  }