Statistics
| Branch: | Tag: | Revision:

root / contrib / snf-pithos-tools / pithos / tools / fs.py @ 896754a6

History | View | Annotate | Download (12 kB)

1
#!/usr/bin/env python
2

    
3
# Copyright 2011-2012 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

    
45
from snf_django.lib.api.parsedate import parse_http_date
46

    
47
from pithos.tools.lib.client import OOS_Client, Fault
48
from pithos.tools.lib.fuse import FUSE, FuseOSError, Operations
49
from pithos.tools.lib.util import get_user, get_auth, get_url
50

    
51

    
52
epoch = int(time())
53

    
54

    
55
class StoreFS(Operations):
56
    def __init__(self, verbose=False):
57
        self.verbose = verbose
58
        self.client = OOS_Client(get_url(), get_auth(), get_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
    def _get_container_meta(self, container, **kwargs):
93
        try:
94
            return self.client.retrieve_container_metadata(container, **kwargs)
95
        except Fault:
96
            raise FuseOSError(ENOENT)
97

    
98
    def _get_object_meta(self, container, object, **kwargs):
99
        try:
100
            return self.client.retrieve_object_metadata(container, object,
101
                                                        **kwargs)
102
        except Fault:
103
            raise FuseOSError(ENOENT)
104

    
105
    # Global
106
    def statfs(self, path):
107
        return dict(f_bsize=1024, f_blocks=1024 ** 2, f_bfree=1024 ** 2,
108
                    f_bavail=1024 ** 2)
109

    
110
    # Account Level
111
    def account_chmod(self, mode):
112
        self.client.update_account_metadata(mode=str(mode))
113

    
114
    def account_chown(self, uid, gid):
115
        self.client.update_account_metadata(uid=uid, gid=gid)
116

    
117
    def account_getattr(self, fh=None):
118
        meta = self.client.retrieve_account_metadata()
119
        mode = int(meta.get('x-account-meta-mode', 0755))
120
        last_modified = meta.get('last-modified', None)
121
        modified = parse_http_date(last_modified) if last_modified else epoch
122
        count = int(meta['x-account-container-count'])
123
        uid = int(meta.get('x-account-meta-uid', 0))
124
        gid = int(meta.get('x-account-meta-gid', 0))
125

    
126
        return {
127
            'st_mode': S_IFDIR | mode,
128
            'st_nlink': 2 + count,
129
            'st_uid': uid,
130
            'st_gid': gid,
131
            'st_ctime': epoch,
132
            'st_mtime': modified,
133
            'st_atime': modified}
134

    
135
    def account_getxattr(self, name, position=0):
136
        meta = self.client.retrieve_account_metadata(restricted=True)
137
        return meta.get('xattr-' + name, '')
138

    
139
    def account_listxattr(self):
140
        meta = self.client.retrieve_account_metadata(restricted=True)
141
        prefix = 'xattr-'
142
        return [k[len(prefix):] for k in meta if k.startswith(prefix)]
143

    
144
    def account_readdir(self, fh):
145
        return ['.', '..'] + self.client.list_containers() or []
146

    
147
    def account_removexattr(self, name):
148
        attr = 'xattr-' + name
149
        self.client.delete_account_metadata([attr])
150

    
151
    def account_setxattr(self, name, value, options, position=0):
152
        attr = 'xattr-' + name
153
        meta = {attr: value}
154
        self.client.update_account_metadata(**meta)
155

    
156
    # Container Level
157
    def container_chmod(self, container, mode):
158
        self.client.update_container_metadata(container, mode=str(mode))
159

    
160
    def container_chown(self, container, uid, gid):
161
        self.client.update_container_metadata(container, uid=uid, gid=gid)
162

    
163
    def container_getattr(self, container, fh=None):
164
        meta = self._get_container_meta(container)
165
        mode = int(meta.get('x-container-meta-mode', 0755))
166
        modified = parse_http_date(meta['last-modified'])
167
        count = int(meta['x-container-object-count'])
168
        uid = int(meta.get('x-account-meta-uid', 0))
169
        gid = int(meta.get('x-account-meta-gid', 0))
170

    
171
        return {
172
            'st_mode': S_IFDIR | mode,
173
            'st_nlink': 2 + count,
174
            'st_uid': uid,
175
            'st_gid': gid,
176
            'st_ctime': epoch,
177
            'st_mtime': modified,
178
            'st_atime': modified}
179

    
180
    def container_getxattr(self, container, name, position=0):
181
        meta = self._get_container_meta(container)
182
        return meta.get('xattr-' + name, '')
183

    
184
    def container_listxattr(self, container):
185
        meta = self._get_container_meta(container, restricted=True)
186
        prefix = 'xattr-'
187
        return [k[len(prefix):] for k in meta if k.startswith(prefix)]
188

    
189
    def container_mkdir(self, container, mode):
190
        mode = str(mode & 0777)
191
        self.client.create_container(container, mode=mode)
192

    
193
    def container_readdir(self, container, fh):
194
        objects = self.client.list_objects(container, delimiter='/', prefix='')
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
    # Object Level
221
    def object_chmod(self, container, object, mode):
222
        self.client.update_object_metadata(container, object, mode=str(mode))
223

    
224
    def object_chown(self, container, uid, gid):
225
        self.client.update_object_metadata(container, object,
226
                                           uid=str(uid), gid=str(gid))
227

    
228
    def object_create(self, container, object, mode, fi=None):
229
        mode &= 0777
230
        self.client.create_object(container, object,
231
                                  f=None,
232
                                  content_type='application/octet-stream',
233
                                  mode=str(mode))
234
        return 0
235

    
236
    def object_getattr(self, container, object, fh=None):
237
        meta = self._get_object_meta(container, object)
238
        modified = parse_http_date(meta['last-modified'])
239
        uid = int(meta.get('x-account-meta-uid', 0))
240
        gid = int(meta.get('x-account-meta-gid', 0))
241
        size = int(meta.get('content-length', 0))
242

    
243
        if meta['content-type'].split(';', 1)[0].strip() == 'application/directory':
244
            mode = int(meta.get('x-object-meta-mode', 0755))
245
            flags = S_IFDIR
246
            nlink = 2
247
        else:
248
            mode = int(meta.get('x-object-meta-mode', 0644))
249
            flags = S_IFREG
250
            nlink = 1
251

    
252
        return {
253
            'st_mode': flags | mode,
254
            'st_nlink': nlink,
255
            'st_uid': uid,
256
            'st_gid': gid,
257
            'st_ctime': epoch,
258
            'st_mtime': modified,
259
            'st_atime': modified,
260
            'st_size': size}
261

    
262
    def object_getxattr(self, container, object, name, position=0):
263
        meta = self._get_object_meta(container, object, restricted=True)
264
        return meta.get('xattr-' + name, '')
265

    
266
    def object_listxattr(self, container, object):
267
        meta = self._get_object_meta(container, object, restricted=True)
268
        prefix = 'xattr-'
269
        return [k[len(prefix):] for k in meta if k.startswith(prefix)]
270

    
271
    def object_mkdir(self, container, object, mode):
272
        mode = str(mode & 0777)
273
        self.client.create_directory_marker(container, object)
274
        self.client.update_object_metadata(container, object, mode=mode)
275

    
276
    def object_read(self, container, object, nbyte, offset, fh):
277
        data = self.client.retrieve_object(container, object)
278
        return data[offset:offset + nbyte]
279

    
280
    def object_readdir(self, container, object, fh):
281
        objects = self.client.list_objects(container, delimiter='/',
282
                                           prefix=object)
283
        files = [o.rpartition('/')[2] for o in objects if not o.endswith('/')]
284
        return ['.', '..'] + files
285

    
286
    def object_removexattr(self, container, object, name):
287
        attr = 'xattr-' + name
288
        self.client.delete_object_metadata(container, object, [attr])
289

    
290
    def object_rename(self, container, object, path):
291
        new_container, sep, new_object = path[1:].partition('/')
292
        if not new_container or not new_object:
293
            raise FuseOSError(EINVAL)
294
        self.client.move_object(container, object, new_container, new_object)
295

    
296
    def object_rmdir(self, container, object):
297
        self.client.delete_object(container, object)
298

    
299
    def object_setxattr(self, container, object, name, value, options,
300
                        position=0):
301
        attr = 'xattr-' + name
302
        meta = {attr: value}
303
        self.client.update_object_metadata(container, object, **meta)
304

    
305
    def object_truncate(self, container, object, length, fh=None):
306
        data = self.client.retrieve_object(container, object)
307
        f = StringIO(data[:length])
308
        self.client.update_object(container, object, f)
309

    
310
    def object_unlink(self, container, object):
311
        self.client.delete_object(container, object)
312

    
313
    def object_write(self, container, object, data, offset, fh):
314
        f = StringIO(data)
315
        self.client.update_object(container, object, f, offset=offset)
316
        return len(data)
317

    
318

    
319
def main():
320
    if len(argv) != 2:
321
        print 'usage: %s <mountpoint>' % argv[0]
322
        exit(1)
323

    
324
    user = getuser()
325
    fs = StoreFS(verbose=True)
326
    fuse = FUSE(fs, argv[1], foreground=True)
327

    
328

    
329
if __name__ == '__main__':
330
    main()