From b39b6a76e43785d6997e07a5ca23c7a8fe84313e Mon Sep 17 00:00:00 2001 From: Roman Loginov Date: Mon, 9 Oct 2023 10:49:15 +0300 Subject: [PATCH] [#85] Fix get latest version node Signed-off-by: Roman Loginov --- CHANGELOG.md | 1 + tree/tree.go | 39 ++++++++++++- tree/tree_test.go | 143 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 180 insertions(+), 3 deletions(-) create mode 100644 tree/tree_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index d323c89..63365db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ This document outlines major changes between releases. - Support impersonate bearer token (#40, #45) - Tracing support (#20, #44, #60) - Object name resolving with tree service (#30) +- Add selection of the node of the latest version of the object (#85) ### Changed - Update prometheus to v1.15.0 (#35) diff --git a/tree/tree.go b/tree/tree.go index 3a673b3..a9135eb 100644 --- a/tree/tree.go +++ b/tree/tree.go @@ -73,6 +73,7 @@ type Meta interface { type NodeResponse interface { GetMeta() []Meta + GetTimestamp() uint64 } func newTreeNode(nodeInfo NodeResponse) (*treeNode, error) { @@ -135,7 +136,7 @@ func (c *Tree) GetLatestVersion(ctx context.Context, cnrID *cid.ID, objectName s TreeID: versionTree, Path: path, Meta: meta, - LatestOnly: true, + LatestOnly: false, AllAttrs: false, } nodes, err := c.service.GetNodes(ctx, p) @@ -143,11 +144,43 @@ func (c *Tree) GetLatestVersion(ctx context.Context, cnrID *cid.ID, objectName s return nil, err } - if len(nodes) == 0 { + 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 newNodeVersion(nodes[0]) + 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 '/'. diff --git a/tree/tree_test.go b/tree/tree_test.go new file mode 100644 index 0000000..7cd2314 --- /dev/null +++ b/tree/tree_test.go @@ -0,0 +1,143 @@ +package tree + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +type nodeMeta struct { + key string + value []byte +} + +func (m nodeMeta) GetKey() string { + return m.key +} + +func (m nodeMeta) GetValue() []byte { + return m.value +} + +type nodeResponse struct { + meta []nodeMeta + timestamp uint64 +} + +func (n nodeResponse) GetTimestamp() uint64 { + return n.timestamp +} + +func (n nodeResponse) GetMeta() []Meta { + res := make([]Meta, len(n.meta)) + for i, value := range n.meta { + res[i] = value + } + return res +} + +func TestGetLatestNode(t *testing.T) { + for _, tc := range []struct { + name string + nodes []NodeResponse + exceptedOID string + error bool + }{ + { + name: "empty", + nodes: []NodeResponse{}, + error: true, + }, + { + name: "one node of the object version", + nodes: []NodeResponse{ + nodeResponse{ + timestamp: 1, + meta: []nodeMeta{ + { + key: oidKV, + value: []byte("oid1"), + }, + }, + }, + }, + exceptedOID: "oid1", + }, + { + name: "one node of the object version and one node of the secondary object", + nodes: []NodeResponse{ + nodeResponse{ + timestamp: 3, + meta: []nodeMeta{}, + }, + nodeResponse{ + timestamp: 1, + meta: []nodeMeta{ + { + key: oidKV, + value: []byte("oid1"), + }, + }, + }, + }, + exceptedOID: "oid1", + }, + { + name: "all nodes represent a secondary object", + nodes: []NodeResponse{ + nodeResponse{ + timestamp: 3, + meta: []nodeMeta{}, + }, + nodeResponse{ + timestamp: 5, + meta: []nodeMeta{}, + }, + }, + error: true, + }, + { + name: "several nodes of different types and with different timestamp", + nodes: []NodeResponse{ + nodeResponse{ + timestamp: 1, + meta: []nodeMeta{ + { + key: oidKV, + value: []byte("oid1"), + }, + }, + }, + nodeResponse{ + timestamp: 3, + meta: []nodeMeta{}, + }, + nodeResponse{ + timestamp: 4, + meta: []nodeMeta{ + { + key: oidKV, + value: []byte("oid2"), + }, + }, + }, + nodeResponse{ + timestamp: 6, + meta: []nodeMeta{}, + }, + }, + exceptedOID: "oid2", + }, + } { + t.Run(tc.name, func(t *testing.T) { + actualNode, err := getLatestNode(tc.nodes) + if tc.error { + require.Error(t, err) + return + } + + require.NoError(t, err) + require.Equal(t, tc.exceptedOID, string(actualNode.GetMeta()[0].GetValue())) + }) + } +}