Merge lp:~james-w/canonical-identity-provider/api-client-timeline into lp:canonical-identity-provider/release

Proposed by James Westby
Status: Merged
Approved by: James Westby
Approved revision: no longer in the source branch.
Merged at revision: 1048
Proposed branch: lp:~james-w/canonical-identity-provider/api-client-timeline
Merge into: lp:canonical-identity-provider/release
Diff against target: 183 lines (+104/-6)
4 files modified
src/identityprovider/apiutils.py (+29/-1)
src/identityprovider/tests/test_apiutils.py (+55/-0)
src/identityprovider/tests/test_timeline.py (+12/-0)
src/identityprovider/timeline.py (+8/-5)
To merge this branch: bzr merge lp:~james-w/canonical-identity-provider/api-client-timeline
Reviewer Review Type Date Requested Status
Ricardo Kirkner (community) Approve
Review via email: mp+183774@code.launchpad.net

Commit message

Record an action in the timeline when using ssoclient.

If a request uses ssoclient to hit the sso api, this will record
an action in the timeline so that we can see it in oopses.

Description of the change

Hi,

We are seeing +new_account view take a long time sometimes. One of the things
it does is use ssoclient to hit the sso api. That may be taking some time,
so record timeline actions for it so we can see it in any oopses.

Thanks,

James

To post a comment you must log in.
Revision history for this message
Ricardo Kirkner (ricardokirkner) wrote :

LGTM

review: Approve
Revision history for this message
Ubuntu One Auto Pilot (otto-pilot) wrote :
Download full text (262.9 KiB)

The attempt to merge lp:~james-w/canonical-identity-provider/api-client-timeline into lp:canonical-identity-provider failed. Below is the output from the failed tests.

Updating download cache at dir /mnt/tarmac/cache/canonical-identity-provider/isd-download-cache
Using saved parent location: bzr+ssh://bazaar.launchpad.net/~canonical-isd-hackers/+junk/download-cache/
No revisions or tags to pull.
[localhost] local: which virtualenv
[localhost] local: /usr/bin/python /usr/bin/virtualenv --distribute --clear --system-site-packages .env
Not deleting .env/bin
New python executable in .env/bin/python
Installing distribute.............................................................................................................................................................................................done.
Installing pip...............done.
[localhost] local: dpkg -l libpq-dev 2> /dev/null | grep '^ii' | wc -l
[localhost] local: dpkg -l libxml2-dev 2> /dev/null | grep '^ii' | wc -l
[localhost] local: dpkg -l libxslt1-dev 2> /dev/null | grep '^ii' | wc -l
[localhost] local: dpkg -l memcached 2> /dev/null | grep '^ii' | wc -l
[localhost] local: dpkg -l postgresql-plpython-9.1 2> /dev/null | grep '^ii' | wc -l
[localhost] local: dpkg -l python-dev 2> /dev/null | grep '^ii' | wc -l
[localhost] local: dpkg -l python-m2crypto 2> /dev/null | grep '^ii' | wc -l
[localhost] local: dpkg -l swig 2> /dev/null | grep '^ii' | wc -l
[localhost] local: dpkg -l config-manager 2> /dev/null | grep '^ii' | wc -l
[localhost] local: dpkg -l python-egenix-mx-base-dev 2> /dev/null | grep '^ii' | wc -l
[localhost] local: /usr/lib/config-manager/cm.py update /tmp/tmpeR8fJY
[localhost] local: . /mnt/tarmac/cache/canonical-identity-provider/merges/trunk/.env/bin/activate && make install PACKAGES="-r /mnt/tarmac/cache/canonical-identity-provider/merges/trunk/requirements.txt"
pip install --find-links=file:///mnt/tarmac/cache/canonical-identity-provider/isd-download-cache --no-index pip==dev
Ignoring indexes: http://pypi.python.org/simple/
Downloading/unpacking pip==dev
  Running setup.py egg_info for package pip

    warning: no files found matching '*.html' under directory 'docs'
    warning: no previously-included files matching '*.txt' found under directory 'docs/_build'
    no previously-included directories found matching 'docs/_build/_sources'
Installing collected packages: pip
  Found existing installation: pip 1.1
    Uninstalling pip:
      Successfully uninstalled pip
  Running setup.py install for pip

    warning: no files found matching '*.html' under directory 'docs'
    warning: no previously-included files matching '*.txt' found under directory 'docs/_build'
    no previously-included directories found matching 'docs/_build/_sources'
    Installing pip script to /mnt/tarmac/cache/canonical-identity-provider/merges/trunk/.env/bin
    Installing pip-2.7 script to /mnt/tarmac/cache/canonical-identity-provider/merges/trunk/.env/bin
Successfully installed pip
Cleaning up...
pip install --find-links=. --no-index -r /mnt/tarmac/cache/canonical-identity-provider/merges/trunk/requirements.txt
Ignoring indexes: http://pypi.python.org/simple/
Downloading/un...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/identityprovider/apiutils.py'
2--- src/identityprovider/apiutils.py 2013-02-13 21:54:09 +0000
3+++ src/identityprovider/apiutils.py 2013-09-04 14:56:55 +0000
4@@ -13,11 +13,39 @@
5 from requests.utils import get_encoding_from_headers
6
7 from ssoclient.v2 import V2ApiClient
8+from ssoclient.v2.http import ApiSession
9+
10+from identityprovider.timeline import (
11+ maybe_contextmanager,
12+ get_request_timing_function,
13+)
14+
15+
16+class TimelineRecordingApiSession(ApiSession):
17+
18+ def __init__(self, endpoint, timer=None):
19+ super(TimelineRecordingApiSession, self).__init__(endpoint)
20+ self.timer = timer
21+
22+ def request(self, method, url, **kwargs):
23+ category = 'ssoclient-{}'.format(method)
24+ with maybe_contextmanager(self.timer, category, url,
25+ allow_nested=True):
26+ return super(TimelineRecordingApiSession, self).request(
27+ method, url, **kwargs)
28+
29+
30+class TimelineRecordingApiClient(V2ApiClient):
31+
32+ def __init__(self, endpoint, timer=None):
33+ self.session = TimelineRecordingApiSession(endpoint, timer=timer)
34
35
36 def get_api_client(request):
37 # TODO: configure caching, auth
38- api = V2ApiClient(settings.API_HOST + settings.API_URL)
39+ timer = get_request_timing_function(request)
40+ api = TimelineRecordingApiClient(settings.API_HOST + settings.API_URL,
41+ timer=timer)
42 if (settings.API_USE_INTERNAL
43 or request.META['SERVER_NAME'] == "testserver"):
44 api.session.mount('http://', DjangoWSGIAdapter(request))
45
46=== modified file 'src/identityprovider/tests/test_apiutils.py'
47--- src/identityprovider/tests/test_apiutils.py 2013-05-16 16:39:41 +0000
48+++ src/identityprovider/tests/test_apiutils.py 2013-09-04 14:56:55 +0000
49@@ -1,10 +1,13 @@
50 import json
51+from contextlib import contextmanager
52
53 import requests
54 from django.conf.urls import patterns, url
55 from django.http import HttpResponse
56 from django.test.client import RequestFactory
57 from django.views.decorators.csrf import csrf_exempt
58+from mock import patch
59+from timeline import Timeline
60
61 from identityprovider.tests.utils import (
62 SSOBaseTestCase,
63@@ -15,6 +18,8 @@
64 from identityprovider.apiutils import (
65 get_api_client,
66 DjangoWSGIAdapter,
67+ TimelineRecordingApiClient,
68+ TimelineRecordingApiSession,
69 )
70
71
72@@ -40,6 +45,56 @@
73 api = get_api_client(request)
74 self.assert_django_adapters(api, request)
75
76+ def test_returns_timeline_recording_client(self):
77+ request = RequestFactory().post('/')
78+ api = get_api_client(request)
79+ self.assertIsInstance(api, TimelineRecordingApiClient)
80+
81+ @patch('requests.Session.request')
82+ def test_makes_timer_function_from_request(self, mock_request):
83+ timeline = Timeline()
84+ request = RequestFactory().post(
85+ '/', **{'timeline.timeline': timeline})
86+ api = get_api_client(request)
87+ api.session.post('/accounts')
88+ # 2 because we use allow_nested, as the request may be routed
89+ # internally if API_USE_INTERNAL is set.
90+ self.assertEqual(2, len(timeline.actions))
91+ self.assertEqual('ssoclient-POST-start', timeline.actions[0].category)
92+ self.assertEqual('/accounts', timeline.actions[0].detail)
93+
94+
95+class TimelineRecordingApiClientTests(SSOBaseUnittestTestCase):
96+
97+ def test_session_is_timeline_recording(self):
98+ api = TimelineRecordingApiClient('localhost')
99+ self.assertIsInstance(api.session, TimelineRecordingApiSession)
100+
101+ def test_passes_timer_to_session(self):
102+ timer = object()
103+ api = TimelineRecordingApiClient('localhost', timer=timer)
104+ self.assertEqual(timer, api.session.timer)
105+
106+
107+class TimelineRecordingApiSessionTests(SSOBaseUnittestTestCase):
108+
109+ @patch('requests.Session.request')
110+ def test_uses_timer(self, mock_request):
111+ expected_response = object()
112+ mock_request.return_value = expected_response
113+ called_args = []
114+
115+ @contextmanager
116+ def timer_fn(category, detail, allow_nested=False):
117+ called_args.append((category, detail, allow_nested))
118+ yield
119+ endpoint = 'http://localhost/'
120+ session = TimelineRecordingApiSession(endpoint, timer=timer_fn)
121+ path = 'test'
122+ response = session.request('GET', path)
123+ self.assertEqual(expected_response, response)
124+ self.assertEqual([('ssoclient-GET', path, True)], called_args)
125+
126
127 @csrf_exempt
128 def testview(request):
129
130=== modified file 'src/identityprovider/tests/test_timeline.py'
131--- src/identityprovider/tests/test_timeline.py 2013-08-26 12:49:37 +0000
132+++ src/identityprovider/tests/test_timeline.py 2013-09-04 14:56:55 +0000
133@@ -41,6 +41,18 @@
134 self.assertEqual(1, len(timeline.actions))
135 self.assertEqual('category', timeline.actions[0].category)
136 self.assertEqual('detail', timeline.actions[0].detail)
137+ self.assertNotEqual(None, timeline.actions[0].duration)
138+
139+ def test_finishes_action_even_with_exception(self):
140+ timeline = Timeline()
141+ timer_fn = get_timing_function(timeline)
142+
143+ def do_test():
144+ with timer_fn('category', 'detail'):
145+ 1/0
146+ self.assertRaises(ZeroDivisionError, do_test)
147+ self.assertEqual(1, len(timeline.actions))
148+ self.assertNotEqual(None, timeline.actions[0].duration)
149
150
151 class MaybeContextManagerTests(TestCase):
152
153=== modified file 'src/identityprovider/timeline.py'
154--- src/identityprovider/timeline.py 2013-08-24 01:12:57 +0000
155+++ src/identityprovider/timeline.py 2013-09-04 14:56:55 +0000
156@@ -14,7 +14,7 @@
157
158 Given
159
160- timer_fn = get_timing_function(timeline, detail_fn)
161+ timer_fn = get_timing_function(timeline)
162
163 def request(method, url):
164 with timer_fn('http-{}'.format(method), url):
165@@ -36,13 +36,16 @@
166 between __enter__ and __exit__.
167 """
168 @contextmanager
169- def timer_fn(category, detail):
170+ def timer_fn(category, detail, allow_nested=False):
171 if not timeline:
172 yield
173 else:
174- action = timeline.start(category, detail)
175- yield
176- action.finish()
177+ action = timeline.start(category, detail,
178+ allow_nested=allow_nested)
179+ try:
180+ yield
181+ finally:
182+ action.finish()
183 return timer_fn
184
185