13111: Add /users virtual dir to siteFS.
[arvados.git] / sdk / go / arvados / fs_site.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: Apache-2.0
4
5 package arvados
6
7 import (
8         "os"
9         "sync"
10         "time"
11 )
12
13 type CustomFileSystem interface {
14         FileSystem
15         MountByID(mount string)
16         MountProject(mount, uuid string)
17         MountUsers(mount string)
18 }
19
20 type customFileSystem struct {
21         fileSystem
22         root *vdirnode
23
24         staleThreshold time.Time
25         staleLock      sync.Mutex
26 }
27
28 func (c *Client) CustomFileSystem(kc keepClient) CustomFileSystem {
29         root := &vdirnode{}
30         fs := &customFileSystem{
31                 root: root,
32                 fileSystem: fileSystem{
33                         fsBackend: keepBackend{apiClient: c, keepClient: kc},
34                         root:      root,
35                 },
36         }
37         root.inode = &treenode{
38                 fs:     fs,
39                 parent: root,
40                 fileinfo: fileinfo{
41                         name:    "/",
42                         mode:    os.ModeDir | 0755,
43                         modTime: time.Now(),
44                 },
45                 inodes: make(map[string]inode),
46         }
47         return fs
48 }
49
50 func (fs *customFileSystem) MountByID(mount string) {
51         fs.root.inode.Child(mount, func(inode) (inode, error) {
52                 return &vdirnode{
53                         inode: &treenode{
54                                 fs:     fs,
55                                 parent: fs.root,
56                                 inodes: make(map[string]inode),
57                                 fileinfo: fileinfo{
58                                         name:    mount,
59                                         modTime: time.Now(),
60                                         mode:    0755 | os.ModeDir,
61                                 },
62                         },
63                         create: fs.mountCollection,
64                 }, nil
65         })
66 }
67
68 func (fs *customFileSystem) MountProject(mount, uuid string) {
69         fs.root.inode.Child(mount, func(inode) (inode, error) {
70                 return fs.newProjectNode(fs.root, mount, uuid), nil
71         })
72 }
73
74 func (fs *customFileSystem) MountUsers(mount string) {
75         fs.root.inode.Child(mount, func(inode) (inode, error) {
76                 return &usersnode{
77                         inode: &treenode{
78                                 fs:     fs,
79                                 parent: fs.root,
80                                 inodes: make(map[string]inode),
81                                 fileinfo: fileinfo{
82                                         name:    mount,
83                                         modTime: time.Now(),
84                                         mode:    0755 | os.ModeDir,
85                                 },
86                         },
87                 }, nil
88         })
89 }
90
91 // SiteFileSystem returns a FileSystem that maps collections and other
92 // Arvados objects onto a filesystem layout.
93 //
94 // This is experimental: the filesystem layout is not stable, and
95 // there are significant known bugs and shortcomings. For example,
96 // writes are not persisted until Sync() is called.
97 func (c *Client) SiteFileSystem(kc keepClient) CustomFileSystem {
98         fs := c.CustomFileSystem(kc)
99         fs.MountByID("by_id")
100         fs.MountUsers("users")
101         return fs
102 }
103
104 func (fs *customFileSystem) Sync() error {
105         fs.staleLock.Lock()
106         defer fs.staleLock.Unlock()
107         fs.staleThreshold = time.Now()
108         return nil
109 }
110
111 // Stale returns true if information obtained at time t should be
112 // considered stale.
113 func (fs *customFileSystem) Stale(t time.Time) bool {
114         fs.staleLock.Lock()
115         defer fs.staleLock.Unlock()
116         return !fs.staleThreshold.Before(t)
117 }
118
119 func (fs *customFileSystem) newNode(name string, perm os.FileMode, modTime time.Time) (node inode, err error) {
120         return nil, ErrInvalidOperation
121 }
122
123 func (fs *customFileSystem) mountCollection(parent inode, id string) inode {
124         var coll Collection
125         err := fs.RequestAndDecode(&coll, "GET", "arvados/v1/collections/"+id, nil, nil)
126         if err != nil {
127                 return nil
128         }
129         cfs, err := coll.FileSystem(fs, fs)
130         if err != nil {
131                 return nil
132         }
133         root := cfs.rootnode()
134         root.SetParent(parent, id)
135         return root
136 }
137
138 func (fs *customFileSystem) newProjectNode(root inode, name, uuid string) inode {
139         return &projectnode{
140                 uuid: uuid,
141                 inode: &treenode{
142                         fs:     fs,
143                         parent: root,
144                         inodes: make(map[string]inode),
145                         fileinfo: fileinfo{
146                                 name:    name,
147                                 modTime: time.Now(),
148                                 mode:    0755 | os.ModeDir,
149                         },
150                 },
151         }
152 }
153
154 func (fs *customFileSystem) newUserNode(root inode, name, uuid string) inode {
155         return &projectnode{
156                 uuid: uuid,
157                 inode: &treenode{
158                         fs:     fs,
159                         parent: root,
160                         inodes: make(map[string]inode),
161                         fileinfo: fileinfo{
162                                 name:    name,
163                                 modTime: time.Now(),
164                                 mode:    0755 | os.ModeDir,
165                         },
166                 },
167         }
168 }
169
170 // vdirnode wraps an inode by ignoring any requests to add/replace
171 // children, and calling a create() func when a non-existing child is
172 // looked up.
173 //
174 // create() can return either a new node, which will be added to the
175 // treenode, or nil for ENOENT.
176 type vdirnode struct {
177         inode
178         create func(parent inode, name string) inode
179 }
180
181 func (vn *vdirnode) Child(name string, replace func(inode) (inode, error)) (inode, error) {
182         return vn.inode.Child(name, func(existing inode) (inode, error) {
183                 if existing == nil && vn.create != nil {
184                         existing = vn.create(vn, name)
185                         if existing != nil {
186                                 existing.SetParent(vn, name)
187                                 vn.inode.(*treenode).fileinfo.modTime = time.Now()
188                         }
189                 }
190                 if replace == nil {
191                         return existing, nil
192                 } else if tryRepl, err := replace(existing); err != nil {
193                         return existing, err
194                 } else if tryRepl != existing {
195                         return existing, ErrInvalidArgument
196                 } else {
197                         return existing, nil
198                 }
199         })
200 }