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 }