Statistics
| Branch: | Tag: | Revision:

root / snf-pithos-tools / pithos / tools / fs.py @ d50ed8d4

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
from synnefo.lib.parsedate import parse_http_date
45

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

    
50

    
51
epoch = int(time())
52

    
53

    
54
class StoreFS(Operations):
55
    def __init__(self, verbose=False):
56
        self.verbose = verbose
57
        self.client = OOS_Client(get_url(), get_auth(), get_user())
58

    
59
    def __call__(self, op, path, *args):
60
        container, sep, object = path[1:].partition('/')
61
        if self.verbose:
62
            data = repr(args)[:100]
63
            print '-> %s %r %r %r' % (op, container, object, data)
64
        ret = '[Unhandled Exception]'
65

    
66
        try:
67
            if object:
68
                func = getattr(self, 'object_' + op, None)
69
                funcargs = (container, object) + args
70
            elif container:
71
                func = getattr(self, 'container_' + op, None)
72
                funcargs = (container,) + args
73
            else:
74
                func = getattr(self, 'account_' + op, None)
75
                funcargs = args
76

    
77
            if not func:
78
                # Fallback to defaults
79
                func = getattr(self, op)
80
                funcargs = (path,) + args
81

    
82
            ret = func(*funcargs)
83
            return ret
84
        except FuseOSError, e:
85
            ret = str(e)
86
            raise
87
        finally:
88
            if self.verbose:
89
                print '<-', op, repr(ret)
90

    
91
    def _get_container_meta(self, container, **kwargs):
92
        try:
93
            return self.client.retrieve_container_metadata(container, **kwargs)
94
        except Fault:
95
            raise FuseOSError(ENOENT)
96

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

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

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

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

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

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

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

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

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

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

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

    
155
    # Container Level
156
    def container_chmod(self, container, mode):
157
        self.client.update_container_metadata(container, mode=str(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
        mode = str(mode & 0777)
190
        self.client.create_container(container, mode=mode)
191

    
192
    def container_readdir(self, container, fh):
193
        objects = self.client.list_objects(container, delimiter='/', prefix='')
194
        files = [o for o in objects if not o.endswith('/')]
195
        return ['.', '..'] + files
196

    
197
    def container_removexattr(self, container, name):
198
        attr = 'xattr-' + name
199
        self.client.delete_container_metadata(container, [attr])
200

    
201
    def container_rename(self, container, path):
202
        new_container, sep, new_object = path[1:].partition('/')
203
        if not new_container or new_object:
204
            raise FuseOSError(EINVAL)
205
        self.client.delete_container(container)
206
        self.client.create_container(new_container)
207

    
208
    def container_rmdir(self, container):
209
        try:
210
            self.client.delete_container(container)
211
        except Fault:
212
            raise FuseOSError(ENOENT)
213

    
214
    def container_setxattr(self, container, name, value, options, position=0):
215
        attr = 'xattr-' + name
216
        meta = {attr: value}
217
        self.client.update_container_metadata(container, **meta)
218

    
219
    # Object Level
220
    def object_chmod(self, container, object, mode):
221
        self.client.update_object_metadata(container, object, mode=str(mode))
222

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

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

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

    
242
        if meta['content-type'].split(';', 1)[0].strip() == 'application/directory':
243
            mode = int(meta.get('x-object-meta-mode', 0755))
244
            flags = S_IFDIR
245
            nlink = 2
246
        else:
247
            mode = int(meta.get('x-object-meta-mode', 0644))
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
        mode = str(mode & 0777)
272
        self.client.create_directory_marker(container, object)
273
        self.client.update_object_metadata(container, object, mode=mode)
274

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

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

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

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

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

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

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

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

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

    
317

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

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

    
327

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