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

     1  package pool
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"crypto/ecdsa"
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"math"
    11  	"math/rand"
    12  	"sort"
    13  	"sync"
    14  	"sync/atomic"
    15  	"time"
    16  
    17  	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/accounting"
    18  	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/ape"
    19  	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
    20  	sdkClient "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
    21  	apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
    22  	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
    23  	cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
    24  	frostfsecdsa "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto/ecdsa"
    25  	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
    26  	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
    27  	oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
    28  	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/relations"
    29  	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
    30  	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
    31  	"github.com/google/uuid"
    32  	"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
    33  	"go.uber.org/zap"
    34  	"go.uber.org/zap/zapcore"
    35  	"google.golang.org/grpc"
    36  )
    37  
    38  // client represents virtual connection to the single FrostFS network endpoint from which Pool is formed.
    39  // This interface is expected to have exactly one production implementation - clientWrapper.
    40  // Others are expected to be for test purposes only.
    41  type client interface {
    42  	// see clientWrapper.balanceGet.
    43  	balanceGet(context.Context, PrmBalanceGet) (accounting.Decimal, error)
    44  	// see clientWrapper.containerPut.
    45  	containerPut(context.Context, PrmContainerPut) (cid.ID, error)
    46  	// see clientWrapper.containerGet.
    47  	containerGet(context.Context, PrmContainerGet) (container.Container, error)
    48  	// see clientWrapper.containerList.
    49  	containerList(context.Context, PrmContainerList) ([]cid.ID, error)
    50  	// see clientWrapper.containerDelete.
    51  	containerDelete(context.Context, PrmContainerDelete) error
    52  	// see clientWrapper.apeManagerAddChain.
    53  	apeManagerAddChain(context.Context, PrmAddAPEChain) error
    54  	// see clientWrapper.apeManagerRemoveChain.
    55  	apeManagerRemoveChain(context.Context, PrmRemoveAPEChain) error
    56  	// see clientWrapper.apeManagerListChains.
    57  	apeManagerListChains(context.Context, PrmListAPEChains) ([]ape.Chain, error)
    58  	// see clientWrapper.endpointInfo.
    59  	endpointInfo(context.Context, prmEndpointInfo) (netmap.NodeInfo, error)
    60  	// see clientWrapper.healthcheck.
    61  	healthcheck(ctx context.Context) (netmap.NodeInfo, error)
    62  	// see clientWrapper.networkInfo.
    63  	networkInfo(context.Context, prmNetworkInfo) (netmap.NetworkInfo, error)
    64  	// see clientWrapper.netMapSnapshot
    65  	netMapSnapshot(context.Context, prmNetMapSnapshot) (netmap.NetMap, error)
    66  	// see clientWrapper.objectPut.
    67  	objectPut(context.Context, PrmObjectPut) (ResPutObject, error)
    68  	// see clientWrapper.objectPatch.
    69  	objectPatch(context.Context, PrmObjectPatch) (ResPatchObject, error)
    70  	// see clientWrapper.objectDelete.
    71  	objectDelete(context.Context, PrmObjectDelete) error
    72  	// see clientWrapper.objectGet.
    73  	objectGet(context.Context, PrmObjectGet) (ResGetObject, error)
    74  	// see clientWrapper.objectHead.
    75  	objectHead(context.Context, PrmObjectHead) (object.Object, error)
    76  	// see clientWrapper.objectRange.
    77  	objectRange(context.Context, PrmObjectRange) (ResObjectRange, error)
    78  	// see clientWrapper.objectSearch.
    79  	objectSearch(context.Context, PrmObjectSearch) (ResObjectSearch, error)
    80  	// see clientWrapper.sessionCreate.
    81  	sessionCreate(context.Context, prmCreateSession) (resCreateSession, error)
    82  
    83  	clientStatus
    84  
    85  	// see clientWrapper.dial.
    86  	dial(ctx context.Context) error
    87  	// see clientWrapper.restart.
    88  	restart(ctx context.Context) error
    89  	// see clientWrapper.close.
    90  	close() error
    91  }
    92  
    93  // clientStatus provide access to some metrics for connection.
    94  type clientStatus interface {
    95  	// isHealthy checks if the connection can handle requests.
    96  	isHealthy() bool
    97  	// setUnhealthy marks client as unhealthy.
    98  	setUnhealthy()
    99  	// setHealthy marks client as healthy.
   100  	setHealthy()
   101  	// address return address of endpoint.
   102  	address() string
   103  	// currentErrorRate returns current errors rate.
   104  	// After specific threshold connection is considered as unhealthy.
   105  	// Pool.startRebalance routine can make this connection healthy again.
   106  	currentErrorRate() uint32
   107  	// overallErrorRate returns the number of all happened errors.
   108  	overallErrorRate() uint64
   109  	// methodsStatus returns statistic for all used methods.
   110  	methodsStatus() []StatusSnapshot
   111  }
   112  
   113  // errPoolClientUnhealthy is an error to indicate that client in pool is unhealthy.
   114  var errPoolClientUnhealthy = errors.New("pool client unhealthy")
   115  
   116  // clientStatusMonitor count error rate and other statistics for connection.
   117  type clientStatusMonitor struct {
   118  	logger         *zap.Logger
   119  	addr           string
   120  	healthy        *atomic.Uint32
   121  	errorThreshold uint32
   122  
   123  	mu                sync.RWMutex // protect counters
   124  	currentErrorCount uint32
   125  	overallErrorCount uint64
   126  	methods           []*MethodStatus
   127  }
   128  
   129  // values for healthy status of clientStatusMonitor.
   130  const (
   131  	// statusUnhealthyOnRequest is set when communication after dialing to the
   132  	// endpoint is failed due to immediate or accumulated errors, connection is
   133  	// available and pool should close it before re-establishing connection once again.
   134  	statusUnhealthyOnRequest = iota
   135  
   136  	// statusHealthy is set when connection is ready to be used by the pool.
   137  	statusHealthy
   138  )
   139  
   140  // MethodIndex index of method in list of statuses in clientStatusMonitor.
   141  type MethodIndex int
   142  
   143  const (
   144  	methodBalanceGet MethodIndex = iota
   145  	methodContainerPut
   146  	methodContainerGet
   147  	methodContainerList
   148  	methodContainerDelete
   149  	methodEndpointInfo
   150  	methodNetworkInfo
   151  	methodNetMapSnapshot
   152  	methodObjectPut
   153  	methodObjectDelete
   154  	methodObjectGet
   155  	methodObjectHead
   156  	methodObjectRange
   157  	methodObjectPatch
   158  	methodSessionCreate
   159  	methodAPEManagerAddChain
   160  	methodAPEManagerRemoveChain
   161  	methodAPEManagerListChains
   162  	methodLast
   163  )
   164  
   165  // String implements fmt.Stringer.
   166  func (m MethodIndex) String() string {
   167  	switch m {
   168  	case methodBalanceGet:
   169  		return "balanceGet"
   170  	case methodContainerPut:
   171  		return "containerPut"
   172  	case methodContainerGet:
   173  		return "containerGet"
   174  	case methodContainerList:
   175  		return "containerList"
   176  	case methodContainerDelete:
   177  		return "containerDelete"
   178  	case methodEndpointInfo:
   179  		return "endpointInfo"
   180  	case methodNetworkInfo:
   181  		return "networkInfo"
   182  	case methodNetMapSnapshot:
   183  		return "netMapSnapshot"
   184  	case methodObjectPut:
   185  		return "objectPut"
   186  	case methodObjectPatch:
   187  		return "objectPatch"
   188  	case methodObjectDelete:
   189  		return "objectDelete"
   190  	case methodObjectGet:
   191  		return "objectGet"
   192  	case methodObjectHead:
   193  		return "objectHead"
   194  	case methodObjectRange:
   195  		return "objectRange"
   196  	case methodSessionCreate:
   197  		return "sessionCreate"
   198  	case methodAPEManagerAddChain:
   199  		return "apeManagerAddChain"
   200  	case methodAPEManagerRemoveChain:
   201  		return "apeManagerRemoveChain"
   202  	case methodAPEManagerListChains:
   203  		return "apeManagerListChains"
   204  	case methodLast:
   205  		return "it's a system name rather than a method"
   206  	default:
   207  		return "unknown"
   208  	}
   209  }
   210  
   211  func newClientStatusMonitor(logger *zap.Logger, addr string, errorThreshold uint32) clientStatusMonitor {
   212  	methods := make([]*MethodStatus, methodLast)
   213  	for i := methodBalanceGet; i < methodLast; i++ {
   214  		methods[i] = &MethodStatus{name: i.String()}
   215  	}
   216  
   217  	healthy := new(atomic.Uint32)
   218  	healthy.Store(statusHealthy)
   219  
   220  	return clientStatusMonitor{
   221  		logger:         logger,
   222  		addr:           addr,
   223  		healthy:        healthy,
   224  		errorThreshold: errorThreshold,
   225  		methods:        methods,
   226  	}
   227  }
   228  
   229  // clientWrapper is used by default, alternative implementations are intended for testing purposes only.
   230  type clientWrapper struct {
   231  	clientMutex sync.RWMutex
   232  	client      *sdkClient.Client
   233  	dialed      bool
   234  	prm         wrapperPrm
   235  
   236  	clientStatusMonitor
   237  }
   238  
   239  // wrapperPrm is params to create clientWrapper.
   240  type wrapperPrm struct {
   241  	logger                  *zap.Logger
   242  	address                 string
   243  	key                     ecdsa.PrivateKey
   244  	dialTimeout             time.Duration
   245  	streamTimeout           time.Duration
   246  	errorThreshold          uint32
   247  	responseInfoCallback    func(sdkClient.ResponseMetaInfo) error
   248  	poolRequestInfoCallback func(RequestInfo)
   249  	dialOptions             []grpc.DialOption
   250  
   251  	gracefulCloseOnSwitchTimeout time.Duration
   252  }
   253  
   254  // setAddress sets endpoint to connect in FrostFS network.
   255  func (x *wrapperPrm) setAddress(address string) {
   256  	x.address = address
   257  }
   258  
   259  // setKey sets sdkClient.Client private key to be used for the protocol communication by default.
   260  func (x *wrapperPrm) setKey(key ecdsa.PrivateKey) {
   261  	x.key = key
   262  }
   263  
   264  // setLogger sets sdkClient.Client logger.
   265  func (x *wrapperPrm) setLogger(logger *zap.Logger) {
   266  	x.logger = logger
   267  }
   268  
   269  // setDialTimeout sets the timeout for connection to be established.
   270  func (x *wrapperPrm) setDialTimeout(timeout time.Duration) {
   271  	x.dialTimeout = timeout
   272  }
   273  
   274  // setStreamTimeout sets the timeout for individual operations in streaming RPC.
   275  func (x *wrapperPrm) setStreamTimeout(timeout time.Duration) {
   276  	x.streamTimeout = timeout
   277  }
   278  
   279  // setErrorThreshold sets threshold after reaching which connection is considered unhealthy
   280  // until Pool.startRebalance routing updates its status.
   281  func (x *wrapperPrm) setErrorThreshold(threshold uint32) {
   282  	x.errorThreshold = threshold
   283  }
   284  
   285  // setGracefulCloseOnSwitchTimeout specifies the timeout after which unhealthy client be closed during rebalancing
   286  // if it will become healthy back.
   287  //
   288  // See also setErrorThreshold.
   289  func (x *wrapperPrm) setGracefulCloseOnSwitchTimeout(timeout time.Duration) {
   290  	x.gracefulCloseOnSwitchTimeout = timeout
   291  }
   292  
   293  // setPoolRequestCallback sets callback that will be invoked after every pool response.
   294  func (x *wrapperPrm) setPoolRequestCallback(f func(RequestInfo)) {
   295  	x.poolRequestInfoCallback = f
   296  }
   297  
   298  // setResponseInfoCallback sets callback that will be invoked after every response.
   299  func (x *wrapperPrm) setResponseInfoCallback(f func(sdkClient.ResponseMetaInfo) error) {
   300  	x.responseInfoCallback = f
   301  }
   302  
   303  // setGRPCDialOptions sets the gRPC dial options for new gRPC client connection.
   304  func (x *wrapperPrm) setGRPCDialOptions(opts []grpc.DialOption) {
   305  	x.dialOptions = opts
   306  }
   307  
   308  // newWrapper creates a clientWrapper that implements the client interface.
   309  func newWrapper(prm wrapperPrm) *clientWrapper {
   310  	var cl sdkClient.Client
   311  	prmInit := sdkClient.PrmInit{
   312  		Key:                  prm.key,
   313  		ResponseInfoCallback: prm.responseInfoCallback,
   314  	}
   315  
   316  	cl.Init(prmInit)
   317  
   318  	res := &clientWrapper{
   319  		client:              &cl,
   320  		clientStatusMonitor: newClientStatusMonitor(prm.logger, prm.address, prm.errorThreshold),
   321  		prm:                 prm,
   322  	}
   323  
   324  	return res
   325  }
   326  
   327  // dial establishes a connection to the server from the FrostFS network.
   328  // Returns an error describing failure reason. If failed, the client
   329  // SHOULD NOT be used.
   330  func (c *clientWrapper) dial(ctx context.Context) error {
   331  	cl, err := c.getClient()
   332  	if err != nil {
   333  		return err
   334  	}
   335  
   336  	prmDial := sdkClient.PrmDial{
   337  		Endpoint:        c.prm.address,
   338  		DialTimeout:     c.prm.dialTimeout,
   339  		StreamTimeout:   c.prm.streamTimeout,
   340  		GRPCDialOptions: c.prm.dialOptions,
   341  	}
   342  
   343  	err = cl.Dial(ctx, prmDial)
   344  	c.setDialed(err == nil)
   345  	if err != nil {
   346  		return err
   347  	}
   348  
   349  	return nil
   350  }
   351  
   352  // restart recreates and redial inner sdk client.
   353  func (c *clientWrapper) restart(ctx context.Context) error {
   354  	var cl sdkClient.Client
   355  	prmInit := sdkClient.PrmInit{
   356  		Key:                  c.prm.key,
   357  		ResponseInfoCallback: c.prm.responseInfoCallback,
   358  	}
   359  
   360  	cl.Init(prmInit)
   361  
   362  	prmDial := sdkClient.PrmDial{
   363  		Endpoint:        c.prm.address,
   364  		DialTimeout:     c.prm.dialTimeout,
   365  		StreamTimeout:   c.prm.streamTimeout,
   366  		GRPCDialOptions: c.prm.dialOptions,
   367  	}
   368  
   369  	// if connection is dialed before, to avoid routine / connection leak,
   370  	// pool has to close it and then initialize once again.
   371  	if c.isDialed() {
   372  		c.scheduleGracefulClose()
   373  	}
   374  
   375  	err := cl.Dial(ctx, prmDial)
   376  	c.setDialed(err == nil)
   377  	if err != nil {
   378  		return err
   379  	}
   380  
   381  	c.clientMutex.Lock()
   382  	c.client = &cl
   383  	c.clientMutex.Unlock()
   384  
   385  	return nil
   386  }
   387  
   388  func (c *clientWrapper) isDialed() bool {
   389  	c.mu.RLock()
   390  	defer c.mu.RUnlock()
   391  	return c.dialed
   392  }
   393  
   394  func (c *clientWrapper) setDialed(dialed bool) {
   395  	c.mu.Lock()
   396  	c.dialed = dialed
   397  	c.mu.Unlock()
   398  }
   399  
   400  func (c *clientWrapper) getClient() (*sdkClient.Client, error) {
   401  	c.clientMutex.RLock()
   402  	defer c.clientMutex.RUnlock()
   403  	if c.isHealthy() {
   404  		return c.client, nil
   405  	}
   406  	return nil, errPoolClientUnhealthy
   407  }
   408  
   409  func (c *clientWrapper) getClientRaw() *sdkClient.Client {
   410  	c.clientMutex.RLock()
   411  	defer c.clientMutex.RUnlock()
   412  	return c.client
   413  }
   414  
   415  // balanceGet invokes sdkClient.BalanceGet parse response status to error and return result as is.
   416  func (c *clientWrapper) balanceGet(ctx context.Context, prm PrmBalanceGet) (accounting.Decimal, error) {
   417  	cl, err := c.getClient()
   418  	if err != nil {
   419  		return accounting.Decimal{}, err
   420  	}
   421  
   422  	cliPrm := sdkClient.PrmBalanceGet{
   423  		Account: prm.account,
   424  	}
   425  
   426  	start := time.Now()
   427  	res, err := cl.BalanceGet(ctx, cliPrm)
   428  	c.incRequests(time.Since(start), methodBalanceGet)
   429  	var st apistatus.Status
   430  	if res != nil {
   431  		st = res.Status()
   432  	}
   433  	if err = c.handleError(ctx, st, err); err != nil {
   434  		return accounting.Decimal{}, fmt.Errorf("balance get on client: %w", err)
   435  	}
   436  
   437  	return res.Amount(), nil
   438  }
   439  
   440  // containerPut invokes sdkClient.ContainerPut parse response status to error and return result as is.
   441  // It also waits for the container to appear on the network.
   442  func (c *clientWrapper) containerPut(ctx context.Context, prm PrmContainerPut) (cid.ID, error) {
   443  	cl, err := c.getClient()
   444  	if err != nil {
   445  		return cid.ID{}, err
   446  	}
   447  
   448  	start := time.Now()
   449  	res, err := cl.ContainerPut(ctx, prm.ClientParams)
   450  	c.incRequests(time.Since(start), methodContainerPut)
   451  	var st apistatus.Status
   452  	if res != nil {
   453  		st = res.Status()
   454  	}
   455  	if err = c.handleError(ctx, st, err); err != nil {
   456  		return cid.ID{}, fmt.Errorf("container put on client: %w", err)
   457  	}
   458  
   459  	if prm.WaitParams == nil {
   460  		prm.WaitParams = defaultWaitParams()
   461  	}
   462  	if err = prm.WaitParams.CheckValidity(); err != nil {
   463  		return cid.ID{}, fmt.Errorf("invalid wait parameters: %w", err)
   464  	}
   465  
   466  	idCnr := res.ID()
   467  
   468  	getPrm := PrmContainerGet{
   469  		ContainerID: idCnr,
   470  		Session:     prm.ClientParams.Session,
   471  	}
   472  
   473  	err = waitForContainerPresence(ctx, c, getPrm, prm.WaitParams)
   474  	if err = c.handleError(ctx, nil, err); err != nil {
   475  		return cid.ID{}, fmt.Errorf("wait container presence on client: %w", err)
   476  	}
   477  
   478  	return idCnr, nil
   479  }
   480  
   481  // containerGet invokes sdkClient.ContainerGet parse response status to error and return result as is.
   482  func (c *clientWrapper) containerGet(ctx context.Context, prm PrmContainerGet) (container.Container, error) {
   483  	cl, err := c.getClient()
   484  	if err != nil {
   485  		return container.Container{}, err
   486  	}
   487  
   488  	cliPrm := sdkClient.PrmContainerGet{
   489  		ContainerID: &prm.ContainerID,
   490  		Session:     prm.Session,
   491  	}
   492  
   493  	start := time.Now()
   494  	res, err := cl.ContainerGet(ctx, cliPrm)
   495  	c.incRequests(time.Since(start), methodContainerGet)
   496  	var st apistatus.Status
   497  	if res != nil {
   498  		st = res.Status()
   499  	}
   500  	if err = c.handleError(ctx, st, err); err != nil {
   501  		return container.Container{}, fmt.Errorf("container get on client: %w", err)
   502  	}
   503  
   504  	return res.Container(), nil
   505  }
   506  
   507  // containerList invokes sdkClient.ContainerList parse response status to error and return result as is.
   508  func (c *clientWrapper) containerList(ctx context.Context, prm PrmContainerList) ([]cid.ID, error) {
   509  	cl, err := c.getClient()
   510  	if err != nil {
   511  		return nil, err
   512  	}
   513  
   514  	cliPrm := sdkClient.PrmContainerList{
   515  		Account: prm.OwnerID,
   516  		Session: prm.Session,
   517  	}
   518  
   519  	start := time.Now()
   520  	res, err := cl.ContainerList(ctx, cliPrm)
   521  	c.incRequests(time.Since(start), methodContainerList)
   522  	var st apistatus.Status
   523  	if res != nil {
   524  		st = res.Status()
   525  	}
   526  	if err = c.handleError(ctx, st, err); err != nil {
   527  		return nil, fmt.Errorf("container list on client: %w", err)
   528  	}
   529  	return res.Containers(), nil
   530  }
   531  
   532  // containerDelete invokes sdkClient.ContainerDelete parse response status to error.
   533  // It also waits for the container to be removed from the network.
   534  func (c *clientWrapper) containerDelete(ctx context.Context, prm PrmContainerDelete) error {
   535  	cl, err := c.getClient()
   536  	if err != nil {
   537  		return err
   538  	}
   539  
   540  	cliPrm := sdkClient.PrmContainerDelete{
   541  		ContainerID: &prm.ContainerID,
   542  		Session:     prm.Session,
   543  	}
   544  
   545  	start := time.Now()
   546  	res, err := cl.ContainerDelete(ctx, cliPrm)
   547  	c.incRequests(time.Since(start), methodContainerDelete)
   548  	var st apistatus.Status
   549  	if res != nil {
   550  		st = res.Status()
   551  	}
   552  	if err = c.handleError(ctx, st, err); err != nil {
   553  		return fmt.Errorf("container delete on client: %w", err)
   554  	}
   555  
   556  	if prm.WaitParams == nil {
   557  		prm.WaitParams = defaultWaitParams()
   558  	}
   559  	if err := prm.WaitParams.CheckValidity(); err != nil {
   560  		return fmt.Errorf("invalid wait parameters: %w", err)
   561  	}
   562  
   563  	getPrm := PrmContainerGet{
   564  		ContainerID: prm.ContainerID,
   565  		Session:     prm.Session,
   566  	}
   567  
   568  	return waitForContainerRemoved(ctx, c, getPrm, prm.WaitParams)
   569  }
   570  
   571  // apeManagerAddChain invokes sdkClient.APEManagerAddChain and parse response status to error.
   572  func (c *clientWrapper) apeManagerAddChain(ctx context.Context, prm PrmAddAPEChain) error {
   573  	cl, err := c.getClient()
   574  	if err != nil {
   575  		return err
   576  	}
   577  
   578  	cliPrm := sdkClient.PrmAPEManagerAddChain{
   579  		ChainTarget: prm.Target,
   580  		Chain:       prm.Chain,
   581  	}
   582  
   583  	start := time.Now()
   584  	res, err := cl.APEManagerAddChain(ctx, cliPrm)
   585  	c.incRequests(time.Since(start), methodAPEManagerAddChain)
   586  	var st apistatus.Status
   587  	if res != nil {
   588  		st = res.Status()
   589  	}
   590  	if err = c.handleError(ctx, st, err); err != nil {
   591  		return fmt.Errorf("add chain error: %w", err)
   592  	}
   593  
   594  	return nil
   595  }
   596  
   597  // apeManagerRemoveChain invokes sdkClient.APEManagerRemoveChain and parse response status to error.
   598  func (c *clientWrapper) apeManagerRemoveChain(ctx context.Context, prm PrmRemoveAPEChain) error {
   599  	cl, err := c.getClient()
   600  	if err != nil {
   601  		return err
   602  	}
   603  
   604  	cliPrm := sdkClient.PrmAPEManagerRemoveChain{
   605  		ChainTarget: prm.Target,
   606  		ChainID:     prm.ChainID,
   607  	}
   608  
   609  	start := time.Now()
   610  	res, err := cl.APEManagerRemoveChain(ctx, cliPrm)
   611  	c.incRequests(time.Since(start), methodAPEManagerRemoveChain)
   612  	var st apistatus.Status
   613  	if res != nil {
   614  		st = res.Status()
   615  	}
   616  	if err = c.handleError(ctx, st, err); err != nil {
   617  		return fmt.Errorf("remove chain error: %w", err)
   618  	}
   619  
   620  	return nil
   621  }
   622  
   623  // apeManagerListChains invokes sdkClient.APEManagerListChains. Returns chains and parsed response status to error.
   624  func (c *clientWrapper) apeManagerListChains(ctx context.Context, prm PrmListAPEChains) ([]ape.Chain, error) {
   625  	cl, err := c.getClient()
   626  	if err != nil {
   627  		return nil, err
   628  	}
   629  
   630  	cliPrm := sdkClient.PrmAPEManagerListChains{
   631  		ChainTarget: prm.Target,
   632  	}
   633  
   634  	start := time.Now()
   635  	res, err := cl.APEManagerListChains(ctx, cliPrm)
   636  	c.incRequests(time.Since(start), methodAPEManagerListChains)
   637  	var st apistatus.Status
   638  	if res != nil {
   639  		st = res.Status()
   640  	}
   641  	if err = c.handleError(ctx, st, err); err != nil {
   642  		return nil, fmt.Errorf("list chains error: %w", err)
   643  	}
   644  
   645  	return res.Chains, nil
   646  }
   647  
   648  // endpointInfo invokes sdkClient.EndpointInfo parse response status to error and return result as is.
   649  func (c *clientWrapper) endpointInfo(ctx context.Context, _ prmEndpointInfo) (netmap.NodeInfo, error) {
   650  	cl, err := c.getClient()
   651  	if err != nil {
   652  		return netmap.NodeInfo{}, err
   653  	}
   654  
   655  	return c.endpointInfoRaw(ctx, cl)
   656  }
   657  
   658  func (c *clientWrapper) healthcheck(ctx context.Context) (netmap.NodeInfo, error) {
   659  	cl := c.getClientRaw()
   660  	return c.endpointInfoRaw(ctx, cl)
   661  }
   662  
   663  func (c *clientWrapper) endpointInfoRaw(ctx context.Context, cl *sdkClient.Client) (netmap.NodeInfo, error) {
   664  	start := time.Now()
   665  	res, err := cl.EndpointInfo(ctx, sdkClient.PrmEndpointInfo{})
   666  	c.incRequests(time.Since(start), methodEndpointInfo)
   667  	var st apistatus.Status
   668  	if res != nil {
   669  		st = res.Status()
   670  	}
   671  	if err = c.handleError(ctx, st, err); err != nil {
   672  		return netmap.NodeInfo{}, fmt.Errorf("endpoint info on client: %w", err)
   673  	}
   674  
   675  	return res.NodeInfo(), nil
   676  }
   677  
   678  // networkInfo invokes sdkClient.NetworkInfo parse response status to error and return result as is.
   679  func (c *clientWrapper) networkInfo(ctx context.Context, _ prmNetworkInfo) (netmap.NetworkInfo, error) {
   680  	cl, err := c.getClient()
   681  	if err != nil {
   682  		return netmap.NetworkInfo{}, err
   683  	}
   684  
   685  	start := time.Now()
   686  	res, err := cl.NetworkInfo(ctx, sdkClient.PrmNetworkInfo{})
   687  	c.incRequests(time.Since(start), methodNetworkInfo)
   688  	var st apistatus.Status
   689  	if res != nil {
   690  		st = res.Status()
   691  	}
   692  	if err = c.handleError(ctx, st, err); err != nil {
   693  		return netmap.NetworkInfo{}, fmt.Errorf("network info on client: %w", err)
   694  	}
   695  
   696  	return res.Info(), nil
   697  }
   698  
   699  // networkInfo invokes sdkClient.NetworkInfo parse response status to error and return result as is.
   700  func (c *clientWrapper) netMapSnapshot(ctx context.Context, _ prmNetMapSnapshot) (netmap.NetMap, error) {
   701  	cl, err := c.getClient()
   702  	if err != nil {
   703  		return netmap.NetMap{}, err
   704  	}
   705  
   706  	start := time.Now()
   707  	res, err := cl.NetMapSnapshot(ctx, sdkClient.PrmNetMapSnapshot{})
   708  	c.incRequests(time.Since(start), methodNetMapSnapshot)
   709  	var st apistatus.Status
   710  	if res != nil {
   711  		st = res.Status()
   712  	}
   713  	if err = c.handleError(ctx, st, err); err != nil {
   714  		return netmap.NetMap{}, fmt.Errorf("network map snapshot on client: %w", err)
   715  	}
   716  
   717  	return res.NetMap(), nil
   718  }
   719  
   720  // objectPatch patches object in FrostFS.
   721  func (c *clientWrapper) objectPatch(ctx context.Context, prm PrmObjectPatch) (ResPatchObject, error) {
   722  	cl, err := c.getClient()
   723  	if err != nil {
   724  		return ResPatchObject{}, err
   725  	}
   726  
   727  	start := time.Now()
   728  	pObj, err := cl.ObjectPatchInit(ctx, sdkClient.PrmObjectPatch{
   729  		Address:        prm.addr,
   730  		Session:        prm.stoken,
   731  		Key:            prm.key,
   732  		BearerToken:    prm.btoken,
   733  		MaxChunkLength: prm.maxPayloadPatchChunkLength,
   734  	})
   735  	if err = c.handleError(ctx, nil, err); err != nil {
   736  		return ResPatchObject{}, fmt.Errorf("init patching on API client: %w", err)
   737  	}
   738  	c.incRequests(time.Since(start), methodObjectPatch)
   739  
   740  	start = time.Now()
   741  	attrPatchSuccess := pObj.PatchAttributes(ctx, prm.newAttrs, prm.replaceAttrs)
   742  	c.incRequests(time.Since(start), methodObjectPatch)
   743  
   744  	if attrPatchSuccess {
   745  		start = time.Now()
   746  		_ = pObj.PatchPayload(ctx, prm.rng, prm.payload)
   747  		c.incRequests(time.Since(start), methodObjectPatch)
   748  	}
   749  
   750  	res, err := pObj.Close(ctx)
   751  	var st apistatus.Status
   752  	if res != nil {
   753  		st = res.Status()
   754  	}
   755  	if err = c.handleError(ctx, st, err); err != nil {
   756  		return ResPatchObject{}, fmt.Errorf("client failure: %w", err)
   757  	}
   758  
   759  	return ResPatchObject{ObjectID: res.ObjectID()}, nil
   760  }
   761  
   762  // objectPut writes object to FrostFS.
   763  func (c *clientWrapper) objectPut(ctx context.Context, prm PrmObjectPut) (ResPutObject, error) {
   764  	if prm.bufferMaxSize == 0 {
   765  		prm.bufferMaxSize = defaultBufferMaxSizeForPut
   766  	}
   767  
   768  	if prm.clientCut {
   769  		return c.objectPutClientCut(ctx, prm)
   770  	}
   771  
   772  	return c.objectPutServerCut(ctx, prm)
   773  }
   774  
   775  func (c *clientWrapper) objectPutServerCut(ctx context.Context, prm PrmObjectPut) (ResPutObject, error) {
   776  	cl, err := c.getClient()
   777  	if err != nil {
   778  		return ResPutObject{}, err
   779  	}
   780  
   781  	cliPrm := sdkClient.PrmObjectPutInit{
   782  		CopiesNumber: prm.copiesNumber,
   783  		Session:      prm.stoken,
   784  		Key:          prm.key,
   785  		BearerToken:  prm.btoken,
   786  	}
   787  
   788  	start := time.Now()
   789  	wObj, err := cl.ObjectPutInit(ctx, cliPrm)
   790  	c.incRequests(time.Since(start), methodObjectPut)
   791  	if err = c.handleError(ctx, nil, err); err != nil {
   792  		return ResPutObject{}, fmt.Errorf("init writing on API client: %w", err)
   793  	}
   794  
   795  	if wObj.WriteHeader(ctx, prm.hdr) {
   796  		sz := prm.hdr.PayloadSize()
   797  
   798  		if data := prm.hdr.Payload(); len(data) > 0 {
   799  			if prm.payload != nil {
   800  				prm.payload = io.MultiReader(bytes.NewReader(data), prm.payload)
   801  			} else {
   802  				prm.payload = bytes.NewReader(data)
   803  				sz = uint64(len(data))
   804  			}
   805  		}
   806  
   807  		if prm.payload != nil {
   808  			if sz == 0 || sz > prm.bufferMaxSize {
   809  				sz = prm.bufferMaxSize
   810  			}
   811  
   812  			buf := make([]byte, sz)
   813  
   814  			var n int
   815  
   816  			for {
   817  				n, err = prm.payload.Read(buf)
   818  				if n > 0 {
   819  					start = time.Now()
   820  					successWrite := wObj.WritePayloadChunk(ctx, buf[:n])
   821  					c.incRequests(time.Since(start), methodObjectPut)
   822  					if !successWrite {
   823  						break
   824  					}
   825  
   826  					continue
   827  				}
   828  
   829  				if errors.Is(err, io.EOF) {
   830  					break
   831  				}
   832  
   833  				return ResPutObject{}, fmt.Errorf("read payload: %w", c.handleError(ctx, nil, err))
   834  			}
   835  		}
   836  	}
   837  
   838  	res, err := wObj.Close(ctx)
   839  	var st apistatus.Status
   840  	if res != nil {
   841  		st = res.Status()
   842  	}
   843  	if err = c.handleError(ctx, st, err); err != nil { // here err already carries both status and client errors
   844  		return ResPutObject{}, fmt.Errorf("client failure: %w", err)
   845  	}
   846  
   847  	return ResPutObject{
   848  		ObjectID: res.StoredObjectID(),
   849  		Epoch:    res.StoredEpoch(),
   850  	}, nil
   851  }
   852  
   853  func (c *clientWrapper) objectPutClientCut(ctx context.Context, prm PrmObjectPut) (ResPutObject, error) {
   854  	putInitPrm := PrmObjectPutClientCutInit{
   855  		PrmObjectPut: prm,
   856  	}
   857  
   858  	start := time.Now()
   859  	wObj, err := c.objectPutInitTransformer(putInitPrm)
   860  	c.incRequests(time.Since(start), methodObjectPut)
   861  	if err = c.handleError(ctx, nil, err); err != nil {
   862  		return ResPutObject{}, fmt.Errorf("init writing on API client: %w", err)
   863  	}
   864  
   865  	if wObj.WriteHeader(ctx, prm.hdr) {
   866  		sz := prm.hdr.PayloadSize()
   867  
   868  		if data := prm.hdr.Payload(); len(data) > 0 {
   869  			if prm.payload != nil {
   870  				prm.payload = io.MultiReader(bytes.NewReader(data), prm.payload)
   871  			} else {
   872  				prm.payload = bytes.NewReader(data)
   873  				sz = uint64(len(data))
   874  			}
   875  		}
   876  
   877  		if prm.payload != nil {
   878  			if sz == 0 || sz > prm.bufferMaxSize {
   879  				sz = prm.bufferMaxSize
   880  			}
   881  
   882  			buf := make([]byte, sz)
   883  
   884  			var n int
   885  
   886  			for {
   887  				n, err = prm.payload.Read(buf)
   888  				if n > 0 {
   889  					start = time.Now()
   890  					successWrite := wObj.WritePayloadChunk(ctx, buf[:n])
   891  					c.incRequests(time.Since(start), methodObjectPut)
   892  					if !successWrite {
   893  						break
   894  					}
   895  
   896  					continue
   897  				}
   898  
   899  				if errors.Is(err, io.EOF) {
   900  					break
   901  				}
   902  
   903  				return ResPutObject{}, fmt.Errorf("read payload: %w", c.handleError(ctx, nil, err))
   904  			}
   905  		}
   906  	}
   907  
   908  	res, err := wObj.Close(ctx)
   909  	var st apistatus.Status
   910  	if res != nil {
   911  		st = res.Status
   912  	}
   913  	if err = c.handleError(ctx, st, err); err != nil { // here err already carries both status and client errors
   914  		return ResPutObject{}, fmt.Errorf("client failure: %w", err)
   915  	}
   916  
   917  	return ResPutObject{
   918  		ObjectID: res.OID,
   919  		Epoch:    res.Epoch,
   920  	}, nil
   921  }
   922  
   923  // objectDelete invokes sdkClient.ObjectDelete parse response status to error.
   924  func (c *clientWrapper) objectDelete(ctx context.Context, prm PrmObjectDelete) error {
   925  	cl, err := c.getClient()
   926  	if err != nil {
   927  		return err
   928  	}
   929  
   930  	cnr := prm.addr.Container()
   931  	obj := prm.addr.Object()
   932  
   933  	cliPrm := sdkClient.PrmObjectDelete{
   934  		BearerToken: prm.btoken,
   935  		Session:     prm.stoken,
   936  		ContainerID: &cnr,
   937  		ObjectID:    &obj,
   938  		Key:         prm.key,
   939  	}
   940  
   941  	start := time.Now()
   942  	res, err := cl.ObjectDelete(ctx, cliPrm)
   943  	c.incRequests(time.Since(start), methodObjectDelete)
   944  	var st apistatus.Status
   945  	if res != nil {
   946  		st = res.Status()
   947  	}
   948  	if err = c.handleError(ctx, st, err); err != nil {
   949  		return fmt.Errorf("delete object on client: %w", err)
   950  	}
   951  	return nil
   952  }
   953  
   954  // objectGet returns reader for object.
   955  func (c *clientWrapper) objectGet(ctx context.Context, prm PrmObjectGet) (ResGetObject, error) {
   956  	cl, err := c.getClient()
   957  	if err != nil {
   958  		return ResGetObject{}, err
   959  	}
   960  
   961  	prmCnr := prm.addr.Container()
   962  	prmObj := prm.addr.Object()
   963  
   964  	cliPrm := sdkClient.PrmObjectGet{
   965  		BearerToken: prm.btoken,
   966  		Session:     prm.stoken,
   967  		ContainerID: &prmCnr,
   968  		ObjectID:    &prmObj,
   969  		Key:         prm.key,
   970  	}
   971  
   972  	var res ResGetObject
   973  
   974  	rObj, err := cl.ObjectGetInit(ctx, cliPrm)
   975  	if err = c.handleError(ctx, nil, err); err != nil {
   976  		return ResGetObject{}, fmt.Errorf("init object reading on client: %w", err)
   977  	}
   978  
   979  	start := time.Now()
   980  	successReadHeader := rObj.ReadHeader(&res.Header)
   981  	c.incRequests(time.Since(start), methodObjectGet)
   982  	if !successReadHeader {
   983  		rObjRes, err := rObj.Close()
   984  		var st apistatus.Status
   985  		if rObjRes != nil {
   986  			st = rObjRes.Status()
   987  		}
   988  		err = c.handleError(ctx, st, err)
   989  		return res, fmt.Errorf("read header: %w", err)
   990  	}
   991  
   992  	res.Payload = &objectReadCloser{
   993  		reader: rObj,
   994  		elapsedTimeCallback: func(elapsed time.Duration) {
   995  			c.incRequests(elapsed, methodObjectGet)
   996  		},
   997  	}
   998  
   999  	return res, nil
  1000  }
  1001  
  1002  // objectHead invokes sdkClient.ObjectHead parse response status to error and return result as is.
  1003  func (c *clientWrapper) objectHead(ctx context.Context, prm PrmObjectHead) (object.Object, error) {
  1004  	cl, err := c.getClient()
  1005  	if err != nil {
  1006  		return object.Object{}, err
  1007  	}
  1008  
  1009  	prmCnr := prm.addr.Container()
  1010  	prmObj := prm.addr.Object()
  1011  
  1012  	cliPrm := sdkClient.PrmObjectHead{
  1013  		BearerToken: prm.btoken,
  1014  		Session:     prm.stoken,
  1015  		Raw:         prm.raw,
  1016  		ContainerID: &prmCnr,
  1017  		ObjectID:    &prmObj,
  1018  		Key:         prm.key,
  1019  	}
  1020  
  1021  	var obj object.Object
  1022  
  1023  	start := time.Now()
  1024  	res, err := cl.ObjectHead(ctx, cliPrm)
  1025  	c.incRequests(time.Since(start), methodObjectHead)
  1026  	var st apistatus.Status
  1027  	if res != nil {
  1028  		st = res.Status()
  1029  	}
  1030  	if err = c.handleError(ctx, st, err); err != nil {
  1031  		return obj, fmt.Errorf("read object header via client: %w", err)
  1032  	}
  1033  	if !res.ReadHeader(&obj) {
  1034  		return obj, errors.New("missing object header in response")
  1035  	}
  1036  
  1037  	return obj, nil
  1038  }
  1039  
  1040  // objectRange returns object range reader.
  1041  func (c *clientWrapper) objectRange(ctx context.Context, prm PrmObjectRange) (ResObjectRange, error) {
  1042  	cl, err := c.getClient()
  1043  	if err != nil {
  1044  		return ResObjectRange{}, err
  1045  	}
  1046  
  1047  	prmCnr := prm.addr.Container()
  1048  	prmObj := prm.addr.Object()
  1049  
  1050  	cliPrm := sdkClient.PrmObjectRange{
  1051  		BearerToken: prm.btoken,
  1052  		Session:     prm.stoken,
  1053  		ContainerID: &prmCnr,
  1054  		ObjectID:    &prmObj,
  1055  		Offset:      prm.off,
  1056  		Length:      prm.ln,
  1057  		Key:         prm.key,
  1058  	}
  1059  
  1060  	start := time.Now()
  1061  	res, err := cl.ObjectRangeInit(ctx, cliPrm)
  1062  	c.incRequests(time.Since(start), methodObjectRange)
  1063  	if err = c.handleError(ctx, nil, err); err != nil {
  1064  		return ResObjectRange{}, fmt.Errorf("init payload range reading on client: %w", err)
  1065  	}
  1066  
  1067  	return ResObjectRange{
  1068  		payload: res,
  1069  		elapsedTimeCallback: func(elapsed time.Duration) {
  1070  			c.incRequests(elapsed, methodObjectRange)
  1071  		},
  1072  	}, nil
  1073  }
  1074  
  1075  // objectSearch invokes sdkClient.ObjectSearchInit parse response status to error and return result as is.
  1076  func (c *clientWrapper) objectSearch(ctx context.Context, prm PrmObjectSearch) (ResObjectSearch, error) {
  1077  	cl, err := c.getClient()
  1078  	if err != nil {
  1079  		return ResObjectSearch{}, err
  1080  	}
  1081  
  1082  	cliPrm := sdkClient.PrmObjectSearch{
  1083  		ContainerID: &prm.cnrID,
  1084  		Filters:     prm.filters,
  1085  		Session:     prm.stoken,
  1086  		BearerToken: prm.btoken,
  1087  		Key:         prm.key,
  1088  	}
  1089  
  1090  	res, err := cl.ObjectSearchInit(ctx, cliPrm)
  1091  	if err = c.handleError(ctx, nil, err); err != nil {
  1092  		return ResObjectSearch{}, fmt.Errorf("init object searching on client: %w", err)
  1093  	}
  1094  
  1095  	return ResObjectSearch{r: res, handleError: c.handleError}, nil
  1096  }
  1097  
  1098  // sessionCreate invokes sdkClient.SessionCreate parse response status to error and return result as is.
  1099  func (c *clientWrapper) sessionCreate(ctx context.Context, prm prmCreateSession) (resCreateSession, error) {
  1100  	cl, err := c.getClient()
  1101  	if err != nil {
  1102  		return resCreateSession{}, err
  1103  	}
  1104  
  1105  	cliPrm := sdkClient.PrmSessionCreate{
  1106  		Expiration: prm.exp,
  1107  		Key:        &prm.key,
  1108  	}
  1109  
  1110  	start := time.Now()
  1111  	res, err := cl.SessionCreate(ctx, cliPrm)
  1112  	c.incRequests(time.Since(start), methodSessionCreate)
  1113  	var st apistatus.Status
  1114  	if res != nil {
  1115  		st = res.Status()
  1116  	}
  1117  	if err = c.handleError(ctx, st, err); err != nil {
  1118  		return resCreateSession{}, fmt.Errorf("session creation on client: %w", err)
  1119  	}
  1120  
  1121  	return resCreateSession{
  1122  		id:         res.ID(),
  1123  		sessionKey: res.PublicKey(),
  1124  	}, nil
  1125  }
  1126  
  1127  func (c *clientStatusMonitor) isHealthy() bool {
  1128  	return c.healthy.Load() == statusHealthy
  1129  }
  1130  
  1131  func (c *clientStatusMonitor) setHealthy() {
  1132  	c.healthy.Store(statusHealthy)
  1133  }
  1134  
  1135  func (c *clientStatusMonitor) setUnhealthy() {
  1136  	c.healthy.Store(statusUnhealthyOnRequest)
  1137  }
  1138  
  1139  func (c *clientStatusMonitor) address() string {
  1140  	return c.addr
  1141  }
  1142  
  1143  func (c *clientStatusMonitor) incErrorRate() {
  1144  	c.mu.Lock()
  1145  	c.currentErrorCount++
  1146  	c.overallErrorCount++
  1147  
  1148  	thresholdReached := c.currentErrorCount >= c.errorThreshold
  1149  	if thresholdReached {
  1150  		c.setUnhealthy()
  1151  		c.currentErrorCount = 0
  1152  	}
  1153  	c.mu.Unlock()
  1154  
  1155  	if thresholdReached {
  1156  		c.log(zapcore.WarnLevel, "error threshold reached",
  1157  			zap.String("address", c.addr), zap.Uint32("threshold", c.errorThreshold))
  1158  	}
  1159  }
  1160  
  1161  func (c *clientStatusMonitor) incErrorRateToUnhealthy(err error) {
  1162  	c.mu.Lock()
  1163  	c.currentErrorCount = 0
  1164  	c.overallErrorCount++
  1165  	c.setUnhealthy()
  1166  	c.mu.Unlock()
  1167  
  1168  	c.log(zapcore.WarnLevel, "explicitly mark node unhealthy", zap.String("address", c.addr), zap.Error(err))
  1169  }
  1170  
  1171  func (c *clientStatusMonitor) log(level zapcore.Level, msg string, fields ...zap.Field) {
  1172  	if c.logger == nil {
  1173  		return
  1174  	}
  1175  
  1176  	c.logger.Log(level, msg, fields...)
  1177  }
  1178  
  1179  func (c *clientStatusMonitor) currentErrorRate() uint32 {
  1180  	c.mu.RLock()
  1181  	defer c.mu.RUnlock()
  1182  	return c.currentErrorCount
  1183  }
  1184  
  1185  func (c *clientStatusMonitor) overallErrorRate() uint64 {
  1186  	c.mu.RLock()
  1187  	defer c.mu.RUnlock()
  1188  	return c.overallErrorCount
  1189  }
  1190  
  1191  func (c *clientStatusMonitor) methodsStatus() []StatusSnapshot {
  1192  	result := make([]StatusSnapshot, len(c.methods))
  1193  	for i, val := range c.methods {
  1194  		result[i] = val.Snapshot()
  1195  	}
  1196  
  1197  	return result
  1198  }
  1199  
  1200  func (c *clientWrapper) incRequests(elapsed time.Duration, method MethodIndex) {
  1201  	methodStat := c.methods[method]
  1202  	methodStat.IncRequests(elapsed)
  1203  	if c.prm.poolRequestInfoCallback != nil {
  1204  		c.prm.poolRequestInfoCallback(RequestInfo{
  1205  			Address: c.prm.address,
  1206  			Method:  method,
  1207  			Elapsed: elapsed,
  1208  		})
  1209  	}
  1210  }
  1211  
  1212  func (c *clientWrapper) close() error {
  1213  	if !c.isDialed() {
  1214  		return nil
  1215  	}
  1216  	if cl := c.getClientRaw(); cl != nil {
  1217  		return cl.Close()
  1218  	}
  1219  	return nil
  1220  }
  1221  
  1222  func (c *clientWrapper) scheduleGracefulClose() {
  1223  	cl := c.getClientRaw()
  1224  	if cl == nil {
  1225  		return
  1226  	}
  1227  
  1228  	time.AfterFunc(c.prm.gracefulCloseOnSwitchTimeout, func() {
  1229  		if err := cl.Close(); err != nil {
  1230  			c.log(zap.DebugLevel, "close unhealthy client during rebalance", zap.String("address", c.address()), zap.Error(err))
  1231  		}
  1232  	})
  1233  }
  1234  
  1235  func (c *clientStatusMonitor) handleError(ctx context.Context, st apistatus.Status, err error) error {
  1236  	if stErr := apistatus.ErrFromStatus(st); stErr != nil {
  1237  		switch stErr.(type) {
  1238  		case *apistatus.ServerInternal,
  1239  			*apistatus.WrongMagicNumber,
  1240  			*apistatus.SignatureVerification:
  1241  			c.incErrorRate()
  1242  		case *apistatus.NodeUnderMaintenance:
  1243  			c.incErrorRateToUnhealthy(stErr)
  1244  		}
  1245  
  1246  		if err == nil {
  1247  			err = stErr
  1248  		}
  1249  
  1250  		return err
  1251  	}
  1252  
  1253  	if err != nil {
  1254  		if needCountError(ctx, err) {
  1255  			if sdkClient.IsErrNodeUnderMaintenance(err) {
  1256  				c.incErrorRateToUnhealthy(err)
  1257  			} else {
  1258  				c.incErrorRate()
  1259  			}
  1260  		}
  1261  
  1262  		return err
  1263  	}
  1264  
  1265  	return nil
  1266  }
  1267  
  1268  func needCountError(ctx context.Context, err error) bool {
  1269  	// non-status logic error that could be returned
  1270  	// from the SDK client; should not be considered
  1271  	// as a connection error
  1272  	var siErr *object.SplitInfoError
  1273  	if errors.As(err, &siErr) {
  1274  		return false
  1275  	}
  1276  	var eiErr *object.ECInfoError
  1277  	if errors.As(err, &eiErr) {
  1278  		return false
  1279  	}
  1280  
  1281  	if ctx != nil && errors.Is(ctx.Err(), context.Canceled) {
  1282  		return false
  1283  	}
  1284  
  1285  	return true
  1286  }
  1287  
  1288  // clientBuilder is a type alias of client constructors which open connection
  1289  // to the given endpoint.
  1290  type clientBuilder = func(endpoint string) client
  1291  
  1292  // RequestInfo groups info about pool request.
  1293  type RequestInfo struct {
  1294  	Address string
  1295  	Method  MethodIndex
  1296  	Elapsed time.Duration
  1297  }
  1298  
  1299  // InitParameters contains values used to initialize connection Pool.
  1300  type InitParameters struct {
  1301  	key                       *ecdsa.PrivateKey
  1302  	logger                    *zap.Logger
  1303  	nodeDialTimeout           time.Duration
  1304  	nodeStreamTimeout         time.Duration
  1305  	healthcheckTimeout        time.Duration
  1306  	clientRebalanceInterval   time.Duration
  1307  	sessionExpirationDuration uint64
  1308  	errorThreshold            uint32
  1309  	nodeParams                []NodeParam
  1310  	requestCallback           func(RequestInfo)
  1311  	dialOptions               []grpc.DialOption
  1312  
  1313  	clientBuilder clientBuilder
  1314  
  1315  	gracefulCloseOnSwitchTimeout time.Duration
  1316  }
  1317  
  1318  // SetKey specifies default key to be used for the protocol communication by default.
  1319  func (x *InitParameters) SetKey(key *ecdsa.PrivateKey) {
  1320  	x.key = key
  1321  }
  1322  
  1323  // SetLogger specifies logger.
  1324  func (x *InitParameters) SetLogger(logger *zap.Logger) {
  1325  	x.logger = logger
  1326  }
  1327  
  1328  // SetNodeDialTimeout specifies the timeout for connection to be established.
  1329  func (x *InitParameters) SetNodeDialTimeout(timeout time.Duration) {
  1330  	x.nodeDialTimeout = timeout
  1331  }
  1332  
  1333  // SetNodeStreamTimeout specifies the timeout for individual operations in streaming RPC.
  1334  func (x *InitParameters) SetNodeStreamTimeout(timeout time.Duration) {
  1335  	x.nodeStreamTimeout = timeout
  1336  }
  1337  
  1338  // SetHealthcheckTimeout specifies the timeout for request to node to decide if it is alive.
  1339  //
  1340  // See also Pool.Dial.
  1341  func (x *InitParameters) SetHealthcheckTimeout(timeout time.Duration) {
  1342  	x.healthcheckTimeout = timeout
  1343  }
  1344  
  1345  // SetClientRebalanceInterval specifies the interval for updating nodes health status.
  1346  //
  1347  // See also Pool.Dial.
  1348  func (x *InitParameters) SetClientRebalanceInterval(interval time.Duration) {
  1349  	x.clientRebalanceInterval = interval
  1350  }
  1351  
  1352  // SetGracefulCloseOnSwitchTimeout specifies the timeout after which unhealthy client be closed during rebalancing
  1353  // if it will become healthy back.
  1354  // Generally this param should be less than client rebalance interval (see SetClientRebalanceInterval).
  1355  //
  1356  // See also SetErrorThreshold.
  1357  func (x *InitParameters) SetGracefulCloseOnSwitchTimeout(timeout time.Duration) {
  1358  	x.gracefulCloseOnSwitchTimeout = timeout
  1359  }
  1360  
  1361  // SetSessionExpirationDuration specifies the session token lifetime in epochs.
  1362  func (x *InitParameters) SetSessionExpirationDuration(expirationDuration uint64) {
  1363  	x.sessionExpirationDuration = expirationDuration
  1364  }
  1365  
  1366  // SetErrorThreshold specifies the number of errors on connection after which node is considered as unhealthy.
  1367  func (x *InitParameters) SetErrorThreshold(threshold uint32) {
  1368  	x.errorThreshold = threshold
  1369  }
  1370  
  1371  // SetRequestCallback makes the pool client to pass RequestInfo for each
  1372  // request to f. Nil (default) means ignore RequestInfo.
  1373  func (x *InitParameters) SetRequestCallback(f func(RequestInfo)) {
  1374  	x.requestCallback = f
  1375  }
  1376  
  1377  // AddNode append information about the node to which you want to connect.
  1378  func (x *InitParameters) AddNode(nodeParam NodeParam) {
  1379  	x.nodeParams = append(x.nodeParams, nodeParam)
  1380  }
  1381  
  1382  // SetGRPCDialOptions sets the gRPC dial options for new gRPC client connection.
  1383  func (x *InitParameters) SetGRPCDialOptions(opts ...grpc.DialOption) {
  1384  	x.dialOptions = opts
  1385  }
  1386  
  1387  // setClientBuilder sets clientBuilder used for client construction.
  1388  // Wraps setClientBuilderContext without a context.
  1389  func (x *InitParameters) setClientBuilder(builder clientBuilder) {
  1390  	x.clientBuilder = builder
  1391  }
  1392  
  1393  // isMissingClientBuilder checks if client constructor was not specified.
  1394  func (x *InitParameters) isMissingClientBuilder() bool {
  1395  	return x.clientBuilder == nil
  1396  }
  1397  
  1398  type rebalanceParameters struct {
  1399  	nodesParams               []*nodesParam
  1400  	nodeRequestTimeout        time.Duration
  1401  	clientRebalanceInterval   time.Duration
  1402  	sessionExpirationDuration uint64
  1403  }
  1404  
  1405  type nodesParam struct {
  1406  	priority  int
  1407  	addresses []string
  1408  	weights   []float64
  1409  }
  1410  
  1411  // NodeParam groups parameters of remote node.
  1412  type NodeParam struct {
  1413  	priority int
  1414  	address  string
  1415  	weight   float64
  1416  }
  1417  
  1418  // NewNodeParam creates NodeParam using parameters.
  1419  func NewNodeParam(priority int, address string, weight float64) (prm NodeParam) {
  1420  	prm.SetPriority(priority)
  1421  	prm.SetAddress(address)
  1422  	prm.SetWeight(weight)
  1423  
  1424  	return
  1425  }
  1426  
  1427  // SetPriority specifies priority of the node.
  1428  // Negative value is allowed. In the result node groups
  1429  // with the same priority will be sorted by descent.
  1430  func (x *NodeParam) SetPriority(priority int) {
  1431  	x.priority = priority
  1432  }
  1433  
  1434  // Priority returns priority of the node.
  1435  // Requests will be served by nodes subset with the highest priority (the smaller value - the higher priority).
  1436  // If there are no healthy nodes in subsets with current or higher priority, requests will be served
  1437  // by nodes subset with lower priority.
  1438  func (x *NodeParam) Priority() int {
  1439  	return x.priority
  1440  }
  1441  
  1442  // SetAddress specifies address of the node.
  1443  func (x *NodeParam) SetAddress(address string) {
  1444  	x.address = address
  1445  }
  1446  
  1447  // Address returns address of the node.
  1448  func (x *NodeParam) Address() string {
  1449  	return x.address
  1450  }
  1451  
  1452  // SetWeight specifies weight of the node.
  1453  // Weights used to adjust requests' distribution between nodes with the same priority.
  1454  func (x *NodeParam) SetWeight(weight float64) {
  1455  	x.weight = weight
  1456  }
  1457  
  1458  // Weight returns weight of the node.
  1459  func (x *NodeParam) Weight() float64 {
  1460  	return x.weight
  1461  }
  1462  
  1463  // WaitParams contains parameters used in polling is a something applied on FrostFS network.
  1464  type WaitParams struct {
  1465  	Timeout      time.Duration
  1466  	PollInterval time.Duration
  1467  }
  1468  
  1469  // SetTimeout specifies the time to wait for the operation to complete.
  1470  //
  1471  // Deprecated: Use WaitParams.Timeout instead.
  1472  func (x *WaitParams) SetTimeout(timeout time.Duration) {
  1473  	x.Timeout = timeout
  1474  }
  1475  
  1476  // SetPollInterval specifies the interval, once it will check the completion of the operation.
  1477  //
  1478  // Deprecated: Use WaitParams.PollInterval instead.
  1479  func (x *WaitParams) SetPollInterval(tick time.Duration) {
  1480  	x.PollInterval = tick
  1481  }
  1482  
  1483  func defaultWaitParams() *WaitParams {
  1484  	return &WaitParams{
  1485  		Timeout:      120 * time.Second,
  1486  		PollInterval: 5 * time.Second,
  1487  	}
  1488  }
  1489  
  1490  // checkForPositive panics if any of the wait params isn't positive.
  1491  func (x *WaitParams) checkForPositive() {
  1492  	if x.Timeout <= 0 || x.PollInterval <= 0 {
  1493  		panic("all wait params must be positive")
  1494  	}
  1495  }
  1496  
  1497  // CheckValidity checks if all wait params are non-negative.
  1498  func (x *WaitParams) CheckValidity() error {
  1499  	if x.Timeout <= 0 {
  1500  		return errors.New("timeout cannot be negative")
  1501  	}
  1502  	if x.PollInterval <= 0 {
  1503  		return errors.New("poll interval cannot be negative")
  1504  	}
  1505  	return nil
  1506  }
  1507  
  1508  type prmContext struct {
  1509  	defaultSession bool
  1510  	verb           session.ObjectVerb
  1511  	cnr            cid.ID
  1512  
  1513  	objSet bool
  1514  	objs   []oid.ID
  1515  }
  1516  
  1517  func (x *prmContext) useDefaultSession() {
  1518  	x.defaultSession = true
  1519  }
  1520  
  1521  func (x *prmContext) useContainer(cnr cid.ID) {
  1522  	x.cnr = cnr
  1523  }
  1524  
  1525  func (x *prmContext) useObjects(ids []oid.ID) {
  1526  	x.objs = ids
  1527  	x.objSet = true
  1528  }
  1529  
  1530  func (x *prmContext) useAddress(addr oid.Address) {
  1531  	x.cnr = addr.Container()
  1532  	x.objs = []oid.ID{addr.Object()}
  1533  	x.objSet = true
  1534  }
  1535  
  1536  func (x *prmContext) useVerb(verb session.ObjectVerb) {
  1537  	x.verb = verb
  1538  }
  1539  
  1540  type prmCommon struct {
  1541  	key    *ecdsa.PrivateKey
  1542  	btoken *bearer.Token
  1543  	stoken *session.Object
  1544  }
  1545  
  1546  // UseKey specifies private key to sign the requests.
  1547  // If key is not provided, then Pool default key is used.
  1548  func (x *prmCommon) UseKey(key *ecdsa.PrivateKey) {
  1549  	x.key = key
  1550  }
  1551  
  1552  // UseBearer attaches bearer token to be used for the operation.
  1553  func (x *prmCommon) UseBearer(token bearer.Token) {
  1554  	x.btoken = &token
  1555  }
  1556  
  1557  // UseSession specifies session within which operation should be performed.
  1558  func (x *prmCommon) UseSession(token session.Object) {
  1559  	x.stoken = &token
  1560  }
  1561  
  1562  // PrmObjectPut groups parameters of PutObject operation.
  1563  type PrmObjectPut struct {
  1564  	prmCommon
  1565  
  1566  	hdr object.Object
  1567  
  1568  	payload io.Reader
  1569  
  1570  	copiesNumber []uint32
  1571  
  1572  	clientCut   bool
  1573  	networkInfo netmap.NetworkInfo
  1574  
  1575  	withoutHomomorphicHash bool
  1576  
  1577  	bufferMaxSize uint64
  1578  }
  1579  
  1580  // SetHeader specifies header of the object.
  1581  func (x *PrmObjectPut) SetHeader(hdr object.Object) {
  1582  	x.hdr = hdr
  1583  }
  1584  
  1585  // SetPayload specifies payload of the object.
  1586  func (x *PrmObjectPut) SetPayload(payload io.Reader) {
  1587  	x.payload = payload
  1588  }
  1589  
  1590  // SetCopiesNumber sets number of object copies that is enough to consider put successful.
  1591  // Zero means using default behavior.
  1592  func (x *PrmObjectPut) SetCopiesNumber(copiesNumber uint32) {
  1593  	x.copiesNumber = []uint32{copiesNumber}
  1594  }
  1595  
  1596  // SetCopiesNumberVector sets number of object copies that is enough to consider put successful, provided as array.
  1597  // Nil/empty vector means using default behavior.
  1598  func (x *PrmObjectPut) SetCopiesNumberVector(copiesNumber []uint32) {
  1599  	x.copiesNumber = copiesNumber
  1600  }
  1601  
  1602  // SetClientCut enables client cut for objects. It means that full object is prepared on client side
  1603  // and retrying is possible. But this leads to additional memory using for buffering object parts.
  1604  // Buffer size for every put is MaxObjectSize value from FrostFS network.
  1605  // There is limit for total memory allocation for in-flight request and
  1606  // can be set by InitParameters.SetMaxClientCutMemory (default value is 1gb).
  1607  // Put requests will fail if this limit be reached.
  1608  func (x *PrmObjectPut) SetClientCut(clientCut bool) {
  1609  	x.clientCut = clientCut
  1610  }
  1611  
  1612  // WithoutHomomorphicHash if set to true do not use Tillich-Zémor hash for payload.
  1613  func (x *PrmObjectPut) WithoutHomomorphicHash(v bool) {
  1614  	x.withoutHomomorphicHash = v
  1615  }
  1616  
  1617  // SetBufferMaxSize sets max buffer size to read payload.
  1618  // This value isn't used if object size is set explicitly and less than this value.
  1619  // Default value 3MB.
  1620  func (x *PrmObjectPut) SetBufferMaxSize(size uint64) {
  1621  	x.bufferMaxSize = size
  1622  }
  1623  
  1624  func (x *PrmObjectPut) setNetworkInfo(ni netmap.NetworkInfo) {
  1625  	x.networkInfo = ni
  1626  }
  1627  
  1628  // PrmObjectPatch groups parameters of PatchObject operation.
  1629  type PrmObjectPatch struct {
  1630  	prmCommon
  1631  
  1632  	addr oid.Address
  1633  
  1634  	rng *object.Range
  1635  
  1636  	payload io.Reader
  1637  
  1638  	newAttrs []object.Attribute
  1639  
  1640  	replaceAttrs bool
  1641  
  1642  	maxPayloadPatchChunkLength int
  1643  }
  1644  
  1645  // SetAddress sets the address of the object that is patched.
  1646  func (x *PrmObjectPatch) SetAddress(addr oid.Address) {
  1647  	x.addr = addr
  1648  }
  1649  
  1650  // SetRange sets the patch's range.
  1651  func (x *PrmObjectPatch) SetRange(rng *object.Range) {
  1652  	x.rng = rng
  1653  }
  1654  
  1655  // SetPayloadReader sets a payload reader.
  1656  func (x *PrmObjectPatch) SetPayloadReader(payload io.Reader) {
  1657  	x.payload = payload
  1658  }
  1659  
  1660  // SetRange sets the new attributes to the patch.
  1661  func (x *PrmObjectPatch) SetNewAttributes(newAttrs []object.Attribute) {
  1662  	x.newAttrs = newAttrs
  1663  }
  1664  
  1665  // SetRange sets the replace attributes flag to the patch.
  1666  func (x *PrmObjectPatch) SetReplaceAttributes(replaceAttrs bool) {
  1667  	x.replaceAttrs = replaceAttrs
  1668  }
  1669  
  1670  // SetMaxPayloadPatchChunkSize sets a max buf size to read the patch's payload.
  1671  func (x *PrmObjectPatch) SetMaxPayloadPatchChunkSize(maxPayloadPatchChunkSize int) {
  1672  	x.maxPayloadPatchChunkLength = maxPayloadPatchChunkSize
  1673  }
  1674  
  1675  // PrmObjectDelete groups parameters of DeleteObject operation.
  1676  type PrmObjectDelete struct {
  1677  	prmCommon
  1678  
  1679  	addr oid.Address
  1680  }
  1681  
  1682  // SetAddress specifies FrostFS address of the object.
  1683  func (x *PrmObjectDelete) SetAddress(addr oid.Address) {
  1684  	x.addr = addr
  1685  }
  1686  
  1687  // PrmObjectGet groups parameters of GetObject operation.
  1688  type PrmObjectGet struct {
  1689  	prmCommon
  1690  
  1691  	addr oid.Address
  1692  }
  1693  
  1694  // SetAddress specifies FrostFS address of the object.
  1695  func (x *PrmObjectGet) SetAddress(addr oid.Address) {
  1696  	x.addr = addr
  1697  }
  1698  
  1699  // PrmObjectHead groups parameters of HeadObject operation.
  1700  type PrmObjectHead struct {
  1701  	prmCommon
  1702  
  1703  	addr oid.Address
  1704  	raw  bool
  1705  }
  1706  
  1707  // SetAddress specifies FrostFS address of the object.
  1708  func (x *PrmObjectHead) SetAddress(addr oid.Address) {
  1709  	x.addr = addr
  1710  }
  1711  
  1712  // MarkRaw marks an intent to read physically stored object.
  1713  func (x *PrmObjectHead) MarkRaw() {
  1714  	x.raw = true
  1715  }
  1716  
  1717  // PrmObjectRange groups parameters of RangeObject operation.
  1718  type PrmObjectRange struct {
  1719  	prmCommon
  1720  
  1721  	addr    oid.Address
  1722  	off, ln uint64
  1723  }
  1724  
  1725  // SetAddress specifies FrostFS address of the object.
  1726  func (x *PrmObjectRange) SetAddress(addr oid.Address) {
  1727  	x.addr = addr
  1728  }
  1729  
  1730  // SetOffset sets offset of the payload range to be read.
  1731  func (x *PrmObjectRange) SetOffset(offset uint64) {
  1732  	x.off = offset
  1733  }
  1734  
  1735  // SetLength sets length of the payload range to be read.
  1736  func (x *PrmObjectRange) SetLength(length uint64) {
  1737  	x.ln = length
  1738  }
  1739  
  1740  // PrmObjectSearch groups parameters of SearchObjects operation.
  1741  type PrmObjectSearch struct {
  1742  	prmCommon
  1743  
  1744  	cnrID   cid.ID
  1745  	filters object.SearchFilters
  1746  }
  1747  
  1748  // SetContainerID specifies the container in which to look for objects.
  1749  func (x *PrmObjectSearch) SetContainerID(cnrID cid.ID) {
  1750  	x.cnrID = cnrID
  1751  }
  1752  
  1753  // SetFilters specifies filters by which to select objects.
  1754  func (x *PrmObjectSearch) SetFilters(filters object.SearchFilters) {
  1755  	x.filters = filters
  1756  }
  1757  
  1758  // PrmContainerPut groups parameters of PutContainer operation.
  1759  type PrmContainerPut struct {
  1760  	ClientParams sdkClient.PrmContainerPut
  1761  
  1762  	WaitParams *WaitParams
  1763  }
  1764  
  1765  // SetContainer container structure to be used as a parameter of the base
  1766  // client's operation.
  1767  //
  1768  // See git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client.PrmContainerPut.SetContainer.
  1769  //
  1770  // Deprecated: Use PrmContainerPut.ClientParams.Container instead.
  1771  func (x *PrmContainerPut) SetContainer(cnr container.Container) {
  1772  	x.ClientParams.SetContainer(cnr)
  1773  }
  1774  
  1775  // WithinSession specifies session to be used as a parameter of the base
  1776  // client's operation.
  1777  //
  1778  // See git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client.PrmContainerPut.WithinSession.
  1779  //
  1780  // Deprecated: Use PrmContainerPut.ClientParams.Session instead.
  1781  func (x *PrmContainerPut) WithinSession(s session.Container) {
  1782  	x.ClientParams.WithinSession(s)
  1783  }
  1784  
  1785  // SetWaitParams specifies timeout params to complete operation.
  1786  // If not provided the default one will be used.
  1787  // Panics if any of the wait params isn't positive.
  1788  //
  1789  // Deprecated: Use PrmContainerPut.ClientParams.WaitParams instead.
  1790  func (x *PrmContainerPut) SetWaitParams(waitParams WaitParams) {
  1791  	waitParams.checkForPositive()
  1792  	x.WaitParams = &waitParams
  1793  }
  1794  
  1795  // PrmContainerGet groups parameters of GetContainer operation.
  1796  type PrmContainerGet struct {
  1797  	ContainerID cid.ID
  1798  
  1799  	Session *session.Container
  1800  }
  1801  
  1802  // SetContainerID specifies identifier of the container to be read.
  1803  //
  1804  // Deprecated: Use PrmContainerGet.ContainerID instead.
  1805  func (prm *PrmContainerGet) SetContainerID(cnrID cid.ID) {
  1806  	prm.ContainerID = cnrID
  1807  }
  1808  
  1809  // PrmContainerList groups parameters of ListContainers operation.
  1810  type PrmContainerList struct {
  1811  	OwnerID user.ID
  1812  
  1813  	Session *session.Container
  1814  }
  1815  
  1816  // SetOwnerID specifies identifier of the FrostFS account to list the containers.
  1817  //
  1818  // Deprecated: Use PrmContainerList.OwnerID instead.
  1819  func (x *PrmContainerList) SetOwnerID(ownerID user.ID) {
  1820  	x.OwnerID = ownerID
  1821  }
  1822  
  1823  // PrmContainerDelete groups parameters of DeleteContainer operation.
  1824  type PrmContainerDelete struct {
  1825  	ContainerID cid.ID
  1826  
  1827  	Session *session.Container
  1828  
  1829  	WaitParams *WaitParams
  1830  }
  1831  
  1832  // SetContainerID specifies identifier of the FrostFS container to be removed.
  1833  //
  1834  // Deprecated: Use PrmContainerDelete.ContainerID instead.
  1835  func (x *PrmContainerDelete) SetContainerID(cnrID cid.ID) {
  1836  	x.ContainerID = cnrID
  1837  }
  1838  
  1839  // SetSessionToken specifies session within which operation should be performed.
  1840  //
  1841  // Deprecated: Use PrmContainerDelete.Session instead.
  1842  func (x *PrmContainerDelete) SetSessionToken(token session.Container) {
  1843  	x.Session = &token
  1844  }
  1845  
  1846  // SetWaitParams specifies timeout params to complete operation.
  1847  // If not provided the default one will be used.
  1848  // Panics if any of the wait params isn't positive.
  1849  //
  1850  // Deprecated: Use PrmContainerDelete.WaitParams instead.
  1851  func (x *PrmContainerDelete) SetWaitParams(waitParams WaitParams) {
  1852  	waitParams.checkForPositive()
  1853  	x.WaitParams = &waitParams
  1854  }
  1855  
  1856  type PrmAddAPEChain struct {
  1857  	Target ape.ChainTarget
  1858  
  1859  	Chain ape.Chain
  1860  }
  1861  
  1862  type PrmRemoveAPEChain struct {
  1863  	Target ape.ChainTarget
  1864  
  1865  	ChainID ape.ChainID
  1866  }
  1867  
  1868  type PrmListAPEChains struct {
  1869  	Target ape.ChainTarget
  1870  }
  1871  
  1872  // PrmBalanceGet groups parameters of Balance operation.
  1873  type PrmBalanceGet struct {
  1874  	account user.ID
  1875  }
  1876  
  1877  // SetAccount specifies identifier of the FrostFS account for which the balance is requested.
  1878  func (x *PrmBalanceGet) SetAccount(id user.ID) {
  1879  	x.account = id
  1880  }
  1881  
  1882  // prmEndpointInfo groups parameters of sessionCreate operation.
  1883  type prmCreateSession struct {
  1884  	exp uint64
  1885  	key ecdsa.PrivateKey
  1886  }
  1887  
  1888  // setExp sets number of the last FrostFS epoch in the lifetime of the session after which it will be expired.
  1889  func (x *prmCreateSession) setExp(exp uint64) {
  1890  	x.exp = exp
  1891  }
  1892  
  1893  // useKey specifies owner private key for session token.
  1894  // If key is not provided, then Pool default key is used.
  1895  func (x *prmCreateSession) useKey(key ecdsa.PrivateKey) {
  1896  	x.key = key
  1897  }
  1898  
  1899  // prmEndpointInfo groups parameters of endpointInfo operation.
  1900  type prmEndpointInfo struct{}
  1901  
  1902  // prmNetworkInfo groups parameters of networkInfo operation.
  1903  type prmNetworkInfo struct{}
  1904  
  1905  // prmNetMapSnapshot groups parameters of netMapSnapshot operation.
  1906  type prmNetMapSnapshot struct{}
  1907  
  1908  // resCreateSession groups resulting values of sessionCreate operation.
  1909  type resCreateSession struct {
  1910  	id []byte
  1911  
  1912  	sessionKey []byte
  1913  }
  1914  
  1915  // Pool represents virtual connection to the FrostFS network to communicate
  1916  // with multiple FrostFS servers without thinking about switching between servers
  1917  // due to load balancing proportions or their unavailability.
  1918  // It is designed to provide a convenient abstraction from the multiple sdkClient.client types.
  1919  //
  1920  // Pool can be created and initialized using NewPool function.
  1921  // Before executing the FrostFS operations using the Pool, connection to the
  1922  // servers MUST BE correctly established (see Dial method).
  1923  // Using the Pool before connecting have been established can lead to a panic.
  1924  // After the work, the Pool SHOULD BE closed (see Close method): it frees internal
  1925  // and system resources which were allocated for the period of work of the Pool.
  1926  // Calling Dial/Close methods during the communication process step strongly discouraged
  1927  // as it leads to undefined behavior.
  1928  //
  1929  // Each method which produces a FrostFS API call may return an error.
  1930  // Status of underlying server response is casted to built-in error instance.
  1931  // Certain statuses can be checked using `sdkClient` and standard `errors` packages.
  1932  // Note that package provides some helper functions to work with status returns
  1933  // (e.g. sdkClient.IsErrContainerNotFound, sdkClient.IsErrObjectNotFound).
  1934  //
  1935  // See pool package overview to get some examples.
  1936  type Pool struct {
  1937  	innerPools      []*innerPool
  1938  	key             *ecdsa.PrivateKey
  1939  	cancel          context.CancelFunc
  1940  	closedCh        chan struct{}
  1941  	cache           *sessionCache
  1942  	stokenDuration  uint64
  1943  	rebalanceParams rebalanceParameters
  1944  	clientBuilder   clientBuilder
  1945  	logger          *zap.Logger
  1946  
  1947  	maxObjectSize uint64
  1948  }
  1949  
  1950  type innerPool struct {
  1951  	lock    sync.RWMutex
  1952  	sampler *sampler
  1953  	clients []client
  1954  }
  1955  
  1956  const (
  1957  	defaultSessionTokenExpirationDuration = 100 // in epochs
  1958  	defaultErrorThreshold                 = 100
  1959  
  1960  	defaultGracefulCloseOnSwitchTimeout = 10 * time.Second
  1961  	defaultRebalanceInterval            = 15 * time.Second
  1962  	defaultHealthcheckTimeout           = 4 * time.Second
  1963  	defaultDialTimeout                  = 5 * time.Second
  1964  	defaultStreamTimeout                = 10 * time.Second
  1965  
  1966  	defaultBufferMaxSizeForPut = 3 * 1024 * 1024 // 3 MB
  1967  )
  1968  
  1969  // NewPool creates connection pool using parameters.
  1970  func NewPool(options InitParameters) (*Pool, error) {
  1971  	if options.key == nil {
  1972  		return nil, fmt.Errorf("missed required parameter 'Key'")
  1973  	}
  1974  
  1975  	nodesParams, err := adjustNodeParams(options.nodeParams)
  1976  	if err != nil {
  1977  		return nil, err
  1978  	}
  1979  
  1980  	cache, err := newCache(options.sessionExpirationDuration)
  1981  	if err != nil {
  1982  		return nil, fmt.Errorf("couldn't create cache: %w", err)
  1983  	}
  1984  
  1985  	fillDefaultInitParams(&options, cache)
  1986  
  1987  	pool := &Pool{
  1988  		key:            options.key,
  1989  		cache:          cache,
  1990  		logger:         options.logger,
  1991  		stokenDuration: options.sessionExpirationDuration,
  1992  		rebalanceParams: rebalanceParameters{
  1993  			nodesParams:               nodesParams,
  1994  			nodeRequestTimeout:        options.healthcheckTimeout,
  1995  			clientRebalanceInterval:   options.clientRebalanceInterval,
  1996  			sessionExpirationDuration: options.sessionExpirationDuration,
  1997  		},
  1998  		clientBuilder: options.clientBuilder,
  1999  	}
  2000  
  2001  	return pool, nil
  2002  }
  2003  
  2004  // Dial establishes a connection to the servers from the FrostFS network.
  2005  // It also starts a routine that checks the health of the nodes and
  2006  // updates the weights of the nodes for balancing.
  2007  // Returns an error describing failure reason.
  2008  //
  2009  // If failed, the Pool SHOULD NOT be used.
  2010  //
  2011  // See also InitParameters.SetClientRebalanceInterval.
  2012  func (p *Pool) Dial(ctx context.Context) error {
  2013  	inner := make([]*innerPool, len(p.rebalanceParams.nodesParams))
  2014  	var atLeastOneHealthy bool
  2015  
  2016  	for i, params := range p.rebalanceParams.nodesParams {
  2017  		clients := make([]client, len(params.weights))
  2018  		for j, addr := range params.addresses {
  2019  			clients[j] = p.clientBuilder(addr)
  2020  			if err := clients[j].dial(ctx); err != nil {
  2021  				p.log(zap.WarnLevel, "failed to build client", zap.String("address", addr), zap.Error(err))
  2022  				continue
  2023  			}
  2024  
  2025  			var st session.Object
  2026  			err := initSessionForDuration(ctx, &st, clients[j], p.rebalanceParams.sessionExpirationDuration, *p.key, false)
  2027  			if err != nil {
  2028  				clients[j].setUnhealthy()
  2029  				p.log(zap.WarnLevel, "failed to create frostfs session token for client",
  2030  					zap.String("address", addr), zap.Error(err))
  2031  				continue
  2032  			}
  2033  
  2034  			_ = p.cache.Put(formCacheKey(addr, p.key, false), st)
  2035  			atLeastOneHealthy = true
  2036  		}
  2037  		source := rand.NewSource(time.Now().UnixNano())
  2038  		sampl := newSampler(params.weights, source)
  2039  
  2040  		inner[i] = &innerPool{
  2041  			sampler: sampl,
  2042  			clients: clients,
  2043  		}
  2044  	}
  2045  
  2046  	if !atLeastOneHealthy {
  2047  		return fmt.Errorf("at least one node must be healthy")
  2048  	}
  2049  
  2050  	ctx, cancel := context.WithCancel(ctx)
  2051  	p.cancel = cancel
  2052  	p.closedCh = make(chan struct{})
  2053  	p.innerPools = inner
  2054  
  2055  	ni, err := p.NetworkInfo(ctx)
  2056  	if err != nil {
  2057  		return fmt.Errorf("get network info for max object size: %w", err)
  2058  	}
  2059  	p.maxObjectSize = ni.MaxObjectSize()
  2060  
  2061  	go p.startRebalance(ctx)
  2062  	return nil
  2063  }
  2064  
  2065  func (p *Pool) log(level zapcore.Level, msg string, fields ...zap.Field) {
  2066  	if p.logger == nil {
  2067  		return
  2068  	}
  2069  
  2070  	p.logger.Log(level, msg, fields...)
  2071  }
  2072  
  2073  func fillDefaultInitParams(params *InitParameters, cache *sessionCache) {
  2074  	if params.sessionExpirationDuration == 0 {
  2075  		params.sessionExpirationDuration = defaultSessionTokenExpirationDuration
  2076  	}
  2077  
  2078  	if params.errorThreshold == 0 {
  2079  		params.errorThreshold = defaultErrorThreshold
  2080  	}
  2081  
  2082  	if params.clientRebalanceInterval <= 0 {
  2083  		params.clientRebalanceInterval = defaultRebalanceInterval
  2084  	}
  2085  
  2086  	if params.gracefulCloseOnSwitchTimeout <= 0 {
  2087  		params.gracefulCloseOnSwitchTimeout = defaultGracefulCloseOnSwitchTimeout
  2088  	}
  2089  
  2090  	if params.healthcheckTimeout <= 0 {
  2091  		params.healthcheckTimeout = defaultHealthcheckTimeout
  2092  	}
  2093  
  2094  	if params.nodeDialTimeout <= 0 {
  2095  		params.nodeDialTimeout = defaultDialTimeout
  2096  	}
  2097  
  2098  	if params.nodeStreamTimeout <= 0 {
  2099  		params.nodeStreamTimeout = defaultStreamTimeout
  2100  	}
  2101  
  2102  	if cache.tokenDuration == 0 {
  2103  		cache.tokenDuration = defaultSessionTokenExpirationDuration
  2104  	}
  2105  
  2106  	if params.isMissingClientBuilder() {
  2107  		params.setClientBuilder(func(addr string) client {
  2108  			var prm wrapperPrm
  2109  			prm.setAddress(addr)
  2110  			prm.setKey(*params.key)
  2111  			prm.setLogger(params.logger)
  2112  			prm.setDialTimeout(params.nodeDialTimeout)
  2113  			prm.setStreamTimeout(params.nodeStreamTimeout)
  2114  			prm.setErrorThreshold(params.errorThreshold)
  2115  			prm.setGracefulCloseOnSwitchTimeout(params.gracefulCloseOnSwitchTimeout)
  2116  			prm.setPoolRequestCallback(params.requestCallback)
  2117  			prm.setGRPCDialOptions(params.dialOptions)
  2118  			prm.setResponseInfoCallback(func(info sdkClient.ResponseMetaInfo) error {
  2119  				cache.updateEpoch(info.Epoch())
  2120  				return nil
  2121  			})
  2122  			return newWrapper(prm)
  2123  		})
  2124  	}
  2125  }
  2126  
  2127  func adjustNodeParams(nodeParams []NodeParam) ([]*nodesParam, error) {
  2128  	if len(nodeParams) == 0 {
  2129  		return nil, errors.New("no FrostFS peers configured")
  2130  	}
  2131  
  2132  	nodesParamsMap := make(map[int]*nodesParam)
  2133  	for _, param := range nodeParams {
  2134  		nodes, ok := nodesParamsMap[param.priority]
  2135  		if !ok {
  2136  			nodes = &nodesParam{priority: param.priority}
  2137  		}
  2138  		nodes.addresses = append(nodes.addresses, param.address)
  2139  		nodes.weights = append(nodes.weights, param.weight)
  2140  		nodesParamsMap[param.priority] = nodes
  2141  	}
  2142  
  2143  	nodesParams := make([]*nodesParam, 0, len(nodesParamsMap))
  2144  	for _, nodes := range nodesParamsMap {
  2145  		nodes.weights = adjustWeights(nodes.weights)
  2146  		nodesParams = append(nodesParams, nodes)
  2147  	}
  2148  
  2149  	sort.Slice(nodesParams, func(i, j int) bool {
  2150  		return nodesParams[i].priority < nodesParams[j].priority
  2151  	})
  2152  
  2153  	return nodesParams, nil
  2154  }
  2155  
  2156  // startRebalance runs loop to monitor connection healthy status.
  2157  func (p *Pool) startRebalance(ctx context.Context) {
  2158  	ticker := time.NewTicker(p.rebalanceParams.clientRebalanceInterval)
  2159  	defer ticker.Stop()
  2160  
  2161  	buffers := make([][]float64, len(p.rebalanceParams.nodesParams))
  2162  	for i, params := range p.rebalanceParams.nodesParams {
  2163  		buffers[i] = make([]float64, len(params.weights))
  2164  	}
  2165  
  2166  	for {
  2167  		select {
  2168  		case <-ctx.Done():
  2169  			close(p.closedCh)
  2170  			return
  2171  		case <-ticker.C:
  2172  			p.updateNodesHealth(ctx, buffers)
  2173  			ticker.Reset(p.rebalanceParams.clientRebalanceInterval)
  2174  		}
  2175  	}
  2176  }
  2177  
  2178  func (p *Pool) updateNodesHealth(ctx context.Context, buffers [][]float64) {
  2179  	wg := sync.WaitGroup{}
  2180  	for i, inner := range p.innerPools {
  2181  		wg.Add(1)
  2182  
  2183  		bufferWeights := buffers[i]
  2184  		go func(i int, _ *innerPool) {
  2185  			defer wg.Done()
  2186  			p.updateInnerNodesHealth(ctx, i, bufferWeights)
  2187  		}(i, inner)
  2188  	}
  2189  	wg.Wait()
  2190  }
  2191  
  2192  func (p *Pool) updateInnerNodesHealth(ctx context.Context, i int, bufferWeights []float64) {
  2193  	if i > len(p.innerPools)-1 {
  2194  		return
  2195  	}
  2196  	pool := p.innerPools[i]
  2197  	options := p.rebalanceParams
  2198  
  2199  	healthyChanged := new(atomic.Bool)
  2200  	wg := sync.WaitGroup{}
  2201  
  2202  	for j, cli := range pool.clients {
  2203  		wg.Add(1)
  2204  		go func(j int, cli client) {
  2205  			defer wg.Done()
  2206  
  2207  			tctx, c := context.WithTimeout(ctx, options.nodeRequestTimeout)
  2208  			defer c()
  2209  
  2210  			changed, err := restartIfUnhealthy(tctx, cli)
  2211  			healthy := err == nil
  2212  			if healthy {
  2213  				bufferWeights[j] = options.nodesParams[i].weights[j]
  2214  			} else {
  2215  				bufferWeights[j] = 0
  2216  				p.cache.DeleteByPrefix(cli.address())
  2217  			}
  2218  
  2219  			if changed {
  2220  				fields := []zap.Field{zap.String("address", cli.address()), zap.Bool("healthy", healthy)}
  2221  				if err != nil {
  2222  					fields = append(fields, zap.String("reason", err.Error()))
  2223  				}
  2224  
  2225  				p.log(zap.DebugLevel, "health has changed", fields...)
  2226  				healthyChanged.Store(true)
  2227  			}
  2228  		}(j, cli)
  2229  	}
  2230  	wg.Wait()
  2231  
  2232  	if healthyChanged.Load() {
  2233  		probabilities := adjustWeights(bufferWeights)
  2234  		source := rand.NewSource(time.Now().UnixNano())
  2235  		pool.lock.Lock()
  2236  		pool.sampler = newSampler(probabilities, source)
  2237  		pool.lock.Unlock()
  2238  	}
  2239  }
  2240  
  2241  // restartIfUnhealthy checks healthy status of client and recreate it if status is unhealthy.
  2242  // Indicating if status was changed by this function call and returns error that caused unhealthy status.
  2243  func restartIfUnhealthy(ctx context.Context, c client) (changed bool, err error) {
  2244  	defer func() {
  2245  		if err != nil {
  2246  			c.setUnhealthy()
  2247  		} else {
  2248  			c.setHealthy()
  2249  		}
  2250  	}()
  2251  
  2252  	wasHealthy := c.isHealthy()
  2253  
  2254  	if res, err := c.healthcheck(ctx); err == nil {
  2255  		if res.Status().IsMaintenance() {
  2256  			return wasHealthy, new(apistatus.NodeUnderMaintenance)
  2257  		}
  2258  
  2259  		return !wasHealthy, nil
  2260  	}
  2261  
  2262  	if err = c.restart(ctx); err != nil {
  2263  		return wasHealthy, err
  2264  	}
  2265  
  2266  	res, err := c.healthcheck(ctx)
  2267  	if err != nil {
  2268  		return wasHealthy, err
  2269  	}
  2270  
  2271  	if res.Status().IsMaintenance() {
  2272  		return wasHealthy, new(apistatus.NodeUnderMaintenance)
  2273  	}
  2274  
  2275  	return !wasHealthy, nil
  2276  }
  2277  
  2278  func adjustWeights(weights []float64) []float64 {
  2279  	adjusted := make([]float64, len(weights))
  2280  	sum := 0.0
  2281  	for _, weight := range weights {
  2282  		sum += weight
  2283  	}
  2284  	if sum > 0 {
  2285  		for i, weight := range weights {
  2286  			adjusted[i] = weight / sum
  2287  		}
  2288  	}
  2289  
  2290  	return adjusted
  2291  }
  2292  
  2293  func (p *Pool) connection() (client, error) {
  2294  	for _, inner := range p.innerPools {
  2295  		cp, err := inner.connection()
  2296  		if err == nil {
  2297  			return cp, nil
  2298  		}
  2299  	}
  2300  
  2301  	return nil, errors.New("no healthy client")
  2302  }
  2303  
  2304  func (p *innerPool) connection() (client, error) {
  2305  	p.lock.RLock() // need lock because of using p.sampler
  2306  	defer p.lock.RUnlock()
  2307  	if len(p.clients) == 1 {
  2308  		cp := p.clients[0]
  2309  		if cp.isHealthy() {
  2310  			return cp, nil
  2311  		}
  2312  		return nil, errors.New("no healthy client")
  2313  	}
  2314  	attempts := 3 * len(p.clients)
  2315  	for range attempts {
  2316  		i := p.sampler.Next()
  2317  		if cp := p.clients[i]; cp.isHealthy() {
  2318  			return cp, nil
  2319  		}
  2320  	}
  2321  
  2322  	return nil, errors.New("no healthy client")
  2323  }
  2324  
  2325  func formCacheKey(address string, key *ecdsa.PrivateKey, clientCut bool) string {
  2326  	k := keys.PrivateKey{PrivateKey: *key}
  2327  
  2328  	stype := "server"
  2329  	if clientCut {
  2330  		stype = "client"
  2331  	}
  2332  
  2333  	return address + stype + k.String()
  2334  }
  2335  
  2336  func (p *Pool) checkSessionTokenErr(err error, address string) bool {
  2337  	if err == nil {
  2338  		return false
  2339  	}
  2340  
  2341  	if sdkClient.IsErrSessionNotFound(err) || sdkClient.IsErrSessionExpired(err) {
  2342  		p.cache.DeleteByPrefix(address)
  2343  		return true
  2344  	}
  2345  
  2346  	return false
  2347  }
  2348  
  2349  func initSessionForDuration(ctx context.Context, dst *session.Object, c client, dur uint64, ownerKey ecdsa.PrivateKey, clientCut bool) error {
  2350  	ni, err := c.networkInfo(ctx, prmNetworkInfo{})
  2351  	if err != nil {
  2352  		return err
  2353  	}
  2354  
  2355  	epoch := ni.CurrentEpoch()
  2356  
  2357  	var exp uint64
  2358  	if math.MaxUint64-epoch < dur {
  2359  		exp = math.MaxUint64
  2360  	} else {
  2361  		exp = epoch + dur
  2362  	}
  2363  	var prm prmCreateSession
  2364  	prm.setExp(exp)
  2365  	prm.useKey(ownerKey)
  2366  
  2367  	var (
  2368  		id  uuid.UUID
  2369  		key frostfsecdsa.PublicKey
  2370  	)
  2371  
  2372  	if clientCut {
  2373  		id = uuid.New()
  2374  		key = frostfsecdsa.PublicKey(ownerKey.PublicKey)
  2375  	} else {
  2376  		res, err := c.sessionCreate(ctx, prm)
  2377  		if err != nil {
  2378  			return err
  2379  		}
  2380  		if err = id.UnmarshalBinary(res.id); err != nil {
  2381  			return fmt.Errorf("invalid session token ID: %w", err)
  2382  		}
  2383  		if err = key.Decode(res.sessionKey); err != nil {
  2384  			return fmt.Errorf("invalid public session key: %w", err)
  2385  		}
  2386  	}
  2387  
  2388  	dst.SetID(id)
  2389  	dst.SetAuthKey(&key)
  2390  	dst.SetExp(exp)
  2391  
  2392  	return nil
  2393  }
  2394  
  2395  type callContext struct {
  2396  	client client
  2397  
  2398  	// client endpoint
  2399  	endpoint string
  2400  
  2401  	// request signer
  2402  	key *ecdsa.PrivateKey
  2403  
  2404  	// flag to open default session if session token is missing
  2405  	sessionDefault bool
  2406  	sessionTarget  func(session.Object)
  2407  	sessionVerb    session.ObjectVerb
  2408  	sessionCnr     cid.ID
  2409  	sessionObjSet  bool
  2410  	sessionObjs    []oid.ID
  2411  
  2412  	sessionClientCut bool
  2413  }
  2414  
  2415  func (p *Pool) initCallContext(ctx *callContext, cfg prmCommon, prmCtx prmContext) error {
  2416  	cp, err := p.connection()
  2417  	if err != nil {
  2418  		return err
  2419  	}
  2420  
  2421  	ctx.key = cfg.key
  2422  	if ctx.key == nil {
  2423  		// use pool key if caller didn't specify its own
  2424  		ctx.key = p.key
  2425  	}
  2426  
  2427  	ctx.endpoint = cp.address()
  2428  	ctx.client = cp
  2429  
  2430  	if ctx.sessionTarget != nil && cfg.stoken != nil {
  2431  		ctx.sessionTarget(*cfg.stoken)
  2432  	}
  2433  
  2434  	// note that we don't override session provided by the caller
  2435  	ctx.sessionDefault = cfg.stoken == nil && prmCtx.defaultSession
  2436  	if ctx.sessionDefault {
  2437  		ctx.sessionVerb = prmCtx.verb
  2438  		ctx.sessionCnr = prmCtx.cnr
  2439  		ctx.sessionObjSet = prmCtx.objSet
  2440  		ctx.sessionObjs = prmCtx.objs
  2441  	}
  2442  
  2443  	return err
  2444  }
  2445  
  2446  // opens new session or uses cached one.
  2447  // Must be called only on initialized callContext with set sessionTarget.
  2448  func (p *Pool) openDefaultSession(ctx context.Context, cc *callContext) error {
  2449  	cacheKey := formCacheKey(cc.endpoint, cc.key, cc.sessionClientCut)
  2450  
  2451  	tok, ok := p.cache.Get(cacheKey)
  2452  	if !ok {
  2453  		// init new session
  2454  		err := initSessionForDuration(ctx, &tok, cc.client, p.stokenDuration, *cc.key, cc.sessionClientCut)
  2455  		if err != nil {
  2456  			return fmt.Errorf("session API client: %w", err)
  2457  		}
  2458  
  2459  		// cache the opened session
  2460  		p.cache.Put(cacheKey, tok)
  2461  	}
  2462  
  2463  	tok.ForVerb(cc.sessionVerb)
  2464  	tok.BindContainer(cc.sessionCnr)
  2465  
  2466  	if cc.sessionObjSet {
  2467  		tok.LimitByObjects(cc.sessionObjs...)
  2468  	}
  2469  
  2470  	// sign the token
  2471  	if err := tok.Sign(*cc.key); err != nil {
  2472  		return fmt.Errorf("sign token of the opened session: %w", err)
  2473  	}
  2474  
  2475  	cc.sessionTarget(tok)
  2476  
  2477  	return nil
  2478  }
  2479  
  2480  // opens default session (if sessionDefault is set), and calls f. If f returns
  2481  // session-related error then cached token is removed.
  2482  func (p *Pool) call(ctx context.Context, cc *callContext, f func() error) error {
  2483  	var err error
  2484  
  2485  	if cc.sessionDefault {
  2486  		err = p.openDefaultSession(ctx, cc)
  2487  		if err != nil {
  2488  			return fmt.Errorf("open default session: %w", err)
  2489  		}
  2490  	}
  2491  
  2492  	err = f()
  2493  	_ = p.checkSessionTokenErr(err, cc.endpoint)
  2494  
  2495  	return err
  2496  }
  2497  
  2498  // fillAppropriateKey use pool key if caller didn't specify its own.
  2499  func (p *Pool) fillAppropriateKey(prm *prmCommon) {
  2500  	if prm.key == nil {
  2501  		prm.key = p.key
  2502  	}
  2503  }
  2504  
  2505  // ResPutObject is designed to provide identifier and creation epoch of the saved object.
  2506  type ResPutObject struct {
  2507  	ObjectID oid.ID
  2508  	Epoch    uint64
  2509  }
  2510  
  2511  // ResPatchObject is designed to provide identifier for the saved patched object.
  2512  type ResPatchObject struct {
  2513  	ObjectID oid.ID
  2514  }
  2515  
  2516  // PatchObject patches an object through a remote server using FrostFS API protocol.
  2517  //
  2518  // Main return value MUST NOT be processed on an erroneous return.
  2519  func (p *Pool) PatchObject(ctx context.Context, prm PrmObjectPatch) (ResPatchObject, error) {
  2520  	var prmCtx prmContext
  2521  	prmCtx.useDefaultSession()
  2522  	prmCtx.useVerb(session.VerbObjectPatch)
  2523  	prmCtx.useContainer(prm.addr.Container())
  2524  
  2525  	p.fillAppropriateKey(&prm.prmCommon)
  2526  
  2527  	var ctxCall callContext
  2528  	if err := p.initCallContext(&ctxCall, prm.prmCommon, prmCtx); err != nil {
  2529  		return ResPatchObject{}, fmt.Errorf("init call context: %w", err)
  2530  	}
  2531  
  2532  	if ctxCall.sessionDefault {
  2533  		ctxCall.sessionTarget = prm.UseSession
  2534  		if err := p.openDefaultSession(ctx, &ctxCall); err != nil {
  2535  			return ResPatchObject{}, fmt.Errorf("open default session: %w", err)
  2536  		}
  2537  	}
  2538  
  2539  	res, err := ctxCall.client.objectPatch(ctx, prm)
  2540  	if err != nil {
  2541  		// removes session token from cache in case of token error
  2542  		p.checkSessionTokenErr(err, ctxCall.endpoint)
  2543  		return ResPatchObject{}, fmt.Errorf("init patching on API client %s: %w", ctxCall.endpoint, err)
  2544  	}
  2545  
  2546  	return res, nil
  2547  }
  2548  
  2549  // PutObject writes an object through a remote server using FrostFS API protocol.
  2550  //
  2551  // Main return value MUST NOT be processed on an erroneous return.
  2552  func (p *Pool) PutObject(ctx context.Context, prm PrmObjectPut) (ResPutObject, error) {
  2553  	cnr, _ := prm.hdr.ContainerID()
  2554  
  2555  	var prmCtx prmContext
  2556  	prmCtx.useDefaultSession()
  2557  	prmCtx.useVerb(session.VerbObjectPut)
  2558  	prmCtx.useContainer(cnr)
  2559  
  2560  	p.fillAppropriateKey(&prm.prmCommon)
  2561  
  2562  	var ctxCall callContext
  2563  	ctxCall.sessionClientCut = prm.clientCut
  2564  	if err := p.initCallContext(&ctxCall, prm.prmCommon, prmCtx); err != nil {
  2565  		return ResPutObject{}, fmt.Errorf("init call context: %w", err)
  2566  	}
  2567  
  2568  	if ctxCall.sessionDefault {
  2569  		ctxCall.sessionTarget = prm.UseSession
  2570  		if err := p.openDefaultSession(ctx, &ctxCall); err != nil {
  2571  			return ResPutObject{}, fmt.Errorf("open default session: %w", err)
  2572  		}
  2573  	}
  2574  
  2575  	if prm.clientCut {
  2576  		var ni netmap.NetworkInfo
  2577  		ni.SetCurrentEpoch(p.cache.Epoch())
  2578  		ni.SetMaxObjectSize(p.maxObjectSize) // we want to use initial max object size in PayloadSizeLimiter
  2579  		prm.setNetworkInfo(ni)
  2580  	}
  2581  
  2582  	res, err := ctxCall.client.objectPut(ctx, prm)
  2583  	if err != nil {
  2584  		// removes session token from cache in case of token error
  2585  		p.checkSessionTokenErr(err, ctxCall.endpoint)
  2586  		return ResPutObject{}, fmt.Errorf("init writing on API client %s: %w", ctxCall.endpoint, err)
  2587  	}
  2588  
  2589  	return res, nil
  2590  }
  2591  
  2592  // DeleteObject marks an object for deletion from the container using FrostFS API protocol.
  2593  // As a marker, a special unit called a tombstone is placed in the container.
  2594  // It confirms the user's intent to delete the object, and is itself a container object.
  2595  // Explicit deletion is done asynchronously, and is generally not guaranteed.
  2596  func (p *Pool) DeleteObject(ctx context.Context, prm PrmObjectDelete) error {
  2597  	var prmCtx prmContext
  2598  	prmCtx.useDefaultSession()
  2599  	prmCtx.useVerb(session.VerbObjectDelete)
  2600  	prmCtx.useAddress(prm.addr)
  2601  
  2602  	if prm.stoken == nil { // collect phy objects only if we are about to open default session
  2603  		var tokens relations.Tokens
  2604  		tokens.Bearer = prm.btoken
  2605  
  2606  		relatives, err := relations.ListAllRelations(ctx, p, prm.addr.Container(), prm.addr.Object(), tokens)
  2607  		if err != nil {
  2608  			return fmt.Errorf("failed to collect relatives: %w", err)
  2609  		}
  2610  
  2611  		if len(relatives) != 0 {
  2612  			prmCtx.useContainer(prm.addr.Container())
  2613  			prmCtx.useObjects(append(relatives, prm.addr.Object()))
  2614  		}
  2615  	}
  2616  
  2617  	p.fillAppropriateKey(&prm.prmCommon)
  2618  
  2619  	var cc callContext
  2620  	cc.sessionTarget = prm.UseSession
  2621  
  2622  	err := p.initCallContext(&cc, prm.prmCommon, prmCtx)
  2623  	if err != nil {
  2624  		return err
  2625  	}
  2626  
  2627  	return p.call(ctx, &cc, func() error {
  2628  		if err = cc.client.objectDelete(ctx, prm); err != nil {
  2629  			return fmt.Errorf("remove object via client %s: %w", cc.endpoint, err)
  2630  		}
  2631  
  2632  		return nil
  2633  	})
  2634  }
  2635  
  2636  type objectReadCloser struct {
  2637  	reader              *sdkClient.ObjectReader
  2638  	elapsedTimeCallback func(time.Duration)
  2639  }
  2640  
  2641  // Read implements io.Reader of the object payload.
  2642  func (x *objectReadCloser) Read(p []byte) (int, error) {
  2643  	start := time.Now()
  2644  	n, err := x.reader.Read(p)
  2645  	x.elapsedTimeCallback(time.Since(start))
  2646  	return n, err
  2647  }
  2648  
  2649  // Close implements io.Closer of the object payload.
  2650  func (x *objectReadCloser) Close() error {
  2651  	_, err := x.reader.Close()
  2652  	return err
  2653  }
  2654  
  2655  // ResGetObject is designed to provide object header nad read one object payload from FrostFS system.
  2656  type ResGetObject struct {
  2657  	Header object.Object
  2658  
  2659  	Payload io.ReadCloser
  2660  }
  2661  
  2662  // GetObject reads object header and initiates reading an object payload through a remote server using FrostFS API protocol.
  2663  //
  2664  // Main return value MUST NOT be processed on an erroneous return.
  2665  func (p *Pool) GetObject(ctx context.Context, prm PrmObjectGet) (ResGetObject, error) {
  2666  	p.fillAppropriateKey(&prm.prmCommon)
  2667  
  2668  	var cc callContext
  2669  	cc.sessionTarget = prm.UseSession
  2670  
  2671  	var res ResGetObject
  2672  
  2673  	err := p.initCallContext(&cc, prm.prmCommon, prmContext{})
  2674  	if err != nil {
  2675  		return res, err
  2676  	}
  2677  
  2678  	return res, p.call(ctx, &cc, func() error {
  2679  		res, err = cc.client.objectGet(ctx, prm)
  2680  		if err != nil {
  2681  			return fmt.Errorf("get object via client %s: %w", cc.endpoint, err)
  2682  		}
  2683  		return nil
  2684  	})
  2685  }
  2686  
  2687  // HeadObject reads object header through a remote server using FrostFS API protocol.
  2688  //
  2689  // Main return value MUST NOT be processed on an erroneous return.
  2690  func (p *Pool) HeadObject(ctx context.Context, prm PrmObjectHead) (object.Object, error) {
  2691  	p.fillAppropriateKey(&prm.prmCommon)
  2692  
  2693  	var cc callContext
  2694  	cc.sessionTarget = prm.UseSession
  2695  
  2696  	var obj object.Object
  2697  
  2698  	err := p.initCallContext(&cc, prm.prmCommon, prmContext{})
  2699  	if err != nil {
  2700  		return obj, err
  2701  	}
  2702  
  2703  	return obj, p.call(ctx, &cc, func() error {
  2704  		obj, err = cc.client.objectHead(ctx, prm)
  2705  		if err != nil {
  2706  			return fmt.Errorf("head object via client %s: %w", cc.endpoint, err)
  2707  		}
  2708  		return nil
  2709  	})
  2710  }
  2711  
  2712  // ResObjectRange is designed to read payload range of one object
  2713  // from FrostFS system.
  2714  //
  2715  // Must be initialized using Pool.ObjectRange, any other
  2716  // usage is unsafe.
  2717  type ResObjectRange struct {
  2718  	payload             *sdkClient.ObjectRangeReader
  2719  	elapsedTimeCallback func(time.Duration)
  2720  }
  2721  
  2722  // Read implements io.Reader of the object payload.
  2723  func (x *ResObjectRange) Read(p []byte) (int, error) {
  2724  	start := time.Now()
  2725  	n, err := x.payload.Read(p)
  2726  	x.elapsedTimeCallback(time.Since(start))
  2727  	return n, err
  2728  }
  2729  
  2730  // Close ends reading the payload range and returns the result of the operation
  2731  // along with the final results. Must be called after using the ResObjectRange.
  2732  func (x *ResObjectRange) Close() error {
  2733  	_, err := x.payload.Close()
  2734  	return err
  2735  }
  2736  
  2737  // ObjectRange initiates reading an object's payload range through a remote
  2738  // server using FrostFS API protocol.
  2739  //
  2740  // Main return value MUST NOT be processed on an erroneous return.
  2741  func (p *Pool) ObjectRange(ctx context.Context, prm PrmObjectRange) (ResObjectRange, error) {
  2742  	p.fillAppropriateKey(&prm.prmCommon)
  2743  
  2744  	var cc callContext
  2745  	cc.sessionTarget = prm.UseSession
  2746  
  2747  	var res ResObjectRange
  2748  
  2749  	err := p.initCallContext(&cc, prm.prmCommon, prmContext{})
  2750  	if err != nil {
  2751  		return res, err
  2752  	}
  2753  
  2754  	return res, p.call(ctx, &cc, func() error {
  2755  		res, err = cc.client.objectRange(ctx, prm)
  2756  		if err != nil {
  2757  			return fmt.Errorf("object range via client %s: %w", cc.endpoint, err)
  2758  		}
  2759  		return nil
  2760  	})
  2761  }
  2762  
  2763  // ResObjectSearch is designed to read list of object identifiers from FrostFS system.
  2764  //
  2765  // Must be initialized using Pool.SearchObjects, any other usage is unsafe.
  2766  type ResObjectSearch struct {
  2767  	r           *sdkClient.ObjectListReader
  2768  	handleError func(context.Context, apistatus.Status, error) error
  2769  }
  2770  
  2771  // Read reads another list of the object identifiers.
  2772  func (x *ResObjectSearch) Read(buf []oid.ID) (int, error) {
  2773  	n, ok := x.r.Read(buf)
  2774  	if !ok {
  2775  		res, err := x.r.Close()
  2776  		if err == nil {
  2777  			return n, io.EOF
  2778  		}
  2779  
  2780  		var status apistatus.Status
  2781  		if res != nil {
  2782  			status = res.Status()
  2783  		}
  2784  		err = x.handleError(nil, status, err)
  2785  
  2786  		return n, err
  2787  	}
  2788  
  2789  	return n, nil
  2790  }
  2791  
  2792  // Iterate iterates over the list of found object identifiers.
  2793  // f can return true to stop iteration earlier.
  2794  //
  2795  // Returns an error if object can't be read.
  2796  func (x *ResObjectSearch) Iterate(f func(oid.ID) bool) error {
  2797  	return x.r.Iterate(f)
  2798  }
  2799  
  2800  // Close ends reading list of the matched objects and returns the result of the operation
  2801  // along with the final results. Must be called after using the ResObjectSearch.
  2802  func (x *ResObjectSearch) Close() {
  2803  	_, _ = x.r.Close()
  2804  }
  2805  
  2806  // SearchObjects initiates object selection through a remote server using FrostFS API protocol.
  2807  //
  2808  // The call only opens the transmission channel, explicit fetching of matched objects
  2809  // is done using the ResObjectSearch. Resulting reader must be finally closed.
  2810  //
  2811  // Main return value MUST NOT be processed on an erroneous return.
  2812  func (p *Pool) SearchObjects(ctx context.Context, prm PrmObjectSearch) (ResObjectSearch, error) {
  2813  	p.fillAppropriateKey(&prm.prmCommon)
  2814  
  2815  	var cc callContext
  2816  	cc.sessionTarget = prm.UseSession
  2817  
  2818  	var res ResObjectSearch
  2819  
  2820  	err := p.initCallContext(&cc, prm.prmCommon, prmContext{})
  2821  	if err != nil {
  2822  		return res, err
  2823  	}
  2824  
  2825  	return res, p.call(ctx, &cc, func() error {
  2826  		res, err = cc.client.objectSearch(ctx, prm)
  2827  		if err != nil {
  2828  			return fmt.Errorf("search object via client %s: %w", cc.endpoint, err)
  2829  		}
  2830  		return nil
  2831  	})
  2832  }
  2833  
  2834  // PutContainer sends request to save container in FrostFS and waits for the operation to complete.
  2835  //
  2836  // Waiting parameters can be specified using SetWaitParams. If not called, defaults are used:
  2837  //
  2838  //	polling interval: 5s
  2839  //	waiting timeout: 120s
  2840  //
  2841  // Success can be verified by reading by identifier (see GetContainer).
  2842  //
  2843  // Main return value MUST NOT be processed on an erroneous return.
  2844  func (p *Pool) PutContainer(ctx context.Context, prm PrmContainerPut) (cid.ID, error) {
  2845  	cp, err := p.connection()
  2846  	if err != nil {
  2847  		return cid.ID{}, err
  2848  	}
  2849  
  2850  	cnrID, err := cp.containerPut(ctx, prm)
  2851  	if err != nil {
  2852  		return cid.ID{}, fmt.Errorf("put container via client '%s': %w", cp.address(), err)
  2853  	}
  2854  
  2855  	return cnrID, nil
  2856  }
  2857  
  2858  // GetContainer reads FrostFS container by ID.
  2859  //
  2860  // Main return value MUST NOT be processed on an erroneous return.
  2861  func (p *Pool) GetContainer(ctx context.Context, prm PrmContainerGet) (container.Container, error) {
  2862  	cp, err := p.connection()
  2863  	if err != nil {
  2864  		return container.Container{}, err
  2865  	}
  2866  
  2867  	cnrs, err := cp.containerGet(ctx, prm)
  2868  	if err != nil {
  2869  		return container.Container{}, fmt.Errorf("get container via client '%s': %w", cp.address(), err)
  2870  	}
  2871  
  2872  	return cnrs, nil
  2873  }
  2874  
  2875  // ListContainers requests identifiers of the account-owned containers.
  2876  func (p *Pool) ListContainers(ctx context.Context, prm PrmContainerList) ([]cid.ID, error) {
  2877  	cp, err := p.connection()
  2878  	if err != nil {
  2879  		return nil, err
  2880  	}
  2881  
  2882  	cnrIDs, err := cp.containerList(ctx, prm)
  2883  	if err != nil {
  2884  		return []cid.ID{}, fmt.Errorf("list containers via client '%s': %w", cp.address(), err)
  2885  	}
  2886  
  2887  	return cnrIDs, nil
  2888  }
  2889  
  2890  // DeleteContainer sends request to remove the FrostFS container and waits for the operation to complete.
  2891  //
  2892  // Waiting parameters can be specified using SetWaitParams. If not called, defaults are used:
  2893  //
  2894  //	polling interval: 5s
  2895  //	waiting timeout: 120s
  2896  //
  2897  // Success can be verified by reading by identifier (see GetContainer).
  2898  func (p *Pool) DeleteContainer(ctx context.Context, prm PrmContainerDelete) error {
  2899  	cp, err := p.connection()
  2900  	if err != nil {
  2901  		return err
  2902  	}
  2903  
  2904  	err = cp.containerDelete(ctx, prm)
  2905  	if err != nil {
  2906  		return fmt.Errorf("delete container via client '%s': %w", cp.address(), err)
  2907  	}
  2908  
  2909  	return nil
  2910  }
  2911  
  2912  // AddAPEChain sends a request to set APE chain rules for a target (basically, for a container).
  2913  func (p *Pool) AddAPEChain(ctx context.Context, prm PrmAddAPEChain) error {
  2914  	cp, err := p.connection()
  2915  	if err != nil {
  2916  		return err
  2917  	}
  2918  
  2919  	err = cp.apeManagerAddChain(ctx, prm)
  2920  	if err != nil {
  2921  		return fmt.Errorf("add ape chain via client '%s': %w", cp.address(), err)
  2922  	}
  2923  
  2924  	return nil
  2925  }
  2926  
  2927  // RemoveAPEChain sends a request to remove APE chain rules for a target.
  2928  func (p *Pool) RemoveAPEChain(ctx context.Context, prm PrmRemoveAPEChain) error {
  2929  	cp, err := p.connection()
  2930  	if err != nil {
  2931  		return err
  2932  	}
  2933  
  2934  	err = cp.apeManagerRemoveChain(ctx, prm)
  2935  	if err != nil {
  2936  		return fmt.Errorf("remove ape chain via client '%s': %w", cp.address(), err)
  2937  	}
  2938  
  2939  	return nil
  2940  }
  2941  
  2942  // ListAPEChains sends a request to list APE chains rules for a target.
  2943  func (p *Pool) ListAPEChains(ctx context.Context, prm PrmListAPEChains) ([]ape.Chain, error) {
  2944  	cp, err := p.connection()
  2945  	if err != nil {
  2946  		return nil, err
  2947  	}
  2948  
  2949  	chains, err := cp.apeManagerListChains(ctx, prm)
  2950  	if err != nil {
  2951  		return nil, fmt.Errorf("list ape chains via client '%s': %w", cp.address(), err)
  2952  	}
  2953  
  2954  	return chains, nil
  2955  }
  2956  
  2957  // Balance requests current balance of the FrostFS account.
  2958  //
  2959  // Main return value MUST NOT be processed on an erroneous return.
  2960  func (p *Pool) Balance(ctx context.Context, prm PrmBalanceGet) (accounting.Decimal, error) {
  2961  	cp, err := p.connection()
  2962  	if err != nil {
  2963  		return accounting.Decimal{}, err
  2964  	}
  2965  
  2966  	balance, err := cp.balanceGet(ctx, prm)
  2967  	if err != nil {
  2968  		return accounting.Decimal{}, fmt.Errorf("get balance via client '%s': %w", cp.address(), err)
  2969  	}
  2970  
  2971  	return balance, nil
  2972  }
  2973  
  2974  // Statistic returns connection statistics.
  2975  func (p Pool) Statistic() Statistic {
  2976  	stat := Statistic{}
  2977  	for _, inner := range p.innerPools {
  2978  		nodes := make([]string, 0, len(inner.clients))
  2979  		inner.lock.RLock()
  2980  		for _, cl := range inner.clients {
  2981  			if cl.isHealthy() {
  2982  				nodes = append(nodes, cl.address())
  2983  			}
  2984  			node := NodeStatistic{
  2985  				address:       cl.address(),
  2986  				methods:       cl.methodsStatus(),
  2987  				overallErrors: cl.overallErrorRate(),
  2988  				currentErrors: cl.currentErrorRate(),
  2989  			}
  2990  			stat.nodes = append(stat.nodes, node)
  2991  			stat.overallErrors += node.overallErrors
  2992  		}
  2993  		inner.lock.RUnlock()
  2994  		if len(stat.currentNodes) == 0 {
  2995  			stat.currentNodes = nodes
  2996  		}
  2997  	}
  2998  
  2999  	return stat
  3000  }
  3001  
  3002  // waitForContainerPresence waits until the container is found on the FrostFS network.
  3003  func waitForContainerPresence(ctx context.Context, cli client, prm PrmContainerGet, waitParams *WaitParams) error {
  3004  	return waitFor(ctx, waitParams, func(ctx context.Context) bool {
  3005  		_, err := cli.containerGet(ctx, prm)
  3006  		return err == nil
  3007  	})
  3008  }
  3009  
  3010  // waitForContainerRemoved waits until the container is removed from the FrostFS network.
  3011  func waitForContainerRemoved(ctx context.Context, cli client, prm PrmContainerGet, waitParams *WaitParams) error {
  3012  	return waitFor(ctx, waitParams, func(ctx context.Context) bool {
  3013  		_, err := cli.containerGet(ctx, prm)
  3014  		return sdkClient.IsErrContainerNotFound(err)
  3015  	})
  3016  }
  3017  
  3018  // waitFor await that given condition will be met in waitParams time.
  3019  func waitFor(ctx context.Context, params *WaitParams, condition func(context.Context) bool) error {
  3020  	wctx, cancel := context.WithTimeout(ctx, params.Timeout)
  3021  	defer cancel()
  3022  	ticker := time.NewTimer(params.PollInterval)
  3023  	defer ticker.Stop()
  3024  	wdone := wctx.Done()
  3025  	done := ctx.Done()
  3026  	for {
  3027  		select {
  3028  		case <-done:
  3029  			return ctx.Err()
  3030  		case <-wdone:
  3031  			return wctx.Err()
  3032  		case <-ticker.C:
  3033  			if condition(ctx) {
  3034  				return nil
  3035  			}
  3036  			ticker.Reset(params.PollInterval)
  3037  		}
  3038  	}
  3039  }
  3040  
  3041  // NetworkInfo requests information about the FrostFS network of which the remote server is a part.
  3042  //
  3043  // Main return value MUST NOT be processed on an erroneous return.
  3044  func (p *Pool) NetworkInfo(ctx context.Context) (netmap.NetworkInfo, error) {
  3045  	cp, err := p.connection()
  3046  	if err != nil {
  3047  		return netmap.NetworkInfo{}, err
  3048  	}
  3049  
  3050  	netInfo, err := cp.networkInfo(ctx, prmNetworkInfo{})
  3051  	if err != nil {
  3052  		return netmap.NetworkInfo{}, fmt.Errorf("get network info via client '%s': %w", cp.address(), err)
  3053  	}
  3054  
  3055  	return netInfo, nil
  3056  }
  3057  
  3058  // NetMapSnapshot requests information about the FrostFS network map.
  3059  //
  3060  // Main return value MUST NOT be processed on an erroneous return.
  3061  func (p *Pool) NetMapSnapshot(ctx context.Context) (netmap.NetMap, error) {
  3062  	cp, err := p.connection()
  3063  	if err != nil {
  3064  		return netmap.NetMap{}, err
  3065  	}
  3066  
  3067  	netMap, err := cp.netMapSnapshot(ctx, prmNetMapSnapshot{})
  3068  	if err != nil {
  3069  		return netmap.NetMap{}, fmt.Errorf("get network map via client '%s': %w", cp.address(), err)
  3070  	}
  3071  
  3072  	return netMap, nil
  3073  }
  3074  
  3075  // Close closes the Pool and releases all the associated resources.
  3076  func (p *Pool) Close() {
  3077  	p.cancel()
  3078  	<-p.closedCh
  3079  
  3080  	// close all clients
  3081  	for _, pools := range p.innerPools {
  3082  		for _, cli := range pools.clients {
  3083  			_ = cli.close()
  3084  		}
  3085  	}
  3086  }
  3087  
  3088  // SyncContainerWithNetwork applies network configuration received via
  3089  // the Pool to the container. Changes the container if it does not satisfy
  3090  // network configuration.
  3091  //
  3092  // Pool and container MUST not be nil.
  3093  //
  3094  // Returns any error that does not allow reading configuration
  3095  // from the network.
  3096  func SyncContainerWithNetwork(ctx context.Context, cnr *container.Container, p *Pool) error {
  3097  	ni, err := p.NetworkInfo(ctx)
  3098  	if err != nil {
  3099  		return fmt.Errorf("network info: %w", err)
  3100  	}
  3101  
  3102  	container.ApplyNetworkConfig(cnr, ni)
  3103  
  3104  	return nil
  3105  }
  3106  
  3107  // GetSplitInfo implements relations.Relations.
  3108  func (p *Pool) GetSplitInfo(ctx context.Context, cnrID cid.ID, objID oid.ID, tokens relations.Tokens) (*object.SplitInfo, error) {
  3109  	var addr oid.Address
  3110  	addr.SetContainer(cnrID)
  3111  	addr.SetObject(objID)
  3112  
  3113  	var prm PrmObjectHead
  3114  	prm.SetAddress(addr)
  3115  	if tokens.Bearer != nil {
  3116  		prm.UseBearer(*tokens.Bearer)
  3117  	}
  3118  	if tokens.Session != nil {
  3119  		prm.UseSession(*tokens.Session)
  3120  	}
  3121  	prm.MarkRaw()
  3122  
  3123  	_, err := p.HeadObject(ctx, prm)
  3124  
  3125  	var errSplit *object.SplitInfoError
  3126  
  3127  	switch {
  3128  	case errors.As(err, &errSplit):
  3129  		return errSplit.SplitInfo(), nil
  3130  	case err == nil:
  3131  		return nil, relations.ErrNoSplitInfo
  3132  	default:
  3133  		return nil, fmt.Errorf("failed to get raw object header: %w", err)
  3134  	}
  3135  }
  3136  
  3137  // ListChildrenByLinker implements relations.Relations.
  3138  func (p *Pool) ListChildrenByLinker(ctx context.Context, cnrID cid.ID, objID oid.ID, tokens relations.Tokens) ([]oid.ID, error) {
  3139  	var addr oid.Address
  3140  	addr.SetContainer(cnrID)
  3141  	addr.SetObject(objID)
  3142  
  3143  	var prm PrmObjectHead
  3144  	prm.SetAddress(addr)
  3145  	if tokens.Bearer != nil {
  3146  		prm.UseBearer(*tokens.Bearer)
  3147  	}
  3148  	if tokens.Session != nil {
  3149  		prm.UseSession(*tokens.Session)
  3150  	}
  3151  
  3152  	res, err := p.HeadObject(ctx, prm)
  3153  	if err != nil {
  3154  		return nil, fmt.Errorf("failed to get linking object's header: %w", err)
  3155  	}
  3156  
  3157  	return res.Children(), nil
  3158  }
  3159  
  3160  // GetLeftSibling implements relations.Relations.
  3161  func (p *Pool) GetLeftSibling(ctx context.Context, cnrID cid.ID, objID oid.ID, tokens relations.Tokens) (oid.ID, error) {
  3162  	var addr oid.Address
  3163  	addr.SetContainer(cnrID)
  3164  	addr.SetObject(objID)
  3165  
  3166  	var prm PrmObjectHead
  3167  	prm.SetAddress(addr)
  3168  	if tokens.Bearer != nil {
  3169  		prm.UseBearer(*tokens.Bearer)
  3170  	}
  3171  	if tokens.Session != nil {
  3172  		prm.UseSession(*tokens.Session)
  3173  	}
  3174  
  3175  	res, err := p.HeadObject(ctx, prm)
  3176  	if err != nil {
  3177  		return oid.ID{}, fmt.Errorf("failed to read split chain member's header: %w", err)
  3178  	}
  3179  
  3180  	idMember, ok := res.PreviousID()
  3181  	if !ok {
  3182  		return oid.ID{}, relations.ErrNoLeftSibling
  3183  	}
  3184  	return idMember, nil
  3185  }
  3186  
  3187  // FindSiblingBySplitID implements relations.Relations.
  3188  func (p *Pool) FindSiblingBySplitID(ctx context.Context, cnrID cid.ID, splitID *object.SplitID, tokens relations.Tokens) ([]oid.ID, error) {
  3189  	var query object.SearchFilters
  3190  	query.AddSplitIDFilter(object.MatchStringEqual, splitID)
  3191  
  3192  	var prm PrmObjectSearch
  3193  	prm.SetContainerID(cnrID)
  3194  	prm.SetFilters(query)
  3195  	if tokens.Bearer != nil {
  3196  		prm.UseBearer(*tokens.Bearer)
  3197  	}
  3198  	if tokens.Session != nil {
  3199  		prm.UseSession(*tokens.Session)
  3200  	}
  3201  
  3202  	res, err := p.SearchObjects(ctx, prm)
  3203  	if err != nil {
  3204  		return nil, fmt.Errorf("failed to search objects by split ID: %w", err)
  3205  	}
  3206  
  3207  	var members []oid.ID
  3208  	err = res.Iterate(func(id oid.ID) bool {
  3209  		members = append(members, id)
  3210  		return false
  3211  	})
  3212  	if err != nil {
  3213  		return nil, fmt.Errorf("failed to iterate found objects: %w", err)
  3214  	}
  3215  
  3216  	return members, nil
  3217  }
  3218  
  3219  // FindSiblingByParentID implements relations.Relations.
  3220  func (p *Pool) FindSiblingByParentID(ctx context.Context, cnrID cid.ID, objID oid.ID, tokens relations.Tokens) ([]oid.ID, error) {
  3221  	var query object.SearchFilters
  3222  	query.AddParentIDFilter(object.MatchStringEqual, objID)
  3223  
  3224  	var prm PrmObjectSearch
  3225  	prm.SetContainerID(cnrID)
  3226  	prm.SetFilters(query)
  3227  	if tokens.Bearer != nil {
  3228  		prm.UseBearer(*tokens.Bearer)
  3229  	}
  3230  	if tokens.Session != nil {
  3231  		prm.UseSession(*tokens.Session)
  3232  	}
  3233  
  3234  	resSearch, err := p.SearchObjects(ctx, prm)
  3235  	if err != nil {
  3236  		return nil, fmt.Errorf("failed to find object children: %w", err)
  3237  	}
  3238  
  3239  	var res []oid.ID
  3240  	err = resSearch.Iterate(func(id oid.ID) bool {
  3241  		res = append(res, id)
  3242  		return false
  3243  	})
  3244  	if err != nil {
  3245  		return nil, fmt.Errorf("failed to iterate found objects: %w", err)
  3246  	}
  3247  
  3248  	return res, nil
  3249  }