[#30] add object name resolving
Signed-off-by: Pavel Pogodaev <p.pogodaev@yadro.com>
This commit is contained in:
parent
37dbb29535
commit
8c3c3782f5
17 changed files with 507 additions and 42 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -3,6 +3,7 @@ bin
|
||||||
temp
|
temp
|
||||||
/plugins/
|
/plugins/
|
||||||
/vendor/
|
/vendor/
|
||||||
|
internal/frostfs/services/tree
|
||||||
|
|
||||||
.test.env
|
.test.env
|
||||||
*~
|
*~
|
||||||
|
|
9
Makefile
9
Makefile
|
@ -15,6 +15,7 @@ METRICS_DUMP_OUT ?= ./metrics-dump.json
|
||||||
BINDIR = bin
|
BINDIR = bin
|
||||||
DIRS = $(BINDIR)
|
DIRS = $(BINDIR)
|
||||||
BINS = $(BINDIR)/frostfs-http-gw
|
BINS = $(BINDIR)/frostfs-http-gw
|
||||||
|
SYNCDIR = internal/frostfs/services/tree
|
||||||
|
|
||||||
.PHONY: all $(BINS) $(DIRS) dep docker/ test cover fmt image image-push dirty-image lint docker/lint pre-commit unpre-commit version clean
|
.PHONY: all $(BINS) $(DIRS) dep docker/ test cover fmt image image-push dirty-image lint docker/lint pre-commit unpre-commit version clean
|
||||||
|
|
||||||
|
@ -27,8 +28,7 @@ PKG_VERSION ?= $(shell echo $(VERSION) | sed "s/^v//" | \
|
||||||
|
|
||||||
# Make all binaries
|
# Make all binaries
|
||||||
all: $(BINS)
|
all: $(BINS)
|
||||||
|
$(BINS): sync-tree $(DIRS) dep
|
||||||
$(BINS): $(DIRS) dep
|
|
||||||
@echo "⇒ Build $@"
|
@echo "⇒ Build $@"
|
||||||
CGO_ENABLED=0 \
|
CGO_ENABLED=0 \
|
||||||
go build -v -trimpath \
|
go build -v -trimpath \
|
||||||
|
@ -39,6 +39,10 @@ $(DIRS):
|
||||||
@echo "⇒ Ensure dir: $@"
|
@echo "⇒ Ensure dir: $@"
|
||||||
@mkdir -p $@
|
@mkdir -p $@
|
||||||
|
|
||||||
|
# Synchronize tree service
|
||||||
|
sync-tree:
|
||||||
|
@./syncTree.sh
|
||||||
|
|
||||||
# Pull go dependencies
|
# Pull go dependencies
|
||||||
dep:
|
dep:
|
||||||
@printf "⇒ Download requirements: "
|
@printf "⇒ Download requirements: "
|
||||||
|
@ -132,6 +136,7 @@ version:
|
||||||
clean:
|
clean:
|
||||||
rm -rf vendor
|
rm -rf vendor
|
||||||
rm -rf $(BINDIR)
|
rm -rf $(BINDIR)
|
||||||
|
rm -rf $(SYNCDIR)
|
||||||
|
|
||||||
# Package for Debian
|
# Package for Debian
|
||||||
debpackage:
|
debpackage:
|
||||||
|
|
22
api/layer/tree_service.go
Normal file
22
api/layer/tree_service.go
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package layer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/api"
|
||||||
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TreeService provide interface to interact with tree service using s3 data models.
|
||||||
|
type TreeService interface {
|
||||||
|
GetLatestVersion(ctx context.Context, cnrID *cid.ID, objectName string) (*api.NodeVersion, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrNodeNotFound is returned from Tree service in case of not found error.
|
||||||
|
ErrNodeNotFound = errors.New("not found")
|
||||||
|
|
||||||
|
// ErrNodeAccessDenied is returned from Tree service in case of access denied error.
|
||||||
|
ErrNodeAccessDenied = errors.New("access denied")
|
||||||
|
)
|
17
api/tree.go
Normal file
17
api/tree.go
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NodeVersion represent node from tree service.
|
||||||
|
type NodeVersion struct {
|
||||||
|
BaseNodeVersion
|
||||||
|
DeleteMarker bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// BaseNodeVersion is minimal node info from tree service.
|
||||||
|
// Basically used for "system" object.
|
||||||
|
type BaseNodeVersion struct {
|
||||||
|
OID oid.ID
|
||||||
|
}
|
41
app.go
41
app.go
|
@ -2,7 +2,6 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/ecdsa"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
@ -14,9 +13,11 @@ import (
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/pkg/tracing"
|
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/pkg/tracing"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/downloader"
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/downloader"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/frostfs/services"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/metrics"
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/metrics"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/resolver"
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/resolver"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/response"
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/response"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/tree"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/uploader"
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/uploader"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/utils"
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/utils"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool"
|
||||||
|
@ -30,6 +31,8 @@ import (
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"github.com/valyala/fasthttp"
|
"github.com/valyala/fasthttp"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -37,6 +40,7 @@ type (
|
||||||
log *zap.Logger
|
log *zap.Logger
|
||||||
logLevel zap.AtomicLevel
|
logLevel zap.AtomicLevel
|
||||||
pool *pool.Pool
|
pool *pool.Pool
|
||||||
|
key *keys.PrivateKey
|
||||||
owner *user.ID
|
owner *user.ID
|
||||||
cfg *viper.Viper
|
cfg *viper.Viper
|
||||||
webServer *fasthttp.Server
|
webServer *fasthttp.Server
|
||||||
|
@ -93,7 +97,6 @@ func WithConfig(c *viper.Viper) Option {
|
||||||
|
|
||||||
func newApp(ctx context.Context, opt ...Option) App {
|
func newApp(ctx context.Context, opt ...Option) App {
|
||||||
var (
|
var (
|
||||||
key *ecdsa.PrivateKey
|
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -120,17 +123,17 @@ func newApp(ctx context.Context, opt ...Option) App {
|
||||||
a.webServer.DisablePreParseMultipartForm = true
|
a.webServer.DisablePreParseMultipartForm = true
|
||||||
a.webServer.StreamRequestBody = a.cfg.GetBool(cfgWebStreamRequestBody)
|
a.webServer.StreamRequestBody = a.cfg.GetBool(cfgWebStreamRequestBody)
|
||||||
// -- -- -- -- -- -- -- -- -- -- -- -- -- --
|
// -- -- -- -- -- -- -- -- -- -- -- -- -- --
|
||||||
key, err = getFrostFSKey(a)
|
a.key, err = getFrostFSKey(a)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.log.Fatal("failed to get frostfs credentials", zap.Error(err))
|
a.log.Fatal("failed to get frostfs credentials", zap.Error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
var owner user.ID
|
var owner user.ID
|
||||||
user.IDFromKey(&owner, key.PublicKey)
|
user.IDFromKey(&owner, a.key.PrivateKey.PublicKey)
|
||||||
a.owner = &owner
|
a.owner = &owner
|
||||||
|
|
||||||
var prm pool.InitParameters
|
var prm pool.InitParameters
|
||||||
prm.SetKey(key)
|
prm.SetKey(&a.key.PrivateKey)
|
||||||
prm.SetNodeDialTimeout(a.cfg.GetDuration(cfgConTimeout))
|
prm.SetNodeDialTimeout(a.cfg.GetDuration(cfgConTimeout))
|
||||||
prm.SetNodeStreamTimeout(a.cfg.GetDuration(cfgStreamTimeout))
|
prm.SetNodeStreamTimeout(a.cfg.GetDuration(cfgStreamTimeout))
|
||||||
prm.SetHealthcheckTimeout(a.cfg.GetDuration(cfgReqTimeout))
|
prm.SetHealthcheckTimeout(a.cfg.GetDuration(cfgReqTimeout))
|
||||||
|
@ -277,7 +280,7 @@ func remove(list []string, element string) []string {
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFrostFSKey(a *app) (*ecdsa.PrivateKey, error) {
|
func getFrostFSKey(a *app) (*keys.PrivateKey, error) {
|
||||||
walletPath := a.cfg.GetString(cfgWalletPath)
|
walletPath := a.cfg.GetString(cfgWalletPath)
|
||||||
|
|
||||||
if len(walletPath) == 0 {
|
if len(walletPath) == 0 {
|
||||||
|
@ -286,7 +289,7 @@ func getFrostFSKey(a *app) (*ecdsa.PrivateKey, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &key.PrivateKey, nil
|
return key, nil
|
||||||
}
|
}
|
||||||
w, err := wallet.NewWalletFromFile(walletPath)
|
w, err := wallet.NewWalletFromFile(walletPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -304,7 +307,7 @@ func getFrostFSKey(a *app) (*ecdsa.PrivateKey, error) {
|
||||||
return getKeyFromWallet(w, address, password)
|
return getKeyFromWallet(w, address, password)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getKeyFromWallet(w *wallet.Wallet, addrStr string, password *string) (*ecdsa.PrivateKey, error) {
|
func getKeyFromWallet(w *wallet.Wallet, addrStr string, password *string) (*keys.PrivateKey, error) {
|
||||||
var addr util.Uint160
|
var addr util.Uint160
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
@ -334,7 +337,7 @@ func getKeyFromWallet(w *wallet.Wallet, addrStr string, password *string) (*ecds
|
||||||
return nil, fmt.Errorf("couldn't decrypt account: %w", err)
|
return nil, fmt.Errorf("couldn't decrypt account: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &acc.PrivateKey().PrivateKey, nil
|
return acc.PrivateKey(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *app) Wait() {
|
func (a *app) Wait() {
|
||||||
|
@ -351,8 +354,9 @@ func (a *app) setHealthStatus() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *app) Serve(ctx context.Context) {
|
func (a *app) Serve(ctx context.Context) {
|
||||||
|
treeClient := a.initTree(ctx)
|
||||||
uploadRoutes := uploader.New(ctx, a.AppParams(), a.settings.Uploader)
|
uploadRoutes := uploader.New(ctx, a.AppParams(), a.settings.Uploader)
|
||||||
downloadRoutes := downloader.New(ctx, a.AppParams(), a.settings.Downloader)
|
downloadRoutes := downloader.New(ctx, a.AppParams(), a.settings.Downloader, treeClient)
|
||||||
|
|
||||||
// Configure router.
|
// Configure router.
|
||||||
a.configureRouter(uploadRoutes, downloadRoutes)
|
a.configureRouter(uploadRoutes, downloadRoutes)
|
||||||
|
@ -475,8 +479,8 @@ func (a *app) configureRouter(uploadRoutes *uploader.Uploader, downloadRoutes *d
|
||||||
}
|
}
|
||||||
r.POST("/upload/{cid}", a.logger(uploadRoutes.Upload))
|
r.POST("/upload/{cid}", a.logger(uploadRoutes.Upload))
|
||||||
a.log.Info("added path /upload/{cid}")
|
a.log.Info("added path /upload/{cid}")
|
||||||
r.GET("/get/{cid}/{oid}", a.logger(downloadRoutes.DownloadByAddress))
|
r.GET("/get/{cid}/{oid:*}", a.logger(downloadRoutes.DownloadByAddressOrBucketName))
|
||||||
r.HEAD("/get/{cid}/{oid}", a.logger(downloadRoutes.HeadByAddress))
|
r.HEAD("/get/{cid}/{oid:*}", a.logger(downloadRoutes.HeadByAddressOrBucketName))
|
||||||
a.log.Info("added path /get/{cid}/{oid}")
|
a.log.Info("added path /get/{cid}/{oid}")
|
||||||
r.GET("/get_by_attribute/{cid}/{attr_key}/{attr_val:*}", a.logger(downloadRoutes.DownloadByAttribute))
|
r.GET("/get_by_attribute/{cid}/{attr_key}/{attr_val:*}", a.logger(downloadRoutes.DownloadByAttribute))
|
||||||
r.HEAD("/get_by_attribute/{cid}/{attr_key}/{attr_val:*}", a.logger(downloadRoutes.HeadByAttribute))
|
r.HEAD("/get_by_attribute/{cid}/{attr_key}/{attr_val:*}", a.logger(downloadRoutes.HeadByAttribute))
|
||||||
|
@ -565,6 +569,19 @@ func (a *app) serverIndex(address string) int {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *app) initTree(ctx context.Context) *tree.Tree {
|
||||||
|
treeServiceEndpoint := a.cfg.GetString(cfgTreeServiceEndpoint)
|
||||||
|
grpcDialOpt := grpc.WithTransportCredentials(insecure.NewCredentials())
|
||||||
|
treeGRPCClient, err := services.NewTreeServiceClientGRPC(ctx, treeServiceEndpoint, a.key, grpcDialOpt)
|
||||||
|
if err != nil {
|
||||||
|
a.log.Fatal("failed to create tree service", zap.Error(err))
|
||||||
|
}
|
||||||
|
treeService := tree.NewTree(treeGRPCClient)
|
||||||
|
a.log.Info("init tree service", zap.String("endpoint", treeServiceEndpoint))
|
||||||
|
|
||||||
|
return treeService
|
||||||
|
}
|
||||||
|
|
||||||
func (a *app) initTracing(ctx context.Context) {
|
func (a *app) initTracing(ctx context.Context) {
|
||||||
instanceID := ""
|
instanceID := ""
|
||||||
if len(a.servers) > 0 {
|
if len(a.servers) > 0 {
|
||||||
|
|
|
@ -93,6 +93,9 @@ HTTP_GW_POOL_ERROR_THRESHOLD=100
|
||||||
# Enable zip compression to download files by common prefix.
|
# Enable zip compression to download files by common prefix.
|
||||||
HTTP_GW_ZIP_COMPRESSION=false
|
HTTP_GW_ZIP_COMPRESSION=false
|
||||||
|
|
||||||
|
# Endpoint of the tree service. Must be provided. Can be one of the node address (from the `peers` section).
|
||||||
|
HTTP_GW_TREE_SERVICE=grpc://s01.frostfs.devenv:8080
|
||||||
|
|
||||||
HTTP_GW_TRACING_ENABLED=true
|
HTTP_GW_TRACING_ENABLED=true
|
||||||
HTTP_GW_TRACING_ENDPOINT="localhost:4317"
|
HTTP_GW_TRACING_ENDPOINT="localhost:4317"
|
||||||
HTTP_GW_TRACING_EXPORTER="otlp_grpc"
|
HTTP_GW_TRACING_EXPORTER="otlp_grpc"
|
|
@ -93,6 +93,10 @@ resolve_order:
|
||||||
upload_header:
|
upload_header:
|
||||||
use_default_timestamp: false # Create timestamp for object if it isn't provided by header.
|
use_default_timestamp: false # Create timestamp for object if it isn't provided by header.
|
||||||
|
|
||||||
|
# Endpoint of the tree service. Must be provided. Can be one of the node address (from the `peers` section).
|
||||||
|
tree:
|
||||||
|
service: 127.0.0.1:8080
|
||||||
|
|
||||||
connect_timeout: 5s # Timeout to dial node.
|
connect_timeout: 5s # Timeout to dial node.
|
||||||
stream_timeout: 10s # Timeout for individual operations in streaming RPC.
|
stream_timeout: 10s # Timeout for individual operations in streaming RPC.
|
||||||
request_timeout: 5s # Timeout to check node health during rebalance.
|
request_timeout: 5s # Timeout to check node health during rebalance.
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/resolver"
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/resolver"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/response"
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/response"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/tokens"
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/tokens"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/tree"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/utils"
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/utils"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
|
||||||
|
@ -212,6 +213,7 @@ type Downloader struct {
|
||||||
pool *pool.Pool
|
pool *pool.Pool
|
||||||
containerResolver *resolver.ContainerResolver
|
containerResolver *resolver.ContainerResolver
|
||||||
settings *Settings
|
settings *Settings
|
||||||
|
tree *tree.Tree
|
||||||
}
|
}
|
||||||
|
|
||||||
// Settings stores reloading parameters, so it has to provide atomic getters and setters.
|
// Settings stores reloading parameters, so it has to provide atomic getters and setters.
|
||||||
|
@ -228,13 +230,14 @@ func (s *Settings) SetZipCompression(val bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates an instance of Downloader using specified options.
|
// New creates an instance of Downloader using specified options.
|
||||||
func New(ctx context.Context, params *utils.AppParams, settings *Settings) *Downloader {
|
func New(ctx context.Context, params *utils.AppParams, settings *Settings, tree *tree.Tree) *Downloader {
|
||||||
return &Downloader{
|
return &Downloader{
|
||||||
appCtx: ctx,
|
appCtx: ctx,
|
||||||
log: params.Logger,
|
log: params.Logger,
|
||||||
pool: params.Pool,
|
pool: params.Pool,
|
||||||
settings: settings,
|
settings: settings,
|
||||||
containerResolver: params.Resolver,
|
containerResolver: params.Resolver,
|
||||||
|
tree: tree,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,9 +248,16 @@ func (d *Downloader) newRequest(ctx *fasthttp.RequestCtx, log *zap.Logger) *requ
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DownloadByAddress handles download requests using simple cid/oid format.
|
// DownloadByAddressOrBucketName handles download requests using simple cid/oid or bucketname/key format.
|
||||||
func (d *Downloader) DownloadByAddress(c *fasthttp.RequestCtx) {
|
func (d *Downloader) DownloadByAddressOrBucketName(c *fasthttp.RequestCtx) {
|
||||||
d.byAddress(c, receiveFile)
|
test, _ := c.UserValue("oid").(string)
|
||||||
|
var id oid.ID
|
||||||
|
err := id.DecodeString(test)
|
||||||
|
if err != nil {
|
||||||
|
d.byBucketname(c, receiveFile)
|
||||||
|
} else {
|
||||||
|
d.byAddress(c, receiveFile)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// byAddress is a wrapper for function (e.g. request.headObject, request.receiveFile) that
|
// byAddress is a wrapper for function (e.g. request.headObject, request.receiveFile) that
|
||||||
|
@ -290,6 +300,47 @@ func (d *Downloader) byAddress(c *fasthttp.RequestCtx, f func(context.Context, r
|
||||||
f(ctx, *d.newRequest(c, log), d.pool, addr)
|
f(ctx, *d.newRequest(c, log), d.pool, addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// byBucketname is a wrapper for function (e.g. request.headObject, request.receiveFile) that
|
||||||
|
// prepares request and object address to it.
|
||||||
|
func (d *Downloader) byBucketname(c *fasthttp.RequestCtx, f func(context.Context, request, *pool.Pool, oid.Address)) {
|
||||||
|
var (
|
||||||
|
bucketname = c.UserValue("cid").(string)
|
||||||
|
key = c.UserValue("oid").(string)
|
||||||
|
log = d.log.With(zap.String("bucketname", bucketname), zap.String("key", key))
|
||||||
|
)
|
||||||
|
|
||||||
|
cnrID, err := utils.GetContainerID(d.appCtx, bucketname, d.containerResolver)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("wrong container id", zap.Error(err))
|
||||||
|
response.Error(c, "wrong container id", fasthttp.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, err := tokens.StoreBearerTokenAppCtx(c, d.appCtx)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("could not fetch and store bearer token", zap.Error(err))
|
||||||
|
response.Error(c, "could not fetch and store bearer token: "+err.Error(), fasthttp.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
foundOid, err := d.tree.GetLatestVersion(ctx, cnrID, key)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("object wasn't found", zap.Error(err))
|
||||||
|
response.Error(c, "object wasn't found", fasthttp.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if foundOid.DeleteMarker {
|
||||||
|
log.Error("object was deleted")
|
||||||
|
response.Error(c, "object deleted", fasthttp.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var addr oid.Address
|
||||||
|
addr.SetContainer(*cnrID)
|
||||||
|
addr.SetObject(foundOid.OID)
|
||||||
|
|
||||||
|
f(ctx, *d.newRequest(c, log), d.pool, addr)
|
||||||
|
}
|
||||||
|
|
||||||
// DownloadByAttribute handles attribute-based download requests.
|
// DownloadByAttribute handles attribute-based download requests.
|
||||||
func (d *Downloader) DownloadByAttribute(c *fasthttp.RequestCtx) {
|
func (d *Downloader) DownloadByAttribute(c *fasthttp.RequestCtx) {
|
||||||
d.byAttribute(c, receiveFile)
|
d.byAttribute(c, receiveFile)
|
||||||
|
|
|
@ -121,9 +121,17 @@ func idsToResponse(resp *fasthttp.Response, obj *object.Object) {
|
||||||
resp.Header.Set(hdrContainerID, cnrID.String())
|
resp.Header.Set(hdrContainerID, cnrID.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// HeadByAddress handles head requests using simple cid/oid format.
|
// HeadByAddressOrBucketName handles head requests using simple cid/oid or bucketname/key format.
|
||||||
func (d *Downloader) HeadByAddress(c *fasthttp.RequestCtx) {
|
func (d *Downloader) HeadByAddressOrBucketName(c *fasthttp.RequestCtx) {
|
||||||
d.byAddress(c, headObject)
|
test, _ := c.UserValue("oid").(string)
|
||||||
|
var id oid.ID
|
||||||
|
|
||||||
|
err := id.DecodeString(test)
|
||||||
|
if err != nil {
|
||||||
|
d.byBucketname(c, headObject)
|
||||||
|
} else {
|
||||||
|
d.byAddress(c, headObject)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HeadByAttribute handles attribute-based head requests.
|
// HeadByAttribute handles attribute-based head requests.
|
||||||
|
|
19
go.mod
19
go.mod
|
@ -4,6 +4,7 @@ go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.15.1-0.20230418080822-bd44a3f47b85
|
git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.15.1-0.20230418080822-bd44a3f47b85
|
||||||
|
git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0
|
||||||
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20230505094539-15b4287092bd
|
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20230505094539-15b4287092bd
|
||||||
github.com/fasthttp/router v1.4.1
|
github.com/fasthttp/router v1.4.1
|
||||||
github.com/nspcc-dev/neo-go v0.101.0
|
github.com/nspcc-dev/neo-go v0.101.0
|
||||||
|
@ -18,11 +19,12 @@ require (
|
||||||
go.opentelemetry.io/otel/trace v1.14.0
|
go.opentelemetry.io/otel/trace v1.14.0
|
||||||
go.uber.org/atomic v1.10.0
|
go.uber.org/atomic v1.10.0
|
||||||
go.uber.org/zap v1.24.0
|
go.uber.org/zap v1.24.0
|
||||||
|
google.golang.org/grpc v1.53.0
|
||||||
|
google.golang.org/protobuf v1.28.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.frostfs.info/TrueCloudLab/frostfs-contract v0.0.0-20230307110621-19a8ef2d02fb // indirect
|
git.frostfs.info/TrueCloudLab/frostfs-contract v0.0.0-20230307110621-19a8ef2d02fb // indirect
|
||||||
git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0 // indirect
|
|
||||||
git.frostfs.info/TrueCloudLab/hrw v1.2.0 // indirect
|
git.frostfs.info/TrueCloudLab/hrw v1.2.0 // indirect
|
||||||
git.frostfs.info/TrueCloudLab/rfc6979 v0.4.0 // indirect
|
git.frostfs.info/TrueCloudLab/rfc6979 v0.4.0 // indirect
|
||||||
git.frostfs.info/TrueCloudLab/tzhash v1.8.0 // indirect
|
git.frostfs.info/TrueCloudLab/tzhash v1.8.0 // indirect
|
||||||
|
@ -56,7 +58,7 @@ require (
|
||||||
github.com/hashicorp/golang-lru v0.6.0 // indirect
|
github.com/hashicorp/golang-lru v0.6.0 // indirect
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.1 // indirect
|
github.com/hashicorp/golang-lru/v2 v2.0.1 // indirect
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
github.com/klauspost/compress v1.15.0 // indirect
|
github.com/klauspost/compress v1.16.4 // indirect
|
||||||
github.com/magiconair/properties v1.8.7 // indirect
|
github.com/magiconair/properties v1.8.7 // indirect
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
|
@ -95,16 +97,15 @@ require (
|
||||||
go.opentelemetry.io/otel/sdk v1.14.0 // indirect
|
go.opentelemetry.io/otel/sdk v1.14.0 // indirect
|
||||||
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
|
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
|
||||||
go.uber.org/multierr v1.9.0 // indirect
|
go.uber.org/multierr v1.9.0 // indirect
|
||||||
golang.org/x/crypto v0.4.0 // indirect
|
golang.org/x/crypto v0.8.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20221227203929-1b447090c38c // indirect
|
golang.org/x/exp v0.0.0-20221227203929-1b447090c38c // indirect
|
||||||
golang.org/x/net v0.7.0 // indirect
|
golang.org/x/net v0.9.0 // indirect
|
||||||
golang.org/x/sync v0.1.0 // indirect
|
golang.org/x/sync v0.1.0 // indirect
|
||||||
golang.org/x/sys v0.5.0 // indirect
|
golang.org/x/sys v0.7.0 // indirect
|
||||||
golang.org/x/term v0.5.0 // indirect
|
golang.org/x/term v0.7.0 // indirect
|
||||||
golang.org/x/text v0.7.0 // indirect
|
golang.org/x/text v0.9.0 // indirect
|
||||||
|
golang.org/x/time v0.3.0 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
|
google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
|
||||||
google.golang.org/grpc v1.53.0 // indirect
|
|
||||||
google.golang.org/protobuf v1.28.1 // indirect
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
|
26
go.sum
26
go.sum
|
@ -586,8 +586,9 @@ github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6
|
||||||
github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||||
github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||||
github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||||
github.com/klauspost/compress v1.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwcyL6U=
|
|
||||||
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||||
|
github.com/klauspost/compress v1.16.4 h1:91KN02FnsOYhuunwU4ssRe8lc2JosWmizWa91B5v1PU=
|
||||||
|
github.com/klauspost/compress v1.16.4/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
@ -1005,8 +1006,8 @@ golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5
|
||||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
|
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
|
||||||
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
|
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
|
@ -1099,8 +1100,8 @@ golang.org/x/net v0.0.0-20211108170745-6635138e15ea/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
@ -1226,13 +1227,13 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210429154555-c04ba851c2a4/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20210429154555-c04ba851c2a4/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
|
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
@ -1242,15 +1243,16 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA=
|
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||||
|
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20180318012157-96caea41033d/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180318012157-96caea41033d/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
|
114
internal/frostfs/services/tree_client_grpc.go
Normal file
114
internal/frostfs/services/tree_client_grpc.go
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
grpcService "git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/frostfs/services/tree"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/tokens"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/tree"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetNodeByPathResponseInfoWrapper struct {
|
||||||
|
response *grpcService.GetNodeByPathResponse_Info
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n GetNodeByPathResponseInfoWrapper) GetMeta() []tree.Meta {
|
||||||
|
res := make([]tree.Meta, len(n.response.Meta))
|
||||||
|
for i, value := range n.response.Meta {
|
||||||
|
res[i] = value
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetSubTreeResponseBodyWrapper struct {
|
||||||
|
response *grpcService.GetSubTreeResponse_Body
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n GetSubTreeResponseBodyWrapper) GetMeta() []tree.Meta {
|
||||||
|
res := make([]tree.Meta, len(n.response.Meta))
|
||||||
|
for i, value := range n.response.Meta {
|
||||||
|
res[i] = value
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServiceClientGRPC struct {
|
||||||
|
key *keys.PrivateKey
|
||||||
|
conn *grpc.ClientConn
|
||||||
|
service grpcService.TreeServiceClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTreeServiceClientGRPC(ctx context.Context, addr string, key *keys.PrivateKey, grpcOpts ...grpc.DialOption) (*ServiceClientGRPC, error) {
|
||||||
|
conn, err := grpc.Dial(addr, grpcOpts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("did not connect: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c := grpcService.NewTreeServiceClient(conn)
|
||||||
|
if _, err = c.Healthcheck(ctx, &grpcService.HealthcheckRequest{}); err != nil {
|
||||||
|
return nil, fmt.Errorf("healthcheck: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ServiceClientGRPC{
|
||||||
|
key: key,
|
||||||
|
conn: conn,
|
||||||
|
service: c,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServiceClientGRPC) GetNodes(ctx context.Context, p *tree.GetNodesParams) ([]tree.NodeResponse, error) {
|
||||||
|
request := &grpcService.GetNodeByPathRequest{
|
||||||
|
Body: &grpcService.GetNodeByPathRequest_Body{
|
||||||
|
ContainerId: p.CnrID[:],
|
||||||
|
TreeId: p.TreeID,
|
||||||
|
Path: p.Path,
|
||||||
|
Attributes: p.Meta,
|
||||||
|
PathAttribute: tree.FileNameKey,
|
||||||
|
LatestOnly: p.LatestOnly,
|
||||||
|
AllAttributes: p.AllAttrs,
|
||||||
|
BearerToken: getBearer(ctx),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.signRequest(request.Body, func(key, sign []byte) {
|
||||||
|
request.Signature = &grpcService.Signature{
|
||||||
|
Key: key,
|
||||||
|
Sign: sign,
|
||||||
|
}
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.service.GetNodeByPath(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, handleError("failed to get node by path", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res := make([]tree.NodeResponse, len(resp.GetBody().GetNodes()))
|
||||||
|
for i, info := range resp.GetBody().GetNodes() {
|
||||||
|
res[i] = GetNodeByPathResponseInfoWrapper{info}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBearer(ctx context.Context) []byte {
|
||||||
|
token, err := tokens.LoadBearerToken(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return token.Marshal()
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleError(msg string, err error) error {
|
||||||
|
if strings.Contains(err.Error(), "not found") {
|
||||||
|
return fmt.Errorf("%w: %s", tree.ErrNodeNotFound, err.Error())
|
||||||
|
} else if strings.Contains(err.Error(), "is denied by") {
|
||||||
|
return fmt.Errorf("%w: %s", tree.ErrNodeAccessDenied, err.Error())
|
||||||
|
}
|
||||||
|
return fmt.Errorf("%s: %w", msg, err)
|
||||||
|
}
|
29
internal/frostfs/services/tree_client_grpc_signature.go
Normal file
29
internal/frostfs/services/tree_client_grpc_signature.go
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
/*REMOVE THIS AFTER SIGNATURE WILL BE AVAILABLE IN TREE CLIENT FROM FROSTFS NODE*/
|
||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
crypto "git.frostfs.info/TrueCloudLab/frostfs-crypto"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *ServiceClientGRPC) signData(buf []byte, f func(key, sign []byte)) error {
|
||||||
|
// crypto package should not be used outside of API libraries (see neofs-node#491).
|
||||||
|
// For now tree service does not include into SDK Client nor SDK Pool, so there is no choice.
|
||||||
|
// When SDK library adopts Tree service client, this should be dropped.
|
||||||
|
sign, err := crypto.Sign(&c.key.PrivateKey, buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
f(c.key.PublicKey().Bytes(), sign)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ServiceClientGRPC) signRequest(requestBody proto.Message, f func(key, sign []byte)) error {
|
||||||
|
buf, err := proto.Marshal(requestBody)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.signData(buf, f)
|
||||||
|
}
|
|
@ -59,6 +59,9 @@ const (
|
||||||
cfgRebalance = "rebalance_timer"
|
cfgRebalance = "rebalance_timer"
|
||||||
cfgPoolErrorThreshold = "pool_error_threshold"
|
cfgPoolErrorThreshold = "pool_error_threshold"
|
||||||
|
|
||||||
|
// Grpc path to tree service.
|
||||||
|
cfgTreeServiceEndpoint = "tree.service"
|
||||||
|
|
||||||
// Logger.
|
// Logger.
|
||||||
cfgLoggerLevel = "logger.level"
|
cfgLoggerLevel = "logger.level"
|
||||||
|
|
||||||
|
|
21
syncTree.sh
Executable file
21
syncTree.sh
Executable file
|
@ -0,0 +1,21 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
mkdir -p internal/frostfs/services/tree 2>/dev/null
|
||||||
|
|
||||||
|
REVISION="f07d4158f50ed5c7f44cc0bc224c3d03edf27f3b"
|
||||||
|
|
||||||
|
echo "tree service revision ${REVISION}"
|
||||||
|
|
||||||
|
# regexp below find all link to source code files which end with ".pb.go" and retrieve the file names
|
||||||
|
# we use `[^.]*` as non greedy workaround for `.*`
|
||||||
|
FILES=$(curl -s https://git.frostfs.info/TrueCloudLab/frostfs-node/src/commit/${REVISION}/pkg/services/tree | sed -n "s,.*\"/TrueCloudLab/frostfs-node/src/commit/${REVISION}/pkg/services/tree/\([^.]*\.pb\.go\)\".*,\1,p")
|
||||||
|
|
||||||
|
for file in $FILES; do
|
||||||
|
if [[ $file == *"frostfs"* ]]; then
|
||||||
|
echo "skip '$file'"
|
||||||
|
continue
|
||||||
|
else
|
||||||
|
echo "sync '$file' in tree service"
|
||||||
|
fi
|
||||||
|
curl -s "https://git.frostfs.info/TrueCloudLab/frostfs-node/raw/commit/${REVISION}/pkg/services/tree/${file}" -o "./internal/frostfs/services/tree/${file}"
|
||||||
|
done
|
|
@ -60,6 +60,17 @@ func StoreBearerToken(ctx *fasthttp.RequestCtx) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StoreBearerTokenAppCtx extracts a bearer token from the header or cookie and stores
|
||||||
|
// it in the application context.
|
||||||
|
func StoreBearerTokenAppCtx(ctx *fasthttp.RequestCtx, appCtx context.Context) (context.Context, error) {
|
||||||
|
tkn, err := fetchBearerToken(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
newCtx := context.WithValue(appCtx, bearerTokenKey, tkn)
|
||||||
|
return newCtx, nil
|
||||||
|
}
|
||||||
|
|
||||||
// LoadBearerToken returns a bearer token stored in the context given (if it's
|
// LoadBearerToken returns a bearer token stored in the context given (if it's
|
||||||
// present there).
|
// present there).
|
||||||
func LoadBearerToken(ctx context.Context) (*bearer.Token, error) {
|
func LoadBearerToken(ctx context.Context) (*bearer.Token, error) {
|
||||||
|
|
156
tree/tree.go
Normal file
156
tree/tree.go
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
package tree
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/api"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/api/layer"
|
||||||
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Tree struct {
|
||||||
|
service ServiceClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServiceClient is a client to interact with tree service.
|
||||||
|
// Each method must return ErrNodeNotFound or ErrNodeAccessDenied if relevant.
|
||||||
|
ServiceClient interface {
|
||||||
|
GetNodes(ctx context.Context, p *GetNodesParams) ([]NodeResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
treeNode struct {
|
||||||
|
ObjID oid.ID
|
||||||
|
Meta map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
GetNodesParams struct {
|
||||||
|
CnrID cid.ID
|
||||||
|
TreeID string
|
||||||
|
Path []string
|
||||||
|
Meta []string
|
||||||
|
LatestOnly bool
|
||||||
|
AllAttrs bool
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrNodeNotFound is returned from ServiceClient in case of not found error.
|
||||||
|
ErrNodeNotFound = layer.ErrNodeNotFound
|
||||||
|
|
||||||
|
// ErrNodeAccessDenied is returned from ServiceClient service in case of access denied error.
|
||||||
|
ErrNodeAccessDenied = layer.ErrNodeAccessDenied
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
FileNameKey = "FileName"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
oidKV = "OID"
|
||||||
|
|
||||||
|
// keys for delete marker nodes.
|
||||||
|
isDeleteMarkerKV = "IsDeleteMarker"
|
||||||
|
|
||||||
|
// versionTree -- ID of a tree with object versions.
|
||||||
|
versionTree = "version"
|
||||||
|
|
||||||
|
separator = "/"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewTree creates instance of Tree using provided address and create grpc connection.
|
||||||
|
func NewTree(service ServiceClient) *Tree {
|
||||||
|
return &Tree{service: service}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Meta interface {
|
||||||
|
GetKey() string
|
||||||
|
GetValue() []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type NodeResponse interface {
|
||||||
|
GetMeta() []Meta
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTreeNode(nodeInfo NodeResponse) (*treeNode, error) {
|
||||||
|
treeNode := &treeNode{
|
||||||
|
Meta: make(map[string]string, len(nodeInfo.GetMeta())),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, kv := range nodeInfo.GetMeta() {
|
||||||
|
switch kv.GetKey() {
|
||||||
|
case oidKV:
|
||||||
|
if err := treeNode.ObjID.DecodeString(string(kv.GetValue())); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
treeNode.Meta[kv.GetKey()] = string(kv.GetValue())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return treeNode, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *treeNode) Get(key string) (string, bool) {
|
||||||
|
value, ok := n.Meta[key]
|
||||||
|
return value, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *treeNode) FileName() (string, bool) {
|
||||||
|
value, ok := n.Meta[FileNameKey]
|
||||||
|
return value, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func newNodeVersion(node NodeResponse) (*api.NodeVersion, error) {
|
||||||
|
treeNode, err := newTreeNode(node)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid tree node: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newNodeVersionFromTreeNode(treeNode), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newNodeVersionFromTreeNode(treeNode *treeNode) *api.NodeVersion {
|
||||||
|
_, isDeleteMarker := treeNode.Get(isDeleteMarkerKV)
|
||||||
|
|
||||||
|
version := &api.NodeVersion{
|
||||||
|
BaseNodeVersion: api.BaseNodeVersion{
|
||||||
|
OID: treeNode.ObjID,
|
||||||
|
},
|
||||||
|
DeleteMarker: isDeleteMarker,
|
||||||
|
}
|
||||||
|
|
||||||
|
return version
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Tree) GetLatestVersion(ctx context.Context, cnrID *cid.ID, objectName string) (*api.NodeVersion, error) {
|
||||||
|
meta := []string{oidKV, isDeleteMarkerKV}
|
||||||
|
path := pathFromName(objectName)
|
||||||
|
|
||||||
|
p := &GetNodesParams{
|
||||||
|
CnrID: *cnrID,
|
||||||
|
TreeID: versionTree,
|
||||||
|
Path: path,
|
||||||
|
Meta: meta,
|
||||||
|
LatestOnly: true,
|
||||||
|
AllAttrs: false,
|
||||||
|
}
|
||||||
|
nodes, err := c.service.GetNodes(ctx, p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(nodes) == 0 {
|
||||||
|
return nil, layer.ErrNodeNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return newNodeVersion(nodes[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// pathFromName splits name by '/'.
|
||||||
|
func pathFromName(objectName string) []string {
|
||||||
|
return strings.Split(objectName, separator)
|
||||||
|
}
|
Loading…
Reference in a new issue