Merge lp:~notmyname/swift/stats_fix into lp:swift/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
Reviewer Review Type Date Requested Status
Swift Release Team Pending
Review via email: mp+43789@code.launchpad.net

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({

Subscribers

People subscribed via source and target branches