Revision 5a96180b

b/MANIFEST.in
1 1
global-include */templates/* */fixtures/* */static/*
2 2
global-exclude */.DS_Store
3
exclude */settings.py
4
include */settings.py.dist
3
include pithos/settings.d/*
5 4
prune docs
6
prune tools
5
prune other
b/README
44 44

  
45 45
This server is useful during development, but should not be used for deployment.
46 46
To deploy Pithos using Apache, take a look at the Administrator Guide in docs.
47

  
48
Using the tools
49
---------------
50

  
51
In the pithos/tools directory you will find the following utilities:
52

  
53
    pithos-sh       Pithos shell
54
    pithos-sync     Pithos synchronization client
55
    pithos-fs       Pithos FUSE implementation
56
    pithos-test     Pithos server tests
57

  
58
Also, the pithos/lib directory contains a python library that can be used
59
to access Pithos and manage stored objects. All tools use the included lib.
60

  
61
Connection options can be set via environmental variables:
62

  
63
    PITHOS_USER             Login user
64
    PITHOS_AUTH             Login token
65
    PITHOS_SERVER           Pithos server (default: plus.pithos.grnet.gr)
66
    PITHOS_API              Pithos server path (default: v1)
67
    PITHOS_SYNC_CONTAINER   Container to sync with (default: pithos)
b/docs/source/backends.rst
33 33
   :members:
34 34
   :undoc-members:
35 35

  
36
Hashfiler
37
~~~~~~~~~
36
Store
37
~~~~~
38 38

  
39
.. automodule:: pithos.backends.lib.hashfiler
39
.. automodule:: pithos.backends.lib.hashfiler.store
40 40
   :show-inheritance:
41 41
   :members:
42 42
   :undoc-members:
b/docs/source/clientlib.rst
1 1
Client Library
2 2
==============
3 3

  
4
.. automodule:: tools.lib.client
4
.. automodule:: pithos.lib.client
5 5
   :show-inheritance:
6 6
   :members:
7 7
   :undoc-members:
b/other/invite.py
1
#!/usr/bin/env python
2

  
3
import sys
4

  
5
if len(sys.argv) != 4:
6
	print "Usage: %s <inviter token> <invitee name> <invitee email>" % (sys.argv[0],)
7
	sys.exit(-1)
8

  
9
import httplib2
10
http = httplib2.Http(disable_ssl_certificate_validation=True)
11

  
12
url = 'https://pithos.dev.grnet.gr/im/invite'
13

  
14
import urllib
15
params = urllib.urlencode({
16
	'uniq': sys.argv[3],
17
	'realname': sys.argv[2]
18
})
19

  
20
response, content = http.request(url, 'POST', params,
21
	headers={'Content-type': 'application/x-www-form-urlencoded', 'X-Auth-Token': sys.argv[1]}
22
)
23

  
24
if response['status'] == '200':
25
	print 'OK'
26
	sys.exit(0)
27
else:
28
	print response, content
29
	sys.exit(-1)
b/other/migrate-data
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 binascii import hexlify
37

  
38
from sqlalchemy import Table
39
from sqlalchemy.sql import select
40

  
41
from pithos import settings
42
from pithos.backends.modular import ModularBackend
43

  
44
from pithos.lib.hashmap import HashMap
45

  
46
from migrate import Migration, Cache
47

  
48
import os
49
    
50
class DataMigration(Migration):
51
    def __init__(self, pithosdb, db):
52
        Migration.__init__(self,  pithosdb)
53
        self.cache = Cache(db)
54
    
55
    def retrieve_files(self):
56
        # Loop for all available files.
57
        filebody = Table('filebody', self.metadata, autoload=True)
58
        s = select([filebody.c.storedfilepath])
59
        rp = self.conn.execute(s)
60
        path = rp.fetchone()
61
        while path:
62
            yield path
63
            path = rp.fetchone()
64
        rp.close()
65
    
66
    def execute(self):
67
        blocksize = self.backend.block_size
68
        blockhash = self.backend.hash_algorithm
69
        
70
        for (path,) in self.retrieve_files():
71
            map = HashMap(blocksize, blockhash)
72
            try:
73
                map.load(open(path))
74
            except Exception, e:
75
                print e
76
                continue
77
            hash = hexlify(map.hash())
78
            
79
            if hash != self.cache.get(path):
80
                missing = self.backend.blocker.block_ping(map) # XXX Backend hack...
81
                status = '[>] ' + path
82
                if missing:
83
                    status += ' - %d block(s) missing' % len(missing)
84
                    with open(path) as fp:
85
                        for h in missing:
86
                            offset = map.index(h) * blocksize
87
                            fp.seek(offset)
88
                            block = fp.read(blocksize)
89
                            self.backend.put_block(block)
90
                else:
91
                    status += ' - no blocks missing'
92
                self.cache.put(path, hash)
93
            else:
94
                status = '[-] ' + path
95
            print status
96

  
97
if __name__ == "__main__":
98
    pithosdb = 'postgresql://gss@127.0.0.1/pithos'
99
    db = 'sqlite:///migrate.db'
100
    
101
    dt = DataMigration(pithosdb, db)
102
    dt.execute()
b/other/migrate-db
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 sqlalchemy import Table
37
from sqlalchemy.sql import select, and_
38

  
39
from binascii import hexlify
40

  
41
from pithos.backends.lib.hashfiler import Blocker
42
from pithos.backends.lib.sqlalchemy import Node
43

  
44
from django.conf import settings
45

  
46
from pithos.backends.modular import CLUSTER_NORMAL, CLUSTER_HISTORY, CLUSTER_DELETED
47
from pithos.backends.lib.sqlalchemy.node import Node, ROOTNODE
48

  
49
from pithos.lib.transfer import upload
50
from pithos.lib.hashmap import HashMap
51
from pithos.lib.client import Fault
52

  
53
from migrate import Migration, Cache
54

  
55
from calendar import timegm
56
from decimal import Decimal
57
from collections import defaultdict
58

  
59
import json
60
import os
61
import sys
62
import hashlib
63
import mimetypes
64
import time
65
import datetime
66

  
67
(ID, CREATIONDATE, MODIFICATIONDATE, DELETED, ICON, NAME, VERSION, CREATEDBY_ID, MODIFIEDBY_ID, OWNER_ID, PARENT_ID, READFORALL, SHARED, USER) = range(14)
68

  
69
class ObjectMigration(Migration):
70
    def __init__(self, old_db, db, f):
71
        Migration.__init__(self, old_db)
72
        self.cache = Cache(db)
73
    
74
    def create_node(self, username, container, object):
75
        node = self.backend.node.node_lookup(object)
76
        if not node:
77
            parent_path = '%s/%s' %(username, container)
78
            parent_node = self.backend.node.node_lookup(parent_path)
79
            if not parent_node:
80
                raise Exception('Missing node')
81
            node = self.backend.node.node_create(parent_node, object)
82
        return node
83
    
84
    def create_history(self, header_id, node_id, deleted=False):
85
        i = 0
86
        map = HashMap(self.backend.block_size, self.backend.hash_algorithm)
87
        v = []
88
        stored_versions = self.backend.node.node_get_versions(node_id, ['mtime'])
89
        stored_versions_mtime = [datetime.datetime.utcfromtimestamp(elem[0]) for elem in stored_versions]
90
        for t, rowcount  in self.retrieve_node_versions(header_id):
91
            size, modyfied_by, filepath, mimetype, mdate = t
92
            if mdate in stored_versions_mtime:
93
                continue
94
            cluster = CLUSTER_HISTORY if i < rowcount - 1 else CLUSTER_NORMAL
95
            cluster = cluster if not deleted else CLUSTER_DELETED
96
            hash = self.cache.get(filepath)
97
            if hash == None:
98
                raise Exception("Missing hash")
99
            args = node_id, hash, size, modyfied_by, cluster, mimetype, mdate
100
            v.append(self.create_version(*args))
101
            i += 1
102
        return v
103
    
104
    def create_version(self, node_id, hash, size, modyfied_by, cluster, mimetype, mdate):
105
        args = (node_id, hash, size, None, modyfied_by, cluster)
106
        serial = self.backend.node.version_create(*args)[0]
107
        meta = {'hash':hash,
108
                'content-type':mimetype}
109
        self.backend.node.attribute_set(serial, ((k, v) for k, v in meta.iteritems()))
110
        timestamp = timegm(mdate.timetuple())
111
        microseconds = mdate.time().microsecond
112
        values = timestamp, microseconds, serial
113
        f.write('update versions set mtime=\'%10d.%6d\' where serial=%s;' %values)
114
        return serial
115
    
116
    def create_tags(self, header_id, node_id, vserials):
117
        tags = self.retrieve_tags(header_id)
118
        if not tags:
119
            return
120
        for v in vserials:
121
            self.backend.node.attribute_set(v, (('X-Object-Meta-Tag', tags),))
122
    
123
    def create_permissions(self, fid, path, owner, is_folder=True):
124
        fpath, fpermissions = self.backend.permissions.access_inherit(path)
125
        permissions = self.retrieve_permissions(fid, is_folder)
126
        if not fpermissions:
127
            keys = ('read', 'write')
128
            for k in keys:
129
                if owner in permissions[k]:
130
                    permissions[k].remove(owner)
131
            self.backend.permissions.access_set(path, permissions)
132
        else:
133
            keys = ('read', 'write')
134
            common_p = {}
135
            for k in keys:
136
                if owner in permissions[k]:
137
                    permissions[k].remove(owner)
138
                common = set(fpermissions[k]).intersection(set(permissions[k]))
139
                common_p[k] = list(common)
140
            #keep only the common permissions
141
            #trade off for securing access only to explicitly authorized users
142
            self.backend.permissions.access_set(fpath, common_p)
143
    
144
    def create_objects(self):
145
        for t in self.retrieve_current_nodes():
146
            username, headerid, folderid, filename, deleted, filepath, mimetype, public, owner_id = t
147
            containers = ['pithos', 'trash']
148
            
149
            for c in containers:
150
                #create container if it does not exist
151
                try:
152
                    self.backend._lookup_container(username, c)
153
                except NameError, e:
154
                    self.backend.put_container(username, username, c) 
155
            
156
            container = 'pithos' if not deleted else 'trash'
157
            path = self.build_path(folderid)
158
            #create node
159
            object = '%s/%s' %(username, container)
160
            object = '%s/%s/%s' %(object, path, filename) if path else '%s/%s' %(object, filename)
161
            args = username, container, object
162
            nodeid = self.create_node(*args)
163
            #create node history 
164
            vserials = self.create_history(headerid, nodeid, deleted)
165
            #set object tags
166
            self.create_tags(headerid, nodeid, vserials)
167
            #set object's publicity
168
            if public:
169
                self.backend.permissions.public_set(object)
170
            #set object's permissions
171
            self.create_permissions(headerid, object, username, is_folder=False)
172
    
173
    def build_path(self, child_id):
174
        folder = Table('folder', self.metadata, autoload=True)
175
        user = Table('gss_user', self.metadata, autoload=True)
176
        j = folder.join(user, folder.c.owner_id == user.c.id)
177
        s = select([folder, user.c.username], from_obj=j)
178
        s = s.where(folder.c.id == child_id)
179
        s.order_by(folder.c.modificationdate)
180
        rp = self.conn.execute(s)
181
        t = rp.fetchone()
182
        md5 = hashlib.md5()
183
        hash = md5.hexdigest().lower()
184
        size = 0
185
        if not t[PARENT_ID]:
186
            return ''
187
        else:
188
            container_path = t[USER]
189
            container_path += '/trash' if t[DELETED] else '/pithos'
190
            parent_node = self.backend.node.node_lookup(container_path)
191
            if not parent_node:
192
                raise Exception('Missing node:', container_path)
193
            parent_path = self.build_path(t[PARENT_ID])
194
            path = '%s/%s/%s' %(container_path, parent_path, t[NAME]) if parent_path else '%s/%s' %(container_path, t[NAME])
195
            node = self.backend.node.node_lookup(path)
196
            if not node:
197
                node = self.backend.node.node_create(parent_node, path)
198
                if not node:
199
                    raise Exception('Unable to create node:', path)
200
                
201
                #create versions
202
                v = self.create_version(node, hash, size, t[USER], CLUSTER_NORMAL, 'application/directory', t[CREATIONDATE])
203
                if t[CREATIONDATE] != t[MODIFICATIONDATE]:
204
                    self.backend.node.version_recluster(v, CLUSTER_HISTORY)
205
                    self.create_version(node, hash, size, t[USER], CLUSTER_NORMAL, 'application/directory', t[MODIFICATIONDATE])
206
                
207
                #set permissions
208
                self.create_permissions(t[ID], path, t[USER], is_folder=True)
209
            return '%s/%s' %(parent_path, t[NAME]) if parent_path else t[NAME]
210
    
211
    def retrieve_current_nodes(self):
212
        fileheader = Table('fileheader', self.metadata, autoload=True)
213
        filebody = Table('filebody', self.metadata, autoload=True)
214
        folder = Table('folder', self.metadata, autoload=True)
215
        gss_user = Table('gss_user', self.metadata, autoload=True)
216
        j = filebody.join(fileheader, filebody.c.id == fileheader.c.currentbody_id)
217
        j = j.join(folder, fileheader.c.folder_id == folder.c.id)
218
        j = j.join(gss_user, fileheader.c.owner_id == gss_user.c.id)
219
        s = select([gss_user.c.username,  fileheader.c.id, fileheader.c.folder_id,
220
                    fileheader.c.name,  fileheader.c.deleted,
221
                    filebody.c.storedfilepath, filebody.c.mimetype,
222
                    fileheader.c.readforall, fileheader.c.owner_id], from_obj=j)
223
        rp = self.conn.execute(s)
224
        object = rp.fetchone()
225
        while object:
226
            yield object
227
            object = rp.fetchone()
228
        rp.close()
229
    
230
    def retrieve_node_versions(self, header_id):
231
        filebody = Table('filebody', self.metadata, autoload=True)
232
        gss_user = Table('gss_user', self.metadata, autoload=True)
233
        j = filebody.join(gss_user, filebody.c.modifiedby_id == gss_user.c.id)
234
        s = select([filebody.c.filesize, gss_user.c.username,
235
                    filebody.c.storedfilepath, filebody.c.mimetype,
236
                    filebody.c.modificationdate], from_obj=j)
237
        s = s.where(filebody.c.header_id == header_id)
238
        s = s.order_by(filebody.c.version)
239
        rp = self.conn.execute(s)
240
        version = rp.fetchone()
241
        while version:
242
            yield version, rp.rowcount
243
            version = rp.fetchone()
244
        rp.close()
245
    
246
    def retrieve_tags(self, header_id):
247
        filetag = Table('filetag', self.metadata, autoload=True)
248
        s = select([filetag.c.tag], filetag.c.fileid == header_id)
249
        rp = self.conn.execute(s)
250
        tags = rp.fetchall() if rp.returns_rows else []
251
        tags = [elem[0] for elem in tags]
252
        rp.close()
253
        return ','.join(tags) if tags else ''
254
    
255
    def retrieve_permissions(self, id, is_folder=True):
256
        permissions = {}
257
        if is_folder:
258
            ftable = Table('folder_permission', self.metadata, autoload=True)
259
        else:
260
            ftable = Table('fileheader_permission', self.metadata, autoload=True)
261
        permission = Table('permission', self.metadata, autoload=True)
262
        group = Table('gss_group', self.metadata, autoload=True)
263
        user = Table('gss_user', self.metadata, autoload=True)
264
        j = ftable.join(permission, ftable.c.permissions_id == permission.c.id)
265
        j1 = j.join(group, group.c.id == permission.c.group_id)
266
        j2 = j.join(user, user.c.id == permission.c.user_id)
267
        
268
        permissions = defaultdict(list)
269
        
270
        def _get_permissions(self, action='read', get_groups=True):
271
            if get_groups:
272
                col, j = group.c.name, j1
273
                cond2 = permission.c.group_id != None
274
            else:
275
                col, j = user.c.username, j2
276
                cond2 = permission.c.user_id != None
277
            s = select([col], from_obj=j)
278
            if is_folder:
279
                s = s.where(ftable.c.folder_id == id)
280
            else:
281
                s = s.where(ftable.c.fileheader_id == id)
282
            if action == 'read':
283
                cond1 = permission.c.read == True
284
            else:
285
                cond1 = permission.c.write == True
286
            s = s.where(and_(cond1, cond2))
287
            print '>', s, s.compile().params
288
            rp = self.conn.execute(s)
289
            p = permissions[action].extend([e[0] for e in rp.fetchall()])
290
            rp.close()
291
            return p
292
        
293
        #get object read groups
294
        _get_permissions(self, action='read', get_groups=True)
295
        
296
        #get object read users
297
        _get_permissions(self, action='read', get_groups=False)
298
        
299
        #get object write groups
300
        _get_permissions(self, action='write', get_groups=True)
301
        
302
        #get object write groups
303
        _get_permissions(self, action='write', get_groups=False)
304
        
305
        return permissions
306
    
307
if __name__ == "__main__":
308
    old_db = ''
309
    db = ''
310
    
311
    f = open('fixdates.sql', 'w')
312
    ot = ObjectMigration(old_db, db, f)
313
    ot.create_objects()
314
    f.close()
315
    
316
    
b/other/migrate-users
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 sqlalchemy import Table
37
from sqlalchemy.sql import select
38

  
39
from pithos.im.models import User
40

  
41
from migrate import Migration
42

  
43
import base64
44

  
45
class UserMigration(Migration):
46
    def __init__(self, db):
47
        Migration.__init__(self, db)
48
        self.gss_users = Table('gss_user', self.metadata, autoload=True)
49
    
50
    def execute(self):
51
        for u in self.retrieve_users():
52
            user = User()
53
            user.pk = u['id']
54
            user.uniq = u['username']
55
            user.realname = u['name']
56
            user.affiliation = u['homeorganization'] if u['homeorganization'] else ''
57
            user.auth_token = base64.b64encode(u['authtoken'])
58
            user.auth_token_created = u['creationdate']
59
            user.auth_token_expires = u['authtokenexpirydate']
60
            user.created = u['creationdate']
61
            user.updated = u['modificationdate']
62
            user.email = u['email']
63
            user.active = 'ACTIVE' if u['active'] else 'SUSPENDED'
64
            print '#', user
65
            user.save(update_timestamps=False)
66
            
67
            #create user groups
68
            for (owner, group, members) in self.retrieve_groups(u['username']):
69
                self.backend.permissions.group_addmany(owner, group, members)
70
    
71
    
72
    def retrieve_users(self):
73
        s = self.gss_users.select()
74
        rp = self.conn.execute(s)
75
        user = rp.fetchone()
76
        while user:
77
            yield user
78
            user = rp.fetchone()
79
        rp.close()
80
    
81
    def retrieve_groups(self, owner):
82
        gss_group = Table('gss_group', self.metadata, autoload=True)
83
        gss_user = Table('gss_user', self.metadata, autoload=True)
84
        group_user = Table('gss_group_gss_user', self.metadata, autoload=True)
85
        j1 = gss_group.join(gss_user, gss_group.c.owner_id == gss_user.c.id)
86
        j2 = group_user.join(gss_user, group_user.c.members_id == gss_user.c.id)
87
        s = select([gss_group.c.id, gss_group.c.name, gss_user.c.username], from_obj=j1)
88
        s = s.where(gss_user.c.username == owner)
89
        rp = self.conn.execute(s)
90
        gr = rp.fetchone()
91
        while gr:
92
            id, group, owner = gr
93
            s = select([gss_user.c.username], from_obj=j2)
94
            s = s.where(group_user.c.groupsmember_id == id)
95
            rp2 = self.conn.execute(s)
96
            members = rp2.fetchall()
97
            rp2.close()
98
            yield owner, group, (m[0] for m in members)
99
            gr = rp.fetchone()
100
        rp.close()
101

  
102
if __name__ == "__main__":
103
    db = ''
104
    m = UserMigration(db)
105
    m.execute()
b/other/migrate.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 sqlalchemy import create_engine
37
from sqlalchemy import Table, Column, String, MetaData
38
from sqlalchemy.sql import select
39

  
40
from django.conf import settings
41

  
42
from pithos.backends.modular import ModularBackend
43

  
44
class Migration(object):
45
    def __init__(self, db):
46
        self.engine = create_engine(db)
47
        self.metadata = MetaData(self.engine)
48
        #self.engine.echo = True
49
        self.conn = self.engine.connect()
50
        
51
        options = getattr(settings, 'BACKEND', None)[1]
52
        self.backend = ModularBackend(*options)
53
    
54
    def execute(self):
55
        pass
56

  
57
class Cache():
58
    def __init__(self, db):
59
        self.engine = create_engine(db)
60
        metadata = MetaData(self.engine)
61
        
62
        columns=[]
63
        columns.append(Column('path', String(2048), primary_key=True))
64
        columns.append(Column('hash', String(255)))
65
        self.files = Table('files', metadata, *columns)
66
        self.conn = self.engine.connect()
67
        self.engine.echo = True
68
        metadata.create_all(self.engine)
69
    
70
    def put(self, path, hash):
71
        # Insert or replace.
72
        s = self.files.delete().where(self.files.c.path==path)
73
        r = self.conn.execute(s)
74
        r.close()
75
        s = self.files.insert()
76
        r = self.conn.execute(s, {'path': path, 'hash': hash})
77
        r.close()
78
    
79
    def get(self, path):
80
        s = select([self.files.c.hash], self.files.c.path == path)
81
        r = self.conn.execute(s)
82
        l = r.fetchone()
83
        r.close()
84
        if not l:
85
            return l
86
        return l[0]
b/pithos/__init__.py
1
# Copyright (c) Django Software Foundation and individual contributors.
2
# All rights reserved.
3
# 
4
# Redistribution and use in source and binary forms, with or without modification,
5
# are permitted provided that the following conditions are met:
6
# 
7
#     1. Redistributions of source code must retain the above copyright notice, 
8
#        this list of conditions and the following disclaimer.
9
#     
10
#     2. Redistributions in binary form must reproduce the above copyright 
11
#        notice, this list of conditions and the following disclaimer in the
12
#        documentation and/or other materials provided with the distribution.
13
# 
14
#     3. Neither the name of Django nor the names of its contributors may be used
15
#        to endorse or promote products derived from this software without
16
#        specific prior written permission.
17
# 
18
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28

  
29
VERSION = (0, 8, 2, 'alpha', 0)
30

  
31
def get_version():
32
    version = '%s.%s' % (VERSION[0], VERSION[1])
33
    if VERSION[2]:
34
        version = '%s.%s' % (version, VERSION[2])
35
    if VERSION[3:] == ('alpha', 0):
36
        version = '%s pre-alpha' % version
37
    else:
38
        if VERSION[3] != 'final':
39
            version = '%s %s %s' % (version, VERSION[3], VERSION[4])
40
    return version
/dev/null
1
# Copyright (c) Django Software Foundation and individual contributors.
2
# All rights reserved.
3
# 
4
# Redistribution and use in source and binary forms, with or without modification,
5
# are permitted provided that the following conditions are met:
6
# 
7
#     1. Redistributions of source code must retain the above copyright notice, 
8
#        this list of conditions and the following disclaimer.
9
#     
10
#     2. Redistributions in binary form must reproduce the above copyright 
11
#        notice, this list of conditions and the following disclaimer in the
12
#        documentation and/or other materials provided with the distribution.
13
# 
14
#     3. Neither the name of Django nor the names of its contributors may be used
15
#        to endorse or promote products derived from this software without
16
#        specific prior written permission.
17
# 
18
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28

  
29
import re
30
import datetime
31
import calendar
32

  
33
MONTHS = 'jan feb mar apr may jun jul aug sep oct nov dec'.split()
34
__D = r'(?P<day>\d{2})'
35
__D2 = r'(?P<day>[ \d]\d)'
36
__M = r'(?P<mon>\w{3})'
37
__Y = r'(?P<year>\d{4})'
38
__Y2 = r'(?P<year>\d{2})'
39
__T = r'(?P<hour>\d{2}):(?P<min>\d{2}):(?P<sec>\d{2})'
40
RFC1123_DATE = re.compile(r'^\w{3}, %s %s %s %s GMT$' % (__D, __M, __Y, __T))
41
RFC850_DATE = re.compile(r'^\w{6,9}, %s-%s-%s %s GMT$' % (__D, __M, __Y2, __T))
42
ASCTIME_DATE = re.compile(r'^\w{3} %s %s %s %s$' % (__M, __D2, __T, __Y))
43

  
44
def parse_http_date(date):
45
    """
46
    Parses a date format as specified by HTTP RFC2616 section 3.3.1.
47

  
48
    The three formats allowed by the RFC are accepted, even if only the first
49
    one is still in widespread use.
50

  
51
    Returns an floating point number expressed in seconds since the epoch, in
52
    UTC.
53
    """
54
    # emails.Util.parsedate does the job for RFC1123 dates; unfortunately
55
    # RFC2616 makes it mandatory to support RFC850 dates too. So we roll
56
    # our own RFC-compliant parsing.
57
    for regex in RFC1123_DATE, RFC850_DATE, ASCTIME_DATE:
58
        m = regex.match(date)
59
        if m is not None:
60
            break
61
    else:
62
        raise ValueError("%r is not in a valid HTTP date format" % date)
63
    try:
64
        year = int(m.group('year'))
65
        if year < 100:
66
            if year < 70:
67
                year += 2000
68
            else:
69
                year += 1900
70
        month = MONTHS.index(m.group('mon').lower()) + 1
71
        day = int(m.group('day'))
72
        hour = int(m.group('hour'))
73
        min = int(m.group('min'))
74
        sec = int(m.group('sec'))
75
        result = datetime.datetime(year, month, day, hour, min, sec)
76
        return calendar.timegm(result.utctimetuple())
77
    except Exception:
78
        raise ValueError("%r is not a valid date" % date)
79

  
80
def parse_http_date_safe(date):
81
    """
82
    Same as parse_http_date, but returns None if the input is invalid.
83
    """
84
    try:
85
        return parse_http_date(date)
86
    except Exception:
87
        pass
b/pithos/api/util.py
47 47
from django.core.files.uploadhandler import FileUploadHandler
48 48
from django.core.files.uploadedfile import UploadedFile
49 49

  
50
from pithos.api.compat import parse_http_date_safe, parse_http_date
50
from pithos.lib.compat import parse_http_date_safe, parse_http_date
51

  
51 52
from pithos.api.faults import (Fault, NotModified, BadRequest, Unauthorized, Forbidden, ItemNotFound,
52 53
                                Conflict, LengthRequired, PreconditionFailed, RequestEntityTooLarge,
53 54
                                RangeNotSatisfiable, ServiceUnavailable)
b/pithos/backends/modular.py
35 35
import os
36 36
import time
37 37
import logging
38
import hashlib
39 38
import binascii
40 39

  
41 40
from base import NotAllowedError, QuotaError, BaseBackend
42 41

  
42
from pithos.lib.hashmap import HashMap
43

  
43 44
( CLUSTER_NORMAL, CLUSTER_HISTORY, CLUSTER_DELETED ) = range(3)
44 45

  
45 46
inf = float('inf')
......
50 51
logger = logging.getLogger(__name__)
51 52

  
52 53

  
53
class HashMap(list):
54
    
55
    def __init__(self, blocksize, blockhash):
56
        super(HashMap, self).__init__()
57
        self.blocksize = blocksize
58
        self.blockhash = blockhash
59
    
60
    def _hash_raw(self, v):
61
        h = hashlib.new(self.blockhash)
62
        h.update(v)
63
        return h.digest()
64
    
65
    def hash(self):
66
        if len(self) == 0:
67
            return self._hash_raw('')
68
        if len(self) == 1:
69
            return self.__getitem__(0)
70
        
71
        h = list(self)
72
        s = 2
73
        while s < len(h):
74
            s = s * 2
75
        h += [('\x00' * len(h[0]))] * (s - len(h))
76
        while len(h) > 1:
77
            h = [self._hash_raw(h[x] + h[x + 1]) for x in range(0, len(h), 2)]
78
        return h[0]
79

  
80

  
81 54
def backend_method(func=None, autocommit=1):
82 55
    if func is None:
83 56
        def fn(func):
b/pithos/lib/client.py
1
# Copyright 2011 GRNET S.A. All rights reserved.
2
# 
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
# 
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
# 
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
# 
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
# 
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

  
34
from httplib import HTTPConnection, HTTP
35
from sys import stdin
36
from xml.dom import minidom
37
from StringIO import StringIO
38
from urllib import quote, unquote
39

  
40
import json
41
import types
42
import socket
43
import urllib
44
import datetime
45

  
46
ERROR_CODES = {304:'Not Modified',
47
               400:'Bad Request',
48
               401:'Unauthorized',
49
               403:'Forbidden',
50
               404:'Not Found',
51
               409:'Conflict',
52
               411:'Length Required',
53
               412:'Precondition Failed',
54
               413:'Request Entity Too Large',
55
               416:'Range Not Satisfiable',
56
               422:'Unprocessable Entity',
57
               503:'Service Unavailable',
58
               }
59

  
60
class Fault(Exception):
61
    def __init__(self, data='', status=None):
62
        if data == '' and status in ERROR_CODES.keys():
63
            data = ERROR_CODES[status]
64
        Exception.__init__(self, data)
65
        self.data = data
66
        self.status = status
67

  
68
class Client(object):
69
    def __init__(self, host, token, account, api='v1', verbose=False, debug=False):
70
        """`host` can also include a port, e.g '127.0.0.1:8000'."""
71
        
72
        self.host = host
73
        self.account = account
74
        self.api = api
75
        self.verbose = verbose or debug
76
        self.debug = debug
77
        self.token = token
78
    
79
    def _req(self, method, path, body=None, headers={}, format='text', params={}):
80
        slash = '/' if self.api else ''
81
        full_path = '%s%s%s?format=%s' % (slash, self.api, quote(path), format)
82
        
83
        for k,v in params.items():
84
            if v:
85
                full_path = '%s&%s=%s' %(full_path, quote(k), quote(str(v)))
86
            else:
87
                full_path = '%s&%s=' %(full_path, k)
88
        conn = HTTPConnection(self.host)
89
        
90
        kwargs = {}
91
        for k,v in headers.items():
92
            headers.pop(k)
93
            k = k.replace('_', '-')
94
            headers[quote(k)] = quote(v, safe='/=,:@ *"') if type(v) == types.StringType else v
95
        
96
        kwargs['headers'] = headers
97
        kwargs['headers']['X-Auth-Token'] = self.token
98
        if body:
99
            kwargs['body'] = body
100
            kwargs['headers'].setdefault('content-type', 'application/octet-stream')
101
        kwargs['headers'].setdefault('content-length', len(body) if body else 0)
102
        
103
        #print '#', method, full_path, kwargs
104
        t1 = datetime.datetime.utcnow()
105
        conn.request(method, full_path, **kwargs)
106
        
107
        resp = conn.getresponse()
108
        t2 = datetime.datetime.utcnow()
109
        #print 'response time:', str(t2-t1)
110
        headers = resp.getheaders()
111
        headers = dict((unquote(h), unquote(v)) for h,v in headers)
112
        
113
        if self.verbose:
114
            print '%d %s' % (resp.status, resp.reason)
115
            for key, val in headers.items():
116
                print '%s: %s' % (key.capitalize(), val)
117
            print
118
        
119
        length = resp.getheader('content-length', None)
120
        data = resp.read(length)
121
        if self.debug:
122
            print data
123
            print
124
        
125
        if int(resp.status) in ERROR_CODES.keys():
126
            raise Fault(data, int(resp.status))
127
        
128
        #print '**',  resp.status, headers, data, '\n'
129
        return resp.status, headers, data
130
    
131
    def _chunked_transfer(self, path, method='PUT', f=stdin, headers=None,
132
                          blocksize=1024, params={}):
133
        """perfomrs a chunked request"""
134
        http = HTTPConnection(self.host)
135
        
136
        # write header
137
        full_path = '/%s%s?' % (self.api, path)
138
        
139
        for k,v in params.items():
140
            if v:
141
                full_path = '%s&%s=%s' %(full_path, k, v)
142
            else:
143
                full_path = '%s&%s=' %(full_path, k)
144
        
145
        full_path = urllib.quote(full_path, '?&:=/')
146
        
147
        http.putrequest(method, full_path)
148
        http.putheader('x-auth-token', self.token)
149
        http.putheader('content-type', 'application/octet-stream')
150
        http.putheader('transfer-encoding', 'chunked')
151
        if headers:
152
            for header,value in headers.items():
153
                http.putheader(header, value)
154
        http.endheaders()
155
        
156
        # write body
157
        data = ''
158
        while True:
159
            if f.closed:
160
                break
161
            block = f.read(blocksize)
162
            if block == '':
163
                break
164
            data = '%x\r\n%s\r\n' % (len(block), block)
165
            try:
166
                http.send(data)
167
            except:
168
                #retry
169
                http.send(data)
170
        data = '0\r\n\r\n'
171
        try:
172
            http.send(data)
173
        except:
174
            #retry
175
            http.send(data)
176
        
177
        # get response
178
        resp = http.getresponse()
179
        
180
        headers = dict(resp.getheaders())
181
        
182
        if self.verbose:
183
            print '%d %s' % (resp.status, resp.reason)
184
            for key, val in headers.items():
185
                print '%s: %s' % (key.capitalize(), val)
186
            print
187
        
188
        length = resp.getheader('Content-length', None)
189
        data = resp.read(length)
190
        if self.debug:
191
            print data
192
            print
193
        
194
        if int(resp.status) in ERROR_CODES.keys():
195
            raise Fault(data, int(resp.status))
196
        
197
        #print '*',  resp.status, headers, data
198
        return resp.status, headers, data
199
    
200
    def delete(self, path, format='text', params={}):
201
        return self._req('DELETE', path, format=format, params=params)
202
    
203
    def get(self, path, format='text', headers={}, params={}):
204
        return self._req('GET', path, headers=headers, format=format,
205
                        params=params)
206
    
207
    def head(self, path, format='text', params={}):
208
         return self._req('HEAD', path, format=format, params=params)
209
    
210
    def post(self, path, body=None, format='text', headers=None, params={}):
211
        return self._req('POST', path, body, headers=headers, format=format,
212
                        params=params)
213
    
214
    def put(self, path, body=None, format='text', headers=None, params={}):
215
        return self._req('PUT', path, body, headers=headers, format=format,
216
                         params=params)
217
    
218
    def _list(self, path, format='text', params={}, **headers):
219
        status, headers, data = self.get(path, format=format, headers=headers,
220
                                         params=params)
221
        if format == 'json':
222
            data = json.loads(data) if data else ''
223
        elif format == 'xml':
224
            data = minidom.parseString(data)
225
        else:
226
            data = data.split('\n')[:-1] if data else ''
227
        return data
228
    
229
    def _get_metadata(self, path, prefix=None, params={}):
230
        status, headers, data = self.head(path, params=params)
231
        prefixlen = len(prefix) if prefix else 0
232
        meta = {}
233
        for key, val in headers.items():
234
            if prefix and not key.startswith(prefix):
235
                continue
236
            elif prefix and key.startswith(prefix):
237
                key = key[prefixlen:]
238
            meta[key] = val
239
        return meta
240
    
241
    def _filter(self, l, d):
242
        """
243
        filter out from l elements having the metadata values provided
244
        """
245
        ll = l
246
        for elem in l:
247
            if type(elem) == types.DictionaryType:
248
                for key in d.keys():
249
                    k = 'x_object_meta_%s' % key
250
                    if k in elem.keys() and elem[k] == d[key]:
251
                        ll.remove(elem)
252
                        break
253
        return ll
254
    
255
class OOS_Client(Client):
256
    """Openstack Object Storage Client"""
257
    
258
    def _update_metadata(self, path, entity, **meta):
259
        """adds new and updates the values of previously set metadata"""
260
        ex_meta = self.retrieve_account_metadata(restricted=True)
261
        ex_meta.update(meta)
262
        headers = {}
263
        prefix = 'x-%s-meta-' % entity
264
        for k,v in ex_meta.items():
265
            k = '%s%s' % (prefix, k)
266
            headers[k] = v
267
        return self.post(path, headers=headers)
268
    
269
    def _reset_metadata(self, path, entity, **meta):
270
        """
271
        overwrites all user defined metadata
272
        """
273
        headers = {}
274
        prefix = 'x-%s-meta-' % entity
275
        for k,v in meta.items():
276
            k = '%s%s' % (prefix, k)
277
            headers[k] = v
278
        return self.post(path, headers=headers)
279
    
280
    def _delete_metadata(self, path, entity, meta=[]):
281
        """delete previously set metadata"""
282
        ex_meta = self.retrieve_account_metadata(restricted=True)
283
        headers = {}
284
        prefix = 'x-%s-meta-' % entity
285
        for k in ex_meta.keys():
286
            if k in meta:
287
                headers['%s%s' % (prefix, k)] = ex_meta[k]
288
        return self.post(path, headers=headers)
289
    
290
    # Storage Account Services
291
    
292
    def list_containers(self, format='text', limit=None,
293
                        marker=None, params={}, account=None, **headers):
294
        """lists containers"""
295
        account = account or self.account
296
        path = '/%s' % account
297
        params.update({'limit':limit, 'marker':marker})
298
        return self._list(path, format, params, **headers)
299
    
300
    def retrieve_account_metadata(self, restricted=False, account=None, **params):
301
        """returns the account metadata"""
302
        account = account or self.account
303
        path = '/%s' % account
304
        prefix = 'x-account-meta-' if restricted else None
305
        return self._get_metadata(path, prefix, params)
306
    
307
    def update_account_metadata(self, account=None, **meta):
308
        """updates the account metadata"""
309
        account = account or self.account
310
        path = '/%s' % account
311
        return self._update_metadata(path, 'account', **meta)
312
        
313
    def delete_account_metadata(self, meta=[], account=None):
314
        """deletes the account metadata"""
315
        account = account or self.account
316
        path = '/%s' % account
317
        return self._delete_metadata(path, 'account', meta)
318
    
319
    def reset_account_metadata(self, account=None, **meta):
320
        """resets account metadata"""
321
        account = account or self.account
322
        path = '/%s' % account
323
        return self._reset_metadata(path, 'account', **meta)
324
    
325
    # Storage Container Services
326
    
327
    def _filter_trashed(self, l):
328
        return self._filter(l, {'trash':'true'})
329
    
330
    def list_objects(self, container, format='text',
331
                     limit=None, marker=None, prefix=None, delimiter=None,
332
                     path=None, include_trashed=False, params={}, account=None,
333
                     **headers):
334
        """returns a list with the container objects"""
335
        account = account or self.account
336
        params.update({'limit':limit, 'marker':marker, 'prefix':prefix,
337
                       'delimiter':delimiter, 'path':path})
338
        l = self._list('/%s/%s' % (account, container), format, params,
339
                       **headers)
340
        #TODO support filter trashed with xml also
341
        if format != 'xml' and not include_trashed:
342
            l = self._filter_trashed(l)
343
        return l
344
    
345
    def create_container(self, container, account=None, **meta):
346
        """creates a container"""
347
        account = account or self.account
348
        headers = {}
349
        for k,v in meta.items():
350
            headers['x-container-meta-%s' %k.strip().upper()] = v.strip()
351
        status, header, data = self.put('/%s/%s' % (account, container),
352
                                        headers=headers)
353
        if status == 202:
354
            return False
355
        elif status != 201:
356
            raise Fault(data, int(status))
357
        return True
358
    
359
    def delete_container(self, container, params={}, account=None):
360
        """deletes a container"""
361
        account = account or self.account
362
        return self.delete('/%s/%s' % (account, container), params=params)
363
    
364
    def retrieve_container_metadata(self, container, restricted=False,
365
                                    account=None, **params):
366
        """returns the container metadata"""
367
        account = account or self.account
368
        prefix = 'x-container-meta-' if restricted else None
369
        return self._get_metadata('/%s/%s' % (account, container), prefix,
370
                                  params)
371
    
372
    def update_container_metadata(self, container, account=None, **meta):
373
        """unpdates the container metadata"""
374
        account = account or self.account
375
        return self._update_metadata('/%s/%s' % (account, container),
376
                                     'container', **meta)
377
        
378
    def delete_container_metadata(self, container, meta=[], account=None):
379
        """deletes the container metadata"""
380
        account = account or self.account
381
        path = '/%s/%s' % (account, container)
382
        return self._delete_metadata(path, 'container', meta)
383
    
384
    # Storage Object Services
385
    
386
    def request_object(self, container, object, format='text', params={},
387
                       account=None, **headers):
388
        """returns tuple containing the status, headers and data response for an object request"""
389
        account = account or self.account
390
        path = '/%s/%s/%s' % (account, container, object)
391
        status, headers, data = self.get(path, format, headers, params)
392
        return status, headers, data
393
    
394
    def retrieve_object(self, container, object, format='text', params={},
395
                        account=None, **headers):
396
        """returns an object's data"""
397
        account = account or self.account
398
        t = self.request_object(container, object, format, params, account,
399
                                **headers)
400
        data = t[2]
401
        if format == 'json':
402
            data = json.loads(data) if data else ''
403
        elif format == 'xml':
404
            data = minidom.parseString(data)
405
        return data
406
    
407
    def retrieve_object_hashmap(self, container, object, params={},
408
                        account=None, **headers):
409
        """returns the hashmap representing object's data"""
410
        args = locals().copy()
411
        for elem in ['self', 'container', 'object']:
412
            args.pop(elem)
413
        return self.retrieve_object(container, object, format='json', **args)
414
    
415
    def create_directory_marker(self, container, object, account=None):
416
        """creates a dierectory marker"""
417
        account = account or self.account
418
        if not object:
419
            raise Fault('Directory markers have to be nested in a container')
420
        h = {'content_type':'application/directory'}
421
        return self.create_zero_length_object(container, object, account=account,
422
                                              **h)
423
    
424
    def create_object(self, container, object, f=stdin, format='text', meta={},
425
                      params={}, etag=None, content_type=None, content_encoding=None,
426
                      content_disposition=None, account=None, **headers):
427
        """creates a zero-length object"""
428
        account = account or self.account
429
        path = '/%s/%s/%s' % (account, container, object)
430
        for k, v  in headers.items():
431
            if v == None:
432
                headers.pop(k)
433
        
434
        l = ['etag', 'content_encoding', 'content_disposition', 'content_type']
435
        l = [elem for elem in l if eval(elem)]
436
        for elem in l:
437
            headers.update({elem:eval(elem)})
438
        headers.setdefault('content-type', 'application/octet-stream')
439
        
440
        for k,v in meta.items():
441
            headers['x-object-meta-%s' %k.strip()] = v.strip()
442
        data = f.read() if f else None
443
        return self.put(path, data, format, headers=headers, params=params)
444
    
445
    def create_zero_length_object(self, container, object, meta={}, etag=None,
446
                                  content_type=None, content_encoding=None,
447
                                  content_disposition=None, account=None,
448
                                  **headers):
449
        account = account or self.account
450
        args = locals().copy()
451
        for elem in ['self', 'container', 'headers', 'account']:
452
            args.pop(elem)
453
        args.update(headers)
454
        return self.create_object(container, account=account, f=None, **args)
455
    
456
    def update_object(self, container, object, f=stdin,
457
                      offset=None, meta={}, params={}, content_length=None,
458
                      content_type=None, content_encoding=None,
459
                      content_disposition=None,  account=None, **headers):
460
        account = account or self.account
461
        path = '/%s/%s/%s' % (account, container, object)
462
        for k, v  in headers.items():
463
            if v == None:
464
                headers.pop(k)
465
        
466
        l = ['content_encoding', 'content_disposition', 'content_type',
467
             'content_length']
468
        l = [elem for elem in l if eval(elem)]
469
        for elem in l:
470
            headers.update({elem:eval(elem)})
471
        
472
        if 'content_range' not in headers.keys():
473
            if offset != None:
474
                headers['content_range'] = 'bytes %s-/*' % offset
475
            else:
476
                headers['content_range'] = 'bytes */*'
477
            
478
        for k,v in meta.items():
479
            headers['x-object-meta-%s' %k.strip()] = v.strip()
480
        data = f.read() if f else None
481
        return self.post(path, data, headers=headers, params=params)
482
    
483
    def update_object_using_chunks(self, container, object, f=stdin,
484
                                   blocksize=1024, offset=None, meta={},
485
                                   params={}, content_type=None, content_encoding=None,
486
                                   content_disposition=None, account=None, **headers):
487
        """updates an object (incremental upload)"""
488
        account = account or self.account
489
        path = '/%s/%s/%s' % (account, container, object)
490
        headers = headers if not headers else {}
491
        l = ['content_type', 'content_encoding', 'content_disposition']
492
        l = [elem for elem in l if eval(elem)]
493
        for elem in l:
494
            headers.update({elem:eval(elem)})
495
        
496
        if offset != None:
497
            headers['content_range'] = 'bytes %s-/*' % offset
498
        else:
499
            headers['content_range'] = 'bytes */*'
500
        
501
        for k,v in meta.items():
502
            v = v.strip()
503
            headers['x-object-meta-%s' %k.strip()] = v
504
        return self._chunked_transfer(path, 'POST', f, headers=headers,
505
                                      blocksize=blocksize, params=params)
506
    
507
    def _change_obj_location(self, src_container, src_object, dst_container,
508
                             dst_object, remove=False, meta={}, account=None,
509
                             content_type=None, **headers):
510
        account = account or self.account
511
        path = '/%s/%s/%s' % (account, dst_container, dst_object)
512
        headers = {} if not headers else headers
513
        for k, v in meta.items():
514
            headers['x-object-meta-%s' % k] = v
515
        if remove:
516
            headers['x-move-from'] = '/%s/%s' % (src_container, src_object)
517
        else:
518
            headers['x-copy-from'] = '/%s/%s' % (src_container, src_object)
519
        headers['content_length'] = 0
520
        if content_type:
521
            headers['content_type'] = content_type 
522
        return self.put(path, headers=headers)
523
    
524
    def copy_object(self, src_container, src_object, dst_container, dst_object,
525
                   meta={}, account=None, content_type=None, **headers):
526
        """copies an object"""
527
        account = account or self.account
528
        return self._change_obj_location(src_container, src_object,
529
                                   dst_container, dst_object, account=account,
530
                                   remove=False, meta=meta,
531
                                   content_type=content_type, **headers)
532
    
533
    def move_object(self, src_container, src_object, dst_container,
534
                             dst_object, meta={}, account=None,
535
                             content_type=None, **headers):
536
        """moves an object"""
537
        account = account or self.account
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff