Several client and client library minor changes
[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 pithos.api.compat import parse_http_date
45 from pithos.lib.client import Client, Fault
46 from pithos.lib.fuse import FUSE, FuseOSError, Operations
47
48 DEFAULT_HOST = 'pithos.dev.grnet.gr'
49 DEFAULT_API = 'v1'
50
51
52 epoch = int(time())
53
54
55 class StoreFS(Operations):
56     def __init__(self, user, verbose=False):
57         self.verbose = verbose
58         self.client = Client(DEFAULT_HOST, user, DEFAULT_API)
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                 ret = getattr(self, 'object_' + op)(container, object, *args)
70             elif container:
71                 ret = getattr(self, 'container_' + op)(container, *args)
72             else:
73                 ret = getattr(self, 'account_' + op)(*args)
74             return ret
75         except AttributeError:
76             ret = getattr(self, op)(path, *args)
77             return ret
78         except FuseOSError, e:
79             ret = str(e)
80             raise
81         finally:
82             if self.verbose:
83                 print '<-', op, repr(ret)
84     
85     
86     def _get_container_meta(self, container, **kwargs):
87         try:
88             return self.client.retrieve_container_metadata(container, **kwargs)
89         except Fault:
90             raise FuseOSError(ENOENT)
91     
92     def _get_object_meta(self, container, object, **kwargs):
93         try:
94             return self.client.retrieve_object_metadata(container, object,
95                                                         **kwargs)
96         except Fault:
97             raise FuseOSError(ENOENT)
98     
99     
100     # Global
101     
102     def statfs(self, path):
103         return dict(f_bsize=1024, f_blocks=1024**2, f_bfree=1024**2,
104                     f_bavail=1024**2)
105     
106     
107     # Account Level
108     
109     def account_chmod(self, mode):
110         self.client.update_account_metadata(mode=mode)
111     
112     def account_chown(self, uid, gid):
113         self.client.update_account_metadata(uid=uid, gid=gid)
114     
115     def account_getattr(self, fh=None):
116         meta = self.client.account_metadata()
117         mode = int(meta.get('x-account-meta-mode', 0755))
118         modified = parse_http_date(meta['last-modified'])
119         count = int(meta['x-account-container-count'])
120         uid = int(meta.get('x-account-meta-uid', 0))
121         gid = int(meta.get('x-account-meta-gid', 0))
122         
123         return {
124             'st_mode': S_IFDIR | mode,
125             'st_nlink': 2 + count,
126             'st_uid': uid,
127             'st_gid': gid,
128             'st_ctime': epoch,
129             'st_mtime': modified,
130             'st_atime': modified}
131     
132     def account_getxattr(self, name, position=0):
133         meta = self.client.retrieve_account_metadata(restricted=True)
134         return meta.get('xattr-' + name, '')
135     
136     def account_listxattr(self):
137         meta = self.client.retrieve_account_metadata(restricted=True)
138         prefix = 'xattr-'
139         return [k[len(prefix):] for k in meta if k.startswith(prefix)]
140     
141     def account_readdir(self, fh):
142         return ['.', '..'] + self.client.list_containers()
143     
144     def account_removexattr(self, name):
145         attr = 'xattr-' + name
146         self.client.delete_account_metadata([attr])
147     
148     def account_setxattr(self, name, value, options, position=0):
149         attr = 'xattr-' + name
150         meta = {attr: value}
151         self.client.update_account_metadata(**meta)
152     
153     
154     # Container Level
155     
156     def container_chmod(self, container, mode):
157         self.client.update_container_metadata(container, mode=mode)
158     
159     def container_chown(self, container, uid, gid):
160         self.client.update_container_metadata(container, uid=uid, gid=gid)
161     
162     def container_getattr(self, container, fh=None):
163         meta = self._get_container_meta(container)
164         mode = int(meta.get('x-container-meta-mode', 0755))
165         modified = parse_http_date(meta['last-modified'])
166         count = int(meta['x-container-object-count'])
167         uid = int(meta.get('x-account-meta-uid', 0))
168         gid = int(meta.get('x-account-meta-gid', 0))
169         
170         return {
171             'st_mode': S_IFDIR | mode,
172             'st_nlink': 2 + count,
173             'st_uid': uid,
174             'st_gid': gid,
175             'st_ctime': epoch,
176             'st_mtime': modified,
177             'st_atime': modified}
178     
179     def container_getxattr(self, container, name, position=0):
180         meta = self._get_container_meta(container)
181         return meta.get('xattr-' + name, '')
182     
183     def container_listxattr(self, container):
184         meta = self._get_container_meta(container, restricted=True)
185         prefix = 'xattr-'
186         return [k[len(prefix):] for k in meta if k.startswith(prefix)]
187     
188     def container_mkdir(self, container, mode):
189         self.client.create_container(container)
190         self.client.update_container_metadata(container, mode=mode)
191     
192     def container_readdir(self, container, fh):
193         params = {'delimiter': '/', 'prefix': ''}
194         objects = self.client.list_objects(container, params=params)
195         files = [o for o in objects if not o.endswith('/')]
196         return ['.', '..'] + files
197     
198     def container_removexattr(self, container, name):
199         attr = 'xattr-' + name
200         self.client.delete_container_metadata(container, [attr])
201     
202     def container_rename(self, container, path):
203         new_container, sep, new_object = path[1:].partition('/')
204         if not new_container or new_object:
205             raise FuseOSError(EINVAL)
206         self.client.delete_container(container)
207         self.client.create_container(new_container)
208     
209     def container_rmdir(self, container):
210         try:
211             self.client.delete_container(container)
212         except Fault:
213             raise FuseOSError(ENOENT)
214     
215     def container_setxattr(self, container, name, value, options, position=0):
216         attr = 'xattr-' + name
217         meta = {attr: value}
218         self.client.update_container_metadata(container, **meta)
219     
220     
221     # Object Level
222     
223     def object_chmod(self, container, object, mode):
224         self.client.update_object_metadata(container, object, mode=mode)
225     
226     def object_chown(self, container, uid, gid):
227         self.client.update_object_metadata(container, object, uid=uid, gid=gid)
228     
229     def object_create(self, container, object, mode, fi=None):
230         mode &= 0777
231         headers = {'Content-Type': 'application/octet-stream'}
232         self.client.create_object(container, object, f=None, headers=headers)
233         self.client.update_object_metadata(container, object, mode=mode)
234         return 0
235     
236     def object_getattr(self, container, object, fh=None):
237         meta = self._get_object_meta(container, object)
238         mode = int(meta.get('x-object-meta-mode', 0644))
239         modified = parse_http_date(meta['last-modified'])
240         uid = int(meta.get('x-account-meta-uid', 0))
241         gid = int(meta.get('x-account-meta-gid', 0))
242         size = int(meta['content-length'])
243         
244         if meta['content-type'] == 'application/directory':
245             flags = S_IFDIR
246             nlink = 2
247         else:
248             flags = S_IFREG
249             nlink = 1
250         
251         return {
252             'st_mode': flags | mode,
253             'st_nlink': nlink,
254             'st_uid': uid,
255             'st_gid': gid,
256             'st_ctime': epoch,
257             'st_mtime': modified,
258             'st_atime': modified,
259             'st_size': size}
260     
261     def object_getxattr(self, container, object, name, position=0):
262         meta = self._get_object_meta(container, object, restricted=True)
263         return meta.get('xattr-' + name, '')
264     
265     def object_listxattr(self, container, object):
266         meta = self._get_object_meta(container, object, restricted=True)
267         prefix = 'xattr-'
268         return [k[len(prefix):] for k in meta if k.startswith(prefix)]
269     
270     def object_mkdir(self, container, object, mode):
271         self.client.create_directory_marker(container, object)
272         self.client.update_object_metadata(container, object, mode=mode)
273     
274     def object_read(self, container, object, nbyte, offset, fh):
275         data = self.client.retrieve_object(container, object)
276         return data[offset:offset + nbyte]
277     
278     def object_readdir(self, container, object, fh):
279         params = {'delimiter': '/', 'prefix': object}
280         objects = self.client.list_objects(container, params=params)
281         files = [o.rpartition('/')[2] for o in objects if not o.endswith('/')]
282         return ['.', '..'] + files
283     
284     def object_removexattr(self, container, object, name):
285         attr = 'xattr-' + name
286         self.client.delete_object_metadata(container, object, [attr])
287     
288     def object_rename(self, container, object, path):
289         new_container, sep, new_object = path[1:].partition('/')
290         if not new_container or not new_object:
291             raise FuseOSError(EINVAL)
292         self.client.move_object(container, object, new_container, new_object)
293     
294     def object_rmdir(self, container, object):
295         self.client.delete_object(container, object)
296     
297     def object_setxattr(self, container, object, name, value, options,
298                         position=0):
299         attr = 'xattr-' + name
300         meta = {attr: value}
301         self.client.update_object_metadata(container, object, **meta)
302     
303     def object_truncate(self, container, object, length, fh=None):
304         data = self.client.retrieve_object(container, object)
305         f = StringIO(data[:length])
306         self.client.update_object(container, object, f)
307     
308     def object_unlink(self, container, object):
309         self.client.delete_object(container, object)
310     
311     def object_write(self, container, object, data, offset, fh):
312         f = StringIO(data)
313         headers = {}
314         if offset:
315             headers['CONTENT_RANGE'] = 'bytes %d-/*' % offset
316         else:
317             headers['CONTENT_RANGE'] = 'bytes */*'
318         
319         self.client.update_object(container, object, f, headers=headers)
320         return len(data)
321
322
323 if __name__ == "__main__":
324     if len(argv) != 2:
325         print 'usage: %s <mountpoint>' % argv[0]
326         exit(1)
327     
328     user = getuser()
329     fs = StoreFS(user, verbose=True)
330     fuse = FUSE(fs, argv[1], foreground=True)