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( |
Also available in: Unified diff