Roman Loginov
b39b6a76e4
All checks were successful
/ DCO (pull_request) Successful in 1m1s
/ Vulncheck (pull_request) Successful in 1m34s
/ Builds (1.20) (pull_request) Successful in 2m0s
/ Builds (1.21) (pull_request) Successful in 1m56s
/ Lint (pull_request) Successful in 13m18s
/ Tests (1.20) (pull_request) Successful in 2m11s
/ Tests (1.21) (pull_request) Successful in 9m15s
Signed-off-by: Roman Loginov <r.loginov@yadro.com>
189 lines
3.9 KiB
Go
189 lines
3.9 KiB
Go
package tree
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/api"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-http-gw/internal/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
|
|
GetTimestamp() uint64
|
|
}
|
|
|
|
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: false,
|
|
AllAttrs: false,
|
|
}
|
|
nodes, err := c.service.GetNodes(ctx, p)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
latestNode, err := getLatestNode(nodes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return newNodeVersion(latestNode)
|
|
}
|
|
|
|
func getLatestNode(nodes []NodeResponse) (NodeResponse, error) {
|
|
var (
|
|
maxCreationTime uint64
|
|
targetIndexNode = -1
|
|
)
|
|
|
|
for i, node := range nodes {
|
|
currentCreationTime := node.GetTimestamp()
|
|
if checkExistOID(node.GetMeta()) && currentCreationTime > maxCreationTime {
|
|
maxCreationTime = currentCreationTime
|
|
targetIndexNode = i
|
|
}
|
|
}
|
|
|
|
if targetIndexNode == -1 {
|
|
return nil, layer.ErrNodeNotFound
|
|
}
|
|
|
|
return nodes[targetIndexNode], nil
|
|
}
|
|
|
|
func checkExistOID(meta []Meta) bool {
|
|
for _, kv := range meta {
|
|
if kv.GetKey() == "OID" {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// pathFromName splits name by '/'.
|
|
func pathFromName(objectName string) []string {
|
|
return strings.Split(objectName, separator)
|
|
}
|