Skip to content

Commit

Permalink
treetime based etag propagation
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 Sep 18, 2020
1 parent d9ace48 commit 1e60927
Show file tree
Hide file tree
Showing 6 changed files with 244 additions and 60 deletions.
1 change: 1 addition & 0 deletions pkg/storage/fs/ocis/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func (fs *ocisfs) SetArbitraryMetadata(ctx context.Context, ref *provider.Refere
}
nodePath := filepath.Join(fs.pw.Root, "nodes", n.ID)
for k, v := range md.Metadata {
// TODO set etag as temporary etag tmpEtagAttr
attrName := metadataPrefix + k
if err = xattr.Set(nodePath, attrName, []byte(v)); err != nil {
return errors.Wrap(err, "ocisfs: could not set metadata attribute "+attrName+" to "+k)
Expand Down
81 changes: 58 additions & 23 deletions pkg/storage/fs/ocis/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,13 @@ package ocis

import (
"context"
"crypto/md5"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"time"

userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
Expand All @@ -47,17 +50,17 @@ type Node struct {

func (n *Node) writeMetadata(owner *userpb.UserId) (err error) {
nodePath := filepath.Join(n.pw.Root, "nodes", n.ID)
if err = xattr.Set(nodePath, "user.ocis.parentid", []byte(n.ParentID)); err != nil {
if err = xattr.Set(nodePath, parentidAttr, []byte(n.ParentID)); err != nil {
return errors.Wrap(err, "ocisfs: could not set parentid attribute")
}
if err = xattr.Set(nodePath, "user.ocis.name", []byte(n.Name)); err != nil {
if err = xattr.Set(nodePath, nameAttr, []byte(n.Name)); err != nil {
return errors.Wrap(err, "ocisfs: could not set name attribute")
}
if owner != nil {
if err = xattr.Set(nodePath, "user.ocis.owner.id", []byte(owner.OpaqueId)); err != nil {
if err = xattr.Set(nodePath, ownerIDAttr, []byte(owner.OpaqueId)); err != nil {
return errors.Wrap(err, "ocisfs: could not set owner id attribute")
}
if err = xattr.Set(nodePath, "user.ocis.owner.idp", []byte(owner.Idp)); err != nil {
if err = xattr.Set(nodePath, ownerIDPAttr, []byte(owner.Idp)); err != nil {
return errors.Wrap(err, "ocisfs: could not set owner idp attribute")
}
}
Expand All @@ -75,13 +78,13 @@ func ReadNode(ctx context.Context, pw *Path, id string) (n *Node, err error) {

// lookup parent id in extended attributes
var attrBytes []byte
if attrBytes, err = xattr.Get(nodePath, "user.ocis.parentid"); err == nil {
if attrBytes, err = xattr.Get(nodePath, parentidAttr); err == nil {
n.ParentID = string(attrBytes)
} else {
return
}
// lookup name in extended attributes
if attrBytes, err = xattr.Get(nodePath, "user.ocis.name"); err == nil {
if attrBytes, err = xattr.Get(nodePath, nameAttr); err == nil {
n.Name = string(attrBytes)
} else {
return
Expand All @@ -99,7 +102,7 @@ func ReadNode(ctx context.Context, pw *Path, id string) (n *Node, err error) {
// walk to root to check node is not part of a deleted subtree
parentPath := filepath.Join(n.pw.Root, "nodes", parentID)

if attrBytes, err = xattr.Get(parentPath, "user.ocis.parentid"); err == nil {
if attrBytes, err = xattr.Get(parentPath, parentidAttr); err == nil {
parentID = string(attrBytes)
log.Debug().Interface("node", n).Str("root.ID", root.ID).Str("parentID", parentID).Msg("ReadNode() found parent")
} else {
Expand Down Expand Up @@ -156,13 +159,13 @@ func (n *Node) Parent() (p *Node, err error) {

// lookup parent id in extended attributes
var attrBytes []byte
if attrBytes, err = xattr.Get(parentPath, "user.ocis.parentid"); err == nil {
if attrBytes, err = xattr.Get(parentPath, parentidAttr); err == nil {
p.ParentID = string(attrBytes)
} else {
return
}
// lookup name in extended attributes
if attrBytes, err = xattr.Get(parentPath, "user.ocis.name"); err == nil {
if attrBytes, err = xattr.Get(parentPath, nameAttr); err == nil {
p.Name = string(attrBytes)
} else {
return
Expand All @@ -186,13 +189,13 @@ func (n *Node) Owner() (id string, idp string, err error) {
// lookup parent id in extended attributes
var attrBytes []byte
// lookup name in extended attributes
if attrBytes, err = xattr.Get(nodePath, "user.ocis.owner.id"); err == nil {
if attrBytes, err = xattr.Get(nodePath, ownerIDAttr); err == nil {
n.ownerID = string(attrBytes)
} else {
return
}
// lookup name in extended attributes
if attrBytes, err = xattr.Get(nodePath, "user.ocis.owner.idp"); err == nil {
if attrBytes, err = xattr.Get(nodePath, ownerIDPAttr); err == nil {
n.ownerIDP = string(attrBytes)
} else {
return
Expand Down Expand Up @@ -230,32 +233,22 @@ func (n *Node) AsResourceInfo(ctx context.Context) (ri *provider.ResourceInfo, e
// nodeType = provider.ResourceType_RESOURCE_TYPE_REFERENCE
}

var etag []byte
// TODO optionally store etag in new `root/attributes/<uuid>` file
if etag, err = xattr.Get(nodePath, "user.ocis.etag"); err != nil {
log.Error().Err(err).Interface("node", n).Msg("could not read etag")
}

id := &provider.ResourceId{OpaqueId: n.ID}

fn, err = n.pw.Path(ctx, n)
if err != nil {
return nil, err
}

ri = &provider.ResourceInfo{
Id: id,
Path: fn,
Type: nodeType,
Etag: string(etag),
MimeType: mime.Detect(nodeType == provider.ResourceType_RESOURCE_TYPE_CONTAINER, fn),
Size: uint64(fi.Size()),
// TODO fix permissions
PermissionSet: &provider.ResourcePermissions{ListContainer: true, CreateContainer: true},
Mtime: &types.Timestamp{
Seconds: uint64(fi.ModTime().Unix()),
// TODO read nanos from where? Nanos: fi.MTimeNanos,
},
Target: string(target),
Target: string(target),
}

if owner, idp, err := n.Owner(); err == nil {
Expand All @@ -265,6 +258,48 @@ func (n *Node) AsResourceInfo(ctx context.Context) (ri *provider.ResourceInfo, e
}
}

// etag currently is a hash of fileid + tmtime (or mtime)
// TODO make etag of files use fileid and checksum
// TODO implment adding temporery etag in an attribute to restore backups
h := md5.New()
if _, err := io.WriteString(h, n.ID); err != nil {
return nil, err
}
var b []byte
var tmTime time.Time
if b, err = xattr.Get(nodePath, treeMTimeAttr); err == nil {
if tmTime, err = time.Parse(time.RFC3339Nano, string(b)); err != nil {
// invalid format, overwrite
log.Error().Err(err).Interface("node", n).Str("tmtime", string(b)).Msg("invalid format, ignoring")
tmTime = fi.ModTime()
}
} else {
// no tmtime, use mtime
tmTime = fi.ModTime()
}
if tb, err := tmTime.UTC().MarshalBinary(); err == nil {
if _, err := h.Write(tb); err != nil {
return nil, err
}
} else {
return nil, err
}

// use temporary etag if it is set
if b, err := xattr.Get(nodePath, tmpEtagAttr); err == nil {
ri.Etag = string(b)
} else {
ri.Etag = fmt.Sprintf("%x", h.Sum(nil))
}

// mtime uses tmtime if present
// TODO expose mtime and tmtime separately?
un := tmTime.UnixNano()
ri.Mtime = &types.Timestamp{
Seconds: uint64(un / 1000000000),
Nanos: uint32(un % 1000000000),
}

// TODO only read the requested metadata attributes
if attrs, err := xattr.List(nodePath); err == nil {
ri.ArbitraryMetadata = &provider.ArbitraryMetadata{
Expand Down
67 changes: 55 additions & 12 deletions pkg/storage/fs/ocis/ocis.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"strings"

provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/cs3org/reva/pkg/appctx"
"github.com/cs3org/reva/pkg/errtypes"
"github.com/cs3org/reva/pkg/logger"
"github.com/cs3org/reva/pkg/storage"
Expand All @@ -47,16 +48,39 @@ const (
// collisions with other apps We are going to introduce a sub namespace
// "user.ocis."

ocisPrefix string = "user.ocis."
parentidAttr string = ocisPrefix + "parentid"
ownerIDAttr string = ocisPrefix + "owner.id"
ownerIDPAttr string = ocisPrefix + "owner.idp"
// the base name of the node
// updated when the file is renamed or moved
nameAttr string = ocisPrefix + "name"

// SharePrefix is the prefix for sharing related extended attributes
sharePrefix string = "user.ocis.acl."
metadataPrefix string = "user.ocis.md."
sharePrefix string = ocisPrefix + "acl."
metadataPrefix string = ocisPrefix + "md."
// TODO implement favorites metadata flag
//favPrefix string = "user.ocis.fav." // favorite flag, per user
// TODO use etag prefix instead of single etag property
//etagPrefix string = "user.ocis.etag." // allow overriding a calculated etag with one from the extended attributes
referenceAttr string = "user.ocis.cs3.ref" // arbitrary metadata
//checksumPrefix string = "user.ocis.cs." // TODO add checksum support
trashOriginAttr string = "user.ocis.trash.origin" // trash origin
//favPrefix string = ocisPrefix + "fav." // favorite flag, per user

// a temporary etag for a folder that is removed when the mtime propagation happens
tmpEtagAttr string = ocisPrefix + "tmp.etag"
referenceAttr string = ocisPrefix + "cs3.ref" // arbitrary metadata
//checksumPrefix string = ocisPrefix + "cs." // TODO add checksum support
trashOriginAttr string = ocisPrefix + "trash.origin" // trash origin

// we use a single attribute to enable or disable propagation of both: synctime and treesize
propagationAttr string = ocisPrefix + "propagation"

// the tree modification time of the tree below this node,
// propagated when synctime_accounting is true and
// user.ocis.propagation=1 is set
// stored as a readable time.RFC3339Nano
treeMTimeAttr string = ocisPrefix + "tmtime"

// the size of the tree below this node,
// propagated when treesize_accounting is true and
// user.ocis.propagation=1 is set
//treesizeAttr string = ocisPrefix + "treesize"
)

func init() {
Expand Down Expand Up @@ -149,18 +173,27 @@ func (fs *ocisfs) CreateHome(ctx context.Context) (err error) {
return errtypes.NotSupported("ocisfs: CreateHome() home supported disabled")
}

var n *Node
var n, h *Node
if n, err = fs.pw.RootNode(ctx); err != nil {
return
}
_, err = fs.pw.WalkPath(ctx, n, fs.pw.mustGetUserLayout(ctx), func(ctx context.Context, n *Node) error {
h, err = fs.pw.WalkPath(ctx, n, fs.pw.mustGetUserLayout(ctx), func(ctx context.Context, n *Node) error {
if !n.Exists {
if err := fs.tp.CreateDir(ctx, n); err != nil {
return err
}
}
return nil
})

if fs.pw.TreeTimeAccounting {
homePath := filepath.Join(fs.pw.Root, "nodes", h.ID)
// mark the home node as the end of propagation
if err = xattr.Set(homePath, propagationAttr, []byte("1")); err != nil {
appctx.GetLogger(ctx).Error().Err(err).Interface("node", h).Msg("could not mark home as propagation root")
return
}
}
return
}

Expand Down Expand Up @@ -190,7 +223,17 @@ func (fs *ocisfs) CreateDir(ctx context.Context, fn string) (err error) {
if node.Exists {
return errtypes.AlreadyExists(fn)
}
return fs.tp.CreateDir(ctx, node)
err = fs.tp.CreateDir(ctx, node)

if fs.pw.TreeTimeAccounting {
nodePath := filepath.Join(fs.pw.Root, "nodes", node.ID)
// mark the home node as the end of propagation
if err = xattr.Set(nodePath, propagationAttr, []byte("1")); err != nil {
appctx.GetLogger(ctx).Error().Err(err).Interface("node", node).Msg("could not mark node to propagate")
return
}
}
return
}

// CreateReference creates a reference as a node folder with the target stored in extended attributes
Expand Down Expand Up @@ -347,7 +390,7 @@ func (fs *ocisfs) copyMD(s string, t string) (err error) {
return err
}
for i := range attrs {
if strings.HasPrefix(attrs[i], "user.ocis.") {
if strings.HasPrefix(attrs[i], ocisPrefix) {
var d []byte
if d, err = xattr.Get(s, attrs[i]); err != nil {
return err
Expand Down
10 changes: 8 additions & 2 deletions pkg/storage/fs/ocis/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,16 @@ type Path struct {
UserLayout string `mapstructure:"user_layout"`

// TODO NodeLayout option to save nodes as eg. nodes/1d/d8/1dd84abf-9466-4e14-bb86-02fc4ea3abcf
ShareFolder string `mapstructure:"share_folder"`

// EnableHome enables the creation of home directories.
EnableHome bool `mapstructure:"enable_home"`
ShareFolder string `mapstructure:"share_folder"`
EnableHome bool `mapstructure:"enable_home"`

// propagate mtime changes as tmtime (tree modification time) to the parent directory when user.ocis.propagation=1 is set on a node
TreeTimeAccounting bool `mapstructure:"treetime_accounting"`

// propagate size changes as treesize
TreeSizeAccounting bool `mapstructure:"treesize_accounting"`
}

// NodeFromResource takes in a request path or request id and converts it to a Node
Expand Down
Loading

0 comments on commit 1e60927

Please sign in to comment.