Merge lp:~notmyname/swift/stats_fix into lp:swift/1.1
- stats_fix
- Merge into 1.1
Proposed by
John Dickinson
Status: | Rejected |
---|---|
Rejected by: | Chuck Thier |
Proposed branch: | lp:~notmyname/swift/stats_fix |
Merge into: | lp:swift/1.1 |
Diff against target: |
1138 lines (+613/-117) 25 files modified
doc/source/_ga/layout.html (+17/-0) doc/source/conf.py (+7/-1) doc/source/development_saio.rst (+3/-0) doc/source/getting_started.rst (+2/-3) doc/source/index.rst (+1/-1) doc/source/ratelimit.rst (+4/-1) etc/proxy-server.conf-sample (+12/-2) setup.py (+2/-0) swift/account/server.py (+8/-11) swift/common/constraints.py (+3/-0) swift/common/memcached.py (+32/-26) swift/common/middleware/catch_errors.py (+48/-0) swift/common/middleware/domain_remap.py (+80/-0) swift/common/middleware/memcache.py (+1/-0) swift/common/middleware/ratelimit.py (+7/-0) swift/container/server.py (+8/-11) swift/proxy/server.py (+5/-3) swift/stats/log_processor.py (+1/-2) test/unit/common/middleware/test_domain_remap.py (+110/-0) test/unit/common/middleware/test_except.py (+49/-0) test/unit/common/middleware/test_ratelimit.py (+10/-12) test/unit/common/test_memcached.py (+28/-0) test/unit/container/test_server.py (+110/-44) test/unit/proxy/test_server.py (+23/-0) test/unit/stats/test_log_processor.py (+42/-0) |
To merge this branch: | bzr merge lp:~notmyname/swift/stats_fix |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Swift Release Team | Pending | ||
Review via email: mp+43789@code.launchpad.net |
Commit message
Description of the change
Fixed lazy-load of internal_proxy attribute in log_processor
To post a comment you must log in.
Unmerged revisions
- 120. By John Dickinson
-
merged with clayg's branch
- 119. By John Dickinson
-
fixed bug in internal_proxy
- 118. By John Dickinson
-
fixed typo on the getting started doc
- 117. By John Dickinson
-
Middleware that catches and logs errors that would otherwise be sent to the client
- 116. By John Dickinson
-
Middleware that translates account and container info from the host header to the path.
- 115. By Mike Barton
-
shore up accept header parsing
- 114. By David Goetz <email address hidden>
-
catching invalid urls and adding tests
- 113. By Anne Gentle
-
Okay, mtaylor worked out a way to have conf.py contain a build environment variable so that the build can insert the GA code but the local builds wouldn't have it.
- 112. By Anne Gentle
-
Add link to RHEL instructions in the SAIO doc
- 111. By John Dickinson
-
PUT with X-Copy-From header and a non-zero Content-Length will now error as a 400
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added directory 'doc/source/_ga' |
2 | === added file 'doc/source/_ga/layout.html' |
3 | --- doc/source/_ga/layout.html 1970-01-01 00:00:00 +0000 |
4 | +++ doc/source/_ga/layout.html 2010-12-15 16:48:57 +0000 |
5 | @@ -0,0 +1,17 @@ |
6 | +{% extends "!layout.html" %} |
7 | + |
8 | +{% block footer %} |
9 | +{{ super() }} |
10 | +<script type="text/javascript"> |
11 | +var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www."); |
12 | +document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E")); |
13 | +</script> |
14 | +<script type="text/javascript"> |
15 | +try { |
16 | +var pageTracker = _gat._getTracker("UA-17511903-1"); |
17 | +pageTracker._setDomainName("none"); |
18 | +pageTracker._setAllowLinker(true); |
19 | +pageTracker._trackPageview(); |
20 | +} catch(err) {}</script> |
21 | +{% endblock %} |
22 | + |
23 | |
24 | === added directory 'doc/source/_templates' |
25 | === modified file 'doc/source/conf.py' |
26 | --- doc/source/conf.py 2010-10-18 15:34:21 +0000 |
27 | +++ doc/source/conf.py 2010-12-15 16:48:57 +0000 |
28 | @@ -41,7 +41,13 @@ |
29 | todo_include_todos = True |
30 | |
31 | # Add any paths that contain templates here, relative to this directory. |
32 | -templates_path = ['_templates'] |
33 | +# Changing the path so that the Hudson build output contains GA code and the source |
34 | +# docs do not contain the code so local, offline sphinx builds are "clean." |
35 | +templates_path = [] |
36 | +if os.getenv('HUDSON_PUBLISH_DOCS'): |
37 | + templates_path = ['_ga', '_templates'] |
38 | +else: |
39 | + templates_path = ['_templates'] |
40 | |
41 | # The suffix of source filenames. |
42 | source_suffix = '.rst' |
43 | |
44 | === modified file 'doc/source/development_saio.rst' |
45 | --- doc/source/development_saio.rst 2010-10-20 20:24:59 +0000 |
46 | +++ doc/source/development_saio.rst 2010-12-15 16:48:57 +0000 |
47 | @@ -17,6 +17,9 @@ |
48 | |
49 | * Create guest virtual machine from the Ubuntu image. |
50 | |
51 | +Additional information about setting up a Swift development snapshot on other distributions is |
52 | +available on the wiki at http://wiki.openstack.org/SAIOInstructions. |
53 | + |
54 | ----------------------------------------- |
55 | Installing dependencies and the core code |
56 | ----------------------------------------- |
57 | |
58 | === modified file 'doc/source/getting_started.rst' |
59 | --- doc/source/getting_started.rst 2010-10-14 18:11:21 +0000 |
60 | +++ doc/source/getting_started.rst 2010-12-15 16:48:57 +0000 |
61 | @@ -6,7 +6,7 @@ |
62 | System Requirements |
63 | ------------------- |
64 | |
65 | -Swift development currently targets Unbuntu Server 10.04, but should work on |
66 | +Swift development currently targets Ubuntu Server 10.04, but should work on |
67 | most Linux platforms with the following software: |
68 | |
69 | * Python 2.6 |
70 | @@ -37,5 +37,4 @@ |
71 | ---------- |
72 | |
73 | We do not have documentation yet on how to set up and configure Swift for a |
74 | -production cluster, but hope to begin work on those soon. |
75 | - |
76 | +production cluster, but hope to begin work on those soon. |
77 | \ No newline at end of file |
78 | |
79 | === modified file 'doc/source/index.rst' |
80 | --- doc/source/index.rst 2010-10-13 20:51:11 +0000 |
81 | +++ doc/source/index.rst 2010-12-15 16:48:57 +0000 |
82 | @@ -40,7 +40,7 @@ |
83 | |
84 | .. toctree:: |
85 | :maxdepth: 1 |
86 | - |
87 | + |
88 | deployment_guide |
89 | admin_guide |
90 | debian_package_guide |
91 | |
92 | === modified file 'doc/source/ratelimit.rst' |
93 | --- doc/source/ratelimit.rst 2010-10-13 21:30:00 +0000 |
94 | +++ doc/source/ratelimit.rst 2010-12-15 16:48:57 +0000 |
95 | @@ -4,7 +4,7 @@ |
96 | |
97 | Rate limiting in swift is implemented as a pluggable middleware. Rate |
98 | limiting is performed on requests that result in database writes to the |
99 | -account and container sqlite dbs. It uses memcached and is dependant on |
100 | +account and container sqlite dbs. It uses memcached and is dependent on |
101 | the proxy servers having highly synchronized time. The rate limits are |
102 | limited by the accuracy of the proxy server clocks. |
103 | |
104 | @@ -27,6 +27,9 @@ |
105 | max_sleep_time_seconds 60 App will immediately return a 498 response |
106 | if the necessary sleep time ever exceeds |
107 | the given max_sleep_time_seconds. |
108 | +log_sleep_time_seconds 0 To allow visibility into rate limiting set |
109 | + this value > 0 and all sleeps greater than |
110 | + the number will be logged. |
111 | account_ratelimit 0 If set, will limit all requests to |
112 | /account_name and PUTs to |
113 | /account_name/container_name. Number is in |
114 | |
115 | === modified file 'etc/proxy-server.conf-sample' |
116 | --- etc/proxy-server.conf-sample 2010-10-14 23:47:14 +0000 |
117 | +++ etc/proxy-server.conf-sample 2010-12-15 16:48:57 +0000 |
118 | @@ -9,7 +9,7 @@ |
119 | # key_file = /etc/swift/proxy.key |
120 | |
121 | [pipeline:main] |
122 | -pipeline = healthcheck cache ratelimit auth proxy-server |
123 | +pipeline = catch_errors healthcheck cache ratelimit auth proxy-server |
124 | |
125 | [app:proxy-server] |
126 | use = egg:swift#proxy |
127 | @@ -60,7 +60,8 @@ |
128 | # clock accuracy. |
129 | # clock_accuracy = 1000 |
130 | # max_sleep_time_seconds = 60 |
131 | - |
132 | +# log_sleep_time_seconds of 0 means disabled |
133 | +# log_sleep_time_seconds = 0 |
134 | # account_ratelimit of 0 means disabled |
135 | # account_ratelimit = 0 |
136 | |
137 | @@ -75,3 +76,12 @@ |
138 | # container_ratelimit_0 = 100 |
139 | # container_ratelimit_10 = 50 |
140 | # container_ratelimit_50 = 20 |
141 | + |
142 | +[filter:domain_remap] |
143 | +use = egg:swift#domain_remap |
144 | +# storage_domain = example.com |
145 | +# path_root = v1 |
146 | + |
147 | +[filter:catch_errors] |
148 | +use = egg:swift#catch_errors |
149 | + |
150 | |
151 | === modified file 'setup.py' |
152 | --- setup.py 2010-10-18 15:34:21 +0000 |
153 | +++ setup.py 2010-12-15 16:48:57 +0000 |
154 | @@ -93,6 +93,8 @@ |
155 | 'healthcheck=swift.common.middleware.healthcheck:filter_factory', |
156 | 'memcache=swift.common.middleware.memcache:filter_factory', |
157 | 'ratelimit=swift.common.middleware.ratelimit:filter_factory', |
158 | + 'catch_errors=swift.common.middleware.catch_errors:filter_factory', |
159 | + 'domain_remap=swift.common.middleware.domain_remap:filter_factory', |
160 | ], |
161 | }, |
162 | ) |
163 | |
164 | === modified file 'swift/account/server.py' |
165 | --- swift/account/server.py 2010-11-02 19:41:24 +0000 |
166 | +++ swift/account/server.py 2010-12-15 16:48:57 +0000 |
167 | @@ -204,16 +204,15 @@ |
168 | except UnicodeDecodeError, err: |
169 | return HTTPBadRequest(body='parameters not utf8', |
170 | content_type='text/plain', request=req) |
171 | - header_format = req.accept.first_match(['text/plain', |
172 | - 'application/json', |
173 | - 'application/xml']) |
174 | - format = query_format if query_format else header_format |
175 | - if format.startswith('application/'): |
176 | - format = format[12:] |
177 | + if query_format: |
178 | + req.accept = 'application/%s' % query_format.lower() |
179 | + out_content_type = req.accept.best_match( |
180 | + ['text/plain', 'application/json', |
181 | + 'application/xml', 'text/xml'], |
182 | + default_match='text/plain') |
183 | account_list = broker.list_containers_iter(limit, marker, prefix, |
184 | delimiter) |
185 | - if format == 'json': |
186 | - out_content_type = 'application/json' |
187 | + if out_content_type == 'application/json': |
188 | json_pattern = ['"name":%s', '"count":%s', '"bytes":%s'] |
189 | json_pattern = '{' + ','.join(json_pattern) + '}' |
190 | json_out = [] |
191 | @@ -225,8 +224,7 @@ |
192 | json_out.append(json_pattern % |
193 | (name, object_count, bytes_used)) |
194 | account_list = '[' + ','.join(json_out) + ']' |
195 | - elif format == 'xml': |
196 | - out_content_type = 'application/xml' |
197 | + elif out_content_type.endswith('/xml'): |
198 | output_list = ['<?xml version="1.0" encoding="UTF-8"?>', |
199 | '<account name="%s">' % account] |
200 | for (name, object_count, bytes_used, is_subdir) in account_list: |
201 | @@ -243,7 +241,6 @@ |
202 | else: |
203 | if not account_list: |
204 | return HTTPNoContent(request=req, headers=resp_headers) |
205 | - out_content_type = 'text/plain' |
206 | account_list = '\n'.join(r[0] for r in account_list) + '\n' |
207 | ret = Response(body=account_list, request=req, headers=resp_headers) |
208 | ret.content_type = out_content_type |
209 | |
210 | === modified file 'swift/common/constraints.py' |
211 | --- swift/common/constraints.py 2010-09-22 19:53:38 +0000 |
212 | +++ swift/common/constraints.py 2010-12-15 16:48:57 +0000 |
213 | @@ -100,6 +100,9 @@ |
214 | if req.content_length is None and \ |
215 | req.headers.get('transfer-encoding') != 'chunked': |
216 | return HTTPLengthRequired(request=req) |
217 | + if 'X-Copy-From' in req.headers and req.content_length: |
218 | + return HTTPBadRequest(body='Copy requests require a zero byte body', |
219 | + request=req, content_type='text/plain') |
220 | if len(object_name) > MAX_OBJECT_NAME_LENGTH: |
221 | return HTTPBadRequest(body='Object name length of %d longer than %d' % |
222 | (len(object_name), MAX_OBJECT_NAME_LENGTH), request=req, |
223 | |
224 | === modified file 'swift/common/memcached.py' |
225 | --- swift/common/memcached.py 2010-10-20 17:31:50 +0000 |
226 | +++ swift/common/memcached.py 2010-12-15 16:48:57 +0000 |
227 | @@ -168,23 +168,42 @@ |
228 | def incr(self, key, delta=1, timeout=0): |
229 | """ |
230 | Increments a key which has a numeric value by delta. |
231 | - If the key can't be found, it's added as delta. |
232 | + If the key can't be found, it's added as delta or 0 if delta < 0. |
233 | + If passed a negative number, will use memcached's decr. Returns |
234 | + the int stored in memcached |
235 | + Note: The data memcached stores as the result of incr/decr is |
236 | + an unsigned int. decr's that result in a number below 0 are |
237 | + stored as 0. |
238 | |
239 | :param key: key |
240 | :param delta: amount to add to the value of key (or set as the value |
241 | - if the key is not found) |
242 | + if the key is not found) will be cast to an int |
243 | :param timeout: ttl in memcache |
244 | """ |
245 | key = md5hash(key) |
246 | + command = 'incr' |
247 | + if delta < 0: |
248 | + command = 'decr' |
249 | + delta = str(abs(int(delta))) |
250 | for (server, fp, sock) in self._get_conns(key): |
251 | try: |
252 | - sock.sendall('incr %s %s\r\n' % (key, delta)) |
253 | + sock.sendall('%s %s %s\r\n' % (command, key, delta)) |
254 | line = fp.readline().strip().split() |
255 | if line[0].upper() == 'NOT_FOUND': |
256 | - line[0] = str(delta) |
257 | - sock.sendall('add %s %d %d %s noreply\r\n%s\r\n' % \ |
258 | - (key, 0, timeout, len(line[0]), line[0])) |
259 | - ret = int(line[0].strip()) |
260 | + add_val = delta |
261 | + if command == 'decr': |
262 | + add_val = '0' |
263 | + sock.sendall('add %s %d %d %s\r\n%s\r\n' % \ |
264 | + (key, 0, timeout, len(add_val), add_val)) |
265 | + line = fp.readline().strip().split() |
266 | + if line[0].upper() == 'NOT_STORED': |
267 | + sock.sendall('%s %s %s\r\n' % (command, key, delta)) |
268 | + line = fp.readline().strip().split() |
269 | + ret = int(line[0].strip()) |
270 | + else: |
271 | + ret = int(add_val) |
272 | + else: |
273 | + ret = int(line[0].strip()) |
274 | self._return_conn(server, fp, sock) |
275 | return ret |
276 | except Exception, e: |
277 | @@ -192,29 +211,16 @@ |
278 | |
279 | def decr(self, key, delta=1, timeout=0): |
280 | """ |
281 | - Decrements a key which has a numeric value by delta. |
282 | - If the key can't be found, it's added as 0. Memcached |
283 | - will treat data values below 0 as 0 with incr/decr. |
284 | + Decrements a key which has a numeric value by delta. Calls incr with |
285 | + -delta. |
286 | |
287 | :param key: key |
288 | - :param delta: amount to subtract to the value of key (or set |
289 | - as the value if the key is not found) |
290 | + :param delta: amount to subtract to the value of key (or set the |
291 | + value to 0 if the key is not found) will be cast to |
292 | + an int |
293 | :param timeout: ttl in memcache |
294 | """ |
295 | - key = md5hash(key) |
296 | - for (server, fp, sock) in self._get_conns(key): |
297 | - try: |
298 | - sock.sendall('decr %s %s\r\n' % (key, delta)) |
299 | - line = fp.readline().strip().split() |
300 | - if line[0].upper() == 'NOT_FOUND': |
301 | - line[0] = '0' |
302 | - sock.sendall('add %s %d %d %s noreply\r\n%s\r\n' % |
303 | - (key, 0, timeout, len(line[0]), line[0])) |
304 | - ret = int(line[0].strip()) |
305 | - self._return_conn(server, fp, sock) |
306 | - return ret |
307 | - except Exception, e: |
308 | - self._exception_occurred(server, e) |
309 | + self.incr(key, delta=-delta, timeout=timeout) |
310 | |
311 | def delete(self, key): |
312 | """ |
313 | |
314 | === added file 'swift/common/middleware/catch_errors.py' |
315 | --- swift/common/middleware/catch_errors.py 1970-01-01 00:00:00 +0000 |
316 | +++ swift/common/middleware/catch_errors.py 2010-12-15 16:48:57 +0000 |
317 | @@ -0,0 +1,48 @@ |
318 | +# Copyright (c) 2010 OpenStack, LLC. |
319 | +# |
320 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
321 | +# you may not use this file except in compliance with the License. |
322 | +# You may obtain a copy of the License at |
323 | +# |
324 | +# http://www.apache.org/licenses/LICENSE-2.0 |
325 | +# |
326 | +# Unless required by applicable law or agreed to in writing, software |
327 | +# distributed under the License is distributed on an "AS IS" BASIS, |
328 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
329 | +# implied. |
330 | +# See the License for the specific language governing permissions and |
331 | +# limitations under the License. |
332 | + |
333 | +from webob import Request |
334 | +from webob.exc import HTTPServerError |
335 | + |
336 | +from swift.common.utils import get_logger |
337 | + |
338 | + |
339 | +class CatchErrorMiddleware(object): |
340 | + """ |
341 | + Middleware that provides high-level error handling. |
342 | + """ |
343 | + |
344 | + def __init__(self, app, conf): |
345 | + self.app = app |
346 | + self.logger = get_logger(conf) |
347 | + |
348 | + def __call__(self, env, start_response): |
349 | + try: |
350 | + return self.app(env, start_response) |
351 | + except Exception, err: |
352 | + self.logger.exception('Error: %s' % err) |
353 | + resp = HTTPServerError(request=Request(env), |
354 | + body='An error occurred', |
355 | + content_type='text/plain') |
356 | + return resp(env, start_response) |
357 | + |
358 | + |
359 | +def filter_factory(global_conf, **local_conf): |
360 | + conf = global_conf.copy() |
361 | + conf.update(local_conf) |
362 | + |
363 | + def except_filter(app): |
364 | + return CatchErrorMiddleware(app, conf) |
365 | + return except_filter |
366 | |
367 | === added file 'swift/common/middleware/domain_remap.py' |
368 | --- swift/common/middleware/domain_remap.py 1970-01-01 00:00:00 +0000 |
369 | +++ swift/common/middleware/domain_remap.py 2010-12-15 16:48:57 +0000 |
370 | @@ -0,0 +1,80 @@ |
371 | +# Copyright (c) 2010 OpenStack, LLC. |
372 | +# |
373 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
374 | +# you may not use this file except in compliance with the License. |
375 | +# You may obtain a copy of the License at |
376 | +# |
377 | +# http://www.apache.org/licenses/LICENSE-2.0 |
378 | +# |
379 | +# Unless required by applicable law or agreed to in writing, software |
380 | +# distributed under the License is distributed on an "AS IS" BASIS, |
381 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
382 | +# implied. |
383 | +# See the License for the specific language governing permissions and |
384 | +# limitations under the License. |
385 | + |
386 | +from webob import Request |
387 | +from webob.exc import HTTPBadRequest |
388 | + |
389 | + |
390 | +class DomainRemapMiddleware(object): |
391 | + """ |
392 | + Middleware that translates container and account parts of a domain to |
393 | + path parameters that the proxy server understands. |
394 | + |
395 | + container.account.storageurl/object gets translated to |
396 | + container.account.storageurl/path_root/account/container/object |
397 | + |
398 | + account.storageurl/path_root/container/object gets translated to |
399 | + account.storageurl/path_root/account/container/object |
400 | + """ |
401 | + |
402 | + def __init__(self, app, conf): |
403 | + self.app = app |
404 | + self.storage_domain = conf.get('storage_domain', 'example.com') |
405 | + if self.storage_domain and self.storage_domain[0] != '.': |
406 | + self.storage_domain = '.' + self.storage_domain |
407 | + self.path_root = conf.get('path_root', 'v1').strip('/') |
408 | + |
409 | + def __call__(self, env, start_response): |
410 | + if not self.storage_domain: |
411 | + return self.app(env, start_response) |
412 | + given_domain = env['HTTP_HOST'] |
413 | + port = '' |
414 | + if ':' in given_domain: |
415 | + given_domain, port = given_domain.rsplit(':', 1) |
416 | + if given_domain.endswith(self.storage_domain): |
417 | + parts_to_parse = given_domain[:-len(self.storage_domain)] |
418 | + parts_to_parse = parts_to_parse.strip('.').split('.') |
419 | + len_parts_to_parse = len(parts_to_parse) |
420 | + if len_parts_to_parse == 2: |
421 | + container, account = parts_to_parse |
422 | + elif len_parts_to_parse == 1: |
423 | + container, account = None, parts_to_parse[0] |
424 | + else: |
425 | + resp = HTTPBadRequest(request=Request(env), |
426 | + body='Bad domain in host header', |
427 | + content_type='text/plain') |
428 | + return resp(env, start_response) |
429 | + if '_' not in account and '-' in account: |
430 | + account = account.replace('-', '_', 1) |
431 | + path = env['PATH_INFO'].strip('/') |
432 | + new_path_parts = ['', self.path_root, account] |
433 | + if container: |
434 | + new_path_parts.append(container) |
435 | + if path.startswith(self.path_root): |
436 | + path = path[len(self.path_root):].lstrip('/') |
437 | + if path: |
438 | + new_path_parts.append(path) |
439 | + new_path = '/'.join(new_path_parts) |
440 | + env['PATH_INFO'] = new_path |
441 | + return self.app(env, start_response) |
442 | + |
443 | + |
444 | +def filter_factory(global_conf, **local_conf): |
445 | + conf = global_conf.copy() |
446 | + conf.update(local_conf) |
447 | + |
448 | + def domain_filter(app): |
449 | + return DomainRemapMiddleware(app, conf) |
450 | + return domain_filter |
451 | |
452 | === modified file 'swift/common/middleware/memcache.py' |
453 | --- swift/common/middleware/memcache.py 2010-09-16 03:52:54 +0000 |
454 | +++ swift/common/middleware/memcache.py 2010-12-15 16:48:57 +0000 |
455 | @@ -1,3 +1,4 @@ |
456 | +# Copyright (c) 2010 OpenStack, LLC. |
457 | # |
458 | # Licensed under the Apache License, Version 2.0 (the "License"); |
459 | # you may not use this file except in compliance with the License. |
460 | |
461 | === modified file 'swift/common/middleware/ratelimit.py' |
462 | --- swift/common/middleware/ratelimit.py 2010-11-02 19:41:24 +0000 |
463 | +++ swift/common/middleware/ratelimit.py 2010-12-15 16:48:57 +0000 |
464 | @@ -41,6 +41,8 @@ |
465 | self.account_ratelimit = float(conf.get('account_ratelimit', 0)) |
466 | self.max_sleep_time_seconds = float(conf.get('max_sleep_time_seconds', |
467 | 60)) |
468 | + self.log_sleep_time_seconds = float(conf.get('log_sleep_time_seconds', |
469 | + 0)) |
470 | self.clock_accuracy = int(conf.get('clock_accuracy', 1000)) |
471 | self.ratelimit_whitelist = [acc.strip() for acc in |
472 | conf.get('account_whitelist', '').split(',') |
473 | @@ -177,6 +179,11 @@ |
474 | obj_name=obj_name): |
475 | try: |
476 | need_to_sleep = self._get_sleep_time(key, max_rate) |
477 | + if self.log_sleep_time_seconds and \ |
478 | + need_to_sleep > self.log_sleep_time_seconds: |
479 | + self.logger.info("Ratelimit sleep log: %s for %s/%s/%s" % ( |
480 | + need_to_sleep, account_name, |
481 | + container_name, obj_name)) |
482 | if need_to_sleep > 0: |
483 | eventlet.sleep(need_to_sleep) |
484 | except MaxSleepTimeHit, e: |
485 | |
486 | === modified file 'swift/container/server.py' |
487 | --- swift/container/server.py 2010-09-22 19:34:52 +0000 |
488 | +++ swift/container/server.py 2010-12-15 16:48:57 +0000 |
489 | @@ -278,16 +278,15 @@ |
490 | except UnicodeDecodeError, err: |
491 | return HTTPBadRequest(body='parameters not utf8', |
492 | content_type='text/plain', request=req) |
493 | - header_format = req.accept.first_match(['text/plain', |
494 | - 'application/json', |
495 | - 'application/xml']) |
496 | - format = query_format if query_format else header_format |
497 | - if format.startswith('application/'): |
498 | - format = format[12:] |
499 | + if query_format: |
500 | + req.accept = 'application/%s' % query_format.lower() |
501 | + out_content_type = req.accept.best_match( |
502 | + ['text/plain', 'application/json', |
503 | + 'application/xml', 'text/xml'], |
504 | + default_match='text/plain') |
505 | container_list = broker.list_objects_iter(limit, marker, prefix, |
506 | delimiter, path) |
507 | - if format == 'json': |
508 | - out_content_type = 'application/json' |
509 | + if out_content_type == 'application/json': |
510 | json_pattern = ['"name":%s', '"hash":"%s"', '"bytes":%s', |
511 | '"content_type":%s, "last_modified":"%s"'] |
512 | json_pattern = '{' + ','.join(json_pattern) + '}' |
513 | @@ -307,8 +306,7 @@ |
514 | content_type, |
515 | created_at)) |
516 | container_list = '[' + ','.join(json_out) + ']' |
517 | - elif format == 'xml': |
518 | - out_content_type = 'application/xml' |
519 | + elif out_content_type.endswith('/xml'): |
520 | xml_output = [] |
521 | for (name, created_at, size, content_type, etag) in container_list: |
522 | # escape name and format date here |
523 | @@ -330,7 +328,6 @@ |
524 | else: |
525 | if not container_list: |
526 | return HTTPNoContent(request=req, headers=resp_headers) |
527 | - out_content_type = 'text/plain' |
528 | container_list = '\n'.join(r[0] for r in container_list) + '\n' |
529 | ret = Response(body=container_list, request=req, headers=resp_headers) |
530 | ret.content_type = out_content_type |
531 | |
532 | === modified file 'swift/proxy/server.py' |
533 | --- swift/proxy/server.py 2010-11-02 19:41:24 +0000 |
534 | +++ swift/proxy/server.py 2010-12-15 16:48:57 +0000 |
535 | @@ -644,7 +644,8 @@ |
536 | environ=req.environ, headers=req.headers) |
537 | new_req.content_length = source_resp.content_length |
538 | new_req.etag = source_resp.etag |
539 | - new_req.headers['X-Copy-From'] = source_header.split('/', 2)[2] |
540 | + # we no longer need the X-Copy-From header |
541 | + del new_req.headers['X-Copy-From'] |
542 | for k, v in source_resp.headers.items(): |
543 | if k.lower().startswith('x-object-meta-'): |
544 | new_req.headers[k] = v |
545 | @@ -767,8 +768,9 @@ |
546 | bodies.append('') |
547 | resp = self.best_response(req, statuses, reasons, bodies, 'Object PUT', |
548 | etag=etag) |
549 | - if 'x-copy-from' in req.headers: |
550 | - resp.headers['X-Copied-From'] = req.headers['x-copy-from'] |
551 | + if source_header: |
552 | + resp.headers['X-Copied-From'] = quote( |
553 | + source_header.split('/', 2)[2]) |
554 | for k, v in req.headers.items(): |
555 | if k.lower().startswith('x-object-meta-'): |
556 | resp.headers[k] = v |
557 | |
558 | === modified file 'swift/stats/log_processor.py' |
559 | --- swift/stats/log_processor.py 2010-10-08 22:20:43 +0000 |
560 | +++ swift/stats/log_processor.py 2010-12-15 16:48:57 +0000 |
561 | @@ -73,8 +73,7 @@ |
562 | self._internal_proxy = InternalProxy(proxy_server_conf, |
563 | self.logger, |
564 | retries=3) |
565 | - else: |
566 | - return self._internal_proxy |
567 | + return self._internal_proxy |
568 | |
569 | def process_one_file(self, plugin_name, account, container, object_name): |
570 | self.logger.info('Processing %s/%s/%s with plugin "%s"' % (account, |
571 | |
572 | === added file 'test/unit/common/middleware/test_domain_remap.py' |
573 | --- test/unit/common/middleware/test_domain_remap.py 1970-01-01 00:00:00 +0000 |
574 | +++ test/unit/common/middleware/test_domain_remap.py 2010-12-15 16:48:57 +0000 |
575 | @@ -0,0 +1,110 @@ |
576 | +# Copyright (c) 2010 OpenStack, LLC. |
577 | +# |
578 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
579 | +# you may not use this file except in compliance with the License. |
580 | +# You may obtain a copy of the License at |
581 | +# |
582 | +# http://www.apache.org/licenses/LICENSE-2.0 |
583 | +# |
584 | +# Unless required by applicable law or agreed to in writing, software |
585 | +# distributed under the License is distributed on an "AS IS" BASIS, |
586 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
587 | +# implied. |
588 | +# See the License for the specific language governing permissions and |
589 | +# limitations under the License. |
590 | + |
591 | +import unittest |
592 | + |
593 | +from webob import Request |
594 | + |
595 | +from swift.common.middleware import domain_remap |
596 | + |
597 | + |
598 | +class FakeApp(object): |
599 | + |
600 | + def __call__(self, env, start_response): |
601 | + return env['PATH_INFO'] |
602 | + |
603 | + |
604 | +def start_response(*args): |
605 | + pass |
606 | + |
607 | + |
608 | +class TestDomainRemap(unittest.TestCase): |
609 | + |
610 | + def setUp(self): |
611 | + self.app = domain_remap.DomainRemapMiddleware(FakeApp(), {}) |
612 | + |
613 | + def test_domain_remap_passthrough(self): |
614 | + req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'}, |
615 | + headers={'Host': 'example.com'}) |
616 | + resp = self.app(req.environ, start_response) |
617 | + self.assertEquals(resp, '/') |
618 | + req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'}, |
619 | + headers={'Host': 'example.com:8080'}) |
620 | + resp = self.app(req.environ, start_response) |
621 | + self.assertEquals(resp, '/') |
622 | + |
623 | + def test_domain_remap_account(self): |
624 | + req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'}, |
625 | + headers={'Host': 'a.example.com'}) |
626 | + resp = self.app(req.environ, start_response) |
627 | + self.assertEquals(resp, '/v1/a') |
628 | + req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'}, |
629 | + headers={'Host': 'a-uuid.example.com'}) |
630 | + resp = self.app(req.environ, start_response) |
631 | + self.assertEquals(resp, '/v1/a_uuid') |
632 | + |
633 | + def test_domain_remap_account_container(self): |
634 | + req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'}, |
635 | + headers={'Host': 'c.a.example.com'}) |
636 | + resp = self.app(req.environ, start_response) |
637 | + self.assertEquals(resp, '/v1/a/c') |
638 | + |
639 | + def test_domain_remap_extra_subdomains(self): |
640 | + req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'}, |
641 | + headers={'Host': 'x.y.c.a.example.com'}) |
642 | + resp = self.app(req.environ, start_response) |
643 | + self.assertEquals(resp, ['Bad domain in host header']) |
644 | + |
645 | + def test_domain_remap_account_with_path_root(self): |
646 | + req = Request.blank('/v1', environ={'REQUEST_METHOD': 'GET'}, |
647 | + headers={'Host': 'a.example.com'}) |
648 | + resp = self.app(req.environ, start_response) |
649 | + self.assertEquals(resp, '/v1/a') |
650 | + |
651 | + def test_domain_remap_account_container_with_path_root(self): |
652 | + req = Request.blank('/v1', environ={'REQUEST_METHOD': 'GET'}, |
653 | + headers={'Host': 'c.a.example.com'}) |
654 | + resp = self.app(req.environ, start_response) |
655 | + self.assertEquals(resp, '/v1/a/c') |
656 | + |
657 | + def test_domain_remap_account_container_with_path(self): |
658 | + req = Request.blank('/obj', environ={'REQUEST_METHOD': 'GET'}, |
659 | + headers={'Host': 'c.a.example.com'}) |
660 | + resp = self.app(req.environ, start_response) |
661 | + self.assertEquals(resp, '/v1/a/c/obj') |
662 | + |
663 | + def test_domain_remap_account_container_with_path_root_and_path(self): |
664 | + req = Request.blank('/v1/obj', environ={'REQUEST_METHOD': 'GET'}, |
665 | + headers={'Host': 'c.a.example.com'}) |
666 | + resp = self.app(req.environ, start_response) |
667 | + self.assertEquals(resp, '/v1/a/c/obj') |
668 | + |
669 | + def test_domain_remap_account_matching_ending_not_domain(self): |
670 | + req = Request.blank('/dontchange', environ={'REQUEST_METHOD': 'GET'}, |
671 | + headers={'Host': 'c.aexample.com'}) |
672 | + resp = self.app(req.environ, start_response) |
673 | + self.assertEquals(resp, '/dontchange') |
674 | + |
675 | + def test_domain_remap_configured_with_empty_storage_domain(self): |
676 | + self.app = domain_remap.DomainRemapMiddleware(FakeApp(), |
677 | + {'storage_domain': ''}) |
678 | + req = Request.blank('/test', environ={'REQUEST_METHOD': 'GET'}, |
679 | + headers={'Host': 'c.a.example.com'}) |
680 | + resp = self.app(req.environ, start_response) |
681 | + self.assertEquals(resp, '/test') |
682 | + |
683 | + |
684 | +if __name__ == '__main__': |
685 | + unittest.main() |
686 | |
687 | === added file 'test/unit/common/middleware/test_except.py' |
688 | --- test/unit/common/middleware/test_except.py 1970-01-01 00:00:00 +0000 |
689 | +++ test/unit/common/middleware/test_except.py 2010-12-15 16:48:57 +0000 |
690 | @@ -0,0 +1,49 @@ |
691 | +# Copyright (c) 2010 OpenStack, LLC. |
692 | +# |
693 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
694 | +# you may not use this file except in compliance with the License. |
695 | +# You may obtain a copy of the License at |
696 | +# |
697 | +# http://www.apache.org/licenses/LICENSE-2.0 |
698 | +# |
699 | +# Unless required by applicable law or agreed to in writing, software |
700 | +# distributed under the License is distributed on an "AS IS" BASIS, |
701 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
702 | +# implied. |
703 | +# See the License for the specific language governing permissions and |
704 | +# limitations under the License. |
705 | + |
706 | +import unittest |
707 | + |
708 | +from webob import Request |
709 | + |
710 | +from swift.common.middleware import catch_errors |
711 | + |
712 | +class FakeApp(object): |
713 | + def __init__(self, error=False): |
714 | + self.error = error |
715 | + |
716 | + def __call__(self, env, start_response): |
717 | + if self.error: |
718 | + raise Exception('augh!') |
719 | + return "FAKE APP" |
720 | + |
721 | +def start_response(*args): |
722 | + pass |
723 | + |
724 | +class TestCatchErrors(unittest.TestCase): |
725 | + |
726 | + def test_catcherrors_passthrough(self): |
727 | + app = catch_errors.CatchErrorMiddleware(FakeApp(), {}) |
728 | + req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'}) |
729 | + resp = app(req.environ, start_response) |
730 | + self.assertEquals(resp, 'FAKE APP') |
731 | + |
732 | + def test_catcherrors(self): |
733 | + app = catch_errors.CatchErrorMiddleware(FakeApp(True), {}) |
734 | + req = Request.blank('/', environ={'REQUEST_METHOD': 'GET'}) |
735 | + resp = app(req.environ, start_response) |
736 | + self.assertEquals(resp, ['An error occurred']) |
737 | + |
738 | +if __name__ == '__main__': |
739 | + unittest.main() |
740 | |
741 | === modified file 'test/unit/common/middleware/test_ratelimit.py' |
742 | --- test/unit/common/middleware/test_ratelimit.py 2010-11-02 19:41:24 +0000 |
743 | +++ test/unit/common/middleware/test_ratelimit.py 2010-12-15 16:48:57 +0000 |
744 | @@ -36,19 +36,14 @@ |
745 | return True |
746 | |
747 | def incr(self, key, delta=1, timeout=0): |
748 | - if delta < 0: |
749 | - raise "Cannot incr by a negative number" |
750 | - self.store[key] = int(self.store.setdefault(key, 0)) + delta |
751 | - return int(self.store[key]) |
752 | - |
753 | - def decr(self, key, delta=1, timeout=0): |
754 | - if delta < 0: |
755 | - raise "Cannot decr by a negative number" |
756 | - self.store[key] = int(self.store.setdefault(key, 0)) - delta |
757 | + self.store[key] = int(self.store.setdefault(key, 0)) + int(delta) |
758 | if self.store[key] < 0: |
759 | self.store[key] = 0 |
760 | return int(self.store[key]) |
761 | |
762 | + def decr(self, key, delta=1, timeout=0): |
763 | + return self.incr(key, delta=-delta, timeout=timeout) |
764 | + |
765 | @contextmanager |
766 | def soft_lock(self, key, timeout=0, retries=5): |
767 | yield True |
768 | @@ -98,9 +93,12 @@ |
769 | |
770 | |
771 | class FakeLogger(object): |
772 | + # a thread safe logger |
773 | |
774 | def error(self, msg): |
775 | - # a thread safe logger |
776 | + pass |
777 | + |
778 | + def info(self, msg): |
779 | pass |
780 | |
781 | |
782 | @@ -289,7 +287,7 @@ |
783 | the_498s = [t for t in all_results if t.startswith('Slow down')] |
784 | self.assertEquals(len(the_498s), 2) |
785 | time_took = time.time() - begin |
786 | - self.assert_(1.5 <= round(time_took,1) < 1.7, time_took) |
787 | + self.assert_(1.5 <= round(time_took, 1) < 1.7, time_took) |
788 | |
789 | def test_ratelimit_max_rate_multiple_acc(self): |
790 | num_calls = 4 |
791 | @@ -326,7 +324,7 @@ |
792 | thread.join() |
793 | time_took = time.time() - begin |
794 | # the all 15 threads still take 1.5 secs |
795 | - self.assert_(1.5 <= round(time_took,1) < 1.7) |
796 | + self.assert_(1.5 <= round(time_took, 1) < 1.7) |
797 | |
798 | def test_ratelimit_acc_vrs_container(self): |
799 | conf_dict = {'clock_accuracy': 1000, |
800 | |
801 | === modified file 'test/unit/common/test_memcached.py' |
802 | --- test/unit/common/test_memcached.py 2010-07-19 16:25:18 +0000 |
803 | +++ test/unit/common/test_memcached.py 2010-12-15 16:48:57 +0000 |
804 | @@ -98,6 +98,17 @@ |
805 | self.outbuf += str(val[2]) + '\r\n' |
806 | else: |
807 | self.outbuf += 'NOT_FOUND\r\n' |
808 | + elif parts[0].lower() == 'decr': |
809 | + if parts[1] in self.cache: |
810 | + val = list(self.cache[parts[1]]) |
811 | + if int(val[2]) - int(parts[2]) > 0: |
812 | + val[2] = str(int(val[2]) - int(parts[2])) |
813 | + else: |
814 | + val[2] = '0' |
815 | + self.cache[parts[1]] = val |
816 | + self.outbuf += str(val[2]) + '\r\n' |
817 | + else: |
818 | + self.outbuf += 'NOT_FOUND\r\n' |
819 | def readline(self): |
820 | if self.down: |
821 | raise Exception('mock is down') |
822 | @@ -151,6 +162,23 @@ |
823 | self.assertEquals(memcache_client.get('some_key'), '10') |
824 | memcache_client.incr('some_key', delta=1) |
825 | self.assertEquals(memcache_client.get('some_key'), '11') |
826 | + memcache_client.incr('some_key', delta=-5) |
827 | + self.assertEquals(memcache_client.get('some_key'), '6') |
828 | + memcache_client.incr('some_key', delta=-15) |
829 | + self.assertEquals(memcache_client.get('some_key'), '0') |
830 | + |
831 | + def test_decr(self): |
832 | + memcache_client = memcached.MemcacheRing(['1.2.3.4:11211']) |
833 | + mock = MockMemcached() |
834 | + memcache_client._client_cache['1.2.3.4:11211'] = [(mock, mock)] * 2 |
835 | + memcache_client.decr('some_key', delta=5) |
836 | + self.assertEquals(memcache_client.get('some_key'), '0') |
837 | + memcache_client.incr('some_key', delta=15) |
838 | + self.assertEquals(memcache_client.get('some_key'), '15') |
839 | + memcache_client.decr('some_key', delta=4) |
840 | + self.assertEquals(memcache_client.get('some_key'), '11') |
841 | + memcache_client.decr('some_key', delta=15) |
842 | + self.assertEquals(memcache_client.get('some_key'), '0') |
843 | |
844 | def test_retry(self): |
845 | logging.getLogger().addHandler(NullLoggingHandler()) |
846 | |
847 | === modified file 'test/unit/container/test_server.py' |
848 | --- test/unit/container/test_server.py 2010-09-09 05:37:27 +0000 |
849 | +++ test/unit/container/test_server.py 2010-12-15 16:48:57 +0000 |
850 | @@ -479,18 +479,20 @@ |
851 | resp = self.controller.GET(req) |
852 | self.assertEquals(resp.status_int, 412) |
853 | |
854 | - def test_GET_format(self): |
855 | + def test_GET_json(self): |
856 | # make a container |
857 | - req = Request.blank('/sda1/p/a/c', environ={'REQUEST_METHOD': 'PUT', |
858 | + req = Request.blank('/sda1/p/a/jsonc', environ={'REQUEST_METHOD': 'PUT', |
859 | 'HTTP_X_TIMESTAMP': '0'}) |
860 | resp = self.controller.PUT(req) |
861 | # test an empty container |
862 | - req = Request.blank('/sda1/p/a/c', environ={'REQUEST_METHOD': 'GET'}) |
863 | + req = Request.blank('/sda1/p/a/jsonc?format=json', |
864 | + environ={'REQUEST_METHOD': 'GET'}) |
865 | resp = self.controller.GET(req) |
866 | - self.assertEquals(resp.status_int, 204) |
867 | + self.assertEquals(resp.status_int, 200) |
868 | + self.assertEquals(eval(resp.body), []) |
869 | # fill the container |
870 | for i in range(3): |
871 | - req = Request.blank('/sda1/p/a/c/%s'%i, environ= |
872 | + req = Request.blank('/sda1/p/a/jsonc/%s'%i, environ= |
873 | {'REQUEST_METHOD': 'PUT', |
874 | 'HTTP_X_TIMESTAMP': '1', |
875 | 'HTTP_X_CONTENT_TYPE': 'text/plain', |
876 | @@ -514,8 +516,88 @@ |
877 | "bytes":0, |
878 | "content_type":"text/plain", |
879 | "last_modified":"1970-01-01T00:00:01"}] |
880 | + |
881 | + req = Request.blank('/sda1/p/a/jsonc?format=json', |
882 | + environ={'REQUEST_METHOD': 'GET'}) |
883 | + resp = self.controller.GET(req) |
884 | + self.assertEquals(resp.content_type, 'application/json') |
885 | + self.assertEquals(eval(resp.body), json_body) |
886 | + |
887 | + for accept in ('application/json', 'application/json;q=1.0,*/*;q=0.9', |
888 | + '*/*;q=0.9,application/json;q=1.0', 'application/*'): |
889 | + req = Request.blank('/sda1/p/a/jsonc', |
890 | + environ={'REQUEST_METHOD': 'GET'}) |
891 | + req.accept = accept |
892 | + resp = self.controller.GET(req) |
893 | + self.assertEquals(eval(resp.body), json_body, |
894 | + 'Invalid body for Accept: %s' % accept) |
895 | + self.assertEquals(resp.content_type, 'application/json', |
896 | + 'Invalid content_type for Accept: %s' % accept) |
897 | + |
898 | + def test_GET_plain(self): |
899 | + # make a container |
900 | + req = Request.blank('/sda1/p/a/plainc', environ={'REQUEST_METHOD': 'PUT', |
901 | + 'HTTP_X_TIMESTAMP': '0'}) |
902 | + resp = self.controller.PUT(req) |
903 | + # test an empty container |
904 | + req = Request.blank('/sda1/p/a/plainc', environ={'REQUEST_METHOD': 'GET'}) |
905 | + resp = self.controller.GET(req) |
906 | + self.assertEquals(resp.status_int, 204) |
907 | + # fill the container |
908 | + for i in range(3): |
909 | + req = Request.blank('/sda1/p/a/plainc/%s'%i, environ= |
910 | + {'REQUEST_METHOD': 'PUT', |
911 | + 'HTTP_X_TIMESTAMP': '1', |
912 | + 'HTTP_X_CONTENT_TYPE': 'text/plain', |
913 | + 'HTTP_X_ETAG': 'x', |
914 | + 'HTTP_X_SIZE': 0}) |
915 | + resp = self.controller.PUT(req) |
916 | + self.assertEquals(resp.status_int, 201) |
917 | + plain_body = '0\n1\n2\n' |
918 | + |
919 | + req = Request.blank('/sda1/p/a/plainc', |
920 | + environ={'REQUEST_METHOD': 'GET'}) |
921 | + resp = self.controller.GET(req) |
922 | + self.assertEquals(resp.content_type, 'text/plain') |
923 | + self.assertEquals(resp.body, plain_body) |
924 | + |
925 | + for accept in ('', 'text/plain', 'application/xml;q=0.8,*/*;q=0.9', |
926 | + '*/*;q=0.9,application/xml;q=0.8', '*/*', |
927 | + 'text/plain,application/xml'): |
928 | + req = Request.blank('/sda1/p/a/plainc', |
929 | + environ={'REQUEST_METHOD': 'GET'}) |
930 | + req.accept = accept |
931 | + resp = self.controller.GET(req) |
932 | + self.assertEquals(resp.body, plain_body, |
933 | + 'Invalid body for Accept: %s' % accept) |
934 | + self.assertEquals(resp.content_type, 'text/plain', |
935 | + 'Invalid content_type for Accept: %s' % accept) |
936 | + |
937 | + # test conflicting formats |
938 | + req = Request.blank('/sda1/p/a/plainc?format=plain', |
939 | + environ={'REQUEST_METHOD': 'GET'}) |
940 | + req.accept = 'application/json' |
941 | + resp = self.controller.GET(req) |
942 | + self.assertEquals(resp.content_type, 'text/plain') |
943 | + self.assertEquals(resp.body, plain_body) |
944 | + |
945 | + def test_GET_xml(self): |
946 | + # make a container |
947 | + req = Request.blank('/sda1/p/a/xmlc', environ={'REQUEST_METHOD': 'PUT', |
948 | + 'HTTP_X_TIMESTAMP': '0'}) |
949 | + resp = self.controller.PUT(req) |
950 | + # fill the container |
951 | + for i in range(3): |
952 | + req = Request.blank('/sda1/p/a/xmlc/%s'%i, environ= |
953 | + {'REQUEST_METHOD': 'PUT', |
954 | + 'HTTP_X_TIMESTAMP': '1', |
955 | + 'HTTP_X_CONTENT_TYPE': 'text/plain', |
956 | + 'HTTP_X_ETAG': 'x', |
957 | + 'HTTP_X_SIZE': 0}) |
958 | + resp = self.controller.PUT(req) |
959 | + self.assertEquals(resp.status_int, 201) |
960 | xml_body = '<?xml version="1.0" encoding="UTF-8"?>\n' \ |
961 | - '<container name="c">' \ |
962 | + '<container name="xmlc">' \ |
963 | '<object><name>0</name><hash>x</hash><bytes>0</bytes>' \ |
964 | '<content_type>text/plain</content_type>' \ |
965 | '<last_modified>1970-01-01T00:00:01' \ |
966 | @@ -529,46 +611,30 @@ |
967 | '<last_modified>1970-01-01T00:00:01' \ |
968 | '</last_modified></object>' \ |
969 | '</container>' |
970 | - plain_body = '0\n1\n2\n' |
971 | - req = Request.blank('/sda1/p/a/c?format=json', environ={'REQUEST_METHOD': 'GET'}) |
972 | - resp = self.controller.GET(req) |
973 | - self.assertEquals(resp.content_type, 'application/json') |
974 | - result = eval(resp.body) |
975 | - self.assertEquals(result, json_body) |
976 | - req = Request.blank('/sda1/p/a/c?format=xml', environ={'REQUEST_METHOD': 'GET'}) |
977 | + # tests |
978 | + req = Request.blank('/sda1/p/a/xmlc?format=xml', |
979 | + environ={'REQUEST_METHOD': 'GET'}) |
980 | resp = self.controller.GET(req) |
981 | self.assertEquals(resp.content_type, 'application/xml') |
982 | - result = resp.body |
983 | - self.assertEquals(result, xml_body) |
984 | - req = Request.blank('/sda1/p/a/c', environ={'REQUEST_METHOD': 'GET'}) |
985 | - req.accept = 'application/json' |
986 | - resp = self.controller.GET(req) |
987 | - self.assertEquals(resp.content_type, 'application/json') |
988 | - result = eval(resp.body) |
989 | - self.assertEquals(result, json_body) |
990 | - req = Request.blank('/sda1/p/a/c', environ={'REQUEST_METHOD': 'GET'}) |
991 | - req.accept = '*/*' |
992 | - resp = self.controller.GET(req) |
993 | - self.assertEquals(resp.content_type, 'text/plain') |
994 | - result = resp.body |
995 | - self.assertEquals(result, plain_body) |
996 | - req = Request.blank('/sda1/p/a/c', environ={'REQUEST_METHOD': 'GET'}) |
997 | - req.accept = 'application/*' |
998 | - resp = self.controller.GET(req) |
999 | - result = eval(resp.body) |
1000 | - self.assertEquals(result, json_body) |
1001 | - req = Request.blank('/sda1/p/a/c', environ={'REQUEST_METHOD': 'GET'}) |
1002 | - req.accept = 'application/xml' |
1003 | - resp = self.controller.GET(req) |
1004 | - result = resp.body |
1005 | - self.assertEquals(result, xml_body) |
1006 | - # test conflicting formats |
1007 | - req = Request.blank('/sda1/p/a/c?format=plain', environ={'REQUEST_METHOD': 'GET'}) |
1008 | - req.accept = 'application/json' |
1009 | - resp = self.controller.GET(req) |
1010 | - self.assertEquals(resp.content_type, 'text/plain') |
1011 | - result = resp.body |
1012 | - self.assertEquals(result, plain_body) |
1013 | + self.assertEquals(resp.body, xml_body) |
1014 | + |
1015 | + for xml_accept in ('application/xml', 'application/xml;q=1.0,*/*;q=0.9', |
1016 | + '*/*;q=0.9,application/xml;q=1.0', 'application/xml,text/xml'): |
1017 | + req = Request.blank('/sda1/p/a/xmlc', |
1018 | + environ={'REQUEST_METHOD': 'GET'}) |
1019 | + req.accept = xml_accept |
1020 | + resp = self.controller.GET(req) |
1021 | + self.assertEquals(resp.body, xml_body, |
1022 | + 'Invalid body for Accept: %s' % xml_accept) |
1023 | + self.assertEquals(resp.content_type, 'application/xml', |
1024 | + 'Invalid content_type for Accept: %s' % xml_accept) |
1025 | + |
1026 | + req = Request.blank('/sda1/p/a/xmlc', |
1027 | + environ={'REQUEST_METHOD': 'GET'}) |
1028 | + req.accept = 'text/xml' |
1029 | + resp = self.controller.GET(req) |
1030 | + self.assertEquals(resp.content_type, 'text/xml') |
1031 | + self.assertEquals(resp.body, xml_body) |
1032 | |
1033 | def test_GET_marker(self): |
1034 | # make a container |
1035 | |
1036 | === modified file 'test/unit/proxy/test_server.py' |
1037 | --- test/unit/proxy/test_server.py 2010-10-15 15:07:19 +0000 |
1038 | +++ test/unit/proxy/test_server.py 2010-12-15 16:48:57 +0000 |
1039 | @@ -1090,6 +1090,17 @@ |
1040 | self.assertEquals(resp.headers['x-copied-from'], 'c/o') |
1041 | |
1042 | req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, |
1043 | + headers={'Content-Length': '5', |
1044 | + 'X-Copy-From': 'c/o'}) |
1045 | + self.app.update_request(req) |
1046 | + proxy_server.http_connect = \ |
1047 | + fake_http_connect(200, 200, 200, 200, 200) |
1048 | + # acct cont acct cont objc |
1049 | + self.app.memcache.store = {} |
1050 | + resp = controller.PUT(req) |
1051 | + self.assertEquals(resp.status_int, 400) |
1052 | + |
1053 | + req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, |
1054 | headers={'Content-Length': '0', |
1055 | 'X-Copy-From': 'c/o/o2'}) |
1056 | req.account = 'a' |
1057 | @@ -1101,6 +1112,18 @@ |
1058 | self.assertEquals(resp.status_int, 201) |
1059 | self.assertEquals(resp.headers['x-copied-from'], 'c/o/o2') |
1060 | |
1061 | + req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, |
1062 | + headers={'Content-Length': '0', |
1063 | + 'X-Copy-From': 'c/o%20o2'}) |
1064 | + req.account = 'a' |
1065 | + proxy_server.http_connect = \ |
1066 | + fake_http_connect(200, 200, 200, 200, 200, 201, 201, 201) |
1067 | + # acct cont acct cont objc obj obj obj |
1068 | + self.app.memcache.store = {} |
1069 | + resp = controller.PUT(req) |
1070 | + self.assertEquals(resp.status_int, 201) |
1071 | + self.assertEquals(resp.headers['x-copied-from'], 'c/o%20o2') |
1072 | + |
1073 | # repeat tests with leading / |
1074 | req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'PUT'}, |
1075 | headers={'Content-Length': '0', |
1076 | |
1077 | === modified file 'test/unit/stats/test_log_processor.py' |
1078 | --- test/unit/stats/test_log_processor.py 2010-10-08 22:20:43 +0000 |
1079 | +++ test/unit/stats/test_log_processor.py 2010-12-15 16:48:57 +0000 |
1080 | @@ -14,9 +14,30 @@ |
1081 | # limitations under the License. |
1082 | |
1083 | import unittest |
1084 | +import os |
1085 | +from contextlib import contextmanager |
1086 | +from tempfile import NamedTemporaryFile |
1087 | |
1088 | +from swift.common import internal_proxy |
1089 | from swift.stats import log_processor |
1090 | |
1091 | + |
1092 | +@contextmanager |
1093 | +def tmpfile(content): |
1094 | + with NamedTemporaryFile('w', delete=False) as f: |
1095 | + file_name = f.name |
1096 | + f.write(str(content)) |
1097 | + try: |
1098 | + yield file_name |
1099 | + finally: |
1100 | + os.unlink(file_name) |
1101 | + |
1102 | + |
1103 | +class FakeUploadApp(object): |
1104 | + def __init__(self, *args, **kwargs): |
1105 | + pass |
1106 | + |
1107 | + |
1108 | class DumbLogger(object): |
1109 | def __getattr__(self, n): |
1110 | return self.foo |
1111 | @@ -63,6 +84,27 @@ |
1112 | } |
1113 | } |
1114 | |
1115 | + def test_lazy_load_internal_proxy(self): |
1116 | + # stub out internal_proxy's upload_app |
1117 | + internal_proxy.BaseApplication = FakeUploadApp |
1118 | + dummy_proxy_config = """[app:proxy-server] |
1119 | +use = egg:swift#proxy |
1120 | +""" |
1121 | + with tmpfile(dummy_proxy_config) as proxy_config_file: |
1122 | + conf = {'log-processor': { |
1123 | + 'proxy_server_conf': proxy_config_file, |
1124 | + } |
1125 | + } |
1126 | + p = log_processor.LogProcessor(conf, DumbLogger()) |
1127 | + self.assert_(isinstance(p._internal_proxy, |
1128 | + None.__class__)) |
1129 | + self.assert_(isinstance(p.internal_proxy, |
1130 | + log_processor.InternalProxy)) |
1131 | + self.assertEquals(p.internal_proxy, p._internal_proxy) |
1132 | + |
1133 | + # reset FakeUploadApp |
1134 | + reload(internal_proxy) |
1135 | + |
1136 | def test_access_log_line_parser(self): |
1137 | access_proxy_config = self.proxy_config.copy() |
1138 | access_proxy_config.update({ |