Revision 56f3c759
b/contrib/migrate-db | ||
---|---|---|
166 | 166 |
self.create_tags(headerid, nodeid, vserials) |
167 | 167 |
#set object's publicity |
168 | 168 |
if public: |
169 |
self.backend.permissions.public_set(object) |
|
169 |
self.backend.permissions.public_set( |
|
170 |
object, |
|
171 |
self.backend.public_url_min_length, |
|
172 |
self.backend.public_url_alphabet |
|
173 |
) |
|
170 | 174 |
#set object's permissions |
171 | 175 |
self.create_permissions(headerid, object, username, is_folder=False) |
172 | 176 |
|
b/snf-pithos-app/README | ||
---|---|---|
27 | 27 |
|
28 | 28 |
Configure in ``settings.py`` or a ``.conf`` file in ``/etc/synnefo`` if using snf-webproject. |
29 | 29 |
|
30 |
=============================== ================================================= ============================================================ |
|
31 |
Name Default value Description |
|
32 |
=============================== ================================================= ============================================================ |
|
33 |
PITHOS_AUTHENTICATION_URL \https://<astakos.host>/im/authenticate/ Astakos Authentication URL |
|
34 |
PITHOS_AUTHENTICATION_USERS A dictionary of sample users (token to username) Set to empty or None to disable |
|
35 |
PITHOS_ASTAKOS_COOKIE_NAME _pithos2_a Cookie name to retrieve fallback token |
|
30 |
=============================== ================================================================ ============================================================
|
|
31 |
Name Default value Description
|
|
32 |
=============================== ================================================================ ============================================================
|
|
33 |
PITHOS_AUTHENTICATION_URL \https://<astakos.host>/im/authenticate/ Astakos Authentication URL
|
|
34 |
PITHOS_AUTHENTICATION_USERS A dictionary of sample users (token to username) Set to empty or None to disable
|
|
35 |
PITHOS_ASTAKOS_COOKIE_NAME _pithos2_a Cookie name to retrieve fallback token
|
|
36 | 36 |
PITHOS_BACKEND_DB_MODULE pithos.backends.lib.sqlalchemy |
37 |
PITHOS_BACKEND_DB_CONNECTION sqlite:////tmp/pithos-backend.db SQLAlchemy database connection string |
|
38 |
PITHOS_BACKEND_BLOCK_MODULE pithos.backends.lib.hashfiler
|
|
39 |
PITHOS_BACKEND_BLOCK_PATH /tmp/pithos-data/ Map and block storage path |
|
40 |
PITHOS_BACKEND_BLOCK_UMASK 0o022 Map and block storage umask |
|
41 |
PITHOS_BACKEND_QUEUE_MODULE None Use ``pithos.backends.lib.rabbitmq`` to enable |
|
42 |
PITHOS_BACKEND_QUEUE_HOSTS None Format like [``amqp://guest:guest@localhost:5672`` |
|
37 |
PITHOS_BACKEND_DB_CONNECTION sqlite:////tmp/pithos-backend.db SQLAlchemy database connection string
|
|
38 |
PITHOS_BACKEND_BLOCK_MODULE pithos.backends.lib.hashfile |
|
39 |
PITHOS_BACKEND_BLOCK_PATH /tmp/pithos-data/ Map and block storage path
|
|
40 |
PITHOS_BACKEND_BLOCK_UMASK 0o022 Map and block storage umask
|
|
41 |
PITHOS_BACKEND_QUEUE_MODULE None Use ``pithos.backends.lib.rabbitmq`` to enable
|
|
42 |
PITHOS_BACKEND_QUEUE_HOSTS None Format like [``amqp://guest:guest@localhost:5672``
|
|
43 | 43 |
PITHOS_BACKEND_QUEUE_EXCHANGE pithos |
44 |
PITHOS_BACKEND_QUOTA 50 GB (50 * 1024 ** 3) Default user quota |
|
45 |
PITHOS_BACKEND_VERSIONING auto Default versioning policy for containers |
|
46 |
PITHOS_BACKEND_FREE_VERSIONING True Default versioning debit policy (default free) |
|
47 |
PITHOS_UPDATE_MD5 True Update object checksums when using hashmaps |
|
48 |
PITHOS_SERVICE_TOKEN '' Service token acquired by the identity provider (astakos) |
|
49 |
PITHOS_RADOS_STORAGE False Enables or disables secondary Pithos storage on RADOS |
|
50 |
PITHOS_RADOS_POOL_BLOCKS None RADOS pool to be used for block storage |
|
51 |
PITHOS_RADOS_POOL_MAPS None RADOS pool to be used for maps storage |
|
52 |
PITHOS_TRANSLATE_UUIDS False Enables a ui compatibility layer for the introduction of UUIDs in identity management. |
|
53 |
PITHOS_PROXY_USER_SERVICES True Whether to proxy user feedback and catalog services |
|
54 |
PITHOS_USER_CATALOG_URL \https://<astakos.host>/user_catalogs/ Astakos User Catalog URL |
|
55 |
PITHOS_USER_FEEDBACK_URL \https://<astakos.host>/feedback/ Astakos User Feedback URL |
|
56 |
PITHOS_USER_LOGIN_URL \https://<astakos.host>/login/ Astakos User Login URL |
|
57 |
PITHOS_USE_QUOTAHOLDER True Enable quotaholder |
|
58 |
PITHOS_QUOTAHOLDER_URL '' Quotaholder URL |
|
59 |
PITHOS_QUOTAHOLDER_TOKEN '' Quotaholder token |
|
60 |
=============================== ================================================= ============================================================ |
|
44 |
PITHOS_BACKEND_QUOTA 50 GB (50 * 1024 ** 3) Default user quota |
|
45 |
PITHOS_BACKEND_VERSIONING auto Default versioning policy for containers |
|
46 |
PITHOS_BACKEND_FREE_VERSIONING True Default versioning debit policy (default free) |
|
47 |
PITHOS_UPDATE_MD5 True Update object checksums when using hashmaps |
|
48 |
PITHOS_SERVICE_TOKEN '' Service token acquired by the identity provider (astakos) |
|
49 |
PITHOS_RADOS_STORAGE False Enables or disables secondary Pithos storage on RADOS |
|
50 |
PITHOS_RADOS_POOL_BLOCKS None RADOS pool to be used for block storage |
|
51 |
PITHOS_RADOS_POOL_MAPS None RADOS pool to be used for maps storage |
|
52 |
PITHOS_TRANSLATE_UUIDS False Enables a ui compatibility layer for the introduction of UUIDs in identity management. |
|
53 |
PITHOS_PROXY_USER_SERVICES True Whether to proxy user feedback and catalog services |
|
54 |
PITHOS_USER_CATALOG_URL \https://<astakos.host>/user_catalogs/ Astakos User Catalog URL |
|
55 |
PITHOS_USER_FEEDBACK_URL \https://<astakos.host>/feedback/ Astakos User Feedback URL |
|
56 |
PITHOS_USER_LOGIN_URL \https://<astakos.host>/login/ Astakos User Login URL |
|
57 |
PITHOS_USE_QUOTAHOLDER True Enable quotaholder |
|
58 |
PITHOS_QUOTAHOLDER_URL '' Quotaholder URL |
|
59 |
PITHOS_QUOTAHOLDER_TOKEN '' Quotaholder token |
|
60 |
PITHOS_PUBLIC_URL_MIN_LENGTH 8 Public URL minimun length |
|
61 |
PITHOS_PUBLIC_URL_ALPHABET '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' Public URL alphabet |
|
62 |
=============================== ================================================================ ============================================================ |
|
61 | 63 |
|
62 | 64 |
To update checksums asynchronously, enable the queue, install snf-pithos-tools and use ``pithos-dispatcher``:: |
63 | 65 |
|
b/snf-pithos-app/conf/20-snf-pithos-app-settings.conf | ||
---|---|---|
51 | 51 |
# |
52 | 52 |
# Tune the size of the http pool for the quotaholder client. |
53 | 53 |
# It limits the maximum number of quota changing requests |
54 |
# that pithos can serve. Extra requests will be blocked
|
|
54 |
# that pithos can serve. Extra requests will be blocked |
|
55 | 55 |
# until another has completed. |
56 | 56 |
# |
57 | 57 |
#PITHOS_QUOTAHOLDER_POOLSIZE = 200 |
58 |
# |
|
59 |
# Set public url length |
|
60 |
#PITHOS_PUBLIC_URL_MIN_LENGTH = 8 |
b/snf-pithos-app/pithos/api/public.py | ||
---|---|---|
44 | 44 |
validate_matching_preconditions, |
45 | 45 |
object_data_response, api_method, |
46 | 46 |
split_container_object_string) |
47 |
from pithos.api.short_url import decode_url |
|
48 | 47 |
from pithos.api.settings import AUTHENTICATION_URL, AUTHENTICATION_USERS |
49 | 48 |
|
50 | 49 |
|
... | ... | |
72 | 71 |
try: |
73 | 72 |
v_account, v_container, v_object = request.backend.get_public( |
74 | 73 |
request.user_uniq, |
75 |
decode_url(v_public))
|
|
74 |
v_public)
|
|
76 | 75 |
meta = request.backend.get_object_meta(request.user_uniq, v_account, |
77 | 76 |
v_container, v_object, 'pithos') |
78 | 77 |
public = request.backend.get_object_public( |
... | ... | |
99 | 98 |
# itemNotFound (404), |
100 | 99 |
# badRequest (400), |
101 | 100 |
# notModified (304) |
101 |
|
|
102 | 102 |
try: |
103 | 103 |
v_account, v_container, v_object = request.backend.get_public( |
104 | 104 |
request.user_uniq, |
105 |
decode_url(v_public))
|
|
105 |
v_public)
|
|
106 | 106 |
meta = request.backend.get_object_meta(request.user_uniq, v_account, |
107 | 107 |
v_container, v_object, 'pithos') |
108 | 108 |
public = request.backend.get_object_public( |
b/snf-pithos-app/pithos/api/settings.py | ||
---|---|---|
65 | 65 |
QUOTAHOLDER_URL = getattr(settings, 'PITHOS_QUOTAHOLDER_URL', '') |
66 | 66 |
QUOTAHOLDER_TOKEN = getattr(settings, 'PITHOS_QUOTAHOLDER_TOKEN', '') |
67 | 67 |
QUOTAHOLDER_POOLSIZE = getattr(settings, 'PITHOS_QUOTAHOLDER_POOLSIZE', 200) |
68 |
|
|
69 |
# Set public url length and alphabet |
|
70 |
PUBLIC_URL_MIN_LENGTH = getattr(settings, 'PITHOS_PUBLIC_URL_MIN_LENGTH', 8) |
|
71 |
PUBLIC_URL_ALPHABET = getattr( |
|
72 |
settings, |
|
73 |
'PITHOS_PUBLIC_URL_ALPHABET', |
|
74 |
'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' |
|
75 |
) |
b/snf-pithos-app/pithos/api/swiss_army/__init__.py | ||
---|---|---|
194 | 194 |
if public: |
195 | 195 |
# set destination object public |
196 | 196 |
fullpath = '/'.join([dest_account, dest_container, dest_name]) |
197 |
self.backend.permissions.public_set(fullpath) |
|
197 |
self.backend.permissions.public_set( |
|
198 |
fullpath, |
|
199 |
self.backend.public_url_min_length, |
|
200 |
self.backend.public_url_alphabet |
|
201 |
) |
|
198 | 202 |
|
199 | 203 |
def _merge_account(self, src_account, dest_account, delete_src=False): |
200 | 204 |
# TODO: handle exceptions |
b/snf-pithos-app/pithos/api/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 random |
|
36 |
import string |
|
37 |
import datetime |
|
38 |
import time as _time |
|
39 |
|
|
40 |
import pithos.api.settings as settings |
|
41 |
|
|
42 |
from pithos.api.swiss_army import SwissArmy |
|
43 |
|
|
44 |
def get_random_data(length=500): |
|
45 |
char_set = string.ascii_uppercase + string.digits |
|
46 |
return ''.join(random.choice(char_set) for x in xrange(length)) |
|
47 |
|
|
48 |
class TestPublic(unittest.TestCase): |
|
49 |
def setUp(self): |
|
50 |
self.utils = SwissArmy() |
|
51 |
self.backend = self.utils.backend |
|
52 |
self.utils.create_account('account') |
|
53 |
|
|
54 |
def tearDown(self): |
|
55 |
self.utils._delete_account('account') |
|
56 |
self.utils.cleanup() |
|
57 |
|
|
58 |
def assert_not_public_object(self, account, container, object): |
|
59 |
public = self.backend.get_object_public( |
|
60 |
account, account, container, object |
|
61 |
) |
|
62 |
self.assertTrue(public == None) |
|
63 |
self.assertRaises( |
|
64 |
NameError, |
|
65 |
self.backend.get_public, |
|
66 |
'$$account$$', |
|
67 |
public |
|
68 |
) |
|
69 |
self.assertRaises( |
|
70 |
Exception, self.backend._can_read, |
|
71 |
'$$account$$', account, container, object |
|
72 |
) |
|
73 |
return public |
|
74 |
|
|
75 |
def assert_public_object(self, account, container, object): |
|
76 |
public = self.backend.get_object_public( |
|
77 |
account, account, container, object |
|
78 |
) |
|
79 |
self.assertTrue(public != None) |
|
80 |
self.assertTrue(len(public) >= settings.PUBLIC_URL_MIN_LENGTH) |
|
81 |
self.assertTrue(set(public) <= set(settings.PUBLIC_URL_ALPHABET)) |
|
82 |
self.assertEqual( |
|
83 |
self.backend.get_public('$$account$$', public), |
|
84 |
(account, container, object) |
|
85 |
) |
|
86 |
try: |
|
87 |
self.backend._can_read('$$account$$', account, container, object) |
|
88 |
except Exception: |
|
89 |
self.fail('Public object should be readable.') |
|
90 |
return public |
|
91 |
|
|
92 |
def test_set_object_public(self): |
|
93 |
self.utils.backend.put_container('account', 'account', 'container') |
|
94 |
data = get_random_data(int(random.random())) |
|
95 |
self.utils.create_update_object( |
|
96 |
'account', |
|
97 |
'container', |
|
98 |
'object', |
|
99 |
'application/octet-stream', |
|
100 |
data |
|
101 |
) |
|
102 |
self.assert_not_public_object('account', 'container', 'object') |
|
103 |
|
|
104 |
self.backend.permissions.public_set( |
|
105 |
'account/container/object', |
|
106 |
self.backend.public_url_min_length, |
|
107 |
self.backend.public_url_alphabet |
|
108 |
) |
|
109 |
self.assert_public_object('account', 'container', 'object') |
|
110 |
|
|
111 |
def test_set_twice(self): |
|
112 |
self.utils.backend.put_container('account', 'account', 'container') |
|
113 |
data = get_random_data(int(random.random())) |
|
114 |
self.utils.create_update_object( |
|
115 |
'account', |
|
116 |
'container', |
|
117 |
'object', |
|
118 |
'application/octet-stream', |
|
119 |
data |
|
120 |
) |
|
121 |
self.backend.permissions.public_set( |
|
122 |
'account/container/object', |
|
123 |
self.backend.public_url_min_length, |
|
124 |
self.backend.public_url_alphabet |
|
125 |
) |
|
126 |
public = self.assert_public_object('account', 'container', 'object') |
|
127 |
|
|
128 |
self.backend.permissions.public_set( |
|
129 |
'account/container/object', |
|
130 |
self.backend.public_url_min_length, |
|
131 |
self.backend.public_url_alphabet |
|
132 |
) |
|
133 |
public2 = self.assert_public_object('account', 'container', 'object') |
|
134 |
|
|
135 |
self.assertEqual(public, public2) |
|
136 |
|
|
137 |
def test_set_unset_set(self): |
|
138 |
self.utils.backend.put_container('account', 'account', 'container') |
|
139 |
data = get_random_data(int(random.random())) |
|
140 |
self.utils.create_update_object( |
|
141 |
'account', |
|
142 |
'container', |
|
143 |
'object', |
|
144 |
'application/octet-stream', |
|
145 |
data |
|
146 |
) |
|
147 |
self.backend.permissions.public_set( |
|
148 |
'account/container/object', |
|
149 |
self.backend.public_url_min_length, |
|
150 |
self.backend.public_url_alphabet |
|
151 |
) |
|
152 |
public = self.assert_public_object('account', 'container', 'object') |
|
153 |
|
|
154 |
self.backend.permissions.public_unset('account/container/object') |
|
155 |
self.assert_not_public_object('account', 'container', 'object') |
|
156 |
|
|
157 |
self.backend.permissions.public_set( |
|
158 |
'account/container/object', |
|
159 |
self.backend.public_url_min_length, |
|
160 |
self.backend.public_url_alphabet |
|
161 |
) |
|
162 |
public3 = self.assert_public_object('account', 'container', 'object') |
|
163 |
|
|
164 |
self.assertTrue(public != public3) |
|
165 |
|
|
166 |
def test_update_object_public(self): |
|
167 |
self.utils.backend.put_container('account', 'account', 'container') |
|
168 |
data = get_random_data(int(random.random())) |
|
169 |
self.utils.create_update_object( |
|
170 |
'account', |
|
171 |
'container', |
|
172 |
'object', |
|
173 |
'application/octet-stream', |
|
174 |
data |
|
175 |
) |
|
176 |
|
|
177 |
self.backend.update_object_public( |
|
178 |
'account', 'account', 'container', 'object', public=False |
|
179 |
) |
|
180 |
self.assert_not_public_object('account', 'container', 'object') |
|
181 |
|
|
182 |
self.backend.update_object_public( |
|
183 |
'account', 'account', 'container', 'object', public=True |
|
184 |
) |
|
185 |
public = self.assert_public_object('account', 'container', 'object') |
|
186 |
|
|
187 |
self.backend.update_object_public( |
|
188 |
'account', 'account', 'container', 'object', public=False |
|
189 |
) |
|
190 |
self.assert_not_public_object('account', 'container', 'object') |
|
191 |
|
|
192 |
self.backend.update_object_public( |
|
193 |
'account', 'account', 'container', 'object', public=True |
|
194 |
) |
|
195 |
new_public = self.assert_public_object('account', 'container', 'object') |
|
196 |
self.assertTrue(public != new_public) |
|
197 |
|
|
198 |
def test_delete_not_public_object(self): |
|
199 |
self.utils.backend.put_container('account', 'account', 'container') |
|
200 |
data = get_random_data(int(random.random())) |
|
201 |
self.utils.create_update_object( |
|
202 |
'account', |
|
203 |
'container', |
|
204 |
'object', |
|
205 |
'application/octet-stream', |
|
206 |
data |
|
207 |
) |
|
208 |
self.assert_not_public_object('account', 'container', 'object') |
|
209 |
|
|
210 |
self.backend.delete_object('account', 'account', 'container', 'object') |
|
211 |
|
|
212 |
self.assert_not_public_object('account', 'container', 'object') |
|
213 |
|
|
214 |
def test_delete_public_object(self): |
|
215 |
self.utils.backend.put_container('account', 'account', 'container') |
|
216 |
data = get_random_data(int(random.random())) |
|
217 |
self.utils.create_update_object( |
|
218 |
'account', |
|
219 |
'container', |
|
220 |
'object', |
|
221 |
'application/octet-stream', |
|
222 |
data |
|
223 |
) |
|
224 |
self.assert_not_public_object('account', 'container', 'object') |
|
225 |
|
|
226 |
self.backend.permissions.public_set( |
|
227 |
'account/container/object', |
|
228 |
self.backend.public_url_min_length, |
|
229 |
self.backend.public_url_alphabet |
|
230 |
) |
|
231 |
self.assert_public_object('account', 'container', 'object') |
|
232 |
|
|
233 |
self.backend.delete_object('account', 'account', 'container', 'object') |
|
234 |
self.assert_not_public_object('account', 'container', 'object') |
|
235 |
|
|
236 |
def test_delete_public_object_history(self): |
|
237 |
self.utils.backend.put_container('account', 'account', 'container') |
|
238 |
for i in range(random.randint(1, 10)): |
|
239 |
data = get_random_data(int(random.random())) |
|
240 |
self.utils.create_update_object( |
|
241 |
'account', |
|
242 |
'container', |
|
243 |
'object', |
|
244 |
'application/octet-stream', |
|
245 |
data |
|
246 |
) |
|
247 |
_time.sleep(1) |
|
248 |
versions = self.backend.list_versions( |
|
249 |
'account', 'account', 'container', 'object' |
|
250 |
) |
|
251 |
mtime = [int(i[1]) for i in versions] |
|
252 |
self.assert_not_public_object('account', 'container', 'object') |
|
253 |
|
|
254 |
self.backend.permissions.public_set( |
|
255 |
'account/container/object', |
|
256 |
self.backend.public_url_min_length, |
|
257 |
self.backend.public_url_alphabet |
|
258 |
) |
|
259 |
public = self.assert_public_object('account', 'container', 'object') |
|
260 |
|
|
261 |
i = random.randrange(len(mtime)) |
|
262 |
self.backend.delete_object( |
|
263 |
'account', 'account', 'container', 'object', until=mtime[i] |
|
264 |
) |
|
265 |
self.assert_public_object('account', 'container', 'object') |
|
266 |
public = self.assert_public_object('account', 'container', 'object') |
|
267 |
|
|
268 |
_time.sleep(1) |
|
269 |
t = datetime.datetime.utcnow() |
|
270 |
now = int(_time.mktime(t.timetuple())) |
|
271 |
self.backend.delete_object( |
|
272 |
'account', 'account', 'container', 'object', until=now |
|
273 |
) |
|
274 |
self.assertRaises( |
|
275 |
NameError, |
|
276 |
self.backend.get_public, |
|
277 |
'$$account$$', |
|
278 |
public |
|
279 |
) |
|
280 |
|
|
281 |
if __name__ == '__main__': |
|
282 |
unittest.main() |
b/snf-pithos-app/pithos/api/util.py | ||
---|---|---|
55 | 55 |
Fault, NotModified, BadRequest, Unauthorized, Forbidden, ItemNotFound, |
56 | 56 |
Conflict, LengthRequired, PreconditionFailed, RequestEntityTooLarge, |
57 | 57 |
RangeNotSatisfiable, InternalServerError, NotImplemented) |
58 |
from pithos.api.short_url import encode_url |
|
59 | 58 |
from pithos.api.settings import (BACKEND_DB_MODULE, BACKEND_DB_CONNECTION, |
60 | 59 |
BACKEND_BLOCK_MODULE, BACKEND_BLOCK_PATH, |
61 | 60 |
BACKEND_BLOCK_UMASK, |
... | ... | |
68 | 67 |
AUTHENTICATION_URL, AUTHENTICATION_USERS, |
69 | 68 |
COOKIE_NAME, USER_CATALOG_URL, |
70 | 69 |
RADOS_STORAGE, RADOS_POOL_BLOCKS, |
71 |
RADOS_POOL_MAPS, TRANSLATE_UUIDS) |
|
70 |
RADOS_POOL_MAPS, TRANSLATE_UUIDS, |
|
71 |
PUBLIC_URL_MIN_LENGTH, |
|
72 |
PUBLIC_URL_ALPHABET) |
|
72 | 73 |
from pithos.backends import connect_backend |
73 | 74 |
from pithos.backends.base import (NotAllowedError, QuotaError, ItemNotExists, |
74 | 75 |
VersionNotExists) |
... | ... | |
387 | 388 |
def update_public_meta(public, meta): |
388 | 389 |
if not public: |
389 | 390 |
return |
390 |
meta['X-Object-Public'] = '/public/' + encode_url(public)
|
|
391 |
meta['X-Object-Public'] = '/public/' + public
|
|
391 | 392 |
|
392 | 393 |
|
393 | 394 |
def validate_modification_preconditions(request, meta): |
... | ... | |
983 | 984 |
quotaholder_token=QUOTAHOLDER_TOKEN, |
984 | 985 |
quotaholder_client_poolsize=QUOTAHOLDER_POOLSIZE, |
985 | 986 |
free_versioning=BACKEND_FREE_VERSIONING, |
986 |
block_params=BLOCK_PARAMS) |
|
987 |
block_params=BLOCK_PARAMS, |
|
988 |
public_url_min_length=PUBLIC_URL_MIN_LENGTH, |
|
989 |
public_url_alphabet=PUBLIC_URL_ALPHABET) |
|
987 | 990 |
|
988 | 991 |
def get_backend(): |
989 | 992 |
backend = _pithos_backend_pool.pool_get() |
b/snf-pithos-backend/pithos/backends/lib/sqlalchemy/alembic/versions/27381099d477_alter_public_add_col.py | ||
---|---|---|
1 |
"""alter public add column url |
|
2 |
|
|
3 |
Revision ID: 27381099d477 |
|
4 |
Revises: 2a309a9a3438 |
|
5 |
Create Date: 2013-03-20 16:14:20.058077 |
|
6 |
|
|
7 |
""" |
|
8 |
|
|
9 |
# revision identifiers, used by Alembic. |
|
10 |
revision = '27381099d477' |
|
11 |
down_revision = '2a309a9a3438' |
|
12 |
|
|
13 |
from alembic import op |
|
14 |
import sqlalchemy as sa |
|
15 |
|
|
16 |
from pithos.backends.modular import ULTIMATE_ANSWER |
|
17 |
from pithos.api.short_url import encode_url |
|
18 |
|
|
19 |
def upgrade(): |
|
20 |
op.add_column('public', sa.Column('url', sa.String(2048))) |
|
21 |
op.create_unique_constraint('idx_public_url', 'public', ['url']) |
|
22 |
|
|
23 |
# migrate old rows |
|
24 |
p = sa.sql.table( |
|
25 |
'public', |
|
26 |
sa.sql.column('public_id', sa.Integer), |
|
27 |
sa.sql.column('url', sa.String), |
|
28 |
) |
|
29 |
get_url = lambda x: encode_url(x + ULTIMATE_ANSWER) |
|
30 |
conn = op.get_bind() |
|
31 |
s = sa.select([p.c.public_id]) |
|
32 |
rows = conn.execute(s).fetchall() |
|
33 |
for r in rows: |
|
34 |
s = p.update().values(url=get_url(r[0])).where(p.c.public_id==r[0]) |
|
35 |
op.execute(s) |
|
36 |
|
|
37 |
def downgrade(): |
|
38 |
op.drop_constraint('idx_public_url', 'public') |
|
39 |
op.drop_column('public', 'url') |
b/snf-pithos-backend/pithos/backends/lib/sqlalchemy/public.py | ||
---|---|---|
37 | 37 |
from sqlalchemy.schema import Index |
38 | 38 |
from sqlalchemy.exc import NoSuchTableError |
39 | 39 |
|
40 |
from pithos.backends.random_word import get_word |
|
40 | 41 |
|
41 | 42 |
def create_tables(engine): |
42 | 43 |
metadata = MetaData() |
... | ... | |
44 | 45 |
columns.append(Column('public_id', Integer, primary_key=True)) |
45 | 46 |
columns.append(Column('path', String(2048), nullable=False)) |
46 | 47 |
columns.append(Column('active', Boolean, nullable=False, default=True)) |
48 |
columns.append(Column('url', String(2048), nullable=True)) |
|
47 | 49 |
public = Table('public', metadata, *columns, mysql_engine='InnoDB', |
48 | 50 |
sqlite_autoincrement=True) |
49 | 51 |
# place an index on path |
50 | 52 |
Index('idx_public_path', public.c.path, unique=True) |
53 |
# place an index on url |
|
54 |
Index('idx_public_url', public.c.url, unique=True) |
|
51 | 55 |
metadata.create_all(engine) |
52 | 56 |
return metadata.sorted_tables |
53 | 57 |
|
... | ... | |
64 | 68 |
tables = create_tables(self.engine) |
65 | 69 |
map(lambda t: self.__setattr__(t.name, t), tables) |
66 | 70 |
|
67 |
def public_set(self, path): |
|
71 |
def get_unique_url(self, serial, public_url_min_length, public_url_alphabet): |
|
72 |
l = public_url_min_length |
|
73 |
while 1: |
|
74 |
candidate = get_word(serial, length=l, alphabet=public_url_alphabet) |
|
75 |
if self.public_path(candidate) is None: |
|
76 |
return candidate |
|
77 |
l +=1 |
|
78 |
|
|
79 |
def public_set(self, path, public_url_min_length, public_url_alphabet): |
|
68 | 80 |
s = select([self.public.c.public_id]) |
69 | 81 |
s = s.where(self.public.c.path == path) |
70 | 82 |
r = self.conn.execute(s) |
71 | 83 |
row = r.fetchone() |
72 | 84 |
r.close() |
73 |
if row: |
|
74 |
s = self.public.update().where(self.public.c.public_id == row[0]) |
|
75 |
s = s.values(active=True) |
|
76 |
else: |
|
85 |
|
|
86 |
if not row: |
|
77 | 87 |
s = self.public.insert() |
78 | 88 |
s = s.values(path=path, active=True) |
79 |
r = self.conn.execute(s) |
|
80 |
r.close() |
|
89 |
r = self.conn.execute(s) |
|
90 |
serial = r.inserted_primary_key[0] |
|
91 |
r.close() |
|
92 |
|
|
93 |
url = self.get_unique_url( |
|
94 |
serial, public_url_min_length, public_url_alphabet |
|
95 |
) |
|
96 |
s = self.public.update().where(self.public.c.public_id == serial) |
|
97 |
s = s.values(url=url) |
|
98 |
self.conn.execute(s).close() |
|
81 | 99 |
|
82 | 100 |
def public_unset(self, path): |
83 |
s = self.public.update()
|
|
101 |
s = self.public.delete()
|
|
84 | 102 |
s = s.where(self.public.c.path == path) |
85 |
s = s.values(active=False) |
|
86 |
r = self.conn.execute(s) |
|
87 |
r.close() |
|
103 |
self.conn.execute(s).close() |
|
88 | 104 |
|
89 | 105 |
def public_unset_bulk(self, paths): |
90 | 106 |
if not paths: |
91 | 107 |
return |
92 |
s = self.public.update()
|
|
108 |
s = self.public.delete()
|
|
93 | 109 |
s = s.where(self.public.c.path.in_(paths)) |
94 |
s = s.values(active=False) |
|
95 |
r = self.conn.execute(s) |
|
96 |
r.close() |
|
110 |
self.conn.execute(s).close() |
|
97 | 111 |
|
98 | 112 |
def public_get(self, path): |
99 |
s = select([self.public.c.public_id])
|
|
113 |
s = select([self.public.c.url])
|
|
100 | 114 |
s = s.where(and_(self.public.c.path == path, |
101 | 115 |
self.public.c.active == True)) |
102 | 116 |
r = self.conn.execute(s) |
... | ... | |
107 | 121 |
return None |
108 | 122 |
|
109 | 123 |
def public_list(self, prefix): |
110 |
s = select([self.public.c.path, self.public.c.public_id])
|
|
124 |
s = select([self.public.c.path, self.public.c.url])
|
|
111 | 125 |
s = s.where(self.public.c.path.like( |
112 | 126 |
self.escape_like(prefix) + '%', escape='\\')) |
113 | 127 |
s = s.where(self.public.c.active == True) |
... | ... | |
118 | 132 |
|
119 | 133 |
def public_path(self, public): |
120 | 134 |
s = select([self.public.c.path]) |
121 |
s = s.where(and_(self.public.c.public_id == public,
|
|
135 |
s = s.where(and_(self.public.c.url == public,
|
|
122 | 136 |
self.public.c.active == True)) |
123 | 137 |
r = self.conn.execute(s) |
124 | 138 |
row = r.fetchone() |
b/snf-pithos-backend/pithos/backends/lib/sqlite/public.py | ||
---|---|---|
33 | 33 |
|
34 | 34 |
from dbworker import DBWorker |
35 | 35 |
|
36 |
from pithos.backends.random_word import get_word |
|
36 | 37 |
|
37 | 38 |
class Public(DBWorker): |
38 | 39 |
"""Paths can be marked as public.""" |
... | ... | |
44 | 45 |
execute(""" create table if not exists public |
45 | 46 |
( public_id integer primary key autoincrement, |
46 | 47 |
path text not null, |
47 |
active boolean not null default 1 ) """) |
|
48 |
active boolean not null default 1, |
|
49 |
url text) """) |
|
48 | 50 |
execute(""" create unique index if not exists idx_public_path |
49 | 51 |
on public(path) """) |
52 |
execute(""" create unique index if not exists idx_public_url |
|
53 |
on public(url) """) |
|
50 | 54 |
|
51 |
def public_set(self, path): |
|
52 |
q = "insert or ignore into public (path) values (?)" |
|
53 |
self.execute(q, (path,)) |
|
54 |
q = "update public set active = 1 where path = ?" |
|
55 |
def get_unique_url(self, serial, public_url_min_length, public_url_alphabet): |
|
56 |
l = public_url_min_length |
|
57 |
while 1: |
|
58 |
candidate = get_word(serial, length=l, alphabet=public_url_alphabet) |
|
59 |
if self.public_path(candidate) is None: |
|
60 |
return candidate |
|
61 |
l +=1 |
|
62 |
|
|
63 |
def public_set(self, path, public_url_min_length, public_url_alphabet): |
|
64 |
q = "select public_id from public where path = ?" |
|
55 | 65 |
self.execute(q, (path,)) |
66 |
row = self.fetchone() |
|
67 |
|
|
68 |
if not row: |
|
69 |
q = "insert into public(path, active) values(?, ?)" |
|
70 |
serial = self.execute(q, (path, active)).lastrowid |
|
71 |
url = self.get_unique_url( |
|
72 |
serial, public_url_min_length, public_url_alphabet |
|
73 |
) |
|
74 |
q = "update public set url=url where public_id = ?" |
|
75 |
self.execute(q, (serial,)) |
|
56 | 76 |
|
57 | 77 |
def public_unset(self, path): |
58 |
q = "update public set active = 0 where path = ?"
|
|
78 |
q = "delete from public where path = ?"
|
|
59 | 79 |
self.execute(q, (path,)) |
60 | 80 |
|
61 | 81 |
def public_unset_bulk(self, paths): |
62 | 82 |
placeholders = ','.join('?' for path in paths) |
63 |
q = "update public set active = 0 where path in (%s)" % placeholders
|
|
83 |
q = "delete from public where path in (%s)"
|
|
64 | 84 |
self.execute(q, paths) |
65 | 85 |
|
66 | 86 |
def public_get(self, path): |
67 |
q = "select public_id from public where path = ? and active = 1"
|
|
87 |
q = "select url from public where path = ? and active = 1"
|
|
68 | 88 |
self.execute(q, (path,)) |
69 | 89 |
row = self.fetchone() |
70 | 90 |
if row: |
... | ... | |
72 | 92 |
return None |
73 | 93 |
|
74 | 94 |
def public_list(self, prefix): |
75 |
q = "select path, public_id from public where path like ? escape '\\' and active = 1"
|
|
95 |
q = "select path, url from public where path like ? escape '\\' and active = 1"
|
|
76 | 96 |
self.execute(q, (self.escape_like(prefix) + '%',)) |
77 | 97 |
return self.fetchall() |
78 | 98 |
|
79 | 99 |
def public_path(self, public): |
80 |
q = "select path from public where public_id = ? and active = 1"
|
|
100 |
q = "select path from public where url = ? and active = 1"
|
|
81 | 101 |
self.execute(q, (public,)) |
82 | 102 |
row = self.fetchone() |
83 | 103 |
if row: |
b/snf-pithos-backend/pithos/backends/modular.py | ||
---|---|---|
84 | 84 |
DEFAULT_BLOCK_PARAMS = { 'mappool': None, 'blockpool': None } |
85 | 85 |
#DEFAULT_QUEUE_HOSTS = '[amqp://guest:guest@localhost:5672]' |
86 | 86 |
#DEFAULT_QUEUE_EXCHANGE = 'pithos' |
87 |
DEFAULT_ALPHABET = ('0123456789' |
|
88 |
'abcdefghijklmnopqrstuvwxyz' |
|
89 |
'ABCDEFGHIJKLMNOPQRSTUVWXYZ') |
|
90 |
DEFAULT_MIN_LENGTH = 8 |
|
87 | 91 |
|
88 | 92 |
QUEUE_MESSAGE_KEY_PREFIX = 'pithos.%s' |
89 | 93 |
QUEUE_CLIENT_ID = 'pithos' |
... | ... | |
148 | 152 |
quotaholder_enabled=False, |
149 | 153 |
quotaholder_url=None, quotaholder_token=None, |
150 | 154 |
quotaholder_client_poolsize=None, |
151 |
free_versioning=True, block_params=None): |
|
155 |
free_versioning=True, block_params=None, |
|
156 |
public_url_min_length=None, |
|
157 |
public_url_alphabet=None): |
|
152 | 158 |
db_module = db_module or DEFAULT_DB_MODULE |
153 | 159 |
db_connection = db_connection or DEFAULT_DB_CONNECTION |
154 | 160 |
block_module = block_module or DEFAULT_BLOCK_MODULE |
... | ... | |
161 | 167 |
#queue_hosts = queue_hosts or DEFAULT_QUEUE_HOSTS |
162 | 168 |
#queue_exchange = queue_exchange or DEFAULT_QUEUE_EXCHANGE |
163 | 169 |
|
170 |
self.public_url_min_length = public_url_min_length or DEFAULT_MIN_LENGTH |
|
171 |
self.public_url_alphabet = public_url_alphabet or DEFAULT_ALPHABET |
|
172 |
|
|
164 | 173 |
self.hash_algorithm = 'sha256' |
165 | 174 |
self.block_size = 4 * 1024 * 1024 # 4MB |
166 | 175 |
self.free_versioning = free_versioning |
... | ... | |
718 | 727 |
account, container, prefix) |
719 | 728 |
public = {} |
720 | 729 |
for path, p in self.permissions.public_list('/'.join((account, container, prefix))): |
721 |
public[path] = p + ULTIMATE_ANSWER
|
|
730 |
public[path] = p |
|
722 | 731 |
return public |
723 | 732 |
|
724 | 733 |
@backend_method |
... | ... | |
815 | 824 |
self._can_read(user, account, container, name) |
816 | 825 |
path = self._lookup_object(account, container, name)[0] |
817 | 826 |
p = self.permissions.public_get(path) |
818 |
if p is not None: |
|
819 |
p += ULTIMATE_ANSWER |
|
820 | 827 |
return p |
821 | 828 |
|
822 | 829 |
@backend_method |
... | ... | |
830 | 837 |
if not public: |
831 | 838 |
self.permissions.public_unset(path) |
832 | 839 |
else: |
833 |
self.permissions.public_set(path) |
|
840 |
self.permissions.public_set( |
|
841 |
path, self.public_url_min_length, self.public_url_alphabet |
|
842 |
) |
|
834 | 843 |
|
835 | 844 |
@backend_method |
836 | 845 |
def get_object_hashmap(self, user, account, container, name, version=None): |
... | ... | |
1097 | 1106 |
"""Return the (account, container, name) for the public id given.""" |
1098 | 1107 |
|
1099 | 1108 |
logger.debug("get_public: %s %s", user, public) |
1100 |
if public is None or public < ULTIMATE_ANSWER: |
|
1101 |
raise NameError |
|
1102 |
path = self.permissions.public_path(public - ULTIMATE_ANSWER) |
|
1109 |
path = self.permissions.public_path(public) |
|
1103 | 1110 |
if path is None: |
1104 | 1111 |
raise NameError |
1105 | 1112 |
account, container, name = path.split('/', 2) |
b/snf-pithos-backend/pithos/backends/random_word.py | ||
---|---|---|
1 |
# Copyright 2011-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 |
DEFAULT_ALPHABET = ('0123456789' |
|
35 |
'abcdefghijklmnopqrstuvwxyz' |
|
36 |
'ABCDEFGHIJKLMNOPQRSTUVWXYZ') |
|
37 |
DEFAULT_MIN_LENGTH = 8 |
|
38 |
|
|
39 |
from random import randrange |
|
40 |
from math import log |
|
41 |
|
|
42 |
def get_random_word(length=DEFAULT_MIN_LENGTH, alphabet=DEFAULT_ALPHABET): |
|
43 |
alphabet_length = len(alphabet) |
|
44 |
word = ''.join(alphabet[randrange(alphabet_length)] |
|
45 |
for _ in xrange(length)) |
|
46 |
return word |
|
47 |
|
|
48 |
def encode_serial(serial, alphabet=DEFAULT_ALPHABET): |
|
49 |
i = 0 |
|
50 |
base = len(alphabet) |
|
51 |
quotient = int(serial) |
|
52 |
digits = [] |
|
53 |
append = digits.append |
|
54 |
while quotient > 0: |
|
55 |
quotient, remainder = divmod(quotient, base) |
|
56 |
append(alphabet[remainder]) |
|
57 |
word = ''.join(reversed(digits)) |
|
58 |
return word |
|
59 |
|
|
60 |
def get_word(serial, length=DEFAULT_MIN_LENGTH, alphabet=DEFAULT_ALPHABET): |
|
61 |
word = encode_serial(serial, alphabet) |
|
62 |
word += get_random_word(length, alphabet) |
|
63 |
return word |
b/snf-pithos-backend/pithos/backends/util.py | ||
---|---|---|
48 | 48 |
quotaholder_enabled=True, |
49 | 49 |
quotaholder_url=None, quotaholder_token=None, |
50 | 50 |
quotaholder_client_poolsize=None, |
51 |
block_params=None): |
|
51 |
block_params=None, |
|
52 |
public_url_min_length=None, |
|
53 |
public_url_alphabet=None): |
|
52 | 54 |
super(PithosBackendPool, self).__init__(size=size) |
53 | 55 |
self.db_module = db_module |
54 | 56 |
self.db_connection = db_connection |
... | ... | |
64 | 66 |
self.quotaholder_token = quotaholder_token |
65 | 67 |
self.quotaholder_client_poolsize = quotaholder_client_poolsize |
66 | 68 |
self.free_versioning = free_versioning |
69 |
self.public_url_min_length=public_url_min_length |
|
70 |
self.public_url_alphabet=public_url_alphabet |
|
67 | 71 |
|
68 | 72 |
def _pool_create(self): |
69 | 73 |
backend = connect_backend( |
... | ... | |
80 | 84 |
quotaholder_url=self.quotaholder_url, |
81 | 85 |
quotaholder_token=self.quotaholder_token, |
82 | 86 |
quotaholder_client_poolsize=self.quotaholder_client_poolsize, |
83 |
free_versioning=self.free_versioning) |
|
87 |
free_versioning=self.free_versioning, |
|
88 |
public_url_min_length=self.public_url_min_length, |
|
89 |
public_url_alphabet=self.public_url_alphabet) |
|
84 | 90 |
|
85 | 91 |
backend._real_close = backend.close |
86 | 92 |
backend.close = instancemethod(_pooled_backend_close, backend, |
Also available in: Unified diff