[#166] Fix getting s3 object with the FrostFS OID key #173
|
@ -508,10 +508,10 @@ func (a *app) Serve() {
|
||||||
close(a.webDone)
|
close(a.webDone)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
handler := handler.New(a.AppParams(), a.settings, tree.NewTree(frostfs.NewPoolWrapper(a.treePool)), workerPool)
|
handle := handler.New(a.AppParams(), a.settings, tree.NewTree(frostfs.NewPoolWrapper(a.treePool)), workerPool)
|
||||||
|
|
||||||
// Configure router.
|
// Configure router.
|
||||||
a.configureRouter(handler)
|
a.configureRouter(handle)
|
||||||
|
|
||||||
a.startServices()
|
a.startServices()
|
||||||
a.initServers(a.ctx)
|
a.initServers(a.ctx)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package api
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
|
@ -7,12 +7,21 @@ import (
|
||||||
// NodeVersion represent node from tree service.
|
// NodeVersion represent node from tree service.
|
||||||
type NodeVersion struct {
|
type NodeVersion struct {
|
||||||
BaseNodeVersion
|
BaseNodeVersion
|
||||||
DeleteMarker bool
|
|
||||||
IsPrefixNode bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BaseNodeVersion is minimal node info from tree service.
|
// BaseNodeVersion is minimal node info from tree service.
|
||||||
// Basically used for "system" object.
|
// Basically used for "system" object.
|
||||||
type BaseNodeVersion struct {
|
type BaseNodeVersion struct {
|
||||||
|
ID uint64
|
||||||
dkirillov marked this conversation as resolved
Outdated
|
|||||||
OID oid.ID
|
OID oid.ID
|
||||||
|
IsDeleteMarker bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type NodeInfo struct {
|
||||||
|
Meta []NodeMeta
|
||||||
|
}
|
||||||
|
|
||||||
|
type NodeMeta interface {
|
||||||
|
GetKey() string
|
||||||
|
GetValue() []byte
|
||||||
}
|
}
|
|
@ -170,7 +170,7 @@ func (h *Handler) getDirObjectsS3(ctx context.Context, bucketInfo *data.BucketIn
|
||||||
objects: make([]ResponseObject, 0, len(nodes)),
|
objects: make([]ResponseObject, 0, len(nodes)),
|
||||||
}
|
}
|
||||||
for _, node := range nodes {
|
for _, node := range nodes {
|
||||||
meta := node.GetMeta()
|
meta := node.Meta
|
||||||
if meta == nil {
|
if meta == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,14 @@ import (
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/layer"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/logs"
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/logs"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/response"
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/response"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/utils"
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/utils"
|
||||||
|
@ -23,21 +25,46 @@ import (
|
||||||
|
|
||||||
// DownloadByAddressOrBucketName handles download requests using simple cid/oid or bucketname/key format.
|
// DownloadByAddressOrBucketName handles download requests using simple cid/oid or bucketname/key format.
|
||||||
func (h *Handler) DownloadByAddressOrBucketName(c *fasthttp.RequestCtx) {
|
func (h *Handler) DownloadByAddressOrBucketName(c *fasthttp.RequestCtx) {
|
||||||
oidURLParam := c.UserValue("oid").(string)
|
cidParam := c.UserValue("cid").(string)
|
||||||
downloadQueryParam := c.QueryArgs().GetBool("download")
|
oidParam := c.UserValue("oid").(string)
|
||||||
|
downloadParam := c.QueryArgs().GetBool("download")
|
||||||
|
|
||||||
switch {
|
ctx := utils.GetContextFromRequest(c)
|
||||||
case isObjectID(oidURLParam):
|
log := utils.GetReqLogOrDefault(ctx, h.log).With(
|
||||||
h.byNativeAddress(c, h.receiveFile)
|
zap.String("cid", cidParam),
|
||||||
case !isContainerRoot(oidURLParam) && (downloadQueryParam || !isDir(oidURLParam)):
|
zap.String("oid", oidParam),
|
||||||
h.byS3Path(c, h.receiveFile)
|
)
|
||||||
default:
|
|
||||||
h.browseIndex(c)
|
bktInfo, err := h.getBucketInfo(ctx, cidParam, log)
|
||||||
|
if err != nil {
|
||||||
|
logAndSendBucketError(c, log, err)
|
||||||
|
return
|
||||||
dkirillov marked this conversation as resolved
Outdated
dkirillov
commented
We should check error like We should check error like `errors.Is(s3checkErr, tree.ErrNodeNotFound)`.
nzinkevich
commented
The problem is that the returned error is not ErrNodeNotFound, but "tree not found", which has not error type in sdk? I will look why it's happening The problem is that the returned error is not ErrNodeNotFound, but "tree not found", which has not error type in sdk? I will look why it's happening
nzinkevich
commented
It occurs, that I used wrong error type. Fixed It occurs, that I used wrong error type. Fixed
|
|||||||
|
}
|
||||||
|
|
||||||
|
checkS3Err := h.tree.CheckSettingsNodeExists(ctx, bktInfo)
|
||||||
|
if checkS3Err != nil && !errors.Is(checkS3Err, layer.ErrNodeNotFound) {
|
||||||
r.loginov marked this conversation as resolved
Outdated
r.loginov
commented
nitpick: Perhaps you should write the acronym in the same case: nitpick: Perhaps you should write the acronym in the same case: `foundOid` -> `foundOID`.
|
|||||||
|
logAndSendBucketError(c, log, checkS3Err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req := h.newRequest(c, log)
|
||||||
|
|
||||||
|
var objID oid.ID
|
||||||
|
if checkS3Err == nil && shouldDownload(oidParam, downloadParam) {
|
||||||
|
h.byS3Path(ctx, req, bktInfo.CID, oidParam, h.receiveFile)
|
||||||
|
} else if err = objID.DecodeString(oidParam); err == nil {
|
||||||
|
h.byNativeAddress(ctx, req, bktInfo.CID, objID, h.receiveFile)
|
||||||
|
} else {
|
||||||
|
h.browseIndex(c, checkS3Err != nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) newRequest(ctx *fasthttp.RequestCtx, log *zap.Logger) *request {
|
func shouldDownload(oidParam string, downloadParam bool) bool {
|
||||||
return &request{
|
return !isDir(oidParam) || downloadParam
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) newRequest(ctx *fasthttp.RequestCtx, log *zap.Logger) request {
|
||||||
|
return request{
|
||||||
RequestCtx: ctx,
|
RequestCtx: ctx,
|
||||||
log: log,
|
log: log,
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,9 +11,9 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/cache"
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/cache"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/data"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/handler/middleware"
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/handler/middleware"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/layer"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/logs"
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/logs"
|
||||||
"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/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"
|
||||||
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||||
|
@ -165,7 +165,7 @@ type Handler struct {
|
||||||
ownerID *user.ID
|
ownerID *user.ID
|
||||||
config Config
|
config Config
|
||||||
containerResolver ContainerResolver
|
containerResolver ContainerResolver
|
||||||
tree *tree.Tree
|
tree layer.TreeService
|
||||||
cache *cache.BucketCache
|
cache *cache.BucketCache
|
||||||
workerPool *ants.Pool
|
workerPool *ants.Pool
|
||||||
}
|
}
|
||||||
|
@ -178,7 +178,7 @@ type AppParams struct {
|
||||||
Cache *cache.BucketCache
|
Cache *cache.BucketCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(params *AppParams, config Config, tree *tree.Tree, workerPool *ants.Pool) *Handler {
|
func New(params *AppParams, config Config, tree layer.TreeService, workerPool *ants.Pool) *Handler {
|
||||||
return &Handler{
|
return &Handler{
|
||||||
log: params.Logger,
|
log: params.Logger,
|
||||||
frostfs: params.FrostFS,
|
frostfs: params.FrostFS,
|
||||||
|
@ -193,77 +193,34 @@ func New(params *AppParams, config Config, tree *tree.Tree, workerPool *ants.Poo
|
||||||
|
|
||||||
// byNativeAddress is a wrapper for function (e.g. request.headObject, request.receiveFile) that
|
// byNativeAddress is a wrapper for function (e.g. request.headObject, request.receiveFile) that
|
||||||
// prepares request and object address to it.
|
// prepares request and object address to it.
|
||||||
func (h *Handler) byNativeAddress(c *fasthttp.RequestCtx, f func(context.Context, request, oid.Address)) {
|
func (h *Handler) byNativeAddress(ctx context.Context, req request, cnrID cid.ID, objID oid.ID, handler func(context.Context, request, oid.Address)) {
|
||||||
idCnr, _ := c.UserValue("cid").(string)
|
addr := newAddress(cnrID, objID)
|
||||||
idObj, _ := url.PathUnescape(c.UserValue("oid").(string))
|
handler(ctx, req, addr)
|
||||||
|
|
||||||
ctx := utils.GetContextFromRequest(c)
|
|
||||||
reqLog := utils.GetReqLogOrDefault(ctx, h.log)
|
|
||||||
log := reqLog.With(zap.String("cid", idCnr), zap.String("oid", idObj))
|
|
||||||
|
|
||||||
bktInfo, err := h.getBucketInfo(ctx, idCnr, log)
|
|
||||||
if err != nil {
|
|
||||||
logAndSendBucketError(c, log, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
objID := new(oid.ID)
|
|
||||||
if err = objID.DecodeString(idObj); err != nil {
|
|
||||||
log.Error(logs.WrongObjectID, zap.Error(err))
|
|
||||||
response.Error(c, "wrong object id", fasthttp.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
addr := newAddress(bktInfo.CID, *objID)
|
|
||||||
|
|
||||||
f(ctx, *h.newRequest(c, log), addr)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// byS3Path is a wrapper for function (e.g. request.headObject, request.receiveFile) that
|
// byS3Path is a wrapper for function (e.g. request.headObject, request.receiveFile) that
|
||||||
// resolves object address from S3-like path <bucket name>/<object key>.
|
// resolves object address from S3-like path <bucket name>/<object key>.
|
||||||
func (h *Handler) byS3Path(c *fasthttp.RequestCtx, f func(context.Context, request, oid.Address)) {
|
func (h *Handler) byS3Path(ctx context.Context, req request, cnrID cid.ID, path string, handler func(context.Context, request, oid.Address)) {
|
||||||
bucketname := c.UserValue("cid").(string)
|
c, log := req.RequestCtx, req.log
|
||||||
key := c.UserValue("oid").(string)
|
|
||||||
|
|
||||||
ctx := utils.GetContextFromRequest(c)
|
foundOID, err := h.tree.GetLatestVersion(ctx, &cnrID, path)
|
||||||
reqLog := utils.GetReqLogOrDefault(ctx, h.log)
|
|
||||||
log := reqLog.With(zap.String("bucketname", bucketname), zap.String("key", key))
|
|
||||||
|
|
||||||
unescapedKey, err := url.QueryUnescape(key)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logAndSendBucketError(c, log, err)
|
logAndSendBucketError(c, log, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if foundOID.IsDeleteMarker {
|
||||||
bktInfo, err := h.getBucketInfo(ctx, bucketname, log)
|
|
||||||
if err != nil {
|
|
||||||
logAndSendBucketError(c, log, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
foundOid, err := h.tree.GetLatestVersion(ctx, &bktInfo.CID, unescapedKey)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, tree.ErrNodeAccessDenied) {
|
|
||||||
response.Error(c, "Access Denied", fasthttp.StatusForbidden)
|
|
||||||
} else {
|
|
||||||
response.Error(c, "object wasn't found", fasthttp.StatusNotFound)
|
|
||||||
log.Error(logs.GetLatestObjectVersion, zap.Error(err))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if foundOid.DeleteMarker {
|
|
||||||
log.Error(logs.ObjectWasDeleted)
|
log.Error(logs.ObjectWasDeleted)
|
||||||
response.Error(c, "object deleted", fasthttp.StatusNotFound)
|
response.Error(c, "object deleted", fasthttp.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
addr := newAddress(bktInfo.CID, foundOid.OID)
|
|
||||||
|
|
||||||
f(ctx, *h.newRequest(c, log), addr)
|
addr := newAddress(cnrID, foundOID.OID)
|
||||||
|
handler(ctx, h.newRequest(c, log), addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// byAttribute is a wrapper similar to byNativeAddress.
|
// byAttribute is a wrapper similar to byNativeAddress.
|
||||||
func (h *Handler) byAttribute(c *fasthttp.RequestCtx, f func(context.Context, request, oid.Address)) {
|
func (h *Handler) byAttribute(c *fasthttp.RequestCtx, handler func(context.Context, request, oid.Address)) {
|
||||||
scid, _ := c.UserValue("cid").(string)
|
cidParam, _ := 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)
|
||||||
|
|
||||||
|
@ -272,21 +229,21 @@ func (h *Handler) byAttribute(c *fasthttp.RequestCtx, f func(context.Context, re
|
||||||
|
|
||||||
key, err := url.QueryUnescape(key)
|
key, err := url.QueryUnescape(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(logs.FailedToUnescapeQuery, zap.String("cid", scid), zap.String("attr_key", key), zap.Error(err))
|
log.Error(logs.FailedToUnescapeQuery, zap.String("cid", cidParam), zap.String("attr_key", key), zap.Error(err))
|
||||||
response.Error(c, "could not unescape attr_key: "+err.Error(), fasthttp.StatusBadRequest)
|
response.Error(c, "could not unescape attr_key: "+err.Error(), fasthttp.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val, err = url.QueryUnescape(val)
|
val, err = url.QueryUnescape(val)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error(logs.FailedToUnescapeQuery, zap.String("cid", scid), zap.String("attr_val", val), zap.Error(err))
|
log.Error(logs.FailedToUnescapeQuery, zap.String("cid", cidParam), zap.String("attr_val", val), zap.Error(err))
|
||||||
response.Error(c, "could not unescape attr_val: "+err.Error(), fasthttp.StatusBadRequest)
|
response.Error(c, "could not unescape attr_val: "+err.Error(), fasthttp.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log = log.With(zap.String("cid", scid), zap.String("attr_key", key), zap.String("attr_val", val))
|
log = log.With(zap.String("cid", cidParam), zap.String("attr_key", key), zap.String("attr_val", val))
|
||||||
|
|
||||||
bktInfo, err := h.getBucketInfo(ctx, scid, log)
|
bktInfo, err := h.getBucketInfo(ctx, cidParam, log)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logAndSendBucketError(c, log, err)
|
logAndSendBucketError(c, log, err)
|
||||||
return
|
return
|
||||||
|
@ -303,11 +260,11 @@ func (h *Handler) byAttribute(c *fasthttp.RequestCtx, f func(context.Context, re
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var addrObj oid.Address
|
var addr oid.Address
|
||||||
addrObj.SetContainer(bktInfo.CID)
|
addr.SetContainer(bktInfo.CID)
|
||||||
addrObj.SetObject(objID)
|
addr.SetObject(objID)
|
||||||
|
|
||||||
f(ctx, *h.newRequest(c, log), addrObj)
|
handler(ctx, h.newRequest(c, log), addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) findObjectByAttribute(ctx context.Context, log *zap.Logger, cnrID cid.ID, attrKey, attrVal string) (oid.ID, error) {
|
func (h *Handler) findObjectByAttribute(ctx context.Context, log *zap.Logger, cnrID cid.ID, attrKey, attrVal string) (oid.ID, error) {
|
||||||
|
@ -412,7 +369,7 @@ func (h *Handler) readContainer(ctx context.Context, cnrID cid.ID) (*data.Bucket
|
||||||
return bktInfo, err
|
return bktInfo, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) browseIndex(c *fasthttp.RequestCtx) {
|
func (h *Handler) browseIndex(c *fasthttp.RequestCtx, isNativeList bool) {
|
||||||
if !h.config.IndexPageEnabled() {
|
if !h.config.IndexPageEnabled() {
|
||||||
c.SetStatusCode(fasthttp.StatusNotFound)
|
c.SetStatusCode(fasthttp.StatusNotFound)
|
||||||
return
|
return
|
||||||
|
@ -438,18 +395,9 @@ func (h *Handler) browseIndex(c *fasthttp.RequestCtx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
listFunc := h.getDirObjectsS3
|
listFunc := h.getDirObjectsS3
|
||||||
isNativeList := false
|
if isNativeList {
|
||||||
|
// tree probe failed, trying to use native
|
||||||
err = h.tree.CheckSettingsNodeExist(ctx, bktInfo)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, tree.ErrNodeNotFound) {
|
|
||||||
// tree probe failed, try to use native
|
|
||||||
listFunc = h.getDirObjectsNative
|
listFunc = h.getDirObjectsNative
|
||||||
isNativeList = true
|
|
||||||
} else {
|
|
||||||
logAndSendBucketError(c, log, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h.browseObjects(c, browseParams{
|
h.browseObjects(c, browseParams{
|
||||||
|
|
|
@ -14,8 +14,8 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/cache"
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/cache"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/data"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/handler/middleware"
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/handler/middleware"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/layer"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/resolver"
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/resolver"
|
||||||
"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/container"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
|
||||||
|
@ -32,14 +32,29 @@ import (
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
type treeClientMock struct {
|
type treeServiceMock struct {
|
||||||
|
system map[string]map[string]*data.BaseNodeVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *treeClientMock) GetNodes(context.Context, *tree.GetNodesParams) ([]tree.NodeResponse, error) {
|
func newTreeService() *treeServiceMock {
|
||||||
return nil, nil
|
return &treeServiceMock{
|
||||||
|
system: make(map[string]map[string]*data.BaseNodeVersion),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *treeClientMock) GetSubTree(context.Context, *data.BucketInfo, string, []uint64, uint32, bool) ([]tree.NodeResponse, error) {
|
func (t *treeServiceMock) CheckSettingsNodeExists(context.Context, *data.BucketInfo) error {
|
||||||
|
_, ok := t.system["bucket-settings"]
|
||||||
|
if !ok {
|
||||||
|
return layer.ErrNodeNotFound
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *treeServiceMock) GetSubTreeByPrefix(context.Context, *data.BucketInfo, string, bool) ([]data.NodeInfo, string, error) {
|
||||||
|
return nil, "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *treeServiceMock) GetLatestVersion(context.Context, *cid.ID, string) (*data.NodeVersion, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,7 +104,7 @@ type handlerContext struct {
|
||||||
|
|
||||||
h *Handler
|
h *Handler
|
||||||
frostfs *TestFrostFS
|
frostfs *TestFrostFS
|
||||||
tree *treeClientMock
|
tree *treeServiceMock
|
||||||
cfg *configMock
|
cfg *configMock
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,14 +145,14 @@ func prepareHandlerContext() (*handlerContext, error) {
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
treeMock := &treeClientMock{}
|
treeMock := newTreeService()
|
||||||
cfgMock := &configMock{}
|
cfgMock := &configMock{}
|
||||||
|
|
||||||
workerPool, err := ants.NewPool(1000)
|
workerPool, err := ants.NewPool(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
handler := New(params, cfgMock, tree.NewTree(treeMock), workerPool)
|
handler := New(params, cfgMock, treeMock, workerPool)
|
||||||
|
|
||||||
return &handlerContext{
|
return &handlerContext{
|
||||||
key: key,
|
key: key,
|
||||||
|
|
|
@ -2,11 +2,13 @@ package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/layer"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/logs"
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/logs"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/utils"
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/utils"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||||
|
@ -102,14 +104,36 @@ func idsToResponse(resp *fasthttp.Response, obj *object.Object) {
|
||||||
|
|
||||||
// HeadByAddressOrBucketName handles head requests using simple cid/oid or bucketname/key format.
|
// HeadByAddressOrBucketName handles head requests using simple cid/oid or bucketname/key format.
|
||||||
func (h *Handler) HeadByAddressOrBucketName(c *fasthttp.RequestCtx) {
|
func (h *Handler) HeadByAddressOrBucketName(c *fasthttp.RequestCtx) {
|
||||||
test, _ := c.UserValue("oid").(string)
|
cidParam, _ := c.UserValue("cid").(string)
|
||||||
var id oid.ID
|
oidParam, _ := c.UserValue("oid").(string)
|
||||||
|
|
||||||
err := id.DecodeString(test)
|
ctx := utils.GetContextFromRequest(c)
|
||||||
|
log := utils.GetReqLogOrDefault(ctx, h.log).With(
|
||||||
|
zap.String("cid", cidParam),
|
||||||
|
zap.String("oid", oidParam),
|
||||||
|
)
|
||||||
|
|
||||||
|
bktInfo, err := h.getBucketInfo(ctx, cidParam, log)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.byS3Path(c, h.headObject)
|
logAndSendBucketError(c, log, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
checkS3Err := h.tree.CheckSettingsNodeExists(ctx, bktInfo)
|
||||||
|
if checkS3Err != nil && !errors.Is(checkS3Err, layer.ErrNodeNotFound) {
|
||||||
|
logAndSendBucketError(c, log, checkS3Err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req := h.newRequest(c, log)
|
||||||
|
|
||||||
|
var objID oid.ID
|
||||||
|
if checkS3Err == nil {
|
||||||
|
h.byS3Path(ctx, req, bktInfo.CID, oidParam, h.headObject)
|
||||||
|
} else if err = objID.DecodeString(oidParam); err == nil {
|
||||||
|
h.byNativeAddress(ctx, req, bktInfo.CID, objID, h.headObject)
|
||||||
dkirillov marked this conversation as resolved
Outdated
dkirillov
commented
Suggestion: Maybe we can keep
Suggestion: Maybe we can keep `handler.byNativeAddress` method?
```diff
diff --git a/internal/handler/download.go b/internal/handler/download.go
index ce58bf1..27f633d 100644
--- a/internal/handler/download.go
+++ b/internal/handler/download.go
@@ -29,7 +29,10 @@ func (h *Handler) DownloadByAddressOrBucketName(c *fasthttp.RequestCtx) {
downloadParam := c.QueryArgs().GetBool("download")
ctx := utils.GetContextFromRequest(c)
- log := utils.GetReqLogOrDefault(ctx, h.log)
+ log := utils.GetReqLogOrDefault(ctx, h.log).With(
+ zap.String("cid", cidParam),
+ zap.String("oid", oidParam),
+ )
bktInfo, err := h.getBucketInfo(ctx, cidParam, log)
if err != nil {
@@ -43,14 +46,13 @@ func (h *Handler) DownloadByAddressOrBucketName(c *fasthttp.RequestCtx) {
return
}
+ req := h.newRequest(c, log)
+
var objID oid.ID
if s3checkErr == nil && shouldDownload(oidParam, downloadParam) {
- // Receive file via S3
- h.byS3Path(c, bktInfo.CID, h.receiveFile)
+ h.byS3Path(ctx, req, bktInfo.CID, oidParam, h.receiveFile)
} else if err = objID.DecodeString(oidParam); err == nil {
- // Receive file via native protocol
- addr := newAddress(bktInfo.CID, objID)
- h.receiveFile(ctx, h.newRequest(c, log), addr)
+ h.byNativeAddress(ctx, req, bktInfo.CID, objID, h.receiveFile)
} else {
h.browseIndex(c, s3checkErr != nil)
}
diff --git a/internal/handler/handler.go b/internal/handler/handler.go
index acc053e..6f24ff9 100644
--- a/internal/handler/handler.go
+++ b/internal/handler/handler.go
@@ -204,27 +204,26 @@ func (h *Handler) resolveContainer(ctx context.Context, containerID string) (*ci
return cnrID, err
}
-func (h *Handler) byS3Path(c *fasthttp.RequestCtx, cnrID cid.ID, handler func(context.Context, request, oid.Address)) {
- oidParam := c.UserValue("oid").(string)
- ctx := utils.GetContextFromRequest(c)
- log := utils.GetReqLogOrDefault(ctx, h.log).With(
- zap.String("cid", cnrID.EncodeToString()),
- zap.String("oid", oidParam),
- )
-
- foundOID, err := h.tree.GetLatestVersion(ctx, &cnrID, oidParam)
+func (h *Handler) byS3Path(ctx context.Context, req request, cnrID cid.ID, path string, handler func(context.Context, request, oid.Address)) {
+ log := req.log
+ foundOID, err := h.tree.GetLatestVersion(ctx, &cnrID, path)
if err != nil {
- logAndSendBucketError(c, log, err)
+ logAndSendBucketError(req.RequestCtx, log, err)
return
}
if foundOID.IsDeleteMarker {
log.Error(logs.ObjectWasDeleted)
- response.Error(c, "object deleted", fasthttp.StatusNotFound)
+ response.Error(req.RequestCtx, "object deleted", fasthttp.StatusNotFound)
return
}
addr := newAddress(cnrID, foundOID.OID)
- handler(ctx, h.newRequest(c, log), addr)
+ handler(ctx, req, addr)
+}
+
+func (h *Handler) byNativeAddress(ctx context.Context, req request, cnrID cid.ID, objID oid.ID, handler func(context.Context, request, oid.Address)) {
+ addr := newAddress(cnrID, objID)
+ handler(ctx, req, addr)
}
func (h *Handler) byAttribute(c *fasthttp.RequestCtx, handler func(context.Context, request, oid.Address)) {
diff --git a/internal/handler/head.go b/internal/handler/head.go
index 9d00b20..19bbf89 100644
--- a/internal/handler/head.go
+++ b/internal/handler/head.go
@@ -123,14 +123,13 @@ func (h *Handler) HeadByAddressOrBucketName(c *fasthttp.RequestCtx) {
return
}
+ req := h.newRequest(c, log)
+
var objID oid.ID
if checkErr == nil {
- // Head object via s3 protocol
- h.byS3Path(c, bktInfo.CID, h.headObject)
+ h.byS3Path(ctx, req, bktInfo.CID, oidParam, h.headObject)
} else if err = objID.DecodeString(oidParam); err == nil {
- // Head object via native protocol
- addr := newAddress(bktInfo.CID, objID)
- h.headObject(ctx, h.newRequest(c, log), addr)
+ h.byNativeAddress(ctx, req, bktInfo.CID, objID, h.headObject)
} else {
logAndSendBucketError(c, log, checkErr)
return
```
|
|||||||
} else {
|
} else {
|
||||||
h.byNativeAddress(c, h.headObject)
|
logAndSendBucketError(c, log, checkS3Err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,16 +42,7 @@ func bearerToken(ctx context.Context) *bearer.Token {
|
||||||
}
|
}
|
||||||
|
|
||||||
func isDir(name string) bool {
|
func isDir(name string) bool {
|
||||||
return strings.HasSuffix(name, "/")
|
return name == "" || strings.HasSuffix(name, "/")
|
||||||
}
|
|
||||||
|
|
||||||
func isObjectID(s string) bool {
|
|
||||||
var objID oid.ID
|
|
||||||
return objID.DecodeString(s) == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isContainerRoot(key string) bool {
|
|
||||||
return key == ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadAttributes(attrs []object.Attribute) map[string]string {
|
func loadAttributes(attrs []object.Attribute) map[string]string {
|
||||||
|
|
|
@ -4,13 +4,15 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/api"
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/data"
|
||||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TreeService provide interface to interact with tree service using s3 data models.
|
// TreeService provide interface to interact with tree service using s3 data models.
|
||||||
type TreeService interface {
|
type TreeService interface {
|
||||||
GetLatestVersion(ctx context.Context, cnrID *cid.ID, objectName string) (*api.NodeVersion, error)
|
GetLatestVersion(ctx context.Context, cnrID *cid.ID, objectName string) (*data.NodeVersion, error)
|
||||||
|
GetSubTreeByPrefix(ctx context.Context, bktInfo *data.BucketInfo, prefix string, latestOnly bool) ([]data.NodeInfo, string, error)
|
||||||
|
CheckSettingsNodeExists(ctx context.Context, bktInfo *data.BucketInfo) error
|
||||||
dkirillov marked this conversation as resolved
Outdated
dkirillov
commented
There are some problems with this
This again leads to wrong dependency direction (consumer of this interface starts rely on specific implementation) We should:
There are some problems with this `TreeService` interface:
* `GetSubTreeByPrefix` return type from package that contains implementation of this interface (so we've got wrong dependency direction)
* `CheckSettingsNodeExists` returns error specific type (that should be mention in comments) from implementation
in client we use:
```golang
import "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool/tree"
//...
errors.Is(checkS3Err, tree.ErrNodeNotFound)
```
This again leads to wrong dependency direction (consumer of this interface starts rely on specific implementation)
We should:
* Define type similar to `tree.NodeResponse` somewhere in `data` package for example (or just in this file)
* Define error that we expect implementation will return. The same way that was in `internal/api/layer/tree_service.go` that was removed in this PR
```golang
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")
)
```
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
|
@ -79,6 +79,11 @@ const (
|
||||||
InvalidLifetimeUsingDefaultValue = "invalid lifetime, using default value (in seconds)" // Error in ../../cmd/http-gw/settings.go
|
InvalidLifetimeUsingDefaultValue = "invalid lifetime, using default value (in seconds)" // Error in ../../cmd/http-gw/settings.go
|
||||||
InvalidCacheSizeUsingDefaultValue = "invalid cache size, using default value" // Error in ../../cmd/http-gw/settings.go
|
InvalidCacheSizeUsingDefaultValue = "invalid cache size, using default value" // Error in ../../cmd/http-gw/settings.go
|
||||||
FailedToUnescapeQuery = "failed to unescape query"
|
FailedToUnescapeQuery = "failed to unescape query"
|
||||||
|
FailedToParseAddressInTreeNode = "failed to parse object addr in tree node"
|
||||||
|
SettingsNodeInvalidOwnerKey = "settings node: invalid owner key"
|
||||||
|
SystemNodeHasMultipleIDs = "system node has multiple ids"
|
||||||
|
FailedToRemoveOldSystemNode = "failed to remove old system node"
|
||||||
|
BucketSettingsNodeHasMultipleIDs = "bucket settings node has multiple ids"
|
||||||
ServerReconnecting = "reconnecting server..."
|
ServerReconnecting = "reconnecting server..."
|
||||||
ServerReconnectedSuccessfully = "server reconnected successfully"
|
ServerReconnectedSuccessfully = "server reconnected successfully"
|
||||||
ServerReconnectFailed = "failed to reconnect server"
|
ServerReconnectFailed = "failed to reconnect server"
|
||||||
|
|
50
tree/tree.go
|
@ -6,9 +6,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/api"
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/api/layer"
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/data"
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/data"
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/layer"
|
||||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||||
)
|
)
|
||||||
|
@ -118,7 +117,7 @@ func (n *treeNode) FileName() (string, bool) {
|
||||||
return value, ok
|
return value, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func newNodeVersion(node NodeResponse) (*api.NodeVersion, error) {
|
func newNodeVersion(node NodeResponse) (*data.NodeVersion, error) {
|
||||||
tNode, err := newTreeNode(node)
|
tNode, err := newTreeNode(node)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid tree node: %w", err)
|
return nil, fmt.Errorf("invalid tree node: %w", err)
|
||||||
|
@ -127,20 +126,30 @@ func newNodeVersion(node NodeResponse) (*api.NodeVersion, error) {
|
||||||
return newNodeVersionFromTreeNode(tNode), nil
|
return newNodeVersionFromTreeNode(tNode), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newNodeVersionFromTreeNode(treeNode *treeNode) *api.NodeVersion {
|
func newNodeVersionFromTreeNode(treeNode *treeNode) *data.NodeVersion {
|
||||||
_, isDeleteMarker := treeNode.Get(isDeleteMarkerKV)
|
_, isDeleteMarker := treeNode.Get(isDeleteMarkerKV)
|
||||||
size, _ := treeNode.Get(sizeKV)
|
version := &data.NodeVersion{
|
||||||
version := &api.NodeVersion{
|
BaseNodeVersion: data.BaseNodeVersion{
|
||||||
BaseNodeVersion: api.BaseNodeVersion{
|
|
||||||
OID: treeNode.ObjID,
|
OID: treeNode.ObjID,
|
||||||
|
IsDeleteMarker: isDeleteMarker,
|
||||||
},
|
},
|
||||||
DeleteMarker: isDeleteMarker,
|
|
||||||
IsPrefixNode: size == "",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return version
|
return version
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newNodeInfo(node NodeResponse) data.NodeInfo {
|
||||||
|
nodeMeta := node.GetMeta()
|
||||||
|
nodeInfo := data.NodeInfo{
|
||||||
|
Meta: make([]data.NodeMeta, 0, len(nodeMeta)),
|
||||||
|
}
|
||||||
|
for _, meta := range nodeMeta {
|
||||||
|
nodeInfo.Meta = append(nodeInfo.Meta, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodeInfo
|
||||||
|
}
|
||||||
|
|
||||||
func newMultiNode(nodes []NodeResponse) (*multiSystemNode, error) {
|
func newMultiNode(nodes []NodeResponse) (*multiSystemNode, error) {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
|
@ -180,7 +189,7 @@ func (m *multiSystemNode) Old() []*treeNode {
|
||||||
return m.nodes[1:]
|
return m.nodes[1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Tree) GetLatestVersion(ctx context.Context, cnrID *cid.ID, objectName string) (*api.NodeVersion, error) {
|
func (c *Tree) GetLatestVersion(ctx context.Context, cnrID *cid.ID, objectName string) (*data.NodeVersion, error) {
|
||||||
nodes, err := c.GetVersions(ctx, cnrID, objectName)
|
nodes, err := c.GetVersions(ctx, cnrID, objectName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -210,7 +219,7 @@ func (c *Tree) GetVersions(ctx context.Context, cnrID *cid.ID, objectName string
|
||||||
return c.service.GetNodes(ctx, p)
|
return c.service.GetNodes(ctx, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Tree) CheckSettingsNodeExist(ctx context.Context, bktInfo *data.BucketInfo) error {
|
func (c *Tree) CheckSettingsNodeExists(ctx context.Context, bktInfo *data.BucketInfo) error {
|
||||||
dkirillov marked this conversation as resolved
Outdated
dkirillov
commented
Why do we refuse this? Why do we refuse this?
nzinkevich
commented
I decided to have similar methods and implementations as s3-gw has. Generally, it would be nice to have some kind of tree service wrappers in sdk in order to avoid copy-pasting similar methods I decided to have similar methods and implementations as s3-gw has. Generally, it would be nice to have some kind of tree service wrappers in sdk in order to avoid copy-pasting similar methods
|
|||||||
_, err := c.getSystemNode(ctx, bktInfo, settingsFileName)
|
_, err := c.getSystemNode(ctx, bktInfo, settingsFileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -236,7 +245,7 @@ func (c *Tree) getSystemNode(ctx context.Context, bktInfo *data.BucketInfo, name
|
||||||
nodes = filterMultipartNodes(nodes)
|
nodes = filterMultipartNodes(nodes)
|
||||||
|
|
||||||
if len(nodes) == 0 {
|
if len(nodes) == 0 {
|
||||||
return nil, ErrNodeNotFound
|
return nil, layer.ErrNodeNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
return newMultiNode(nodes)
|
return newMultiNode(nodes)
|
||||||
|
@ -298,14 +307,14 @@ func pathFromName(objectName string) []string {
|
||||||
return strings.Split(objectName, separator)
|
return strings.Split(objectName, separator)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Tree) GetSubTreeByPrefix(ctx context.Context, bktInfo *data.BucketInfo, prefix string, latestOnly bool) ([]NodeResponse, string, error) {
|
func (c *Tree) GetSubTreeByPrefix(ctx context.Context, bktInfo *data.BucketInfo, prefix string, latestOnly bool) ([]data.NodeInfo, string, error) {
|
||||||
rootID, tailPrefix, err := c.determinePrefixNode(ctx, bktInfo, versionTree, prefix)
|
rootID, tailPrefix, err := c.determinePrefixNode(ctx, bktInfo, versionTree, prefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
subTree, err := c.service.GetSubTree(ctx, bktInfo, versionTree, rootID, 2, false)
|
subTree, err := c.service.GetSubTree(ctx, bktInfo, versionTree, rootID, 2, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, layer.ErrNodeNotFound) {
|
if errors.Is(err, ErrNodeNotFound) {
|
||||||
return nil, "", nil
|
return nil, "", nil
|
||||||
}
|
}
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
|
@ -340,14 +349,23 @@ func (c *Tree) GetSubTreeByPrefix(ctx context.Context, bktInfo *data.BucketInfo,
|
||||||
nodesMap[fileName] = nodes
|
nodesMap[fileName] = nodes
|
||||||
}
|
}
|
||||||
|
|
||||||
result := make([]NodeResponse, 0, len(subTree))
|
result := make([]data.NodeInfo, 0, len(subTree))
|
||||||
for _, nodes := range nodesMap {
|
for _, nodes := range nodesMap {
|
||||||
result = append(result, nodes...)
|
result = append(result, nodeResponseToNodeInfo(nodes)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, strings.TrimSuffix(prefix, tailPrefix), nil
|
return result, strings.TrimSuffix(prefix, tailPrefix), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func nodeResponseToNodeInfo(nodes []NodeResponse) []data.NodeInfo {
|
||||||
|
nodesInfo := make([]data.NodeInfo, 0, len(nodes))
|
||||||
|
for _, node := range nodes {
|
||||||
|
nodesInfo = append(nodesInfo, newNodeInfo(node))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodesInfo
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Tree) determinePrefixNode(ctx context.Context, bktInfo *data.BucketInfo, treeID, prefix string) ([]uint64, string, error) {
|
func (c *Tree) determinePrefixNode(ctx context.Context, bktInfo *data.BucketInfo, treeID, prefix string) ([]uint64, string, error) {
|
||||||
rootID := []uint64{0}
|
rootID := []uint64{0}
|
||||||
path := strings.Split(prefix, separator)
|
path := strings.Split(prefix, separator)
|
||||||
|
|
As I see we don't use these fields