Skip to content
This repository has been archived by the owner on Feb 26, 2020. It is now read-only.

Commit

Permalink
Polish documentation and build
Browse files Browse the repository at this point in the history
Signed-off-by: Xavier Lucas <xavier.lucas@corp.ovh.com>
  • Loading branch information
Xavier Lucas committed Mar 9, 2016
1 parent 41b4ce7 commit 0cb0049
Show file tree
Hide file tree
Showing 12 changed files with 177 additions and 51 deletions.
5 changes: 5 additions & 0 deletions .travis.yml
@@ -0,0 +1,5 @@
language: go
go:
- 1.6
script: go install
install: true
3 changes: 3 additions & 0 deletions README.md
@@ -1,5 +1,8 @@
# The Swift Virtual File System

[![Build Status](https://travis-ci.org/xlucas/svfs.svg?branch=master)](https://travis-ci.org/xlucas/svfs)
[![GoDoc](https://godoc.org/github.com/xlucas/svfs/svfs?status.svg)](https://godoc.org/github.com/xlucas/svfs/svfs)

**SVFS** is a Virtual File System over Openstack Swift built upon fuse. It is compatible with [hubic](https://hubic.com),
[OVH Public Cloud Storage](https://www.ovh.com/fr/cloud/storage/object-storage) and basically every endpoint using a standard Openstack Swift setup. It brings a layer of abstraction over object storage, making it as accessible and convenient as a filesystem, without being intrusive on the way your data is stored.

Expand Down
92 changes: 56 additions & 36 deletions main.go
Expand Up @@ -16,18 +16,7 @@ import (
fusefs "bazil.org/fuse/fs"
)

func main() {
var (
debug bool
fs *svfs.SVFS
sc = swift.Connection{}
srv *fusefs.Server
conf = svfs.Config{}
cconf = svfs.CacheConfig{}
cpuProf string
memProf string
)

func parseFlags(debug *bool, conf *svfs.Config, cconf *svfs.CacheConfig, sc *swift.Connection, cpuProf, memProf *string) {
// Swift options
flag.StringVar(&sc.AuthUrl, "os-auth-url", "https://auth.cloud.ovh.net/v2.0", "Authentication URL")
flag.StringVar(&conf.Container, "os-container-name", "", "Container name")
Expand Down Expand Up @@ -57,31 +46,71 @@ func main() {

// Debug and profiling
log.SetOutput(os.Stdout)
flag.BoolVar(&debug, "debug", false, "Enable fuse debug log")
flag.StringVar(&cpuProf, "profile-cpu", "", "Write cpu profile to this file")
flag.StringVar(&memProf, "profile-ram", "", "Write memory profile to this file")
flag.BoolVar(debug, "debug", false, "Enable fuse debug log")
flag.StringVar(cpuProf, "profile-cpu", "", "Write cpu profile to this file")
flag.StringVar(memProf, "profile-ram", "", "Write memory profile to this file")

flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage : %s [OPTIONS] DEVICE MOUNTPOINT\n\n", os.Args[0])
fmt.Fprintf(os.Stderr, "Available options :\n")
flag.PrintDefaults()
}

flag.Parse()
}

func setDebug() {
fuse.Debug = func(msg interface{}) {
log.Printf("FUSE: %s\n", msg)
}
}

func createCPUProf(cpuProf string) {
f, err := os.Create(cpuProf)
if err != nil {
log.Fatal(err)
}
pprof.StartCPUProfile(f)
}

func createMemProf(memProf string) {
f, err := os.Create(memProf)
if err != nil {
log.Fatal(err)
}
pprof.WriteHeapProfile(f)
f.Close()
}

func main() {
var (
debug bool
fs = &svfs.SVFS{}
sc = swift.Connection{}
srv *fusefs.Server
conf = svfs.Config{}
cconf = svfs.CacheConfig{}
cpuProf string
memProf string
)

parseFlags(
&debug,
&conf,
&cconf,
&sc,
&cpuProf,
&memProf,
)

// Debug
if debug {
fuse.Debug = func(msg interface{}) {
log.Printf("FUSE: %s\n", msg)
}
setDebug()
}

// CPU profiling
if cpuProf != "" {
f, err := os.Create(cpuProf)
if err != nil {
log.Fatal(err)
}
pprof.StartCPUProfile(f)
createCPUProf(cpuProf)
defer pprof.StopCPUProfile()
}

Expand Down Expand Up @@ -110,14 +139,11 @@ func main() {

// Pre-Serve: authenticate to identity endpoint
// if no token is specified
if !sc.Authenticated() {
if err = sc.Authenticate(); err != nil {
goto Err
}
if !sc.Authenticated() && sc.Authenticate() != nil {
err = fmt.Errorf("Failed to authenticate to %s", sc.AuthUrl)
goto Err
}

// Init SVFS
fs = &svfs.SVFS{}
if err = fs.Init(&sc, &conf, &cconf); err != nil {
goto Err
}
Expand All @@ -133,13 +159,7 @@ func main() {

// Memory profiling
if memProf != "" {
f, err := os.Create(memProf)
if err != nil {
log.Fatal(err)
}
pprof.WriteHeapProfile(f)
f.Close()
return
createMemProf(memProf)
}

if err = c.MountError; err != nil {
Expand Down
24 changes: 24 additions & 0 deletions svfs/cache.go
Expand Up @@ -5,18 +5,26 @@ import (
"time"
)

// Cache holds a map of cache entries. Its size can be configured
// as well as cache entries access limit and expiration time.
type Cache struct {
config *CacheConfig
content map[string]*CacheValue
nodeCount uint64
}

// CacheConfig is the cache configuration.
type CacheConfig struct {
Timeout time.Duration
MaxEntries int64
MaxAccess int64
}

// CacheValue is the representation of a cache entry.
// It tracks expiration date, access count and holds
// a parent node with its children. It can be set
// as temporary, meaning that it will be stored within
// the cache but evicted on first access.
type CacheValue struct {
date time.Time
accessCount uint64
Expand All @@ -25,6 +33,7 @@ type CacheValue struct {
nodes map[string]Node
}

// NewCache creates a new cache
func NewCache(cconf *CacheConfig) *Cache {
return &Cache{
config: cconf,
Expand All @@ -36,6 +45,9 @@ func (c *Cache) key(container, path string) string {
return fmt.Sprintf("%s:%s", container, path)
}

// AddAll creates a new cache entry with the key container:path and a map of nodes
// as a value. Node represent the parent node type. If the cache entry count limit is
// reached, it will be marked as temporary thus evicted after one read.
func (c *Cache) AddAll(container, path string, node Node, nodes map[string]Node) {
entry := &CacheValue{
date: time.Now(),
Expand All @@ -54,6 +66,7 @@ func (c *Cache) AddAll(container, path string, node Node, nodes map[string]Node)
c.content[c.key(container, path)] = entry
}

// Delete removes a node from cache.
func (c *Cache) Delete(container, path, name string) {
v, ok := c.content[c.key(container, path)]
if !ok {
Expand All @@ -62,6 +75,7 @@ func (c *Cache) Delete(container, path, name string) {
delete(v.nodes, name)
}

// DeleteAll removes all nodes for the cache key container:path.
func (c *Cache) DeleteAll(container, path string) {
v, found := c.content[c.key(container, path)]
if found &&
Expand All @@ -71,6 +85,8 @@ func (c *Cache) DeleteAll(container, path string) {
}
}

// Get retrieves a specific node from the cache. It returns nil if
// the cache key container:path is missing.
func (c *Cache) Get(container, path, name string) Node {
v, ok := c.content[c.key(container, path)]
if !ok {
Expand All @@ -79,6 +95,9 @@ func (c *Cache) Get(container, path, name string) Node {
return v.nodes[name]
}

// GetAll retrieves all nodes for the cache key container:path. It returns
// the parent node and its children nodes. If the cache entry is not found
// or expired or access count exceeds the limit, both values will be nil.
func (c *Cache) GetAll(container, path string) (Node, map[string]Node) {
v, found := c.content[c.key(container, path)]

Expand All @@ -104,6 +123,9 @@ func (c *Cache) GetAll(container, path string) (Node, map[string]Node) {
return v.node, v.nodes
}

// Peek checks if a valid cache entry belongs to container:path
// key without changing cache access count for this entry.
// Returns the parent node with the result.
func (c *Cache) Peek(container, path string) (Node, bool) {
v, found := c.content[c.key(container, path)]

Expand All @@ -120,6 +142,8 @@ func (c *Cache) Peek(container, path string) (Node, bool) {
return v.node, true
}

// Set adds a specific node in cache, given a previous peek
// operation succeeded.
func (c *Cache) Set(container, path, name string, node Node) {
v, ok := c.content[c.key(container, path)]
if !ok {
Expand Down
3 changes: 3 additions & 0 deletions svfs/container.go
Expand Up @@ -6,10 +6,13 @@ import (
"golang.org/x/net/context"
)

// Container is a node representing a directory entry bound to
// a Swift container.
type Container struct {
*Directory
}

// Attr fills the container file attributes within the current context.
func (c *Container) Attr(ctx context.Context, a *fuse.Attr) error {
c.Directory.Attr(ctx, a)
a.Size = c.size()
Expand Down
34 changes: 34 additions & 0 deletions svfs/directory.go
Expand Up @@ -27,16 +27,28 @@ var (
DirectoryLister = new(DirLister)
)

// DirLister is a concurrent processor for segmented objects.
// Its job is to get information about manifests stored within
// directories.
type DirLister struct {
concurrency uint64
taskChan chan DirListerTask
}

// DirListerTask represents a manifest ready to be processed by
// the DirLister. Every task must provide a manifest object and
// a result channel to which retrieved information will be send.
type DirListerTask struct {
o *Object
rc chan<- *Object
}

// Start spawns workers waiting for tasks. Once a task comes
// in the task channel, one worker will process it by opening
// a connection to swift and asking information about the
// current manifest. The real size of the object is modified
// then it sends the modified object into the task result
// channel.
func (dl *DirLister) Start() {
dl.taskChan = make(chan DirListerTask, dl.concurrency)
for i := 0; uint64(i) < dl.concurrency; i++ {
Expand All @@ -53,6 +65,9 @@ func (dl *DirLister) Start() {
}
}

// AddTask asynchronously adds a new task to be processed. It
// returns immediately with no guarantee that the task has been
// added to the channel nor retrieved by a worker.
func (dl *DirLister) AddTask(o *Object, c chan<- *Object) {
go func() {
dl.taskChan <- DirListerTask{
Expand All @@ -62,6 +77,7 @@ func (dl *DirLister) AddTask(o *Object, c chan<- *Object) {
}()
}

// Directory represents a standard directory entry.
type Directory struct {
apex bool
name string
Expand All @@ -70,6 +86,7 @@ type Directory struct {
cs *swift.Container
}

// Attr fills file attributes of a directory within the current context.
func (d *Directory) Attr(ctx context.Context, a *fuse.Attr) error {
a.Mode = os.ModeDir | os.FileMode(DefaultMode)
a.Gid = uint32(DefaultGID)
Expand All @@ -78,6 +95,8 @@ func (d *Directory) Attr(ctx context.Context, a *fuse.Attr) error {
return nil
}

// Create makes a new object node represented by a file. It returns
// an object node and an opened file handle.
func (d *Directory) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) {
// Create an empty object in swift
path := d.path + req.Name
Expand Down Expand Up @@ -117,13 +136,17 @@ func (d *Directory) Create(ctx context.Context, req *fuse.CreateRequest, resp *f
return node, h, nil
}

// Export gives a direntry for the current directory node.
func (d *Directory) Export() fuse.Dirent {
return fuse.Dirent{
Name: d.name,
Type: fuse.DT_Dir,
}
}

// ReadDirAll reads the content of a directory and returns a
// list of children nodes as direntries, using/filling the
// cache of nodes.
func (d *Directory) ReadDirAll(ctx context.Context) (direntries []fuse.Dirent, err error) {
var (
dirs = make(map[string]bool)
Expand Down Expand Up @@ -230,6 +253,10 @@ func (d *Directory) ReadDirAll(ctx context.Context) (direntries []fuse.Dirent, e
return direntries, nil
}

// Lookup gets a children node if its name matches the requested direntry name.
// If the cache is empty for the current directory, it will fill it and try to
// match the requested direnty after this operation.
// It returns ENOENT if not found.
func (d *Directory) Lookup(ctx context.Context, req *fuse.LookupRequest, resp *fuse.LookupResponse) (fs.Node, error) {
if _, found := DirectoryCache.Peek(d.c.Name, d.path); !found {
d.ReadDirAll(ctx)
Expand All @@ -251,6 +278,8 @@ func (d *Directory) Lookup(ctx context.Context, req *fuse.LookupRequest, resp *f
return nil, fuse.ENOENT
}

// Mkdir creates a new directory node within the current directory. It is represented
// by an empty object ending with a slash in the Swift container.
func (d *Directory) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (fs.Node, error) {
var (
objName = req.Name + "/"
Expand All @@ -276,10 +305,13 @@ func (d *Directory) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (fs.Node,
return node, nil
}

// Name gets the direntry name
func (d *Directory) Name() string {
return d.name
}

// Remove deletes a direntry and relevant node. It is not supported on container
// nodes. It handles standard and segmented object deletion.
func (d *Directory) Remove(ctx context.Context, req *fuse.RemoveRequest) error {
path := d.path + req.Name

Expand Down Expand Up @@ -373,6 +405,8 @@ func (d *Directory) moveManifest(oldContainer, oldPath, oldName, newContainer, n
return nil
}

// Rename moves a node from its current directory node to a new directory node and updates
// the cache.
func (d *Directory) Rename(ctx context.Context, req *fuse.RenameRequest, newDir fs.Node) error {
if t, ok := newDir.(*Container); ok {
return d.move(d.c.Name, d.path, req.OldName, t.c.Name, t.path, req.NewName)
Expand Down

0 comments on commit 0cb0049

Please sign in to comment.