Skip to content

Commit

Permalink
add missing lookup package
Browse files Browse the repository at this point in the history
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
  • Loading branch information
butonic committed Feb 16, 2022
1 parent af48c52 commit c7acad0
Show file tree
Hide file tree
Showing 2 changed files with 330 additions and 0 deletions.
198 changes: 198 additions & 0 deletions pkg/storage/utils/decomposedfs/lookup/lookup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
// Copyright 2018-2021 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.

package lookup

import (
"context"
"fmt"
"path/filepath"
"strings"

provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/cs3org/reva/pkg/appctx"
ctxpkg "github.com/cs3org/reva/pkg/ctx"
"github.com/cs3org/reva/pkg/errtypes"
"github.com/cs3org/reva/pkg/storage/utils/decomposedfs/node"
"github.com/cs3org/reva/pkg/storage/utils/decomposedfs/options"
"github.com/cs3org/reva/pkg/storage/utils/decomposedfs/xattrs"
"github.com/cs3org/reva/pkg/storage/utils/templates"
)

// Lookup implements transformations from filepath to node and back
type Lookup struct {
Options *options.Options
}

// NodeFromResource takes in a request path or request id and converts it to a Node
func (lu *Lookup) NodeFromResource(ctx context.Context, ref *provider.Reference) (*node.Node, error) {
if ref.ResourceId != nil {
// check if a storage space reference is used
// currently, the decomposed fs uses the root node id as the space id
n, err := lu.NodeFromID(ctx, ref.ResourceId)
if err != nil {
return nil, err
}
// is this a relative reference?
if ref.Path != "" {
p := filepath.Clean(ref.Path)
if p != "." && p != "/" {
// walk the relative path
n, err = lu.WalkPath(ctx, n, p, false, func(ctx context.Context, n *node.Node) error { return nil })
if err != nil {
return nil, err
}
n.SpaceID = ref.ResourceId.StorageId
}
}
return n, nil
}

// reference is invalid
return nil, fmt.Errorf("invalid reference %+v. resource_id must be set", ref)
}

// NodeFromID returns the internal path for the id
func (lu *Lookup) NodeFromID(ctx context.Context, id *provider.ResourceId) (n *node.Node, err error) {
if id == nil {
return nil, fmt.Errorf("invalid resource id %+v", id)
}
if id.OpaqueId == "" {
// The Resource references the root of a space
return lu.NodeFromSpaceID(ctx, id)
}
n, err = node.ReadNode(ctx, lu, id.StorageId, id.OpaqueId)
if err != nil {
return nil, err
}

return n, n.FindStorageSpaceRoot()
}

// Pathify segments the beginning of a string into depth segments of width length
// Pathify("aabbccdd", 3, 1) will return "a/a/b/bccdd"
func Pathify(id string, depth, width int) string {
b := strings.Builder{}
i := 0
for ; i < depth; i++ {
if len(id) <= i*width+width {
break
}
b.WriteString(id[i*width : i*width+width])
b.WriteRune(filepath.Separator)
}
b.WriteString(id[i*width:])
return b.String()
}

// NodeFromSpaceID converts a resource id without an opaque id into a Node
func (lu *Lookup) NodeFromSpaceID(ctx context.Context, id *provider.ResourceId) (n *node.Node, err error) {
node, err := node.ReadNode(ctx, lu, id.StorageId, id.StorageId)
if err != nil {
return nil, err
}

node.SpaceRoot = node
return node, nil
}

// Path returns the path for node
func (lu *Lookup) Path(ctx context.Context, n *node.Node) (p string, err error) {
root := n.SpaceRoot
for n.ID != root.ID {
p = filepath.Join(n.Name, p)
if n, err = n.Parent(); err != nil {
appctx.GetLogger(ctx).
Error().Err(err).
Str("path", p).
Interface("node", n).
Msg("Path()")
return
}
}
p = filepath.Join("/", p)
return
}

// RootNode returns the root node of the storage
func (lu *Lookup) RootNode(ctx context.Context) (*node.Node, error) {
n := node.New(node.NoSpaceID, node.RootID, "", "", 0, "", nil, lu)
n.Exists = true
return n, nil
}

// WalkPath calls n.Child(segment) on every path segment in p starting at the node r.
// If a function f is given it will be executed for every segment node, but not the root node r.
// If followReferences is given the current visited reference node is replaced by the referenced node.
func (lu *Lookup) WalkPath(ctx context.Context, r *node.Node, p string, followReferences bool, f func(ctx context.Context, n *node.Node) error) (*node.Node, error) {
segments := strings.Split(strings.Trim(p, "/"), "/")
var err error
for i := range segments {
if r, err = r.Child(ctx, segments[i]); err != nil {
return r, err
}

if followReferences {
if attrBytes, err := r.GetMetadata(xattrs.ReferenceAttr); err == nil {
realNodeID := attrBytes
ref, err := xattrs.ReferenceFromAttr([]byte(realNodeID))
if err != nil {
return nil, err
}

r, err = lu.NodeFromID(ctx, ref.ResourceId)
if err != nil {
return nil, err
}
}
}
if node.IsSpaceRoot(r) {
r.SpaceRoot = r
}

if !r.Exists && i < len(segments)-1 {
return r, errtypes.NotFound(segments[i])
}
if f != nil {
if err = f(ctx, r); err != nil {
return r, err
}
}
}
return r, nil
}

// InternalRoot returns the internal storage root directory
func (lu *Lookup) InternalRoot() string {
return lu.Options.Root
}

// InternalPath returns the internal path for a given ID
func (lu *Lookup) InternalPath(spaceID, nodeID string) string {
return filepath.Join(lu.Options.Root, "spaces", Pathify(spaceID, 1, 2), "nodes", Pathify(nodeID, 4, 2))
}

func (lu *Lookup) mustGetUserLayout(ctx context.Context) string {
u := ctxpkg.ContextMustGetUser(ctx)
return templates.WithUser(u, lu.Options.UserLayout)
}

// ShareFolder returns the internal storage root directory
func (lu *Lookup) ShareFolder() string {
return lu.Options.ShareFolder
}
132 changes: 132 additions & 0 deletions pkg/storage/utils/decomposedfs/lookup/lookup_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// Copyright 2018-2021 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.

package lookup_test

import (
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
helpers "github.com/cs3org/reva/pkg/storage/utils/decomposedfs/testhelpers"
"github.com/cs3org/reva/pkg/storage/utils/decomposedfs/xattrs"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

var _ = Describe("Lookup", func() {
var (
env *helpers.TestEnv
)

JustBeforeEach(func() {
var err error
env, err = helpers.NewTestEnv()
Expect(err).ToNot(HaveOccurred())
})

AfterEach(func() {
if env != nil {
env.Cleanup()
}
})

Describe("Node from path", func() {
It("returns the path including a leading slash", func() {
n, err := env.Lookup.NodeFromResource(env.Ctx, &provider.Reference{
ResourceId: env.SpaceRootRes,
Path: "/dir1/file1",
})
Expect(err).ToNot(HaveOccurred())

path, err := env.Lookup.Path(env.Ctx, n)
Expect(err).ToNot(HaveOccurred())
Expect(path).To(Equal("/dir1/file1"))
})
})

Describe("Node From Resource only by path", func() {
It("returns the path including a leading slash and the space root is set", func() {
n, err := env.Lookup.NodeFromResource(env.Ctx, &provider.Reference{
ResourceId: env.SpaceRootRes,
Path: "/dir1/subdir1/file2",
})
Expect(err).ToNot(HaveOccurred())

path, err := env.Lookup.Path(env.Ctx, n)
Expect(err).ToNot(HaveOccurred())
Expect(path).To(Equal("/dir1/subdir1/file2"))
Expect(n.SpaceRoot.Name).To(Equal("userid"))
Expect(n.SpaceRoot.ParentID).To(Equal("root"))
})
})

Describe("Node From Resource only by id", func() {
It("returns the path including a leading slash and the space root is set", func() {
// do a node lookup by path
nRef, err := env.Lookup.NodeFromResource(env.Ctx, &provider.Reference{
ResourceId: env.SpaceRootRes,
Path: "/dir1/file1",
})
Expect(err).ToNot(HaveOccurred())

// try to find the same node by id
n, err := env.Lookup.NodeFromResource(env.Ctx, &provider.Reference{ResourceId: &provider.ResourceId{OpaqueId: nRef.ID}})
Expect(err).ToNot(HaveOccurred())
Expect(n.SpaceRoot).ToNot(BeNil())

// Check if we got the right node and spaceRoot
path, err := env.Lookup.Path(env.Ctx, n)
Expect(err).ToNot(HaveOccurred())
Expect(path).To(Equal("/dir1/file1"))
Expect(n.SpaceRoot.Name).To(Equal("userid"))
Expect(n.SpaceRoot.ParentID).To(Equal("root"))
})
})

Describe("Node From Resource by id and relative path", func() {
It("returns the path including a leading slash and the space root is set", func() {
// do a node lookup by path for the parent
nRef, err := env.Lookup.NodeFromResource(env.Ctx, &provider.Reference{
ResourceId: env.SpaceRootRes,
Path: "/dir1",
})
Expect(err).ToNot(HaveOccurred())

// try to find the child node by parent id and relative path
n, err := env.Lookup.NodeFromResource(env.Ctx, &provider.Reference{ResourceId: &provider.ResourceId{OpaqueId: nRef.ID}, Path: "./file1"})
Expect(err).ToNot(HaveOccurred())
Expect(n.SpaceRoot).ToNot(BeNil())

// Check if we got the right node and spaceRoot
path, err := env.Lookup.Path(env.Ctx, n)
Expect(err).ToNot(HaveOccurred())
Expect(path).To(Equal("/dir1/file1"))
Expect(n.SpaceRoot.Name).To(Equal("userid"))
Expect(n.SpaceRoot.ParentID).To(Equal("root"))
})
})

Describe("Reference Parsing", func() {
It("parses a valid cs3 reference", func() {
in := []byte("cs3:bede11a0-ea3d-11eb-a78b-bf907adce8ed/c402d01c-ea3d-11eb-a0fc-c32f9d32528f")
ref, err := xattrs.ReferenceFromAttr(in)

Expect(err).ToNot(HaveOccurred())
Expect(ref.ResourceId.StorageId).To(Equal("bede11a0-ea3d-11eb-a78b-bf907adce8ed"))
Expect(ref.ResourceId.OpaqueId).To(Equal("c402d01c-ea3d-11eb-a0fc-c32f9d32528f"))
})
})
})

0 comments on commit c7acad0

Please sign in to comment.