Merge lp:~kelvin.li/rnr-server/charm-supports into lp:rnr-server
- charm-supports
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Kelvin Li |
Approved revision: | 358 |
Merged at revision: | 302 |
Proposed branch: | lp:~kelvin.li/rnr-server/charm-supports |
Merge into: | lp:rnr-server |
Diff against target: |
621 lines (+264/-182) 12 files modified
Makefile (+49/-11) README (+2/-9) config-manager.txt (+3/-1) django_project/settings_base.py (+10/-3) django_project/settings_build.py (+7/-0) django_project/settings_devel.py (+1/-0) django_project/wsgi.py (+1/-1) src/core/tests/test_wsgi.py (+114/-0) src/core/wsgi.py (+75/-0) src/reviewsapp/templates/reviewsapp/overview.html (+2/-1) src/reviewsapp/tests/test_wsgi.py (+0/-104) src/reviewsapp/wsgi.py (+0/-52) |
To merge this branch: | bzr merge lp:~kelvin.li/rnr-server/charm-supports |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michael Nelson (community) | Approve | ||
Review via email: mp+260244@code.launchpad.net |
Commit message
- use the whitenoise dependency to manage the static assets
- add the conf-manager-
- some juju purpose improvement in MakeFile
Description of the change
use the whitenoise dependency to manage the static assets
add the conf-manager-
some juju purpose improvement in MakeFile
Michael Nelson (michael.nelson) wrote : | # |
Kelvin Li (kelvin.li) : | # |
Michael Nelson (michael.nelson) wrote : | # |
If you're still around when Fabien starts, ask him to check too, but +1 from me :) (and we'll be able to verify on staging that it works as expected)
06:06 < noodles> kelvin: so, for the current metal deployment, whitenoise's middleware won't even be hit correct? As the apache middle-end (?) will continue to serve the /assets/ directly?
06:15 < kelvin> noodles: yes, I think the /assets/ is serving by apache directly and rnr-project didn't involve the collectstatic by itself in production but by some other scripts which I don't know. If we want to make the whitenoise work without issue on metal, we have to point the STATIC_ROOT to the correct path.
06:22 < noodles> kelvin: I'd leave the metal service as is - if it's working as is with apache serving those files, great. FWIW, I don't think there are any other scripts - it doesn't need to collectstatic, as it's only serving the static for those two apps, which are both explicitly Alias'd in the apache config.
Michael Nelson (michael.nelson) : | # |
Fabián Ezequiel Gallina (fgallina) wrote : | # |
Added a few nitpicks and questions but otherwise this looks great!
Kelvin Li (kelvin.li) wrote : | # |
some updates according to the review comments.
Preview Diff
1 | === modified file 'Makefile' |
2 | --- Makefile 2015-05-06 06:56:11 +0000 |
3 | +++ Makefile 2015-06-17 04:45:47 +0000 |
4 | @@ -6,11 +6,10 @@ |
5 | HERE := $(shell pwd) |
6 | PYTHONPATH := ${HERE}/src:${HERE}/lib:${PYTHONPATH} |
7 | DJANGO_SETTINGS_MODULE ?= django_project.settings |
8 | +CONN_CHECK_CONFIG_PATH ?= /tmp/rnr_conn_check_config.yaml |
9 | |
10 | -VIRTUALENV = virtualenv |
11 | -VIRTUALENV_DIR ?= ./virtualenv |
12 | +VIRTUALENV_DIR ?= ${HERE}/virtualenv |
13 | VIRTUALENV_BIN := ${VIRTUALENV_DIR}/bin |
14 | -VIRTUALENV_ARGS ?= --distribute --clear --system-site-packages |
15 | |
16 | LOCAL_SETTINGS_DIR = ../local_config |
17 | LOCAL_SETTINGS_PATH := ${LOCAL_SETTINGS_DIR}/settings.py |
18 | @@ -19,13 +18,14 @@ |
19 | JUJU_REPO ?= ../.juju-repo |
20 | PROJECT_NAME = rnr-server |
21 | |
22 | -# Create archives in labelled directories (ie. r27/$(PROJECT_NAME).tbz2) |
23 | +# Create archives in labelled directories (ie. r27/ratings-and-review.tbz2) |
24 | TARBALL_BUILD_LABEL ?= r$(shell bzr revno) |
25 | -TARBALL_FILE_NAME = $(PROJECT_NAME).tbz2 |
26 | +TARBALL_FILE_NAME = ratings-and-reviews.tbz2 |
27 | TARBALL_BUILDS_DIR ?= $(JUJU_REPO)/builds |
28 | TARBALL_BUILD_DIR = $(TARBALL_BUILDS_DIR)/$(TARBALL_BUILD_LABEL) |
29 | TARBALL_BUILD_PATH = $(TARBALL_BUILD_DIR)/$(TARBALL_FILE_NAME) |
30 | |
31 | +DJANGO_MANAGE := $(PYTHON) $(CURDIR)/django_project/manage.py |
32 | |
33 | export PYTHONPATH |
34 | export DJANGO_SETTINGS_MODULE |
35 | @@ -40,10 +40,12 @@ |
36 | |
37 | virtualenv: |
38 | @if test ! -e $(VIRTUALENV_DIR); ! -e $(VIRTUALENV_BIN); then \ |
39 | - $(VIRTUALENV) $(VIRTUALENV_DIR) $(VIRTUALENV_ARGS); \ |
40 | + echo 'virtualenv not found, creating new one...'; \ |
41 | + virtualenv --distribute --clear --system-site-packages $(VIRTUALENV_DIR); \ |
42 | fi; |
43 | |
44 | clean-virtualenv: |
45 | + echo 'cleaning virtualenv...' |
46 | rm -rf $(VIRTUALENV_DIR) |
47 | |
48 | install-virtualenv-requirements: virtualenv |
49 | @@ -58,6 +60,9 @@ |
50 | sanitize-sourcedeps: |
51 | sed -e '1,/# Dependencies/d;s/^rnr-server/./g;s/bazaar.isd/bazaar.launchpad.net/g' config-manager.txt > $(CM_TMP_CONFIG) |
52 | |
53 | +sanitize-sourcedeps-for-juju: |
54 | + sed -e '1,/# juju deployment/d;s/^rnr-server/./g;s/bazaar.isd/bazaar.launchpad.net/g' config-manager.txt > $(CM_TMP_CONFIG) |
55 | + |
56 | build: |
57 | $(CM) update $(CM_TMP_CONFIG) |
58 | |
59 | @@ -72,19 +77,52 @@ |
60 | |
61 | sourcedeps: fetch-sourcedeps install-virtualenv-requirements |
62 | |
63 | -bootstrap: local-config sourcedeps |
64 | - |
65 | +bootstrap: local-config sourcedeps |
66 | + |
67 | +bootstrap-juju: local-config sourcedeps-juju collectstatic |
68 | + |
69 | +clean: |
70 | + @echo "Cleaning ..." |
71 | + rm -rf $(VIRTUALENV_DIR) |
72 | + rm -rf branches/* |
73 | + rm -rf staticfiles |
74 | + rm -f lib/versioninfo.py |
75 | + find -name '*.pyc' -delete |
76 | + find -name '*.~*' -delete |
77 | + |
78 | +conn-check: |
79 | + @echo "Generating conn-check config from Django settings..." |
80 | + @sudo -u $$(ls -ld $(CURDIR) | awk '{print $$3}') PYTHONPATH=$(PYTHONPATH) \ |
81 | + python scripts/settings-to-conncheck.py -m $(DJANGO_SETTINGS_MODULE) -f $(CONN_CHECK_CONFIG_PATH) |
82 | + @echo "Running conn-check..." |
83 | + conn-check $(CONN_CHECK_CONFIG_PATH) |
84 | + |
85 | + |
86 | +############### |
87 | +# for juju |
88 | +branches/last_build_for_juju: config-manager.txt |
89 | + $(MAKE) sanitize-sourcedeps-for-juju |
90 | + $(MAKE) CONFIGMANAGER=$(CM_TMP_CONFIG) build |
91 | + @rm $(CM_TMP_CONFIG) |
92 | + touch $@ |
93 | + |
94 | +fetch-sourcedeps-juju: branches/last_build_for_juju version |
95 | + |
96 | +sourcedeps-juju: fetch-sourcedeps-juju |
97 | + |
98 | +collectstatic: |
99 | + @DJANGO_SETTINGS_MODULE=django_project.settings_build $(DJANGO_MANAGE) collectstatic --noinput |
100 | |
101 | #Tarball |
102 | $(TARBALL_BUILD_DIR): |
103 | @mkdir -p $(TARBALL_BUILD_DIR) |
104 | |
105 | -$(TARBALL_BUILD_PATH): $(TARBALL_BUILD_DIR) |
106 | +$(TARBALL_BUILD_PATH): $(TARBALL_BUILD_DIR) tarball_exclude.txt collectstatic |
107 | @echo "creating deployment tarball at $(TARBALL_BUILD_PATH)" |
108 | @tar -cj --exclude-from tarball_exclude.txt -f $(TARBALL_BUILD_PATH) ./ |
109 | |
110 | build-tarball: $(TARBALL_BUILD_PATH) |
111 | |
112 | |
113 | -.PHONY: local-config sanitize-sourcedeps clean-virtualenv virtualenv \ |
114 | - install-virtualenv-requirements sourcedeps bootstrap prod-setting |
115 | +.PHONY: local-config sanitize-sourcedeps clean-virtualenv \ |
116 | + install-virtualenv-requirements sourcedeps bootstrap clean collectstatic |
117 | |
118 | === modified file 'README' |
119 | --- README 2014-11-04 19:24:55 +0000 |
120 | +++ README 2015-06-17 04:45:47 +0000 |
121 | @@ -5,17 +5,10 @@ |
122 | Getting started |
123 | =============== |
124 | |
125 | -0. Enable the dependencies PPA |
126 | -:: |
127 | - |
128 | - sudo apt-get install python-software-properties |
129 | - sudo add-apt-repository ppa:ubuntuone/rnr-dependencies |
130 | - sudo apt-get update |
131 | - |
132 | 1. Install system dependencies |
133 | :: |
134 | - |
135 | - sudo apt-get install rnr-server-{developer-,}dependencies |
136 | + sudo xargs apt-get install -y < dependencies.txt |
137 | + sudo xargs apt-get install -y < dependencies-devel.txt (only used for dev env) |
138 | |
139 | 2. Get the code |
140 | :: |
141 | |
142 | === modified file 'config-manager.txt' |
143 | --- config-manager.txt 2015-04-30 06:18:54 +0000 |
144 | +++ config-manager.txt 2015-06-17 04:45:47 +0000 |
145 | @@ -9,8 +9,9 @@ |
146 | rnr-server bzr+ssh://bazaar.isd/~rnr-developers/rnr-server/trunk |
147 | # Config branch |
148 | rnr-server/branches/config bzr+ssh://bazaar.isd/~canonical-ca-hackers/ca-configs/rnr-config;revno=103 |
149 | +# juju deployment |
150 | # Web themes |
151 | -rnr-server/branches/django-web-themes bzr+ssh://bazaar.isd/~canonical-isd-hackers/web-themes/django;revno=155 |
152 | +rnr-server/branches/django-web-themes bzr+ssh://bazaar.isd/~canonical-isd-hackers/web-themes/django;revno=171 |
153 | |
154 | # Dependencies |
155 | rnr-server/branches/amqp bzr+ssh://bazaar.isd/~ubuntuone-pqm-team/amqp/stable;revno=v1.4.5 |
156 | @@ -36,3 +37,4 @@ |
157 | rnr-server/branches/ssoclient bzr+ssh://bazaar.isd/~canonical-isd-hackers/canonical-identity-provider/ssoclient;revno=18 |
158 | rnr-server/branches/txstatsd bzr+ssh://bazaar.isd/~txstatsd-dev/txstatsd/trunk;revno=104 |
159 | rnr-server/branches/conn-check-config bzr+ssh://bazaar.isd/~ubuntuone-hackers/conn-check/configs;revno=23 |
160 | +rnr-server/branches/whitenoise bzr+ssh://bazaar.isd/~ubuntuone-pqm-team/whitenoise/stable |
161 | |
162 | === modified file 'django_project/settings_base.py' |
163 | --- django_project/settings_base.py 2014-10-27 14:43:27 +0000 |
164 | +++ django_project/settings_base.py 2015-06-17 04:45:47 +0000 |
165 | @@ -132,6 +132,7 @@ |
166 | 'django.contrib.contenttypes', |
167 | 'django.contrib.sessions', |
168 | 'django.contrib.sites', |
169 | + 'django.contrib.staticfiles', |
170 | 'django_openid_auth', |
171 | 'core', |
172 | 'reviewsapp', |
173 | @@ -377,14 +378,20 @@ |
174 | 'token_secret': '', |
175 | 'token_key': '', |
176 | } |
177 | -STATICFILES_DIRS = [] |
178 | +STATICFILES_DIRS = [ |
179 | + ('reviewsapp', os.path.join(SRC_DIR, 'reviewsapp/media')), |
180 | + ('light', os.path.join(BASE_DIR, 'branches/django-web-themes/light')), |
181 | + ('light', os.path.join(BASE_DIR, 'branches/django-web-themes/assets/light')), |
182 | +] |
183 | STATICFILES_FINDERS = [ |
184 | 'django.contrib.staticfiles.finders.FileSystemFinder', |
185 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder', |
186 | ] |
187 | STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.StaticFilesStorage' |
188 | -STATIC_ROOT = '' |
189 | -STATIC_URL = '/static/' |
190 | +#TODO:will open following storage once switch to juju env |
191 | +#STATICFILES_STORAGE = 'whitenoise.django.GzipManifestStaticFilesStorage' |
192 | +STATIC_ROOT = 'staticfiles' |
193 | +STATIC_URL = '/assets/' |
194 | TEMPLATE_CONTEXT_PROCESSORS = [ |
195 | 'django.contrib.auth.context_processors.auth', |
196 | 'django.core.context_processors.debug', |
197 | |
198 | === added file 'django_project/settings_build.py' |
199 | --- django_project/settings_build.py 1970-01-01 00:00:00 +0000 |
200 | +++ django_project/settings_build.py 2015-06-17 04:45:47 +0000 |
201 | @@ -0,0 +1,7 @@ |
202 | +import os |
203 | +os.environ.setdefault('RNR_LOGS_DIR', '/tmp') |
204 | +from django_project.settings_base import * # noqa |
205 | +SECRET_KEY = 'build key' |
206 | + |
207 | +LOGGING['handlers']['file']['filename'] = os.path.join( |
208 | + LOGS_DIR, 'rnr_hudson.log') |
209 | |
210 | === modified file 'django_project/settings_devel.py' |
211 | --- django_project/settings_devel.py 2014-10-27 16:03:35 +0000 |
212 | +++ django_project/settings_devel.py 2015-06-17 04:45:47 +0000 |
213 | @@ -18,3 +18,4 @@ |
214 | TEMPLATE_DIRS = [ |
215 | os.path.join(BASE_DIR, 'django_project', 'templates'), |
216 | ] |
217 | +STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.StaticFilesStorage' |
218 | |
219 | === modified file 'django_project/wsgi.py' |
220 | --- django_project/wsgi.py 2014-08-07 16:02:58 +0000 |
221 | +++ django_project/wsgi.py 2015-06-17 04:45:47 +0000 |
222 | @@ -32,7 +32,7 @@ |
223 | default_id = ''.join(x for x in platform.node() if x.isalpha()) |
224 | appserver_id = getattr(settings, 'APPSERVER_ID', default_id) |
225 | |
226 | -from reviewsapp.wsgi import make_app |
227 | +from core.wsgi import make_app |
228 | app = make_app() |
229 | |
230 | |
231 | |
232 | === added symlink 'lib/conn_check_configs' |
233 | === target is u'../branches/conn-check-config/conn_check_configs' |
234 | === added symlink 'lib/whitenoise' |
235 | === target is u'../branches/whitenoise/whitenoise' |
236 | === added file 'src/core/tests/test_wsgi.py' |
237 | --- src/core/tests/test_wsgi.py 1970-01-01 00:00:00 +0000 |
238 | +++ src/core/tests/test_wsgi.py 2015-06-17 04:45:47 +0000 |
239 | @@ -0,0 +1,114 @@ |
240 | +import bson |
241 | +import os |
242 | + |
243 | +from tempfile import mkdtemp |
244 | +from shutil import rmtree |
245 | + |
246 | +from django.test.utils import override_settings |
247 | +from django.utils.http import urlencode |
248 | +from mock import Mock |
249 | + |
250 | +from core.wsgi import make_app, BzrRevnoMiddleware |
251 | +from core.tests.factory import TestCaseWithFactory |
252 | + |
253 | + |
254 | +@override_settings(ENABLE_ARTIFICIAL_OOPS=True) |
255 | +class WSGIDispatchTestCase(TestCaseWithFactory): |
256 | + |
257 | + def setUp(self): |
258 | + """Create a temporary directory for oopses.""" |
259 | + self.oops_dir = mkdtemp() |
260 | + self.addCleanup(rmtree, self.oops_dir) |
261 | + |
262 | + self.environ = { |
263 | + 'PATH_INFO': '/', |
264 | + 'QUERY_STRING': '', |
265 | + 'REMOTE_ADDR': '127.0.0.1', |
266 | + 'REQUEST_METHOD': 'GET', |
267 | + 'SCRIPT_NAME': '', |
268 | + 'SERVER_NAME': 'testserver', |
269 | + 'SERVER_PORT': '80', |
270 | + 'SERVER_PROTOCOL': 'HTTP/1.1', |
271 | + 'wsgi.version': (1, 0), |
272 | + 'wsgi.url_scheme': 'http', |
273 | + 'wsgi.multiprocess': True, |
274 | + 'wsgi.multithread': False, |
275 | + 'wsgi.run_once': False, |
276 | + 'wsgi.input': '', |
277 | + 'oops.report': {}, |
278 | + } |
279 | + super(WSGIDispatchTestCase, self).setUp() |
280 | + |
281 | + def request_500_exception(self, data={}): |
282 | + """Helper for creating a 500 exception.""" |
283 | + self.environ['PATH_INFO'] = '/reviews/die/' |
284 | + if data != {}: |
285 | + self.environ['QUERY_STRING'] = urlencode(data, doseq=True) |
286 | + |
287 | + response = self.request_wsgi_response(self.environ) |
288 | + return response |
289 | + |
290 | + def request_wsgi_response(self, environ): |
291 | + start_response = Mock() |
292 | + oops_config = { |
293 | + 'publishers': [{ |
294 | + 'inherit_id': True, |
295 | + 'error_dir': self.oops_dir, |
296 | + 'type': 'datedir', |
297 | + 'new_only': False, |
298 | + }]} |
299 | + with override_settings(OOPSES=oops_config): |
300 | + app = make_app() |
301 | + response = app(environ, start_response).next() |
302 | + return response |
303 | + |
304 | + def read_oops_from_disk(self): |
305 | + # Helper to read in the oops written to disk. |
306 | + # There is only one date directory in the oops directory. |
307 | + dated_dirs = os.listdir(self.oops_dir) |
308 | + self.assertEqual(1, len(dated_dirs)) |
309 | + |
310 | + # There is only one oops. |
311 | + todays_oops_path = dated_dirs[0] |
312 | + oops_list = os.listdir(self.oops_dir + '/' + todays_oops_path) |
313 | + self.assertEqual(1, len(oops_list)) |
314 | + |
315 | + oops_path = self.oops_dir + '/' + todays_oops_path + '/' + oops_list[0] |
316 | + with open(oops_path, "r") as oops_file: |
317 | + oops_file_contents = oops_file.read() |
318 | + return bson.decode_all(oops_file_contents) |
319 | + |
320 | + def test_oopsid_in_500_response(self): |
321 | + # The rendered response for a 500 exception includes the oops id. |
322 | + response = self.request_500_exception() |
323 | + |
324 | + self.assertTrue(self.environ['oops.report']['id'] in response) |
325 | + |
326 | + def test_traceback_in_500_oops_on_disk(self): |
327 | + # A request resulting in a 500 writes the oops to disk. |
328 | + self.request_500_exception() |
329 | + |
330 | + oops = self.read_oops_from_disk()[0] |
331 | + self.assertEqual(oops.get('type'), 'ZeroDivisionError') |
332 | + self.assertIn('tb_text', oops) |
333 | + self.assertTrue(oops['tb_text'].startswith(' File ')) |
334 | + |
335 | + def test_delay_triggers_an_oops(self): |
336 | + self.environ['PATH_INFO'] = '/reviews/delay/0.11/' |
337 | + with override_settings(SOFT_TIMEOUT_MILLISECONDS=1): |
338 | + self.request_wsgi_response(self.environ) |
339 | + |
340 | + oops_content = self.read_oops_from_disk()[0] |
341 | + self.assertEqual('SoftRequestTimeout', oops_content['type']) |
342 | + expected = u'Start_response over timeout 1.' |
343 | + self.assertEqual(expected, oops_content['value']) |
344 | + |
345 | + def test_bzr_header(self): |
346 | + BzrRevnoMiddleware.REVNO_HEADER = ('HEADER', 'VALUE') |
347 | + app = make_app() |
348 | + _start_response = Mock('/',) |
349 | + app(self.environ, _start_response).next() |
350 | + start_response_args = _start_response.mock_calls[0][1] |
351 | + headers = start_response_args[1] |
352 | + self.assertIn(('HEADER', 'VALUE'), headers) |
353 | + |
354 | |
355 | === added file 'src/core/wsgi.py' |
356 | --- src/core/wsgi.py 1970-01-01 00:00:00 +0000 |
357 | +++ src/core/wsgi.py 2015-06-17 04:45:47 +0000 |
358 | @@ -0,0 +1,75 @@ |
359 | +import oops_dictconfig |
360 | +import oops_wsgi |
361 | +import uuid |
362 | + |
363 | +from django.conf import settings |
364 | +from django.core.handlers import wsgi |
365 | + |
366 | +from versioninfo import version_info |
367 | + |
368 | +from whitenoise.django import DjangoWhiteNoise |
369 | + |
370 | + |
371 | +class EagerOOPSWSGIHandler(wsgi.WSGIHandler): |
372 | + """Pre-generates an oops id on exception.""" |
373 | + def handle_uncaught_exception(self, request, resolver, exc_info): |
374 | + # This will pass the exception information back to oops. It |
375 | + # should not be necessary once |
376 | + # https://code.djangoproject.com/ticket/16674 has been merged. |
377 | + if 'oops.context' in request.environ: |
378 | + request.environ['oops.context']['exc_info'] = exc_info |
379 | + # Generate OOPS id early so that we can render the error page right |
380 | + # away and not depend on passing thread locals around. |
381 | + if 'oops.report' in request.environ: |
382 | + unique_id = uuid.uuid4().hex |
383 | + request.environ['oops.report']['id'] = "OOPS-%s" % unique_id |
384 | + |
385 | + return super(EagerOOPSWSGIHandler, self).handle_uncaught_exception( |
386 | + request, resolver, exc_info) |
387 | + |
388 | + def __call__(self, environ, start_response): |
389 | + def start_response_with_exc_info(status, headers, exc_info=None): |
390 | + """Custom start_response callback for wsgi.""" |
391 | + # This will pass the exception information back to oops_wgi. It |
392 | + # should not be necessary once |
393 | + # https://code.djangoproject.com/ticket/16674 has been merged. |
394 | + if exc_info is None: |
395 | + exc_info = environ['oops.context'].get('exc_info', None) |
396 | + return start_response(status, headers, exc_info) |
397 | + return super(EagerOOPSWSGIHandler, self).__call__( |
398 | + environ, start_response_with_exc_info) |
399 | + |
400 | + |
401 | +class BzrRevnoMiddleware(object): |
402 | + REVNO_HEADER = ('X-Bzr-Revision-Number', version_info['revno']) |
403 | + |
404 | + def __init__(self, app): |
405 | + self.app = app |
406 | + |
407 | + def __call__(self, environ, start_response): |
408 | + |
409 | + def custom_start_response(status, headers, exc_info=None): |
410 | + headers.append(self.REVNO_HEADER) |
411 | + return start_response(status, headers, exc_info) |
412 | + |
413 | + return self.app(environ, custom_start_response) |
414 | + |
415 | + |
416 | +def make_app(): |
417 | + """Encapsulate our RNR handler in an OOPS app for error reporting.""" |
418 | + |
419 | + non_oops_app = EagerOOPSWSGIHandler() |
420 | + |
421 | + non_oops_app = BzrRevnoMiddleware(non_oops_app) |
422 | + |
423 | + non_oops_app = DjangoWhiteNoise(non_oops_app) |
424 | + |
425 | + config = oops_dictconfig.config_from_dict(settings.OOPSES) |
426 | + oops_wsgi.install_hooks(config) |
427 | + soft_timeout = settings.SOFT_TIMEOUT_MILLISECONDS |
428 | + oops_app = oops_wsgi.make_app(non_oops_app, config, oops_on_status=['500'], |
429 | + soft_start_timeout=soft_timeout) |
430 | + return oops_app |
431 | + |
432 | + |
433 | +application = make_app() |
434 | |
435 | === modified file 'src/reviewsapp/templates/reviewsapp/overview.html' |
436 | --- src/reviewsapp/templates/reviewsapp/overview.html 2013-04-10 13:15:09 +0000 |
437 | +++ src/reviewsapp/templates/reviewsapp/overview.html 2015-06-17 04:45:47 +0000 |
438 | @@ -1,5 +1,6 @@ |
439 | {% extends "light/index.1col.html" %} |
440 | {% load i18n %} |
441 | +{% load static from staticfiles %} |
442 | |
443 | {% block title %} |
444 | {% trans "Ubuntu Software Center Reviews" %} |
445 | @@ -10,7 +11,7 @@ |
446 | {% endblock %} |
447 | |
448 | {% block content %} |
449 | -<img style="float:right" src="/assets/reviewsapp/images/softwarecenter_icon_150.png" alt="Ubuntu Software Center" /> |
450 | +<img style="float:right" src="{% static 'reviewsapp/images/softwarecenter_icon_150.png' %}" alt="Ubuntu Software Center" /> |
451 | |
452 | <p class="introduction">{% blocktrans %}You can now review your favourite software in Ubuntu directly from Ubuntu Software Center.{% endblocktrans %}</p> |
453 | |
454 | |
455 | === removed file 'src/reviewsapp/tests/test_wsgi.py' |
456 | --- src/reviewsapp/tests/test_wsgi.py 2014-09-22 16:58:33 +0000 |
457 | +++ src/reviewsapp/tests/test_wsgi.py 1970-01-01 00:00:00 +0000 |
458 | @@ -1,104 +0,0 @@ |
459 | -import bson |
460 | -import os |
461 | - |
462 | -from tempfile import mkdtemp |
463 | -from shutil import rmtree |
464 | - |
465 | -from django.test.utils import override_settings |
466 | -from django.utils.http import urlencode |
467 | -from mock import Mock |
468 | - |
469 | -from reviewsapp.wsgi import make_app |
470 | -from reviewsapp.tests.factory import TestCaseWithFactory |
471 | - |
472 | - |
473 | -@override_settings(ENABLE_ARTIFICIAL_OOPS=True) |
474 | -class WSGIDispatchTestCase(TestCaseWithFactory): |
475 | - |
476 | - def setUp(self): |
477 | - super(WSGIDispatchTestCase, self).setUp() |
478 | - """Create a temporary directory for oopses.""" |
479 | - self.oops_dir = mkdtemp() |
480 | - self.addCleanup(rmtree, self.oops_dir) |
481 | - |
482 | - self.environ = { |
483 | - 'PATH_INFO': '/', |
484 | - 'QUERY_STRING': '', |
485 | - 'REMOTE_ADDR': '127.0.0.1', |
486 | - 'REQUEST_METHOD': 'GET', |
487 | - 'SCRIPT_NAME': '', |
488 | - 'SERVER_NAME': 'testserver', |
489 | - 'SERVER_PORT': '80', |
490 | - 'SERVER_PROTOCOL': 'HTTP/1.1', |
491 | - 'wsgi.version': (1, 0), |
492 | - 'wsgi.url_scheme': 'http', |
493 | - 'wsgi.multiprocess': True, |
494 | - 'wsgi.multithread': False, |
495 | - 'wsgi.run_once': False, |
496 | - 'wsgi.input': '', |
497 | - 'oops.report': {}, |
498 | - } |
499 | - |
500 | - def request_500_exception(self, data={}): |
501 | - """Helper for creating a 500 exception.""" |
502 | - self.environ['PATH_INFO'] = '/reviews/die/' |
503 | - if data != {}: |
504 | - self.environ['QUERY_STRING'] = urlencode(data, doseq=True) |
505 | - |
506 | - response = self.request_wsgi_response(self.environ) |
507 | - return response |
508 | - |
509 | - def request_wsgi_response(self, environ): |
510 | - start_response = Mock() |
511 | - oops_config = { |
512 | - 'publishers': [{ |
513 | - 'inherit_id': True, |
514 | - 'error_dir': self.oops_dir, |
515 | - 'type': 'datedir', |
516 | - 'new_only': False, |
517 | - }]} |
518 | - with override_settings(OOPSES=oops_config): |
519 | - app = make_app() |
520 | - response = app(environ, start_response).next() |
521 | - return response |
522 | - |
523 | - def read_oops_from_disk(self): |
524 | - # Helper to read in the oops written to disk. |
525 | - # There is only one date directory in the oops directory. |
526 | - dated_dirs = os.listdir(self.oops_dir) |
527 | - self.assertEqual(1, len(dated_dirs)) |
528 | - |
529 | - # There is only one oops. |
530 | - todays_oops_path = dated_dirs[0] |
531 | - oops_list = os.listdir(self.oops_dir + '/' + todays_oops_path) |
532 | - self.assertEqual(1, len(oops_list)) |
533 | - |
534 | - oops_path = self.oops_dir + '/' + todays_oops_path + '/' + oops_list[0] |
535 | - with open(oops_path, "r") as oops_file: |
536 | - oops_file_contents = oops_file.read() |
537 | - return bson.decode_all(oops_file_contents) |
538 | - |
539 | - def test_oopsid_in_500_response(self): |
540 | - # The rendered response for a 500 exception includes the oops id. |
541 | - response = self.request_500_exception() |
542 | - |
543 | - self.assertTrue(self.environ['oops.report']['id'] in response) |
544 | - |
545 | - def test_traceback_in_500_oops_on_disk(self): |
546 | - # A request resulting in a 500 writes the oops to disk. |
547 | - self.request_500_exception() |
548 | - |
549 | - oops = self.read_oops_from_disk()[0] |
550 | - self.assertEqual(oops.get('type'), 'ZeroDivisionError') |
551 | - self.assertIn('tb_text', oops) |
552 | - self.assertTrue(oops['tb_text'].startswith(' File ')) |
553 | - |
554 | - def test_delay_triggers_an_oops(self): |
555 | - self.environ['PATH_INFO'] = '/reviews/delay/0.11/' |
556 | - with override_settings(SOFT_TIMEOUT_MILLISECONDS=1): |
557 | - self.request_wsgi_response(self.environ) |
558 | - |
559 | - oops_content = self.read_oops_from_disk()[0] |
560 | - self.assertEqual('SoftRequestTimeout', oops_content['type']) |
561 | - expected = u'Start_response over timeout 1.' |
562 | - self.assertEqual(expected, oops_content['value']) |
563 | |
564 | === removed file 'src/reviewsapp/wsgi.py' |
565 | --- src/reviewsapp/wsgi.py 2014-01-03 11:11:18 +0000 |
566 | +++ src/reviewsapp/wsgi.py 1970-01-01 00:00:00 +0000 |
567 | @@ -1,52 +0,0 @@ |
568 | -import oops_dictconfig |
569 | -import oops_wsgi |
570 | -import uuid |
571 | - |
572 | -from django.conf import settings |
573 | -from django.core.handlers import wsgi |
574 | - |
575 | - |
576 | -class EagerOOPSWSGIHandler(wsgi.WSGIHandler): |
577 | - """Pre-generates an oops id on exception.""" |
578 | - def handle_uncaught_exception(self, request, resolver, exc_info): |
579 | - # This will pass the exception information back to oops. It |
580 | - # should not be necessary once |
581 | - # https://code.djangoproject.com/ticket/16674 has been merged. |
582 | - if 'oops.context' in request.environ: |
583 | - request.environ['oops.context']['exc_info'] = exc_info |
584 | - # Generate OOPS id early so that we can render the error page right |
585 | - # away and not depend on passing thread locals around. |
586 | - if 'oops.report' in request.environ: |
587 | - unique_id = uuid.uuid4().hex |
588 | - request.environ['oops.report']['id'] = "OOPS-%s" % unique_id |
589 | - |
590 | - return super(EagerOOPSWSGIHandler, self).handle_uncaught_exception( |
591 | - request, resolver, exc_info) |
592 | - |
593 | - def __call__(self, environ, start_response): |
594 | - def start_response_with_exc_info(status, headers, exc_info=None): |
595 | - """Custom start_response callback for wsgi.""" |
596 | - # This will pass the exception information back to oops_wgi. It |
597 | - # should not be necessary once |
598 | - # https://code.djangoproject.com/ticket/16674 has been merged. |
599 | - if exc_info is None: |
600 | - exc_info = environ['oops.context'].get('exc_info', None) |
601 | - return start_response(status, headers, exc_info) |
602 | - return super(EagerOOPSWSGIHandler, self).__call__( |
603 | - environ, start_response_with_exc_info) |
604 | - |
605 | - |
606 | -def make_app(): |
607 | - """Encapsulate our RNR handler in an OOPS app for error reporting.""" |
608 | - |
609 | - non_oops_app = EagerOOPSWSGIHandler() |
610 | - |
611 | - config = oops_dictconfig.config_from_dict(settings.OOPSES) |
612 | - oops_wsgi.install_hooks(config) |
613 | - soft_timeout = settings.SOFT_TIMEOUT_MILLISECONDS |
614 | - oops_app = oops_wsgi.make_app(non_oops_app, config, oops_on_status=['500'], |
615 | - soft_start_timeout=soft_timeout) |
616 | - return oops_app |
617 | - |
618 | - |
619 | -application = make_app() |
620 | |
621 | === removed directory 'virtualenv' |
Looks good. I've got a few comments below, but I think it'd be great to get Fabien's review too, as he knows more about rnr (and it's current deployment setup) I think.
Great stuff!