Statistics
| Branch: | Tag: | Revision:

root / tools / storefs.py @ 51f905a2

History | View | Annotate | Download (11.9 kB)

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)