12167: Merge branch 'master'
[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         "strings"
10         "sync"
11         "time"
12 )
13
14 type CustomFileSystem interface {
15         FileSystem
16         MountByID(mount string)
17         MountProject(mount, uuid string)
18         MountUsers(mount string)
19 }
20
21 type customFileSystem struct {
22         fileSystem
23         root *vdirnode
24
25         staleThreshold time.Time
26         staleLock      sync.Mutex
27 }
28
29 func (c *Client) CustomFileSystem(kc keepClient) CustomFileSystem {
30         root := &vdirnode{}
31         fs := &customFileSystem{
32                 root: root,
33                 fileSystem: fileSystem{
34                         fsBackend: keepBackend{apiClient: c, keepClient: kc},
35                         root:      root,
36                 },
37         }
38         root.inode = &treenode{
39                 fs:     fs,
40                 parent: root,
41                 fileinfo: fileinfo{
42                         name:    "/",
43                         mode:    os.ModeDir | 0755,
44                         modTime: time.Now(),
45                 },
46                 inodes: make(map[string]inode),
47         }
48         return fs
49 }
50
51 func (fs *customFileSystem) MountByID(mount string) {
52         fs.root.inode.Child(mount, func(inode) (inode, error) {
53                 return &vdirnode{
54                         inode: &treenode{
55                                 fs:     fs,
56                                 parent: fs.root,
57                                 inodes: make(map[string]inode),
58                                 fileinfo: fileinfo{
59                                         name:    mount,
60                                         modTime: time.Now(),
61                                         mode:    0755 | os.ModeDir,
62                                 },
63                         },
64                         create: fs.mountByID,
65                 }, nil
66         })
67 }
68
69 func (fs *customFileSystem) MountProject(mount, uuid string) {
70         fs.root.inode.Child(mount, func(inode) (inode, error) {
71                 return fs.newProjectNode(fs.root, mount, uuid), nil
72         })
73 }
74
75 func (fs *customFileSystem) MountUsers(mount string) {
76         fs.root.inode.Child(mount, func(inode) (inode, error) {
77                 return &lookupnode{
78                         stale:   fs.Stale,
79                         loadOne: fs.usersLoadOne,
80                         loadAll: fs.usersLoadAll,
81                         inode: &treenode{
82                                 fs:     fs,
83                                 parent: fs.root,
84                                 inodes: make(map[string]inode),
85                                 fileinfo: fileinfo{
86                                         name:    mount,
87                                         modTime: time.Now(),
88                                         mode:    0755 | os.ModeDir,
89                                 },
90                         },
91                 }, nil
92         })
93 }
94
95 // SiteFileSystem returns a FileSystem that maps collections and other
96 // Arvados objects onto a filesystem layout.
97 //
98 // This is experimental: the filesystem layout is not stable, and
99 // there are significant known bugs and shortcomings. For example,
100 // writes are not persisted until Sync() is called.
101 func (c *Client) SiteFileSystem(kc keepClient) CustomFileSystem {
102         fs := c.CustomFileSystem(kc)
103         fs.MountByID("by_id")
104         fs.MountUsers("users")
105         return fs
106 }
107
108 func (fs *customFileSystem) Sync() error {
109         fs.staleLock.Lock()
110         defer fs.staleLock.Unlock()
111         fs.staleThreshold = time.Now()
112         return nil
113 }
114
115 // Stale returns true if information obtained at time t should be
116 // considered stale.
117 func (fs *customFileSystem) Stale(t time.Time) bool {
118         fs.staleLock.Lock()
119         defer fs.staleLock.Unlock()
120         return !fs.staleThreshold.Before(t)
121 }
122
123 func (fs *customFileSystem) newNode(name string, perm os.FileMode, modTime time.Time) (node inode, err error) {
124         return nil, ErrInvalidOperation
125 }
126
127 func (fs *customFileSystem) mountByID(parent inode, id string) inode {
128         if strings.Contains(id, "-4zz18-") || pdhRegexp.MatchString(id) {
129                 return fs.mountCollection(parent, id)
130         } else if strings.Contains(id, "-j7d0g-") {
131                 return fs.newProjectNode(fs.root, id, id)
132         } else {
133                 return nil
134         }
135 }
136
137 func (fs *customFileSystem) mountCollection(parent inode, id string) inode {
138         var coll Collection
139         err := fs.RequestAndDecode(&coll, "GET", "arvados/v1/collections/"+id, nil, nil)
140         if err != nil {
141                 return nil
142         }
143         cfs, err := coll.FileSystem(fs, fs)
144         if err != nil {
145                 return nil
146         }
147         root := cfs.rootnode()
148         root.SetParent(parent, id)
149         return root
150 }
151
152 func (fs *customFileSystem) newProjectNode(root inode, name, uuid string) inode {
153         return &lookupnode{
154                 stale:   fs.Stale,
155                 loadOne: func(parent inode, name string) (inode, error) { return fs.projectsLoadOne(parent, uuid, name) },
156                 loadAll: func(parent inode) ([]inode, error) { return fs.projectsLoadAll(parent, 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 }