forked from TrueCloudLab/frostfs-http-gw
Put artifacts into pool solely
Signed-off-by: Pavel Korotkov <pavel@nspcc.ru>
This commit is contained in:
parent
d7617110b7
commit
fad05b76d4
5 changed files with 78 additions and 70 deletions
2
app.go
2
app.go
|
@ -2,6 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/fasthttp/router"
|
"github.com/fasthttp/router"
|
||||||
|
@ -104,6 +105,7 @@ func newApp(ctx context.Context, opt ...Option) App {
|
||||||
NodeConnectionTimeout: a.cfg.GetDuration(cfgConTimeout),
|
NodeConnectionTimeout: a.cfg.GetDuration(cfgConTimeout),
|
||||||
NodeRequestTimeout: a.cfg.GetDuration(cfgReqTimeout),
|
NodeRequestTimeout: a.cfg.GetDuration(cfgReqTimeout),
|
||||||
ClientRebalanceInterval: a.cfg.GetDuration(cfgRebalance),
|
ClientRebalanceInterval: a.cfg.GetDuration(cfgRebalance),
|
||||||
|
SessionExpirationEpoch: math.MaxUint64,
|
||||||
}
|
}
|
||||||
pool, err := pb.Build(ctx, opts)
|
pool, err := pb.Build(ctx, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -3,13 +3,14 @@ package connections
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"errors"
|
|
||||||
"math"
|
"math"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/pkg/client"
|
"github.com/nspcc-dev/neofs-api-go/pkg/client"
|
||||||
|
"github.com/nspcc-dev/neofs-api-go/pkg/token"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -18,6 +19,7 @@ type PoolBuilderOptions struct {
|
||||||
NodeConnectionTimeout time.Duration
|
NodeConnectionTimeout time.Duration
|
||||||
NodeRequestTimeout time.Duration
|
NodeRequestTimeout time.Duration
|
||||||
ClientRebalanceInterval time.Duration
|
ClientRebalanceInterval time.Duration
|
||||||
|
SessionExpirationEpoch uint64
|
||||||
weights []float64
|
weights []float64
|
||||||
connections []*grpc.ClientConn
|
connections []*grpc.ClientConn
|
||||||
}
|
}
|
||||||
|
@ -59,47 +61,54 @@ func (pb *PoolBuilder) Build(ctx context.Context, options *PoolBuilderOptions) (
|
||||||
}
|
}
|
||||||
|
|
||||||
type Pool interface {
|
type Pool interface {
|
||||||
Client() client.Client
|
ConnectionArtifacts() (client.Client, *token.SessionToken, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type clientPack struct {
|
||||||
|
client client.Client
|
||||||
|
sessionToken *token.SessionToken
|
||||||
|
healthy bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type pool struct {
|
type pool struct {
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
sampler *Sampler
|
sampler *Sampler
|
||||||
clients []client.Client
|
clientPacks []*clientPack
|
||||||
healthy []bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func new(ctx context.Context, options *PoolBuilderOptions) (Pool, error) {
|
func new(ctx context.Context, options *PoolBuilderOptions) (Pool, error) {
|
||||||
n := len(options.weights)
|
clientPacks := make([]*clientPack, len(options.weights))
|
||||||
clients := make([]client.Client, n)
|
|
||||||
healthy := make([]bool, n)
|
|
||||||
for i, con := range options.connections {
|
for i, con := range options.connections {
|
||||||
c, err := client.New(client.WithDefaultPrivateKey(options.Key), client.WithGRPCConnection(con))
|
c, err := client.New(client.WithDefaultPrivateKey(options.Key), client.WithGRPCConnection(con))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
clients[i] = c
|
st, err := c.CreateSession(ctx, options.SessionExpirationEpoch)
|
||||||
healthy[i] = true
|
if err != nil {
|
||||||
|
address := "unknown"
|
||||||
|
if epi, err := c.EndpointInfo(ctx); err == nil {
|
||||||
|
address = epi.NodeInfo().Address()
|
||||||
|
}
|
||||||
|
return nil, errors.Wrapf(err, "failed to create neofs session token for client %s", address)
|
||||||
|
}
|
||||||
|
clientPacks[i] = &clientPack{client: c, sessionToken: st, healthy: true}
|
||||||
}
|
}
|
||||||
source := rand.NewSource(time.Now().UnixNano())
|
source := rand.NewSource(time.Now().UnixNano())
|
||||||
pool := &pool{
|
sampler := NewSampler(options.weights, source)
|
||||||
sampler: NewSampler(options.weights, source),
|
pool := &pool{sampler: sampler, clientPacks: clientPacks}
|
||||||
clients: clients,
|
|
||||||
healthy: healthy,
|
|
||||||
}
|
|
||||||
go func() {
|
go func() {
|
||||||
ticker := time.NewTimer(options.ClientRebalanceInterval)
|
ticker := time.NewTimer(options.ClientRebalanceInterval)
|
||||||
for range ticker.C {
|
for range ticker.C {
|
||||||
ok := true
|
ok := true
|
||||||
for i, client := range pool.clients {
|
for i, clientPack := range pool.clientPacks {
|
||||||
func() {
|
func() {
|
||||||
tctx, c := context.WithTimeout(ctx, options.NodeRequestTimeout)
|
tctx, c := context.WithTimeout(ctx, options.NodeRequestTimeout)
|
||||||
defer c()
|
defer c()
|
||||||
if _, err := client.EndpointInfo(tctx); err != nil {
|
if _, err := clientPack.client.EndpointInfo(tctx); err != nil {
|
||||||
ok = false
|
ok = false
|
||||||
}
|
}
|
||||||
pool.lock.Lock()
|
pool.lock.Lock()
|
||||||
pool.healthy[i] = ok
|
pool.clientPacks[i].healthy = ok
|
||||||
pool.lock.Unlock()
|
pool.lock.Unlock()
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
@ -109,24 +118,26 @@ func new(ctx context.Context, options *PoolBuilderOptions) (Pool, error) {
|
||||||
return pool, nil
|
return pool, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *pool) Client() client.Client {
|
func (p *pool) ConnectionArtifacts() (client.Client, *token.SessionToken, error) {
|
||||||
p.lock.RLock()
|
p.lock.RLock()
|
||||||
defer p.lock.RUnlock()
|
defer p.lock.RUnlock()
|
||||||
if len(p.clients) == 1 {
|
if len(p.clientPacks) == 1 {
|
||||||
if p.healthy[0] {
|
cp := p.clientPacks[0]
|
||||||
return p.clients[0]
|
if cp.healthy {
|
||||||
|
return cp.client, cp.sessionToken, nil
|
||||||
}
|
}
|
||||||
return nil
|
return nil, nil, errors.New("no healthy client")
|
||||||
}
|
}
|
||||||
var i *int = nil
|
var i *int = nil
|
||||||
for k := 0; k < 10; k++ {
|
for k := 0; k < 10; k++ {
|
||||||
i_ := p.sampler.Next()
|
i_ := p.sampler.Next()
|
||||||
if p.healthy[i_] {
|
if p.clientPacks[i_].healthy {
|
||||||
i = &i_
|
i = &i_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if i != nil {
|
if i != nil {
|
||||||
return p.clients[*i]
|
cp := p.clientPacks[*i]
|
||||||
|
return cp.client, cp.sessionToken, nil
|
||||||
}
|
}
|
||||||
return nil
|
return nil, nil, errors.New("no healthy client")
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,10 +10,8 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/pkg/client"
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/pkg/container"
|
"github.com/nspcc-dev/neofs-api-go/pkg/container"
|
||||||
"github.com/nspcc-dev/neofs-api-go/pkg/object"
|
"github.com/nspcc-dev/neofs-api-go/pkg/object"
|
||||||
"github.com/nspcc-dev/neofs-api-go/pkg/token"
|
|
||||||
"github.com/nspcc-dev/neofs-http-gate/neofs"
|
"github.com/nspcc-dev/neofs-http-gate/neofs"
|
||||||
"github.com/nspcc-dev/neofs-http-gate/tokens"
|
"github.com/nspcc-dev/neofs-http-gate/tokens"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
@ -138,40 +136,35 @@ func (o objectIDs) Slice() []string {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Downloader struct {
|
type Downloader struct {
|
||||||
log *zap.Logger
|
log *zap.Logger
|
||||||
plant neofs.ClientPlant
|
plant neofs.ClientPlant
|
||||||
getOperations struct {
|
|
||||||
client client.Client
|
|
||||||
sessionToken *token.SessionToken
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(ctx context.Context, log *zap.Logger, plant neofs.ClientPlant) (*Downloader, error) {
|
func New(ctx context.Context, log *zap.Logger, plant neofs.ClientPlant) (*Downloader, error) {
|
||||||
var err error
|
var err error
|
||||||
d := &Downloader{log: log, plant: plant}
|
d := &Downloader{log: log, plant: plant}
|
||||||
d.getOperations.client, d.getOperations.sessionToken, err = d.plant.GetReusableArtifacts(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to get neofs client's reusable artifacts")
|
return nil, errors.Wrap(err, "failed to get neofs client's reusable artifacts")
|
||||||
}
|
}
|
||||||
return d, nil
|
return d, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Downloader) newRequest(ctx *fasthttp.RequestCtx, log *zap.Logger) *request {
|
func (d *Downloader) newRequest(ctx *fasthttp.RequestCtx, log *zap.Logger) *request {
|
||||||
return &request{
|
return &request{
|
||||||
RequestCtx: ctx,
|
RequestCtx: ctx,
|
||||||
log: log,
|
log: log,
|
||||||
objectClient: a.plant.Object(),
|
objectClient: d.plant.Object(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Downloader) DownloadByAddress(c *fasthttp.RequestCtx) {
|
func (d *Downloader) DownloadByAddress(c *fasthttp.RequestCtx) {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
address = object.NewAddress()
|
address = object.NewAddress()
|
||||||
cid, _ = c.UserValue("cid").(string)
|
cid, _ = c.UserValue("cid").(string)
|
||||||
oid, _ = c.UserValue("oid").(string)
|
oid, _ = c.UserValue("oid").(string)
|
||||||
val = strings.Join([]string{cid, oid}, "/")
|
val = strings.Join([]string{cid, oid}, "/")
|
||||||
log = a.log.With(zap.String("cid", cid), zap.String("oid", oid))
|
log = d.log.With(zap.String("cid", cid), zap.String("oid", oid))
|
||||||
)
|
)
|
||||||
if err = address.Parse(val); err != nil {
|
if err = address.Parse(val); err != nil {
|
||||||
log.Error("wrong object address", zap.Error(err))
|
log.Error("wrong object address", zap.Error(err))
|
||||||
|
@ -180,20 +173,24 @@ func (a *Downloader) DownloadByAddress(c *fasthttp.RequestCtx) {
|
||||||
}
|
}
|
||||||
getOpts := getOptionsPool.Get().(*neofs.GetOptions)
|
getOpts := getOptionsPool.Get().(*neofs.GetOptions)
|
||||||
defer getOptionsPool.Put(getOpts)
|
defer getOptionsPool.Put(getOpts)
|
||||||
getOpts.Client = a.getOperations.client
|
getOpts.Client, getOpts.SessionToken, err = d.plant.ConnectionArtifacts()
|
||||||
getOpts.SessionToken = a.getOperations.sessionToken
|
if err != nil {
|
||||||
|
log.Error("failed to get neofs connection artifacts", zap.Error(err))
|
||||||
|
c.Error("failed to get neofs connection artifacts", fasthttp.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
getOpts.ObjectAddress = address
|
getOpts.ObjectAddress = address
|
||||||
getOpts.Writer = nil
|
getOpts.Writer = nil
|
||||||
a.newRequest(c, log).receiveFile(getOpts)
|
d.newRequest(c, log).receiveFile(getOpts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Downloader) DownloadByAttribute(c *fasthttp.RequestCtx) {
|
func (d *Downloader) DownloadByAttribute(c *fasthttp.RequestCtx) {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
scid, _ = c.UserValue("cid").(string)
|
scid, _ = c.UserValue("cid").(string)
|
||||||
key, _ = c.UserValue("attr_key").(string)
|
key, _ = c.UserValue("attr_key").(string)
|
||||||
val, _ = c.UserValue("attr_val").(string)
|
val, _ = c.UserValue("attr_val").(string)
|
||||||
log = a.log.With(zap.String("cid", scid), zap.String("attr_key", key), zap.String("attr_val", val))
|
log = d.log.With(zap.String("cid", scid), zap.String("attr_key", key), zap.String("attr_val", val))
|
||||||
)
|
)
|
||||||
cid := container.NewID()
|
cid := container.NewID()
|
||||||
if err = cid.Parse(scid); err != nil {
|
if err = cid.Parse(scid); err != nil {
|
||||||
|
@ -203,14 +200,18 @@ func (a *Downloader) DownloadByAttribute(c *fasthttp.RequestCtx) {
|
||||||
}
|
}
|
||||||
searchOpts := searchOptionsPool.Get().(*neofs.SearchOptions)
|
searchOpts := searchOptionsPool.Get().(*neofs.SearchOptions)
|
||||||
defer searchOptionsPool.Put(searchOpts)
|
defer searchOptionsPool.Put(searchOpts)
|
||||||
searchOpts.Client = a.getOperations.client
|
searchOpts.Client, searchOpts.SessionToken, err = d.plant.ConnectionArtifacts()
|
||||||
searchOpts.SessionToken = a.getOperations.sessionToken
|
if err != nil {
|
||||||
|
log.Error("failed to get neofs connection artifacts", zap.Error(err))
|
||||||
|
c.Error("failed to get neofs connection artifacts", fasthttp.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
searchOpts.BearerToken = nil
|
searchOpts.BearerToken = nil
|
||||||
searchOpts.ContainerID = cid
|
searchOpts.ContainerID = cid
|
||||||
searchOpts.Attribute.Key = key
|
searchOpts.Attribute.Key = key
|
||||||
searchOpts.Attribute.Value = val
|
searchOpts.Attribute.Value = val
|
||||||
var ids []*object.ID
|
var ids []*object.ID
|
||||||
if ids, err = a.plant.Object().Search(c, searchOpts); err != nil {
|
if ids, err = d.plant.Object().Search(c, searchOpts); err != nil {
|
||||||
log.Error("something went wrong", zap.Error(err))
|
log.Error("something went wrong", zap.Error(err))
|
||||||
c.Error("something went wrong", fasthttp.StatusBadRequest)
|
c.Error("something went wrong", fasthttp.StatusBadRequest)
|
||||||
return
|
return
|
||||||
|
@ -229,9 +230,13 @@ func (a *Downloader) DownloadByAttribute(c *fasthttp.RequestCtx) {
|
||||||
address.SetObjectID(ids[0])
|
address.SetObjectID(ids[0])
|
||||||
getOpts := getOptionsPool.Get().(*neofs.GetOptions)
|
getOpts := getOptionsPool.Get().(*neofs.GetOptions)
|
||||||
defer getOptionsPool.Put(getOpts)
|
defer getOptionsPool.Put(getOpts)
|
||||||
getOpts.Client = a.getOperations.client
|
getOpts.Client, getOpts.SessionToken, err = d.plant.ConnectionArtifacts()
|
||||||
getOpts.SessionToken = a.getOperations.sessionToken
|
if err != nil {
|
||||||
|
log.Error("failed to get neofs connection artifacts", zap.Error(err))
|
||||||
|
c.Error("failed to get neofs connection artifacts", fasthttp.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
getOpts.ObjectAddress = address
|
getOpts.ObjectAddress = address
|
||||||
getOpts.Writer = nil
|
getOpts.Writer = nil
|
||||||
a.newRequest(c, log).receiveFile(getOpts)
|
d.newRequest(c, log).receiveFile(getOpts)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/pkg/client"
|
"github.com/nspcc-dev/neofs-api-go/pkg/client"
|
||||||
"github.com/nspcc-dev/neofs-api-go/pkg/container"
|
"github.com/nspcc-dev/neofs-api-go/pkg/container"
|
||||||
|
@ -15,7 +14,6 @@ import (
|
||||||
"github.com/nspcc-dev/neofs-http-gate/connections"
|
"github.com/nspcc-dev/neofs-http-gate/connections"
|
||||||
objectCore "github.com/nspcc-dev/neofs-node/pkg/core/object"
|
objectCore "github.com/nspcc-dev/neofs-node/pkg/core/object"
|
||||||
"github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transformer"
|
"github.com/nspcc-dev/neofs-node/pkg/services/object_manager/transformer"
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const maxObjectSize = uint64(1 << 28) // Limit objects to 256 MiB.
|
const maxObjectSize = uint64(1 << 28) // Limit objects to 256 MiB.
|
||||||
|
@ -62,7 +60,7 @@ type ObjectClient interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClientPlant interface {
|
type ClientPlant interface {
|
||||||
GetReusableArtifacts(ctx context.Context) (client.Client, *token.SessionToken, error)
|
ConnectionArtifacts() (client.Client, *token.SessionToken, error)
|
||||||
Object() ObjectClient
|
Object() ObjectClient
|
||||||
OwnerID() *owner.ID
|
OwnerID() *owner.ID
|
||||||
}
|
}
|
||||||
|
@ -78,16 +76,8 @@ type neofsClientPlant struct {
|
||||||
pool connections.Pool
|
pool connections.Pool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cp *neofsClientPlant) GetReusableArtifacts(ctx context.Context) (client.Client, *token.SessionToken, error) {
|
func (cp *neofsClientPlant) ConnectionArtifacts() (client.Client, *token.SessionToken, error) {
|
||||||
c := cp.pool.Client()
|
return cp.pool.ConnectionArtifacts()
|
||||||
if c == nil {
|
|
||||||
return nil, nil, errors.New("failed to peek a healthy node to connect to")
|
|
||||||
}
|
|
||||||
st, err := c.CreateSession(ctx, math.MaxUint64)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, errors.Wrap(err, "failed to create reusable neofs session token")
|
|
||||||
}
|
|
||||||
return c, st, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cc *neofsClientPlant) Object() ObjectClient {
|
func (cc *neofsClientPlant) Object() ObjectClient {
|
||||||
|
|
|
@ -56,7 +56,7 @@ func (u *Uploader) Upload(c *fasthttp.RequestCtx) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
// if temporary reader can be closed - close it
|
// If the temporary reader can be closed - let's close it.
|
||||||
if file == nil {
|
if file == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -106,10 +106,10 @@ func (u *Uploader) Upload(c *fasthttp.RequestCtx) {
|
||||||
putOpts := putOptionsPool.Get().(*neofs.PutOptions)
|
putOpts := putOptionsPool.Get().(*neofs.PutOptions)
|
||||||
defer putOptionsPool.Put(putOpts)
|
defer putOptionsPool.Put(putOpts)
|
||||||
// Try to put file into NeoFS or throw an error.
|
// Try to put file into NeoFS or throw an error.
|
||||||
putOpts.Client, putOpts.SessionToken, err = u.plant.GetReusableArtifacts(c)
|
putOpts.Client, putOpts.SessionToken, err = u.plant.ConnectionArtifacts()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("failed to get neofs client's reusable artifacts", zap.Error(err))
|
log.Error("failed to get neofs connection artifacts", zap.Error(err))
|
||||||
c.Error("failed to get neofs client's reusable artifacts", fasthttp.StatusInternalServerError)
|
c.Error("failed to get neofs connection artifacts", fasthttp.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
putOpts.BearerToken = bt
|
putOpts.BearerToken = bt
|
||||||
|
@ -118,8 +118,8 @@ func (u *Uploader) Upload(c *fasthttp.RequestCtx) {
|
||||||
putOpts.PrepareObjectOnsite = false
|
putOpts.PrepareObjectOnsite = false
|
||||||
putOpts.Reader = file
|
putOpts.Reader = file
|
||||||
if addr, err = u.plant.Object().Put(c, putOpts); err != nil {
|
if addr, err = u.plant.Object().Put(c, putOpts); err != nil {
|
||||||
log.Error("could not store file in NeoFS", zap.Error(err))
|
log.Error("could not store file in neofs", zap.Error(err))
|
||||||
c.Error("could not store file in NeoFS", fasthttp.StatusBadRequest)
|
c.Error("could not store file in neofs", fasthttp.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Try to return the response, otherwise, if something went wrong, throw an error.
|
// Try to return the response, otherwise, if something went wrong, throw an error.
|
||||||
|
|
Loading…
Reference in a new issue