Fix some imports.
[pithos] / tools / storefs.py
1 #!/usr/bin/env python
2
3 # Copyright 2011 GRNET S.A. All rights reserved.
4
5 # Redistribution and use in source and binary forms, with or
6 # without modification, are permitted provided that the following
7 # conditions are met:
8
9 #   1. Redistributions of source code must retain the above
10 #      copyright notice, this list of conditions and the following
11 #      disclaimer.
12
13 #   2. Redistributions in binary form must reproduce the above
14 #      copyright notice, this list of conditions and the following
15 #      disclaimer in the documentation and/or other materials
16 #      provided with the distribution.
17
18 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
19 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
22 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
25 # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 # POSSIBILITY OF SUCH DAMAGE.
30
31 # The views and conclusions contained in the software and
32 # documentation are those of the authors and should not be
33 # interpreted as representing official policies, either expressed
34 # or implied, of GRNET S.A.
35
36 from cStringIO import StringIO
37 from errno import (EACCES, EBADF, EINVAL, EISDIR, EIO, ENOENT, ENOTDIR,
38                     ENOTEMPTY)
39 from getpass import getuser
40 from stat import S_IFDIR, S_IFREG
41 from sys import argv
42 from time import time
43
44 from lib.client import OOS_Client, Fault
45 from lib.fuse import FUSE, FuseOSError, Operations
46
47 from pithos.api.compat import parse_http_date
48
49 DEFAULT_HOST = 'pithos.dev.grnet.gr'
50
51
52 epoch = int(time())
53
54
55 class StoreFS(Operations):
56     def __init__(self, user, token, verbose=False):
57         self.verbose = verbose
58         self.client = OOS_Client(DEFAULT_HOST, token, user)
59     
60     def __call__(self, op, path, *args):
61         container, sep, object = path[1:].partition('/')
62         if self.verbose:
63             data = repr(args)[:100]
64             print '-> %s %r %r %r' % (op, container, object, data)
65         ret = '[Unhandled Exception]'
66         
67         try:
68             if object:
69                 func = getattr(self, 'object_' + op, None)
70                 funcargs = (container, object) + args
71             elif container:
72                 func = getattr(self, 'container_' + op, None)
73                 funcargs = (container,) + args
74             else:
75                 func = getattr(self, 'account_' + op, None)
76                 funcargs = args
77
78             if not func:
79                 # Fallback to defaults
80                 func = getattr(self, op)
81                 funcargs = (path,) + args
82             
83             ret = func(*funcargs)
84             return ret
85         except FuseOSError, e:
86             ret = str(e)
87             raise
88         finally:
89             if self.verbose:
90                 print '<-', op, repr(ret)
91     
92     
93     def _get_container_meta(self, container, **kwargs):
94         try:
95             return self.client.retrieve_container_metadata(container, **kwargs)
96         except Fault:
97             raise FuseOSError(ENOENT)
98     
99     def _get_object_meta(self, container, object, **kwargs):
100         try:
101             return self.client.retrieve_object_metadata(container, object,
102                                                         **kwargs)
103         except Fault:
104             raise FuseOSError(ENOENT)
105     
106     
107     # Global
108     
109     def statfs(self, path):
110         return dict(f_bsize=1024, f_blocks=1024**2, f_bfree=1024**2,
111                     f_bavail=1024**2)
112     
113     
114     # Account Level
115     
116     def account_chmod(self, mode):
117         self.client.update_account_metadata(mode=str(mode))
118     
119     def account_chown(self, uid, gid):
120         self.client.update_account_metadata(uid=uid, gid=gid)
121     
122     def account_getattr(self, fh=None):
123         meta = self.client.retrieve_account_metadata()
124         mode = int(meta.get('x-account-meta-mode', 0755))
125         last_modified = meta.get('last-modified', None)
126         modified = parse_http_date(last_modified) if last_modified else epoch
127         count = int(meta['x-account-container-count'])
128         uid = int(meta.get('x-account-meta-uid', 0))
129         gid = int(meta.get('x-account-meta-gid', 0))
130         
131         return {
132             'st_mode': S_IFDIR | mode,
133             'st_nlink': 2 + count,
134             'st_uid': uid,
135             'st_gid': gid,
136             'st_ctime': epoch,
137             'st_mtime': modified,
138             'st_atime': modified}
139     
140     def account_getxattr(self, name, position=0):
141         meta = self.client.retrieve_account_metadata(restricted=True)
142         return meta.get('xattr-' + name, '')
143     
144     def account_listxattr(self):
145         meta = self.client.retrieve_account_metadata(restricted=True)
146         prefix = 'xattr-'
147         return [k[len(prefix):] for k in meta if k.startswith(prefix)]
148     
149     def account_readdir(self, fh):
150         return ['.', '..'] + self.client.list_containers()
151     
152     def account_removexattr(self, name):
153         attr = 'xattr-' + name
154         self.client.delete_account_metadata([attr])
155     
156     def account_setxattr(self, name, value, options, position=0):
157         attr = 'xattr-' + name
158         meta = {attr: value}
159         self.client.update_account_metadata(**meta)
160     
161     
162     # Container Level
163     
164     def container_chmod(self, container, mode):
165         self.client.update_container_metadata(container, mode=str(mode))
166     
167     def container_chown(self, container, uid, gid):
168         self.client.update_container_metadata(container, uid=uid, gid=gid)
169     
170     def container_getattr(self, container, fh=None):
171         meta = self._get_container_meta(container)
172         mode = int(meta.get('x-container-meta-mode', 0755))
173         modified = parse_http_date(meta['last-modified'])
174         count = int(meta['x-container-object-count'])
175         uid = int(meta.get('x-account-meta-uid', 0))
176         gid = int(meta.get('x-account-meta-gid', 0))
177         
178         return {
179             'st_mode': S_IFDIR | mode,
180             'st_nlink': 2 + count,
181             'st_uid': uid,
182             'st_gid': gid,
183             'st_ctime': epoch,
184             'st_mtime': modified,
185             'st_atime': modified}
186     
187     def container_getxattr(self, container, name, position=0):
188         meta = self._get_container_meta(container)
189         return meta.get('xattr-' + name, '')
190     
191     def container_listxattr(self, container):
192         meta = self._get_container_meta(container, restricted=True)
193         prefix = 'xattr-'
194         return [k[len(prefix):] for k in meta if k.startswith(prefix)]
195     
196     def container_mkdir(self, container, mode):
197         mode = str(mode & 0777)
198         self.client.create_container(container, mode=mode)
199     
200     def container_readdir(self, container, fh):
201         params = {'delimiter': '/', 'prefix': ''}
202         objects = self.client.list_objects(container, params=params)
203         files = [o for o in objects if not o.endswith('/')]
204         return ['.', '..'] + files
205     
206     def container_removexattr(self, container, name):
207         attr = 'xattr-' + name
208         self.client.delete_container_metadata(container, [attr])
209     
210     def container_rename(self, container, path):
211         new_container, sep, new_object = path[1:].partition('/')
212         if not new_container or new_object:
213             raise FuseOSError(EINVAL)
214         self.client.delete_container(container)
215         self.client.create_container(new_container)
216     
217     def container_rmdir(self, container):
218         try:
219             self.client.delete_container(container)
220         except Fault:
221             raise FuseOSError(ENOENT)
222     
223     def container_setxattr(self, container, name, value, options, position=0):
224         attr = 'xattr-' + name
225         meta = {attr: value}
226         self.client.update_container_metadata(container, **meta)
227     
228     
229     # Object Level
230     
231     def object_chmod(self, container, object, mode):
232         self.client.update_object_metadata(container, object, mode=str(mode))
233     
234     def object_chown(self, container, uid, gid):
235         self.client.update_object_metadata(container, object,
236                                             uid=str(uid), gid=str(gid))
237     
238     def object_create(self, container, object, mode, fi=None):
239         mode &= 0777
240         self.client.create_object(container, object,
241                                     f=None,
242                                     content_type='application/octet-stream',
243                                     mode=str(mode))
244         return 0
245     
246     def object_getattr(self, container, object, fh=None):
247         meta = self._get_object_meta(container, object)
248         mode = int(meta.get('x-object-meta-mode', 0644))
249         modified = parse_http_date(meta['last-modified'])
250         uid = int(meta.get('x-account-meta-uid', 0))
251         gid = int(meta.get('x-account-meta-gid', 0))
252         size = int(meta['content-length'])
253         
254         if meta['content-type'] == 'application/directory':
255             flags = S_IFDIR
256             nlink = 2
257         else:
258             flags = S_IFREG
259             nlink = 1
260         
261         return {
262             'st_mode': flags | mode,
263             'st_nlink': nlink,
264             'st_uid': uid,
265             'st_gid': gid,
266             'st_ctime': epoch,
267             'st_mtime': modified,
268             'st_atime': modified,
269             'st_size': size}
270     
271     def object_getxattr(self, container, object, name, position=0):
272         meta = self._get_object_meta(container, object, restricted=True)
273         return meta.get('xattr-' + name, '')
274     
275     def object_listxattr(self, container, object):
276         meta = self._get_object_meta(container, object, restricted=True)
277         prefix = 'xattr-'
278         return [k[len(prefix):] for k in meta if k.startswith(prefix)]
279     
280     def object_mkdir(self, container, object, mode):
281         mode = str(mode & 0777)
282         self.client.create_directory_marker(container, object)
283         self.client.update_object_metadata(container, object, mode=mode)
284     
285     def object_read(self, container, object, nbyte, offset, fh):
286         data = self.client.retrieve_object(container, object)
287         return data[offset:offset + nbyte]
288     
289     def object_readdir(self, container, object, fh):
290         params = {'delimiter': '/', 'prefix': object}
291         objects = self.client.list_objects(container, params=params)
292         files = [o.rpartition('/')[2] for o in objects if not o.endswith('/')]
293         return ['.', '..'] + files
294     
295     def object_removexattr(self, container, object, name):
296         attr = 'xattr-' + name
297         self.client.delete_object_metadata(container, object, [attr])
298     
299     def object_rename(self, container, object, path):
300         new_container, sep, new_object = path[1:].partition('/')
301         if not new_container or not new_object:
302             raise FuseOSError(EINVAL)
303         self.client.move_object(container, object, new_container, new_object)
304     
305     def object_rmdir(self, container, object):
306         self.client.delete_object(container, object)
307     
308     def object_setxattr(self, container, object, name, value, options,
309                         position=0):
310         attr = 'xattr-' + name
311         meta = {attr: value}
312         self.client.update_object_metadata(container, object, **meta)
313     
314     def object_truncate(self, container, object, length, fh=None):
315         data = self.client.retrieve_object(container, object)
316         f = StringIO(data[:length])
317         self.client.update_object(container, object, f)
318     
319     def object_unlink(self, container, object):
320         self.client.delete_object(container, object)
321     
322     def object_write(self, container, object, data, offset, fh):
323         f = StringIO(data)
324         self.client.update_object(container, object, f, offset=offset)
325         return len(data)
326
327
328 if __name__ == "__main__":
329     if len(argv) != 4:
330         print 'usage: %s <user> <token> <mountpoint>' % argv[0]
331         exit(1)
332     
333     user = getuser()
334     fs = StoreFS(argv[1], argv[2], verbose=True)
335     fuse = FUSE(fs, argv[3], foreground=True)