Revision ae6199c5

b/snf-pithos-app/pithos/api/manage_accounts/__init__.py
1
# Copyright 2012 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 pithos.api.util import get_backend, split_container_object_string
35

  
36
import re
37
import hashlib
38
import os
39

  
40

  
41
def data_read_iterator(str, size=1024):
42
    offset = 0
43
    while True:
44
        data = str[offset:offset + size]
45
        offset = offset + size
46
        if not data:
47
            break
48
        yield data
49

  
50

  
51
class ManageAccounts():
52
    def __init__(self):
53
        self.backend = get_backend()
54

  
55
    def cleanup(self):
56
        self.backend.close()
57

  
58
    def existing_accounts(self):
59
        return sorted([path for path, _ in self.backend.node.node_accounts()])
60

  
61
    def duplicate_accounts(self):
62
        accounts = self.existing_accounts()
63
        duplicates = []
64
        for i in range(len(accounts)):
65
            account = accounts[i]
66
            matcher = re.compile(account, re.IGNORECASE)
67
            duplicate = filter(matcher.match, (i for i in accounts[i + 1:] \
68
                if len(i) == len(account)))
69
            if duplicate:
70
                duplicate.insert(0, account)
71
                duplicates.append(duplicate)
72
        return duplicates
73

  
74
    def list_all_containers(self, account, step=10):
75
        containers = []
76
        marker = None
77
        while 1:
78
            more = self.backend.list_containers(account, account, limit=10,
79
                                                marker=marker)
80
            if not more:
81
                break
82
            containers.extend(more)
83
            marker = more[-1]
84
        return containers
85

  
86
    def list_all_container_objects(self, account, container, virtual=False):
87
        objects = []
88
        marker = None
89
        while 1:
90
            more = self.backend.list_objects(account, account, container,
91
                                             marker=marker, virtual=virtual)
92
            if not more:
93
                break
94
            objects.extend((i[0] for i in more))
95
            marker = more[-1][0]
96
        return objects
97

  
98
    def list_all_objects(self, account, virtual=False):
99
        containers = self.list_all_containers(account)
100
        objects = []
101
        extend = objects.extend
102
        for c in containers:
103
            more = self.list_all_container_objects(account, c, virtual=virtual)
104
            extend([os.path.join(c, i) for i in more])
105
        return objects
106

  
107
    def list_past_versions(self, account, container, name):
108
        versions = self.backend.list_versions(account, account, container,
109
                                              name)
110
        # do not return the current version
111
        return list(x[0] for x in versions[:-1])
112

  
113
    def move_object(self, src_account, src_container, src_name,
114
                    dest_account, dry=True, silent=False):
115
        if src_account not in self.existing_accounts():
116
            raise NameError('%s does not exist' % src_account)
117
        if dest_account not in self.existing_accounts():
118
            raise NameError('%s does not exist' % dest_account)
119

  
120
        trans = self.backend.wrapper.conn.begin()
121
        try:
122
            self._copy_object(src_account, src_container, src_name,
123
                              dest_account, move=True)
124

  
125
            if dry:
126
                if not silent:
127
                    print "Skipping database commit."
128
                trans.rollback()
129
            else:
130
                trans.commit()
131
                if not silent:
132
                    print "%s is deleted." % src_account
133
        except:
134
            trans.rollback()
135
            raise
136

  
137
    def _copy_object(self, src_account, src_container, src_name,
138
                    dest_account, move=False):
139
        path = os.path.join(src_container, src_name)
140
        fullpath = os.path.join(src_account, path)
141
        dest_container = src_container
142
        dest_name = src_name
143

  
144
        meta = self.backend.get_object_meta(src_account, src_account,
145
                                            src_container, src_name, 'pithos',
146
                                            version=None)
147
        content_type = meta.get('type')
148

  
149
        # get source object history
150
        versions = self.list_past_versions(src_account, src_container,
151
                                           src_name)
152

  
153
        # get source object permissions
154
        permissions = self.backend.permissions.access_get(fullpath)
155

  
156
        # get source object public
157
        public = self.backend.get_object_public(src_account, src_account,
158
                                                src_container, src_name)
159

  
160
        if dest_container in self.backend.list_containers(dest_account,
161
                                                          dest_account):
162
            # Note: if dest_container contains an object with the same name
163
            # a new version with the contents of the source object will be
164
            # created and the one in the destination container will pass to
165
            # history
166
            self.backend.copy_object(dest_account, src_account, src_container,
167
                                     src_name, dest_account, dest_container,
168
                                     dest_name, content_type, 'pithos',
169
                                     meta={}, replace_meta=False,
170
                                     permissions=permissions)
171
        else:
172
            # create destination container and retry
173
            self.backend.put_container(dest_account, dest_account,
174
                                       dest_container)
175
            self.backend.copy_object(dest_account, src_account, src_container,
176
                                     src_name, dest_account, dest_container,
177
                                     dest_name, content_type, 'pithos',
178
                                     meta={}, replace_meta=False,
179
                                     permissions=permissions)
180

  
181
        if move:
182
            self.backend.delete_object(src_account, src_account,
183
                                       src_container, src_name)
184

  
185
        dest_path, dest_node = self.backend._lookup_object(dest_account,
186
                                                           dest_container,
187
                                                           dest_name)
188
        assert dest_path == '/'.join([dest_account, path])
189

  
190
        # turn history versions to point to the newly created node
191
        for serial in versions:
192
            self.backend.node.version_put_property(serial, 'node', dest_node)
193

  
194
        if public:
195
            # set destination object public
196
            fullpath = '/'.join([dest_account, dest_container, dest_name])
197
            self.backend.permissions.public_set(
198
                fullpath,
199
                self.backend.public_url_security,
200
                self.backend.public_url_alphabet
201
            )
202

  
203
    def _merge_account(self, src_account, dest_account, delete_src=False):
204
            # TODO: handle exceptions
205
            # copy all source objects
206
            for path in self.list_all_objects(src_account):
207
                src_container, src_name = split_container_object_string(
208
                    '/%s' % path)
209

  
210
                # give read permissions to the dest_account
211
                permissions = self.backend.get_object_permissions(
212
                    src_account, src_account, src_container, src_name)
213
                if permissions:
214
                    permissions = permissions[2]
215
                permissions['read'] = permissions.get('read', [])
216
                permissions['read'].append(dest_account)
217
                self.backend.update_object_permissions(src_account,
218
                                                       src_account,
219
                                                       src_container,
220
                                                       src_name,
221
                                                       permissions)
222

  
223
                self._copy_object(src_account, src_container, src_name,
224
                                 dest_account, move=delete_src)
225

  
226
            # move groups also
227
            groups = self.backend.get_account_groups(src_account, src_account)
228
            (v.replace(src_account, dest_account) for v in groups.values())
229
            self.backend.update_account_groups(dest_account, dest_account,
230
                                               groups)
231
            if delete_src:
232
                self._delete_account(src_account)
233

  
234
    def merge_account(self, src_account, dest_account, only_stats=True,
235
                      dry=True, silent=False, delete_src=False):
236
        if src_account not in self.existing_accounts():
237
            raise NameError('%s does not exist' % src_account)
238
        if dest_account not in self.existing_accounts():
239
            raise NameError('%s does not exist' % dest_account)
240

  
241
        if only_stats:
242
            print "The following %s's entries will be moved to %s:" \
243
                % (src_account, dest_account)
244
            print "Objects: %r" % self.list_all_objects(src_account)
245
            print "Groups: %r" \
246
                % self.backend.get_account_groups(src_account,
247
                                                  src_account).keys()
248
            return
249

  
250
        trans = self.backend.wrapper.conn.begin()
251
        try:
252
            self._merge_account(src_account, dest_account, delete_src)
253

  
254
            if dry:
255
                if not silent:
256
                    print "Skipping database commit."
257
                trans.rollback()
258
            else:
259
                trans.commit()
260
                if not silent:
261
                    msg = "%s merged into %s."
262
                    print msg % (src_account, dest_account)
263
        except:
264
            trans.rollback()
265
            raise
266

  
267
    def delete_container_contents(self, account, container):
268
        self.backend.delete_container(account, account, container,
269
                                      delimiter='/')
270

  
271
    def delete_container(self, account, container):
272
        self.backend.delete_container(account, account, container)
273

  
274
    def _delete_account(self, account):
275
        for c in self.list_all_containers(account):
276
            self.delete_container_contents(account, c)
277
            self.delete_container(account, c)
278
        self.backend.delete_account(account, account)
279

  
280
    def delete_account(self, account, only_stats=True, dry=True, silent=False):
281
        if account not in self.existing_accounts():
282
            raise NameError('%s does not exist' % account)
283
        if only_stats:
284
            print "The following %s's entries will be removed:" % account
285
            print "Objects: %r" % self.list_all_objects(account)
286
            print "Groups: %r" \
287
                % self.backend.get_account_groups(account, account).keys()
288
            return
289

  
290
        trans = self.backend.wrapper.conn.begin()
291
        try:
292
            self._delete_account(account)
293

  
294
            if dry:
295
                if not silent:
296
                    print "Skipping database commit."
297
                trans.rollback()
298
            else:
299
                trans.commit()
300
                if not silent:
301
                    print "%s is deleted." % account
302
        except:
303
            trans.rollback()
304
            raise
305

  
306
    def create_account(self, account):
307
        return self.backend._lookup_account(account, create=True)
308

  
309
    def create_update_object(self, account, container, name, content_type,
310
                             data, meta=None, permissions=None, request_user=None):
311
        meta = meta or {}
312
        permissions = permissions or {}
313
        md5 = hashlib.md5()
314
        size = 0
315
        hashmap = []
316
        for block_data in data_read_iterator(data, self.backend.block_size):
317
            size += len(block_data)
318
            hashmap.append(self.backend.put_block(block_data))
319
            md5.update(block_data)
320

  
321
        checksum = md5.hexdigest().lower()
322

  
323
        request_user = request_user or account
324
        return self.backend.update_object_hashmap(request_user, account,
325
                                                  container, name, size,
326
                                                  content_type, hashmap,
327
                                                  checksum, 'pithos', meta,
328
                                                  True, permissions)
b/snf-pithos-app/pithos/api/manage_accounts/cli/__init__.py
1
# Copyright 2012 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 pithos.api.manage_accounts import ManageAccounts
35
from synnefo.webproject.management.utils import pprint_table
36

  
37
import argparse
38

  
39
import os
40
import sys
41

  
42

  
43
def _double_list_str(l):
44
    return '\n'.join(', '.join(sublist) for sublist in l)
45

  
46

  
47
def list(args):
48
    try:
49
        utils = ManageAccounts()
50
        if args.only_duplicate:
51
            accounts = utils.duplicate_accounts()
52
        else:
53
            accounts = utils.existing_accounts()
54
        headers = ['uuid']
55
        table = [(a,) for a in accounts]
56
        if args.output_format != "json" and not args.headers:
57
            headers = None
58
        pprint_table(sys.stdout, table, headers, args.output_format)
59
    except Exception, e:
60
        sys.stderr.write('%s\n' % e)
61
    finally:
62
        utils.cleanup()
63

  
64

  
65
def delete(args):
66
    try:
67
        utils = ManageAccounts()
68
        utils.delete_account(args.delete_account, only_stats=True)
69

  
70
        confirm = raw_input(
71
            "Type 'yes' if you are sure you want "
72
            "to remove those entries: "
73
        )
74
        if not confirm == 'yes':
75
            return
76
        else:
77
            utils.delete_account(
78
                args.delete_account, only_stats=False, dry=args.dry
79
            )
80
    except Exception, e:
81
        sys.stderr.write('%s\n' % e)
82
    finally:
83
        utils.cleanup()
84

  
85

  
86
def merge(args):
87
    try:
88
        utils = ManageAccounts()
89
        utils.merge_account(
90
            args.src_account, args.dest_account, only_stats=True
91
        )
92

  
93
        confirm = raw_input(
94
            "Type 'yes' if you are sure you want"
95
            " to move those entries to %s: " % args.dest_account
96
        )
97
        if not confirm == 'yes':
98
            return
99
        else:
100
            utils.merge_account(
101
                args.src_account, args.dest_account, only_stats=False,
102
                dry=args.dry
103
            )
104
    except Exception, e:
105
        sys.stderr.write('%s\n' % e)
106
    finally:
107
        utils.cleanup()
108

  
109

  
110
def export_quota(args):
111
    try:
112
        utils = ManageAccounts()
113
        d = utils.backend.node.node_account_quotas()
114

  
115
        location = os.path.abspath(os.path.expanduser(args.location))
116
        f = open(location, 'w')
117

  
118
        for uuid, capacity in d.iteritems():
119
            f.write(' '.join([uuid, 'pithos.diskspace', capacity]))
120
            f.write('\n')
121
    except Exception, e:
122
        sys.stderr.write('%s\n' % e)
123
    finally:
124
        utils.cleanup()
125

  
126

  
127
def set_container_quota(args):
128
    try:
129
        utils = ManageAccounts()
130
        try:
131
            quota = int(args.quota)
132
        except:
133
            raise ValueError('Invalid quota')
134

  
135
        accounts = [args.account] if args.account \
136
            else utils.existing_accounts()
137

  
138
        failed = []
139

  
140
        def update_container_policy(account):
141
            trans = utils.backend.wrapper.conn.begin()
142
            try:
143
                utils.backend.update_container_policy(
144
                    account, account, args.container, {'quota': quota}
145
                )
146
                if args.dry:
147
                    print "Skipping database commit."
148
                    trans.rollback()
149
                else:
150
                    trans.commit()
151
            except Exception, e:
152
                failed.append((account, e))
153

  
154
        map(update_container_policy, accounts)
155
        if failed:
156
            sys.stdout.write(
157
                'Failed for the following accounts:\n'
158
            )
159
            pprint_table(sys.stdout, failed, headers=[])
160
    except Exception, e:
161
        sys.stderr.write('%s\n' % e)
162
    finally:
163
        utils.cleanup()
164

  
165

  
166
def main(argv=None):
167
    # create the top-level parser
168
    parser = argparse.ArgumentParser()
169
    subparsers = parser.add_subparsers()
170

  
171
    # create the parser for the "list" command
172
    parser_list = subparsers.add_parser(
173
        'list', description="List existing accounts"
174
    )
175
    parser_list.add_argument(
176
        '--dublicate', dest='only_duplicate', action="store_true",
177
        default=False, help="Display only case insensitive duplicate accounts."
178
    )
179
    parser_list.add_argument(
180
        "--no-headers", dest="headers", action="store_false", default=True,
181
        help="Do not display headers"
182
    )
183
    parser_list.add_argument(
184
        "--output-format", dest="output_format", metavar="[pretty, csv, json]",
185
        default="pretty", choices=["pretty", "csv", "json"],
186
        help="Select the output format: pretty [the default], tabs"
187
             " [tab-separated output], csv [comma-separated output]")
188
    parser_list.set_defaults(func=list)
189

  
190
    # create the parser for the "delete" command
191
    parser_delete = subparsers.add_parser('delete')
192
    parser_delete.add_argument('delete_account')
193
    parser_delete.add_argument(
194
        '--dry', action="store_true", default=False,
195
        help="Do not commit database changes."
196
    )
197
    parser_delete.set_defaults(func=delete)
198

  
199
    # create the parser for the "merge" command
200
    parser_merge = subparsers.add_parser('merge')
201
    parser_merge.add_argument('src_account')
202
    parser_merge.add_argument('dest_account')
203
    parser_merge.add_argument(
204
        '--dry', action="store_true", default=False,
205
        help="Do not commit database changes."
206
    )
207
    parser_merge.set_defaults(func=merge)
208

  
209
    # create the parser for the "export-quota" command
210
    parser_export_quota = subparsers.add_parser('export-quota')
211
    parser_export_quota.add_argument(
212
        '--location', dest='location', required=True,
213
        help="Where to save the exported quotas"
214
    )
215
    parser_export_quota.set_defaults(func=export_quota)
216

  
217
    # create the parser for the "set-container-quota" command
218
    parser_set_container_quota = subparsers.add_parser(
219
        'set-container-quota',
220
        description="Set container quota for all the existing accounts"
221
    )
222
    parser_set_container_quota.add_argument(
223
        '--account', dest='account',
224
        help="Set container quota for a specific account"
225
    )
226
    parser_set_container_quota.add_argument(
227
        '--dry', action="store_true", default=False,
228
        help="Do not commit database changes."
229
    )
230
    parser_set_container_quota.add_argument('container')
231
    parser_set_container_quota.add_argument('quota')
232
    parser_set_container_quota.set_defaults(func=set_container_quota)
233

  
234
    args = parser.parse_args()
235
    args.func(args)
236

  
237
if __name__ == '__main__':
238
    main(sys.argv)
b/snf-pithos-app/pithos/api/manage_accounts/tests.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
import unittest
35
import uuid
36
import random
37
import string
38
import os
39

  
40
from collections import defaultdict
41

  
42
from pithos.api.manage_accounts import ManageAccounts
43

  
44

  
45
def get_random_data(length=500):
46
    char_set = string.ascii_uppercase + string.digits
47
    return ''.join(random.choice(char_set) for x in xrange(length))
48

  
49

  
50
class ManageAccountsTests(unittest.TestCase):
51
    def setUp(self):
52
        self.utils = ManageAccounts()
53
        self.accounts = ('account1', 'Account1', 'account2', 'account3')
54
        for i in self.accounts:
55
            self.utils.create_account(i)
56

  
57
    def tearDown(self):
58
        for i in self.accounts:
59
            self.utils._delete_account(i)
60
        self.utils.cleanup()
61

  
62
    def _verify_object(self, account, container, object, expected=None,
63
                       strict=True):
64
        expected = expected or {}
65
        self._verify_object_metadata(account, container, object,
66
                                     expected.get('meta'))
67
        self._verify_object_history(account, container, object,
68
                                    expected.get('versions'),
69
                                    strict=strict)
70
        self._verify_object_permissions(account, container, object,
71
                                        expected.get('permissions'))
72

  
73
    def _verify_object_metadata(self, account, container, object, expected):
74
        object_meta = self.utils.backend.get_object_meta(
75
            account, account, container, object, 'pithos')
76
        for k in expected:
77
            self.assertTrue(k in object_meta)
78
            self.assertEquals(object_meta[k], expected[k])
79

  
80
    def _verify_object_history(self, account, container, object, expected,
81
                               strict=True):
82
        history = self.utils.list_past_versions(account, container, object)
83
        if strict:
84
            self.assertEquals(sorted(expected), history)
85
        else:
86
            self.assertTrue(set(expected) <= set(history))
87

  
88
    def _verify_object_permissions(self, account, container, object, expected):
89
        expected = expected or {}
90
        perms_tuple = self.utils.backend.get_object_permissions(
91
            account, account, container, object)
92

  
93
        self.assertEqual(len(perms_tuple), 3)
94

  
95
        object_perms = perms_tuple[2]
96

  
97
        for k in expected:
98
            self.assertTrue(set(expected.get(k)) <= set(object_perms.get(k)))
99

  
100
        for holder in expected.get('read', []):
101
            if holder == '*':
102
                continue
103
            try:
104
                # check first for a group permission
105
                owner, group = holder.split(':', 1)
106
            except ValueError:
107
                holders = [holder]
108
            else:
109
                holders = self.utils.backend.permissions.group_members(owner,
110
                                                                       group)
111

  
112
            for h in holders:
113
                try:
114
                    self.utils.backend.get_object_meta(
115
                        holder, account, container, object, 'pithos')
116
                except Exception, e:
117
                    self.fail(e)
118

  
119
    def test_existing_accounts(self):
120
        accounts = self.utils.existing_accounts()
121
        self.assertEquals(sorted(accounts), accounts)
122
        self.assertTrue(set(['account1', 'account2']) <= set(accounts))
123

  
124
    def test_duplicate_accounts(self):
125
        duplicates = self.utils.duplicate_accounts()
126
        self.assertTrue(['Account1', 'account1'] in duplicates)
127

  
128
    def test_list_all_containers(self):
129
        step = 10
130
        containers = []
131
        append = containers.append
132
        for i in range(3 * step + 1):
133
            while 1:
134
                cname = unicode(uuid.uuid4())
135
                if cname not in containers:
136
                    append(cname)
137
                    break
138
            self.utils.backend.put_container('account1', 'account1', cname)
139
        self.assertEquals(sorted(containers),
140
                          self.utils.list_all_containers('account1',
141
                                                         step=step))
142

  
143
    def test_list_all_container_objects(self):
144
        containers = ('container1', 'container2')
145
        objects = defaultdict(list)
146
        for c in containers:
147
            self.utils.backend.put_container('account1', 'account1', c)
148
            step = 10
149
            append = objects[c].append
150
            content_type = 'application/octet-stream'
151
            for i in range(3 * step + 1):
152
                while 1:
153
                    oname = unicode(uuid.uuid4())
154
                    if oname not in objects:
155
                        append(oname)
156
                        break
157
                data = get_random_data(int(random.random()))
158
                self.utils.create_update_object('account1', c, oname,
159
                                                content_type, data)
160

  
161
        (self.assertEquals(sorted(objects.get(c)),
162
                           self.utils.list_all_container_objects('account1', c)
163
                           ) for c in containers)
164

  
165
    def test_list_all_objects(self):
166
        containers = ('container1', 'container2')
167
        objects = []
168
        append = objects.append
169
        for c in containers:
170
            self.utils.backend.put_container('account1', 'account1', c)
171
            step = 10
172
            content_type = 'application/octet-stream'
173
            for i in range(3 * step + 1):
174
                while 1:
175
                    oname = unicode(uuid.uuid4())
176
                    if oname not in objects:
177
                        append(os.path.join(c, oname))
178
                        break
179
                data = get_random_data(int(random.random()))
180
                self.utils.create_update_object('account1', c, oname,
181
                                                content_type, data)
182

  
183
        self.assertEquals(len(objects),
184
                          len(self.utils.list_all_objects('account1')))
185
        self.assertEquals(sorted(objects),
186
                          self.utils.list_all_objects('account1'))
187

  
188
    def test_list_past_versions(self):
189
        self.utils.backend.put_container('account1', 'account1', 'container1')
190
        versions = []
191
        append = versions.append
192
        for i in range(5):
193
            data = get_random_data(int(random.random()))
194
            append(self.utils.create_update_object('account1', 'container1',
195
                                                   'object1',
196
                                                   'application/octet-stream',
197
                                                   data))
198
        self.assertEquals(sorted(versions[:-1]),
199
                          self.utils.list_past_versions('account1',
200
                                                        'container1',
201
                                                        'object1'))
202

  
203
    def test_move(self):
204
        # create containers
205
        self.utils.backend.put_container('account1', 'account1', 'container1')
206
        self.utils.backend.put_container('Account1', 'Account1', 'container1')
207

  
208
        # add group
209
        self.utils.backend.update_account_groups('Account1', 'Account1',
210
                                                 {'test': ['account3']})
211

  
212
        # upload object and update it several times
213
        versions = []
214
        append = versions.append
215
        meta = {'foo': 'bar'}
216
        permissions = {'read': ['account1', 'account2', 'Account1:test'],
217
                       'write': ['account2', 'Account1:test']}
218
        for i in range(5):
219
            data = get_random_data(int(random.random()))
220
            append(self.utils.create_update_object('Account1', 'container1',
221
                                                   'object1',
222
                                                   'application/octet-stream',
223
                                                   data, meta, permissions))
224

  
225
        self.utils.move_object('Account1', 'container1', 'object1', 'account1',
226
                               dry=False, silent=True)
227

  
228
        expected = {'meta': meta,
229
                    'versions': versions[:-1],
230
                    'permissions': permissions}
231
        self._verify_object('account1', 'container1', 'object1', expected)
232

  
233
    def test_merge(self):
234
        # create container
235
        self.utils.backend.put_container('Account1', 'Account1', 'container0')
236
        self.utils.backend.put_container('Account1', 'Account1', 'container1')
237

  
238
        # add group
239
        self.utils.backend.update_account_groups('Account1', 'Account1',
240
                                                 {'test': ['account3']})
241

  
242
        # upload objects and update them several times
243
        versions = defaultdict(list)
244
        meta = {'foo': 'bar'}
245
        permissions = {'read': ['account2', 'Account1:test'],
246
                       'write': ['account2', 'Account1:test']}
247

  
248
        for i in range(2):
249
            container = 'container%s' % i
250
            versions[container] = {}
251
            for j in range(3):
252
                object = 'object%s' % j
253
                versions[container][object] = []
254
                append = versions[container][object].append
255
                for k in range(5):
256
                    data = get_random_data(int(random.random()))
257
                    append(self.utils.create_update_object(
258
                        'Account1', container, object,
259
                        'application/octet-stream', data, meta, permissions))
260

  
261
        self.utils.merge_account('Account1', 'account1', only_stats=False,
262
                                 dry=False, silent=True)
263

  
264
        self.assertTrue('Account1' in self.utils.existing_accounts())
265
        self.assertTrue('account1' in self.utils.existing_accounts())
266

  
267
        # assert container has been created
268
        try:
269
            self.utils.backend.get_container_meta('account1', 'account1',
270
                                                  'container1', 'pithos')
271
        except NameError, e:
272
            self.fail(e)
273

  
274
        expected = {'meta': meta,
275
                    'permissions': permissions}
276
        for c, o_dict in versions.iteritems():
277
            for o, versions in o_dict.iteritems():
278
                expected['versions'] = versions[:-1]
279
                self._verify_object('account1', c, o, expected)
280

  
281
    def test_merge_existing_dest_container(self):
282
        # create container
283
        self.utils.backend.put_container('Account1', 'Account1', 'container1')
284
        self.utils.backend.put_container('account1', 'account1', 'container1')
285

  
286
        # add group
287
        self.utils.backend.update_account_groups('Account1', 'Account1',
288
                                                 {'test': ['account3']})
289

  
290
        # upload objects and update them several times
291
        versions = defaultdict(list)
292
        meta = {'foo': 'bar'}
293
        permissions = {'read': ['account2', 'Account1:test'],
294
                       'write': ['account2', 'Account1:test']}
295

  
296
        versions = []
297
        append = versions.append
298
        for k in range(5):
299
            data = get_random_data(int(random.random()))
300
            append(self.utils.create_update_object(
301
                'Account1', 'container1', 'object1',
302
                'application/octet-stream', data, meta, permissions))
303

  
304
        self.utils.merge_account('Account1', 'account1', only_stats=False,
305
                                 dry=False, silent=True)
306

  
307
        self.assertTrue('Account1' in self.utils.existing_accounts())
308
        self.assertTrue('account1' in self.utils.existing_accounts())
309

  
310
        try:
311
            self.utils.backend.get_container_meta('account1', 'account1',
312
                                                  'container1', 'pithos')
313
        except NameError, e:
314
            self.fail(e)
315

  
316
        expected = {'meta': meta,
317
                    'versions': versions[:-1],
318
                    'permissions': permissions}
319
        self._verify_object('account1', 'container1', 'object1', expected)
320

  
321
    def test_merge_existing_dest_object(self):
322
        # create container
323
        self.utils.backend.put_container('Account1', 'Account1', 'container1')
324
        self.utils.backend.put_container('account1', 'account1', 'container1')
325

  
326
        # add group
327
        self.utils.backend.update_account_groups('Account1', 'Account1',
328
                                                 {'test': ['account3']})
329

  
330
        # upload objects and update them several times
331
        versions = defaultdict(list)
332
        meta = {'foo': 'bar'}
333
        permissions = {'read': ['account2', 'Account1:test'],
334
                       'write': ['account2', 'Account1:test']}
335

  
336
        container = 'container1'
337
        object = 'object1'
338
        versions = []
339
        append = versions.append
340
        for k in range(5):
341
            data = get_random_data(int(random.random()))
342
            append(self.utils.create_update_object(
343
                   'Account1', container, object,
344
                   'application/octet-stream', data, meta, permissions))
345
            data = get_random_data(int(random.random()))
346
            self.utils.create_update_object(
347
                   'account1', container, object,
348
                   'application/octet-stream', data, meta, permissions)
349

  
350
        self.utils.merge_account('Account1', 'account1', only_stats=False,
351
                                 dry=False, silent=True)
352

  
353
        self.assertTrue('Account1' in self.utils.existing_accounts())
354
        self.assertTrue('account1' in self.utils.existing_accounts())
355

  
356
        try:
357
            self.utils.backend.get_container_meta('account1', 'account1',
358
                                                  'container1', 'pithos')
359
        except NameError, e:
360
            self.fail(e)
361

  
362
        expected = {'meta': meta,
363
                    'permissions': permissions,
364
                    'versions': versions[:-1]}
365
        self._verify_object('account1', container, object, expected,
366
                            strict=False)
367

  
368

  
369
if __name__ == '__main__':
370
    unittest.main()
/dev/null
1
# Copyright 2012 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 optparse import make_option
35

  
36
from django.core.management.base import NoArgsCommand, CommandError
37

  
38
from sqlalchemy.sql import select, and_
39

  
40
from pithos.api.util import get_backend
41

  
42
import os
43

  
44
backend = get_backend()
45
table = {}
46
table['nodes'] = backend.node.nodes
47
table['policy'] = backend.node.policy
48
conn = backend.node.conn
49

  
50

  
51
class Command(NoArgsCommand):
52
    help = "Export account quota policies"
53

  
54
    option_list = NoArgsCommand.option_list + (
55
        make_option('--location',
56
                    dest='location',
57
                    default='exported_policies',
58
                    help="Where to save the output file"),
59
    )
60

  
61
    def handle_noargs(self, **options):
62
        # retrieve account policies
63
        s = select([table['nodes'].c.path, table['policy'].c.value])
64
        s = s.where(and_(table['nodes'].c.node != 0,
65
                         table['nodes'].c.parent == 0))
66
        s = s.where(table['nodes'].c.node == table['policy'].c.node)
67
        s = s.where(table['policy'].c.key == 'quota')
68

  
69
        location = os.path.abspath(options['location'])
70
        try:
71
            f = open(location, 'w')
72
        except IOError, e:
73
            raise CommandError(e)
74

  
75
        INF = str(10**30)
76
        for p in conn.execute(s).fetchall():
77
            f.write(' '.join(
78
                [p.path, 'pithos+.diskspace', p.value, '0', INF, INF]))
79
            f.write('\n')
80
        f.close()
81
        backend.close()
/dev/null
1
# Copyright 2012 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 optparse import make_option
35

  
36
from django.core.management.base import NoArgsCommand, CommandError
37

  
38
from pithos.api.swiss_army import SwissArmy
39

  
40

  
41
def double_list_str(l):
42
    return '\n'.join(', '.join(sublist) for sublist in l)
43

  
44
class Command(NoArgsCommand):
45
    help = "Quotas migration helper"
46

  
47
    option_list = NoArgsCommand.option_list + (
48
        make_option('--list-duplicate',
49
                    dest='duplicate-accounts',
50
                    action="store_true",
51
                    default=False,
52
                    help="Display case insensitive duplicate accounts."),
53
        make_option('--list-all',
54
                    dest='existing-accounts',
55
                    action="store_true",
56
                    default=False,
57
                    help="Display existing accounts."),
58
        make_option('--merge-accounts',
59
                    dest='merge_accounts',
60
                    action='store_true',
61
                    default=False,
62
                    help="Merge SRC_ACCOUNT and DEST_ACCOUNT."),
63
        make_option('--delete-account',
64
                    dest='delete_account',
65
                    action='store',
66
                    help="Delete DELETE_ACCOUNT."),
67
        make_option('--src-account',
68
                    dest='src_account',
69
                    action='store',
70
                    help="Account to be merged and then deleted."),
71
        make_option('--dest-account',
72
                    dest='dest_account',
73
                    action='store',
74
                    help="Account where SRC_ACCOUNT contents will be copied."),
75
        make_option('--dry',
76
                    dest='dry',
77
                    action="store_true",
78
                    default=False,
79
                    help="Do not commit database changes.")
80
    )
81

  
82
    def handle(self, *args, **options):
83
        try:
84
            utils = SwissArmy()
85
            self.dry = options.get('dry')
86

  
87
            if not options.get('duplicate-accounts') and \
88
                not options.get('existing-accounts') and \
89
                not options.get('merge_accounts') and \
90
                not options.get('delete_account'):
91
                self.print_help('pithos-manage-accounts', '')
92

  
93
            if options.get('duplicate-accounts') and \
94
                    not options.get('existing-accounts') and \
95
                    not options.get('merge_accounts') and \
96
                    not options.get('delete_account'):
97
                duplicates = utils.duplicate_accounts()
98
                if duplicates:
99
                    msg = "The following case insensitive duplicates found:\n%s"
100
                    raise CommandError(msg % double_list_str(duplicates))
101
                else:
102
                    print "No duplicate accounts are found."
103

  
104
            if options.get('existing-accounts') and \
105
                    not options.get('merge_accounts') and \
106
                    not options.get('delete_account'):
107
                accounts = utils.existing_accounts()
108
                print "The following accounts found:"
109
                print "%s" % '\n'.join(accounts)
110

  
111
            if options.get('merge_accounts'):
112
                src_account = options.get('src_account')
113
                dest_account = options.get('dest_account')
114
                if not src_account:
115
                    raise CommandError('Please specify a source account')
116
                if not dest_account:
117
                    raise CommandError('Please specify a destination account')
118
                utils.merge_account(src_account, dest_account,
119
                                         only_stats=True)
120

  
121
                confirm = raw_input("Type 'yes' if you are sure you want"
122
                                    " to move those entries to %s: " %\
123
                                    dest_account)
124
                if not confirm == 'yes':
125
                    return
126
                else:
127
                    utils.merge_account(options.get('src_account'),
128
                                        options.get('dest_account'),
129
                                        only_stats=False,
130
                                        dry=self.dry)
131
                return
132

  
133
            if options.get('delete_account'):
134
                utils.delete_account(options.get('delete_account'),
135
                                     only_stats=True)
136

  
137
                confirm = raw_input("Type 'yes' if you are sure you want"
138
                                    " to remove those entries: ")
139
                if not confirm == 'yes':
140
                    return
141
                else:
142
                    utils.delete_account(options.get('delete_account'),
143
                                         only_stats=False,
144
                                         dry=self.dry)
145
                return
146
        except Exception, e:
147
            raise CommandError(e)
148
        finally:
149
            utils.backend.close()
/dev/null
1
# Copyright 2012 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 optparse import make_option
35

  
36
from django.core.management.base import BaseCommand, CommandError
37

  
38
from pithos.api.util import get_backend
39

  
40

  
41
class Command(BaseCommand):
42
    args = "<user>"
43
    help = "Get/set a user's quota"
44

  
45
    option_list = BaseCommand.option_list + (
46
        make_option('--set-quota',
47
                    dest='quota',
48
                    metavar='BYTES',
49
                    help="Set user's quota"),
50
    )
51

  
52
    def handle(self, *args, **options):
53
        if len(args) != 1:
54
            raise CommandError("Please provide a user")
55

  
56
        user = args[0]
57
        quota = options.get('quota')
58
        if quota is not None:
59
            try:
60
                quota = int(quota)
61
            except ValueError:
62
                raise CommandError("Invalid quota")
63

  
64
        backend = get_backend()
65

  
66
        if backend.using_external_quotaholder:
67
            raise CommandError("The system uses an extrenal quota holder.")
68

  
69
        if quota is not None:
70
            backend.update_account_policy(user, user, {'quota': quota})
71
        else:
72
            self.stdout.write("Quota for %s: %s\n" % (
73
                user, backend.get_account_policy(user, user)['quota']))
74
        backend.close()
/dev/null
1
# Copyright 2012 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 pithos.api.util import get_backend, split_container_object_string
35

  
36
import re
37
import hashlib
38
import os
39

  
40

  
41
def data_read_iterator(str, size=1024):
42
    offset = 0
43
    while True:
44
        data = str[offset:offset + size]
45
        offset = offset + size
46
        if not data:
47
            break
48
        yield data
49

  
50

  
51
class SwissArmy():
52
    def __init__(self):
53
        self.backend = get_backend()
54

  
55
    def cleanup(self):
56
        self.backend.close()
57

  
58
    def existing_accounts(self):
59
        return sorted([path for path, _ in self.backend.node.node_accounts()])
60

  
61
    def duplicate_accounts(self):
62
        accounts = self.existing_accounts()
63
        duplicates = []
64
        for i in range(len(accounts)):
65
            account = accounts[i]
66
            matcher = re.compile(account, re.IGNORECASE)
67
            duplicate = filter(matcher.match, (i for i in accounts[i + 1:] \
68
                if len(i) == len(account)))
69
            if duplicate:
70
                duplicate.insert(0, account)
71
                duplicates.append(duplicate)
72
        return duplicates
73

  
74
    def list_all_containers(self, account, step=10):
75
        containers = []
76
        marker = None
77
        while 1:
78
            more = self.backend.list_containers(account, account, limit=10,
79
                                                marker=marker)
80
            if not more:
81
                break
82
            containers.extend(more)
83
            marker = more[-1]
84
        return containers
85

  
86
    def list_all_container_objects(self, account, container, virtual=False):
87
        objects = []
88
        marker = None
89
        while 1:
90
            more = self.backend.list_objects(account, account, container,
91
                                             marker=marker, virtual=virtual)
92
            if not more:
93
                break
94
            objects.extend((i[0] for i in more))
95
            marker = more[-1][0]
96
        return objects
97

  
98
    def list_all_objects(self, account, virtual=False):
99
        containers = self.list_all_containers(account)
100
        objects = []
101
        extend = objects.extend
102
        for c in containers:
103
            more = self.list_all_container_objects(account, c, virtual=virtual)
104
            extend([os.path.join(c, i) for i in more])
105
        return objects
106

  
107
    def list_past_versions(self, account, container, name):
108
        versions = self.backend.list_versions(account, account, container,
109
                                              name)
110
        # do not return the current version
111
        return list(x[0] for x in versions[:-1])
112

  
113
    def move_object(self, src_account, src_container, src_name,
114
                    dest_account, dry=True, silent=False):
115
        if src_account not in self.existing_accounts():
116
            raise NameError('%s does not exist' % src_account)
117
        if dest_account not in self.existing_accounts():
118
            raise NameError('%s does not exist' % dest_account)
119

  
120
        trans = self.backend.wrapper.conn.begin()
121
        try:
122
            self._copy_object(src_account, src_container, src_name,
123
                              dest_account, move=True)
124

  
125
            if dry:
126
                if not silent:
127
                    print "Skipping database commit."
128
                trans.rollback()
129
            else:
130
                trans.commit()
131
                if not silent:
132
                    print "%s is deleted." % src_account
133
        except:
134
            trans.rollback()
135
            raise
136

  
137
    def _copy_object(self, src_account, src_container, src_name,
138
                    dest_account, move=False):
139
        path = os.path.join(src_container, src_name)
140
        fullpath = os.path.join(src_account, path)
141
        dest_container = src_container
142
        dest_name = src_name
143

  
144
        meta = self.backend.get_object_meta(src_account, src_account,
145
                                            src_container, src_name, 'pithos',
146
                                            version=None)
147
        content_type = meta.get('type')
148

  
149
        # get source object history
150
        versions = self.list_past_versions(src_account, src_container,
151
                                           src_name)
152

  
153
        # get source object permissions
154
        permissions = self.backend.permissions.access_get(fullpath)
155

  
156
        # get source object public
157
        public = self.backend.get_object_public(src_account, src_account,
158
                                                src_container, src_name)
159

  
160
        if dest_container in self.backend.list_containers(dest_account,
161
                                                          dest_account):
162
            # Note: if dest_container contains an object with the same name
163
            # a new version with the contents of the source object will be
164
            # created and the one in the destination container will pass to
165
            # history
166
            self.backend.copy_object(dest_account, src_account, src_container,
167
                                     src_name, dest_account, dest_container,
168
                                     dest_name, content_type, 'pithos',
169
                                     meta={}, replace_meta=False,
170
                                     permissions=permissions)
171
        else:
172
            # create destination container and retry
173
            self.backend.put_container(dest_account, dest_account,
174
                                       dest_container)
175
            self.backend.copy_object(dest_account, src_account, src_container,
176
                                     src_name, dest_account, dest_container,
177
                                     dest_name, content_type, 'pithos',
178
                                     meta={}, replace_meta=False,
179
                                     permissions=permissions)
180

  
181
        if move:
182
            self.backend.delete_object(src_account, src_account,
183
                                       src_container, src_name)
184

  
185
        dest_path, dest_node = self.backend._lookup_object(dest_account,
186
                                                           dest_container,
187
                                                           dest_name)
188
        assert dest_path == '/'.join([dest_account, path])
189

  
190
        # turn history versions to point to the newly created node
191
        for serial in versions:
192
            self.backend.node.version_put_property(serial, 'node', dest_node)
193

  
194
        if public:
195
            # set destination object public
196
            fullpath = '/'.join([dest_account, dest_container, dest_name])
197
            self.backend.permissions.public_set(
198
                fullpath,
199
                self.backend.public_url_security,
200
                self.backend.public_url_alphabet
201
            )
202

  
203
    def _merge_account(self, src_account, dest_account, delete_src=False):
204
            # TODO: handle exceptions
205
            # copy all source objects
206
            for path in self.list_all_objects(src_account):
207
                src_container, src_name = split_container_object_string(
208
                    '/%s' % path)
209

  
210
                # give read permissions to the dest_account
211
                permissions = self.backend.get_object_permissions(
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff