Skip to content

Commit

Permalink
feat: add optional sidecar files for metadata
Browse files Browse the repository at this point in the history
This adds the option to store metadata for objects and buckets
within a specified directory:
bucket: <sidecar dir>/<bucket>/meta/<attribute>
object: <sidecar dir>/bucket/<object>/meta/<attribute>

Example invocation:
./versitygw -a myaccess -s mysecret posix --sidecar /tmp/sidecar /tmp/gw

The attributes are stored by name within the hidden directory.
  • Loading branch information
benmcclelland committed Jan 7, 2025
1 parent a1eb66d commit 000a20d
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 8 deletions.
125 changes: 125 additions & 0 deletions backend/meta/sidecar.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package meta

import (
"errors"
"fmt"
"os"
"path/filepath"
)

// SideCar is a metadata storer that uses sidecar files to store metadata.
type SideCar struct {
dir string
}

const (
sidecarmeta = "meta"
)

// NewSideCar creates a new SideCar metadata storer.
func NewSideCar(dir string) (SideCar, error) {
fi, err := os.Lstat(dir)
if err != nil {
return SideCar{}, fmt.Errorf("failed to stat directory: %v", err)
}
if !fi.IsDir() {
return SideCar{}, fmt.Errorf("not a directory")
}

return SideCar{dir: dir}, nil
}

// RetrieveAttribute retrieves the value of a specific attribute for an object or a bucket.
func (s SideCar) RetrieveAttribute(_ *os.File, bucket, object, attribute string) ([]byte, error) {
metadir := filepath.Join(s.dir, bucket, object, sidecarmeta)
if object == "" {
metadir = filepath.Join(s.dir, bucket, sidecarmeta)
}
attr := filepath.Join(metadir, attribute)

value, err := os.ReadFile(attr)
if errors.Is(err, os.ErrNotExist) {
return nil, ErrNoSuchKey
}
if err != nil {
return nil, fmt.Errorf("failed to read attribute: %v", err)
}

return value, nil
}

// StoreAttribute stores the value of a specific attribute for an object or a bucket.
func (s SideCar) StoreAttribute(_ *os.File, bucket, object, attribute string, value []byte) error {
metadir := filepath.Join(s.dir, bucket, object, sidecarmeta)
if object == "" {
metadir = filepath.Join(s.dir, bucket, sidecarmeta)
}
err := os.MkdirAll(metadir, 0777)
if err != nil {
return fmt.Errorf("failed to create metadata directory: %v", err)
}

attr := filepath.Join(metadir, attribute)
err = os.WriteFile(attr, value, 0666)
if err != nil {
return fmt.Errorf("failed to write attribute: %v", err)
}

return nil
}

// DeleteAttribute removes the value of a specific attribute for an object or a bucket.
func (s SideCar) DeleteAttribute(bucket, object, attribute string) error {
metadir := filepath.Join(s.dir, bucket, object, sidecarmeta)
if object == "" {
metadir = filepath.Join(s.dir, bucket, sidecarmeta)
}
attr := filepath.Join(metadir, attribute)

err := os.Remove(attr)
if errors.Is(err, os.ErrNotExist) {
return ErrNoSuchKey
}
if err != nil {
return fmt.Errorf("failed to remove attribute: %v", err)
}

return nil
}

// ListAttributes lists all attributes for an object or a bucket.
func (s SideCar) ListAttributes(bucket, object string) ([]string, error) {
metadir := filepath.Join(s.dir, bucket, object, sidecarmeta)
if object == "" {
metadir = filepath.Join(s.dir, bucket, sidecarmeta)
}

ents, err := os.ReadDir(metadir)
if errors.Is(err, os.ErrNotExist) {
return []string{}, nil
}
if err != nil {
return nil, fmt.Errorf("failed to list attributes: %v", err)
}

var attrs []string
for _, ent := range ents {
attrs = append(attrs, ent.Name())
}

return attrs, nil
}

// DeleteAttributes removes all attributes for an object or a bucket.
func (s SideCar) DeleteAttributes(bucket, object string) error {
metadir := filepath.Join(s.dir, bucket, object, sidecarmeta)
if object == "" {
metadir = filepath.Join(s.dir, bucket, sidecarmeta)
}

err := os.RemoveAll(metadir)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("failed to remove attributes: %v", err)
}
return nil
}
18 changes: 17 additions & 1 deletion backend/posix/posix.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,14 @@ type PosixOpts struct {
BucketLinks bool
VersioningDir string
NewDirPerm fs.FileMode
SideCarDir string
}

func New(rootdir string, meta meta.MetadataStorer, opts PosixOpts) (*Posix, error) {
if opts.SideCarDir != "" && strings.HasPrefix(opts.SideCarDir, rootdir) {
return nil, fmt.Errorf("sidecar directory cannot be inside the gateway root directory")
}

err := os.Chdir(rootdir)
if err != nil {
return nil, fmt.Errorf("chdir %v: %w", rootdir, err)
Expand Down Expand Up @@ -159,7 +164,13 @@ func New(rootdir string, meta meta.MetadataStorer, opts PosixOpts) (*Posix, erro
}
}

fmt.Printf("Bucket versioning enabled with directory: %v\n", verioningdirAbs)
if verioningdirAbs != "" {
fmt.Printf("Bucket versioning enabled with directory: %v\n", verioningdirAbs)
}

if opts.SideCarDir != "" {
fmt.Println("Using sidecar directory for metadata: ", opts.SideCarDir)
}

return &Posix{
meta: meta,
Expand Down Expand Up @@ -219,6 +230,11 @@ func (p *Posix) ListBuckets(_ context.Context, input s3response.ListBucketsInput

var buckets []s3response.ListAllMyBucketsEntry
for _, entry := range entries {
if !entry.IsDir() {
// buckets must be a directory
continue
}

fi, err := entry.Info()
if err != nil {
// skip entries returning errors
Expand Down
36 changes: 29 additions & 7 deletions cmd/versitygw/posix.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ var (
bucketlinks bool
versioningDir string
dirPerms uint
sidecar string
)

func posixCommand() *cli.Command {
Expand Down Expand Up @@ -79,6 +80,12 @@ will be translated into the file /mnt/fs/gwroot/mybucket/a/b/c/myobject`,
DefaultText: "0755",
Value: 0755,
},
&cli.StringFlag{
Name: "sidecar",
Usage: "use provided sidecar directory to store metadata",
EnvVars: []string{"VGW_META_SIDECAR"},
Destination: &sidecar,
},
},
}
}
Expand All @@ -89,24 +96,39 @@ func runPosix(ctx *cli.Context) error {
}

gwroot := (ctx.Args().Get(0))
err := meta.XattrMeta{}.Test(gwroot)
if err != nil {
return fmt.Errorf("posix xattr check: %v", err)
}

if dirPerms > math.MaxUint32 {
return fmt.Errorf("invalid directory permissions: %d", dirPerms)
}

be, err := posix.New(gwroot, meta.XattrMeta{}, posix.PosixOpts{
opts := posix.PosixOpts{
ChownUID: chownuid,
ChownGID: chowngid,
BucketLinks: bucketlinks,
VersioningDir: versioningDir,
NewDirPerm: fs.FileMode(dirPerms),
})
}

var ms meta.MetadataStorer
switch {
case sidecar != "":
sc, err := meta.NewSideCar(sidecar)
if err != nil {
return fmt.Errorf("failed to init sidecar metadata: %w", err)
}
ms = sc
opts.SideCarDir = sidecar
default:
ms = meta.XattrMeta{}
err := meta.XattrMeta{}.Test(gwroot)
if err != nil {
return fmt.Errorf("xattr check failed: %w", err)
}
}

be, err := posix.New(gwroot, ms, opts)
if err != nil {
return fmt.Errorf("init posix: %v", err)
return fmt.Errorf("failed to init posix backend: %w", err)
}

return runGateway(ctx.Context, be)
Expand Down

0 comments on commit 000a20d

Please sign in to comment.