Merge lp:~kelvin.li/rnr-server/charm-supports into lp:rnr-server

Proposed by Kelvin Li
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
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-juju.txt for rnr charm and keep old bootstrap work without issue
- some juju purpose improvement in MakeFile

Description of the change

use the whitenoise dependency to manage the static assets
add the conf-manager-juju.txt for rnr charm and keep old bootstrap work without issue
some juju purpose improvement in MakeFile

To post a comment you must log in.
Revision history for this message
Michael Nelson (michael.nelson) wrote :

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!

350. By Kelvin Li

turn the SERVE_STATIC_MEDIA off

Revision history for this message
Kelvin Li (kelvin.li) :
Revision history for this message
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.

review: Approve
Revision history for this message
Michael Nelson (michael.nelson) :
351. By Kelvin Li

revert static storage back to the normal one, to make sure it works in metal environment

352. By Kelvin Li

add the revno in header

353. By Kelvin Li

update the project name in MakeFile to keep consistent in charm

Revision history for this message
Fabián Ezequiel Gallina (fgallina) wrote :

Added a few nitpicks and questions but otherwise this looks great!

354. By Kelvin Li

updates according reviews

Revision history for this message
Kelvin Li (kelvin.li) wrote :

some updates according to the review comments.

355. By Kelvin Li

fix the incorrect tarball name

356. By Kelvin Li

added the relocated wsgi and related test py files(missed in last commit

357. By Kelvin Li

removed virtualenv dependency for juju deployment

358. By Kelvin Li

Merged 2 config-manager.txt to remove the mutiple-files maintenance

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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'

Subscribers

People subscribed via source and target branches

to all changes: