Merge lp:~nataliabidart/ubuntuone-client/improve-error-handling into lp:ubuntuone-client
- improve-error-handling
- Merge into trunk
Status: | Merged | ||||||||
---|---|---|---|---|---|---|---|---|---|
Approved by: | John Lenton | ||||||||
Approved revision: | 493 | ||||||||
Merged at revision: | not available | ||||||||
Proposed branch: | lp:~nataliabidart/ubuntuone-client/improve-error-handling | ||||||||
Merge into: | lp:ubuntuone-client | ||||||||
Diff against target: |
780 lines (+587/-107) 4 files modified
contrib/testing/testcase.py (+1/-5) tests/syncdaemon/test_action_queue.py (+485/-10) ubuntuone/syncdaemon/action_queue.py (+100/-92) ubuntuone/syncdaemon/event_queue.py (+1/-0) |
||||||||
To merge this branch: | bzr merge lp:~nataliabidart/ubuntuone-client/improve-error-handling | ||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Facundo Batista (community) | Approve | ||
John Lenton (community) | Approve | ||
Review via email: mp+23140@code.launchpad.net |
Commit message
Unified server error handling for check_version, set_capabilities, and authenticate.
Description of the change
Unified server error handling for check_version, set_capabilities, and authenticate.
This is a big branh but most of the diff lines are test cases added for existing functionality that had to be refactored, and had no test asserting over its functionality.
John Lenton (chipaca) wrote : | # |
OK, brown paper bag for me. I promise I *looked* for the bugs.
Getting more coffee, now.
John Lenton (chipaca) : | # |
John Lenton (chipaca) wrote : | # |
I like it.
I do have nits to pick:
* with the class called A:
class A(object):
"""Do nothing."""
pass
it isn't that it's hard to understand what the class does :) In this case, I think maybe the docstring should explain the intent a little more?
* a question: isn't using inlineCallbacks for a method that just calls one deferred-returning method, overkill?
* There's a pattern in the code that goes like
kwargs = dict( ... a dict ... )
yield self.foo(**kwargs)
without any real reason. That is a rather silly use of kwargs.
None of these are Need Fixings. On the whole, a lovely refactoring.
Natalia Bidart (nataliabidart) wrote : | # |
> I like it.
> I do have nits to pick:
>
> * with the class called A:
>
> class A(object):
> """Do nothing."""
> pass
>
> it isn't that it's hard to understand what the class does :) In this case, I
> think maybe the docstring should explain the intent a little more?
Improved the docstring and renamed to SillyClass for the sake of explicitness.
> * a question: isn't using inlineCallbacks for a method that just calls one
> deferred-returning method, overkill?
Actually, I don't know. I preferred leave that way "to maintain" the "shape" of the former implementation. I'm changing check_versionto avoid using it.
> * There's a pattern in the code that goes like
>
> kwargs = dict( ... a dict ... )
> yield self.foo(**kwargs)
>
> without any real reason. That is a rather silly use of kwargs.
The real reason is that I find this approach the best way to pass a lot of parameters to a function and keep that call readable and within 80 columns. I'm not sure how to change it and keep it with those properties.
> None of these are Need Fixings. On the whole, a lovely refactoring.
Thank you very much for the input, and the review!
- 494. By Natalia Bidart
-
Applying review tips frm Chipaca.
- 495. By Natalia Bidart
-
Merged trunk in.
- 496. By Natalia Bidart
-
Removing unused ().
- 497. By Natalia Bidart
-
Adding improvements suggested by facundobatista.
- 498. By Natalia Bidart
-
Fixing typos.
Preview Diff
1 | === modified file 'contrib/testing/testcase.py' |
2 | --- contrib/testing/testcase.py 2010-03-15 19:18:32 +0000 |
3 | +++ contrib/testing/testcase.py 2010-04-12 13:34:34 +0000 |
4 | @@ -651,14 +651,10 @@ |
5 | self.events.append((event_name, args, kwargs)) |
6 | |
7 | |
8 | -def noop(*args, **kwargs): |
9 | - """No op.""" |
10 | - |
11 | - |
12 | class DummyClass(object): |
13 | """Dummy class, does nothing.""" |
14 | |
15 | def __getattribute__(self, name): |
16 | """Any attribute is a no-op.""" |
17 | - return noop |
18 | + return lambda *args, **kwargs: None |
19 | |
20 | |
21 | === modified file 'tests/syncdaemon/test_action_queue.py' |
22 | --- tests/syncdaemon/test_action_queue.py 2010-04-09 05:12:37 +0000 |
23 | +++ tests/syncdaemon/test_action_queue.py 2010-04-12 13:34:34 +0000 |
24 | @@ -37,10 +37,10 @@ |
25 | from twisted.web import server |
26 | |
27 | from contrib.testing.testcase import ( |
28 | - BaseTwistedTestCase, MementoHandler |
29 | + BaseTwistedTestCase, MementoHandler, DummyClass |
30 | ) |
31 | |
32 | -from ubuntuone.storageprotocol import client, volumes |
33 | +from ubuntuone.storageprotocol import client, errors, protocol_pb2, volumes |
34 | from ubuntuone.syncdaemon import states |
35 | from ubuntuone.syncdaemon.dbus_interface import DBusInterface |
36 | from ubuntuone.syncdaemon.main import Main |
37 | @@ -163,7 +163,7 @@ |
38 | self.main.event_q.push = keep_a_copy(self.main.event_q.push) |
39 | |
40 | self.handler = MementoHandler() |
41 | - self.handler.setLevel(logging.ERROR) |
42 | + self.handler.setLevel(logging.INFO) |
43 | logging.getLogger('ubuntuone.SyncDaemon').addHandler(self.handler) |
44 | |
45 | # fake local rescan call to not be executed |
46 | @@ -1439,16 +1439,491 @@ |
47 | """Fake get_root.""" |
48 | d.callback(mdid) |
49 | |
50 | - class FakeClient(object): |
51 | - """Fake Client""" |
52 | - def __getattribute__(self, attr): |
53 | - """Return a noop.""" |
54 | - return lambda *args, **kwargs: None |
55 | - |
56 | - self.action_queue.client = FakeClient() |
57 | + self.action_queue.client = DummyClass() |
58 | self.action_queue.get_root = get_root |
59 | self.action_queue.server_rescan('foo', lambda: list()) |
60 | def check(result): |
61 | self.assertEquals('foo', result) |
62 | d.addCallback(check) |
63 | return d |
64 | + |
65 | + |
66 | +class SpecificException(Exception): |
67 | + """The specific exception.""" |
68 | + |
69 | + |
70 | +class SillyClass(object): |
71 | + """Silly class that accepts the set of any attribute. |
72 | + |
73 | + We can't use object() directly, since its raises AttributeError. |
74 | + |
75 | + """ |
76 | + |
77 | + |
78 | +class ErrorHandlingTestCase(BasicTestCase): |
79 | + """Error handling tests for ActionQueue.""" |
80 | + |
81 | + def setUp(self): |
82 | + """Init.""" |
83 | + BasicTestCase.setUp(self) |
84 | + |
85 | + self.called = False |
86 | + self.action_queue.client = SillyClass() |
87 | + self.action_queue.deferred = defer.Deferred() |
88 | + self.patch(self.main, 'restart', lambda: None) |
89 | + |
90 | + self.main.start() |
91 | + |
92 | + def fail_please(self, an_exception): |
93 | + """Raise the given exception.""" |
94 | + def inner(*args, **kwargs): |
95 | + """A request to the server that fails.""" |
96 | + self.called = True |
97 | + return defer.fail(an_exception) |
98 | + return inner |
99 | + |
100 | + def succeed_please(self, result): |
101 | + """Return the given result.""" |
102 | + def inner(*args, **kwargs): |
103 | + """A request to the server that succeeds.""" |
104 | + self.called = True |
105 | + return defer.succeed(result) |
106 | + return inner |
107 | + |
108 | + def mock_caps(self, accepted): |
109 | + """Reply to query caps with False.""" |
110 | + def gset_caps(caps): |
111 | + """get/set caps helper.""" |
112 | + req = SillyClass() |
113 | + req.caps = caps |
114 | + req.accepted = accepted |
115 | + return defer.succeed(req) |
116 | + return gset_caps |
117 | + |
118 | + def test_valid_event(self): |
119 | + """SYS_SERVER_ERROR is valid in EventQueue.""" |
120 | + event = 'SYS_SERVER_ERROR' |
121 | + self.assertTrue(event in EVENTS) |
122 | + self.assertEquals(('error',), EVENTS[event]) |
123 | + |
124 | + @defer.inlineCallbacks |
125 | + def test_send_request_and_handle_errors_on_no_error(self): |
126 | + """_send_request_and_handle_errors is correct when no error.""" |
127 | + |
128 | + event = 'SYS_SPECIFIC_OK' |
129 | + EVENTS[event] = () # add event to the global valid events list |
130 | + self.addCleanup(lambda: EVENTS.pop(event)) |
131 | + |
132 | + result = object() |
133 | + request = self.succeed_please(result) |
134 | + kwargs = dict(request=request, request_error=SpecificException, |
135 | + event_error='YADDA_YADDA', event_ok=event, |
136 | + args=(1, 2), kwargs={}) |
137 | + d = self.action_queue._send_request_and_handle_errors(**kwargs) |
138 | + actual_result = yield d |
139 | + |
140 | + self.assertTrue(self.called, 'the request was called') |
141 | + self.assertEqual(actual_result, result) |
142 | + self.assertEqual((event, (), {}), |
143 | + self.action_queue.event_queue.events[-1]) |
144 | + |
145 | + # assert over logging |
146 | + self.assertEqual(1, len(self.handler.records)) |
147 | + self.assertIn(request.__name__, self.handler.records[0].message) |
148 | + self.assertIn('OK', self.handler.records[0].message) |
149 | + |
150 | + # assert internal deferred wasn't fired |
151 | + self.assertFalse(self.action_queue.deferred.called) |
152 | + |
153 | + @defer.inlineCallbacks |
154 | + def test_send_request_and_handle_errors_with_no_event_ok(self): |
155 | + """_send_request_and_handle_errors does not push event if is None.""" |
156 | + original_events = self.action_queue.event_queue.events[:] |
157 | + |
158 | + result = object() |
159 | + request = self.succeed_please(result) |
160 | + kwargs = dict(request=request, request_error=SpecificException, |
161 | + event_error='YADDA_YADDA', event_ok=None) |
162 | + d = self.action_queue._send_request_and_handle_errors(**kwargs) |
163 | + actual_result = yield d |
164 | + |
165 | + self.assertTrue(self.called, 'the request was called') |
166 | + self.assertEqual(actual_result, result) |
167 | + self.assertEqual(original_events, |
168 | + self.action_queue.event_queue.events) |
169 | + |
170 | + # assert over logging |
171 | + self.assertEqual(1, len(self.handler.records)) |
172 | + self.assertIn(request.__name__, self.handler.records[0].message) |
173 | + self.assertIn('OK', self.handler.records[0].message) |
174 | + |
175 | + # assert internal deferred wasn't fired |
176 | + self.assertFalse(self.action_queue.deferred.called) |
177 | + |
178 | + @defer.inlineCallbacks |
179 | + def test_send_request_and_handle_errors_on_valid_error(self): |
180 | + """_send_request_and_handle_errors is correct when expected error.""" |
181 | + |
182 | + event = 'SYS_SPECIFIC_ERROR' |
183 | + EVENTS[event] = ('error',) # add event to the global valid events list |
184 | + self.addCleanup(lambda: EVENTS.pop(event)) |
185 | + |
186 | + exc = SpecificException('The request failed! please be happy.') |
187 | + request = self.fail_please(exc) |
188 | + kwargs = dict(request=request, request_error=SpecificException, |
189 | + event_error=event, event_ok='YADDA_YADDA') |
190 | + d = self.action_queue._send_request_and_handle_errors(**kwargs) |
191 | + yield d |
192 | + |
193 | + self.assertTrue(self.called, 'the request was called') |
194 | + self.assertEqual((event, (), {'error': str(exc)}), |
195 | + self.action_queue.event_queue.events[-1]) |
196 | + |
197 | + # assert over logging |
198 | + self.assertEqual(1, len(self.handler.records)) |
199 | + self.assertIn(request.__name__, self.handler.records[0].message) |
200 | + self.assertIn(str(exc), self.handler.records[0].message) |
201 | + |
202 | + # assert internal deferred was fired |
203 | + self.assertTrue(self.action_queue.deferred.called) |
204 | + self.assertIsInstance(self.action_queue.deferred.result, Failure) |
205 | + self.assertEqual(self.action_queue.deferred.result.value, exc) |
206 | + |
207 | + @defer.inlineCallbacks |
208 | + def assert_send_request_and_handle_errors_on_server_error(self, serr): |
209 | + """_send_request_and_handle_errors is correct when server error.""" |
210 | + # XXX: we need to replace this list with and exception list |
211 | + # once bug #557718 is resolved |
212 | + msg = protocol_pb2.Message() |
213 | + msg.type = protocol_pb2.Message.ERROR |
214 | + msg.error.type = serr |
215 | + msg.error.comment = 'Error message for %s.' % serr |
216 | + exc = errors.error_to_exception(serr)(request=None, message=msg) |
217 | + |
218 | + request = self.fail_please(exc) |
219 | + kwargs = dict(request=request, request_error=SpecificException, |
220 | + event_error='BAR', event_ok='FOO') |
221 | + d = self.action_queue._send_request_and_handle_errors(**kwargs) |
222 | + yield d |
223 | + |
224 | + self.assertEqual(( 'SYS_SERVER_ERROR', (), {'error': str(exc)}), |
225 | + self.action_queue.event_queue.events[-1]) |
226 | + |
227 | + # assert over logging |
228 | + self.assertTrue(len(self.handler.records) > 0) |
229 | + self.assertIn(request.__name__, self.handler.records[-1].message) |
230 | + self.assertIn(str(exc), self.handler.records[-1].message) |
231 | + |
232 | + # assert internal deferred was fired |
233 | + self.assertTrue(self.action_queue.deferred.called) |
234 | + self.assertIsInstance(self.action_queue.deferred.result, Failure) |
235 | + self.assertEqual(self.action_queue.deferred.result.value, exc) |
236 | + |
237 | + @defer.inlineCallbacks |
238 | + def test_send_request_and_handle_errors_on_try_again(self): |
239 | + """_send_request_and_handle_errors is correct when server error.""" |
240 | + serr = protocol_pb2.Error.TRY_AGAIN |
241 | + yield self.assert_send_request_and_handle_errors_on_server_error(serr) |
242 | + |
243 | + @defer.inlineCallbacks |
244 | + def test_send_request_and_handle_errors_on_internal_error(self): |
245 | + """_send_request_and_handle_errors is correct when server error.""" |
246 | + serr = protocol_pb2.Error.INTERNAL_ERROR |
247 | + yield self.assert_send_request_and_handle_errors_on_server_error(serr) |
248 | + |
249 | + @defer.inlineCallbacks |
250 | + def test_send_request_and_handle_errors_on_protocol_error(self): |
251 | + """_send_request_and_handle_errors is correct when server error.""" |
252 | + serr = protocol_pb2.Error.PROTOCOL_ERROR |
253 | + yield self.assert_send_request_and_handle_errors_on_server_error(serr) |
254 | + |
255 | + @defer.inlineCallbacks |
256 | + def test_send_request_and_handle_errors_on_unsupported_version(self): |
257 | + """_send_request_and_handle_errors is correct when server error.""" |
258 | + serr = protocol_pb2.Error.UNSUPPORTED_VERSION |
259 | + yield self.assert_send_request_and_handle_errors_on_server_error(serr) |
260 | + |
261 | + @defer.inlineCallbacks |
262 | + def test_send_request_and_handle_errors_on_authetication_failed(self): |
263 | + """_send_request_and_handle_errors is correct when server error.""" |
264 | + serr = protocol_pb2.Error.AUTHENTICATION_FAILED |
265 | + yield self.assert_send_request_and_handle_errors_on_server_error(serr) |
266 | + |
267 | + @defer.inlineCallbacks |
268 | + def test_send_request_and_handle_errors_on_no_permission(self): |
269 | + """_send_request_and_handle_errors is correct when server error.""" |
270 | + serr = protocol_pb2.Error.NO_PERMISSION |
271 | + yield self.assert_send_request_and_handle_errors_on_server_error(serr) |
272 | + |
273 | + @defer.inlineCallbacks |
274 | + def test_send_request_and_handle_errors_on_already_exists(self): |
275 | + """_send_request_and_handle_errors is correct when server error.""" |
276 | + serr = protocol_pb2.Error.ALREADY_EXISTS |
277 | + yield self.assert_send_request_and_handle_errors_on_server_error(serr) |
278 | + |
279 | + @defer.inlineCallbacks |
280 | + def test_send_request_and_handle_errors_on_does_not_exist(self): |
281 | + """_send_request_and_handle_errors is correct when server error.""" |
282 | + serr = protocol_pb2.Error.DOES_NOT_EXIST |
283 | + yield self.assert_send_request_and_handle_errors_on_server_error(serr) |
284 | + |
285 | + @defer.inlineCallbacks |
286 | + def test_send_request_and_handle_errors_on_not_a_dir(self): |
287 | + """_send_request_and_handle_errors is correct when server error.""" |
288 | + serr = protocol_pb2.Error.NOT_A_DIRECTORY |
289 | + yield self.assert_send_request_and_handle_errors_on_server_error(serr) |
290 | + |
291 | + @defer.inlineCallbacks |
292 | + def test_send_request_and_handle_errors_on_not_empty(self): |
293 | + """_send_request_and_handle_errors is correct when server error.""" |
294 | + serr = protocol_pb2.Error.NOT_EMPTY |
295 | + yield self.assert_send_request_and_handle_errors_on_server_error(serr) |
296 | + |
297 | + @defer.inlineCallbacks |
298 | + def test_send_request_and_handle_errors_on_not_available(self): |
299 | + """_send_request_and_handle_errors is correct when server error.""" |
300 | + serr = protocol_pb2.Error.NOT_AVAILABLE |
301 | + yield self.assert_send_request_and_handle_errors_on_server_error(serr) |
302 | + |
303 | + @defer.inlineCallbacks |
304 | + def test_send_request_and_handle_errors_on_upload_in_porgress(self): |
305 | + """_send_request_and_handle_errors is correct when server error.""" |
306 | + serr = protocol_pb2.Error.UPLOAD_IN_PROGRESS |
307 | + yield self.assert_send_request_and_handle_errors_on_server_error(serr) |
308 | + |
309 | + @defer.inlineCallbacks |
310 | + def test_send_request_and_handle_errors_on_upload_corrupt(self): |
311 | + """_send_request_and_handle_errors is correct when server error.""" |
312 | + serr = protocol_pb2.Error.UPLOAD_CORRUPT |
313 | + yield self.assert_send_request_and_handle_errors_on_server_error(serr) |
314 | + |
315 | + @defer.inlineCallbacks |
316 | + def test_send_request_and_handle_errors_on_upload_canceled(self): |
317 | + """_send_request_and_handle_errors is correct when server error.""" |
318 | + serr = protocol_pb2.Error.UPLOAD_CANCELED |
319 | + yield self.assert_send_request_and_handle_errors_on_server_error(serr) |
320 | + |
321 | + @defer.inlineCallbacks |
322 | + def test_send_request_and_handle_errors_on_conflict(self): |
323 | + """_send_request_and_handle_errors is correct when server error.""" |
324 | + serr = protocol_pb2.Error.CONFLICT |
325 | + yield self.assert_send_request_and_handle_errors_on_server_error(serr) |
326 | + |
327 | + @defer.inlineCallbacks |
328 | + def test_send_request_and_handle_errors_on_quota_exceeded(self): |
329 | + """_send_request_and_handle_errors is correct when server error.""" |
330 | + serr = protocol_pb2.Error.QUOTA_EXCEEDED |
331 | + yield self.assert_send_request_and_handle_errors_on_server_error(serr) |
332 | + |
333 | + @defer.inlineCallbacks |
334 | + def test_send_request_and_handle_errors_on_invalid_filename(self): |
335 | + """_send_request_and_handle_errors is correct when server error.""" |
336 | + serr = protocol_pb2.Error.INVALID_FILENAME |
337 | + yield self.assert_send_request_and_handle_errors_on_server_error(serr) |
338 | + |
339 | + @defer.inlineCallbacks |
340 | + def test_send_request_and_handle_errors_on_unknown_error(self): |
341 | + """_send_request_and_handle_errors is correct when unknown error.""" |
342 | + # XXX: we need to replace this list with and exception list |
343 | + # once bug #557718 is resolved |
344 | + serr = protocol_pb2.Error.AUTHENTICATION_REQUIRED |
345 | + msg = protocol_pb2.Message() |
346 | + msg.type = protocol_pb2.Message.ERROR |
347 | + msg.error.type = serr |
348 | + msg.error.comment = 'Error message for %s.' % serr |
349 | + exc = errors.error_to_exception(serr)(request=None, message=msg) |
350 | + |
351 | + request = self.fail_please(exc) |
352 | + kwargs = dict(request=request, request_error=SpecificException, |
353 | + event_error='BAR', event_ok='FOO') |
354 | + d = self.action_queue._send_request_and_handle_errors(**kwargs) |
355 | + yield d |
356 | + |
357 | + self.assertIn(('SYS_UNKNOWN_ERROR', (), {}), |
358 | + self.action_queue.event_queue.events) |
359 | + |
360 | + # assert over logging |
361 | + self.assertEqual(1, len(self.handler.records)) |
362 | + self.assertIn(request.__name__, self.handler.records[0].message) |
363 | + self.assertIn(str(exc), self.handler.records[0].message) |
364 | + |
365 | + # assert internal deferred was fired |
366 | + self.assertTrue(self.action_queue.deferred.called) |
367 | + self.assertIsInstance(self.action_queue.deferred.result, Failure) |
368 | + self.assertEqual(self.action_queue.deferred.result.value, exc) |
369 | + |
370 | + @defer.inlineCallbacks |
371 | + def test_send_request_and_handle_errors_on_no_protocol_error(self): |
372 | + """_send_request_and_handle_errors is correct when no-protocol error.""" |
373 | + |
374 | + event = 'SYS_UNKNOWN_ERROR' |
375 | + error_msg = 'Error message for any Exception.' |
376 | + exc = Exception(error_msg) |
377 | + request = self.fail_please(exc) |
378 | + kwargs = dict(request=request, request_error=SpecificException, |
379 | + event_error='BAR', event_ok='FOO') |
380 | + d = self.action_queue._send_request_and_handle_errors(**kwargs) |
381 | + yield d |
382 | + |
383 | + self.assertIn((event, (), {}), |
384 | + self.action_queue.event_queue.events) |
385 | + |
386 | + # assert over logging |
387 | + self.assertEqual(1, len(self.handler.records)) |
388 | + self.assertIn(request.__name__, self.handler.records[0].message) |
389 | + self.assertIn(str(exc), self.handler.records[0].message) |
390 | + |
391 | + # assert internal deferred was fired |
392 | + self.assertTrue(self.action_queue.deferred.called) |
393 | + self.assertIsInstance(self.action_queue.deferred.result, Failure) |
394 | + self.assertEqual(self.action_queue.deferred.result.value, exc) |
395 | + |
396 | + @defer.inlineCallbacks |
397 | + def test_send_request_and_handle_errors_on_client_mismatch(self): |
398 | + """_send_request_and_handle_errors is correct when client mismatch.""" |
399 | + |
400 | + def change_client(*args, **kwargs): |
401 | + """Change AQ's client while doing the request.""" |
402 | + self.action_queue.client = object() |
403 | + |
404 | + self.action_queue.event_queue.events = [] # event cleanup |
405 | + kwargs = dict(request=change_client, request_error=SpecificException, |
406 | + event_error='BAR', event_ok='FOO') |
407 | + d = self.action_queue._send_request_and_handle_errors(**kwargs) |
408 | + yield d |
409 | + |
410 | + self.assertEqual([], self.action_queue.event_queue.events) |
411 | + |
412 | + # assert over logging |
413 | + self.assertEqual(1, len(self.handler.records)) |
414 | + self.assertIn(change_client.__name__, self.handler.records[0].message) |
415 | + self.assertIn('Client mismatch', self.handler.records[0].message) |
416 | + |
417 | + # assert internal deferred wasn't fired |
418 | + self.assertFalse(self.action_queue.deferred.called) |
419 | + |
420 | + @defer.inlineCallbacks |
421 | + def test_check_version_when_unsupported_version_exception(self): |
422 | + """Test error handling after UnsupportedVersionError.""" |
423 | + # raise a UnsupportedVersionError |
424 | + msg = protocol_pb2.Message() |
425 | + msg.type = protocol_pb2.Message.ERROR |
426 | + msg.error.type = protocol_pb2.Error.UNSUPPORTED_VERSION |
427 | + msg.error.comment = 'This is a funny comment.' |
428 | + exc = errors.UnsupportedVersionError(request=None, message=msg) |
429 | + |
430 | + self.action_queue.client.protocol_version = self.fail_please(exc) |
431 | + yield self.action_queue.check_version() |
432 | + event = ('SYS_PROTOCOL_VERSION_ERROR', (), {'error': str(exc)}) |
433 | + self.assertEqual(event, self.action_queue.event_queue.events[-1]) |
434 | + |
435 | + @defer.inlineCallbacks |
436 | + def test_set_capabilities_when_query_caps_not_accepted(self): |
437 | + """Test error handling when the query caps are not accepeted.""" |
438 | + |
439 | + # query_caps returns False |
440 | + self.action_queue.client.query_caps = self.mock_caps(accepted=False) |
441 | + |
442 | + yield self.action_queue.set_capabilities(caps=None) |
443 | + msg = "The server doesn't have the requested capabilities" |
444 | + event = ('SYS_SET_CAPABILITIES_ERROR', (), {'error': msg}) |
445 | + self.assertEqual(event, self.action_queue.event_queue.events[-1]) |
446 | + self.assertNotIn(('SYS_SET_CAPABILITIES_OK', (), {}), |
447 | + self.action_queue.event_queue.events) |
448 | + |
449 | + # assert internal deferred was fired |
450 | + self.assertTrue(self.action_queue.deferred.called) |
451 | + self.assertIsInstance(self.action_queue.deferred.result, Failure) |
452 | + self.assertIsInstance(self.action_queue.deferred.result.value, |
453 | + StandardError) |
454 | + self.assertEqual(str(self.action_queue.deferred.result.value), msg) |
455 | + |
456 | + @defer.inlineCallbacks |
457 | + def test_set_capabilities_when_set_caps_not_accepted(self): |
458 | + """Test error handling when the query caps are not accepeted.""" |
459 | + |
460 | + # query_caps returns True and set_caps returns False |
461 | + self.action_queue.client.query_caps = self.mock_caps(accepted=True) |
462 | + self.action_queue.client.set_caps = self.mock_caps(accepted=False) |
463 | + |
464 | + caps = 'very difficult cap' |
465 | + yield self.action_queue.set_capabilities(caps=caps) |
466 | + msg = "The server denied setting '%s' capabilities" % caps |
467 | + event = ('SYS_SET_CAPABILITIES_ERROR', (), {'error': msg}) |
468 | + self.assertEqual(event, self.action_queue.event_queue.events[-1]) |
469 | + self.assertNotIn(('SYS_SET_CAPABILITIES_OK', (), {}), |
470 | + self.action_queue.event_queue.events) |
471 | + |
472 | + # assert internal deferred was fired |
473 | + self.assertTrue(self.action_queue.deferred.called) |
474 | + self.assertIsInstance(self.action_queue.deferred.result, Failure) |
475 | + self.assertIsInstance(self.action_queue.deferred.result.value, |
476 | + StandardError) |
477 | + self.assertEqual(str(self.action_queue.deferred.result.value), msg) |
478 | + |
479 | + @defer.inlineCallbacks |
480 | + def test_set_capabilities_when_client_is_none(self): |
481 | + """Test error handling when the client is None.""" |
482 | + |
483 | + self.action_queue.client = None |
484 | + |
485 | + yield self.action_queue.set_capabilities(caps=None) |
486 | + msg = "'NoneType' object has no attribute 'query_caps'" |
487 | + event = ('SYS_SET_CAPABILITIES_ERROR', (), {'error': msg}) |
488 | + self.assertEqual(event, self.action_queue.event_queue.events[-1]) |
489 | + self.assertNotIn(('SYS_SET_CAPABILITIES_OK', (), {}), |
490 | + self.action_queue.event_queue.events) |
491 | + |
492 | + # assert internal deferred was fired |
493 | + self.assertTrue(self.action_queue.deferred.called) |
494 | + self.assertIsInstance(self.action_queue.deferred.result, Failure) |
495 | + self.assertIsInstance(self.action_queue.deferred.result.value, |
496 | + StandardError) |
497 | + self.assertEqual(str(self.action_queue.deferred.result.value), msg) |
498 | + |
499 | + @defer.inlineCallbacks |
500 | + def test_set_capabilities_when_set_caps_is_accepted(self): |
501 | + """Test error handling when the query caps are not accepeted.""" |
502 | + |
503 | + # query_caps returns True and set_caps returns True |
504 | + self.action_queue.client.query_caps = self.mock_caps(accepted=True) |
505 | + self.action_queue.client.set_caps = self.mock_caps(accepted=True) |
506 | + |
507 | + yield self.action_queue.set_capabilities(caps=None) |
508 | + event = ('SYS_SET_CAPABILITIES_OK', (), {}) |
509 | + self.assertEqual(event, self.action_queue.event_queue.events[-1]) |
510 | + |
511 | + # assert internal deferred wasn't fired |
512 | + self.assertFalse(self.action_queue.deferred.called) |
513 | + |
514 | + @defer.inlineCallbacks |
515 | + def test_authenticate_when_authenticated(self): |
516 | + """Test error handling after authenticate with no error.""" |
517 | + |
518 | + self.action_queue.client.oauth_authenticate = \ |
519 | + self.succeed_please(result=self.action_queue.client) |
520 | + yield self.action_queue.authenticate(oauth_consumer=object()) |
521 | + event = ('SYS_AUTH_OK', (), {}) |
522 | + self.assertEqual(event, self.action_queue.event_queue.events[-1]) |
523 | + |
524 | + # assert internal deferred was fired with the client as result |
525 | + self.assertTrue(self.action_queue.deferred.called) |
526 | + self.assertTrue(self.action_queue.deferred.result is self.action_queue.client) |
527 | + |
528 | + @defer.inlineCallbacks |
529 | + def test_authenticate_when_authentication_failed_exception(self): |
530 | + """Test error handling after AuthenticationFailedError.""" |
531 | + # raise a AuthenticationFailedError |
532 | + msg = protocol_pb2.Message() |
533 | + msg.type = protocol_pb2.Message.ERROR |
534 | + msg.error.type = protocol_pb2.Error.AUTHENTICATION_FAILED |
535 | + msg.error.comment = 'This is a funny comment.' |
536 | + exc = errors.AuthenticationFailedError(request=None, message=msg) |
537 | + |
538 | + self.action_queue.client.oauth_authenticate = self.fail_please(exc) |
539 | + yield self.action_queue.authenticate(oauth_consumer=object()) |
540 | + event = ('SYS_AUTH_ERROR', (), {'error': str(exc)}) |
541 | + self.assertEqual(event, self.action_queue.event_queue.events[-1]) |
542 | + |
543 | + # assert internal deferred was fired |
544 | + self.assertTrue(self.action_queue.deferred.called) |
545 | |
546 | === modified file 'ubuntuone/syncdaemon/action_queue.py' |
547 | --- ubuntuone/syncdaemon/action_queue.py 2010-04-09 18:21:13 +0000 |
548 | +++ ubuntuone/syncdaemon/action_queue.py 2010-04-12 13:34:34 +0000 |
549 | @@ -48,7 +48,7 @@ |
550 | from twisted.python.failure import Failure, DefaultException |
551 | |
552 | from oauth import oauth |
553 | -from ubuntuone.storageprotocol import protocol_pb2, request |
554 | +from ubuntuone.storageprotocol import errors, protocol_pb2 |
555 | from ubuntuone.storageprotocol.client import ( |
556 | ThrottlingStorageClient, ThrottlingStorageClientFactory |
557 | ) |
558 | @@ -882,111 +882,119 @@ |
559 | logger.warning('Connection lost: %s', reason.getErrorMessage()) |
560 | |
561 | @defer.inlineCallbacks |
562 | - def check_version(self): |
563 | - """ |
564 | - Check the client protocol version matches that of the |
565 | - server. Call callback on success, errback on failure. |
566 | + def _send_request_and_handle_errors(self, request, request_error, |
567 | + event_error, event_ok, |
568 | + args=(), kwargs={}): |
569 | + """Send 'request' to the server, using params 'args' and 'kwargs'. |
570 | + |
571 | + Expect 'request_error' as valid error, and push 'event_error' in that |
572 | + case. Do generic error handling for the rest of the protocol errors. |
573 | + |
574 | """ |
575 | # if the client changes while we're waiting, this message is |
576 | # old news and should be discarded (the message would |
577 | # typically be a failure: timeout or disconnect). So keep the |
578 | # original client around for comparison. |
579 | client = self.client |
580 | + req_name = request.__name__ |
581 | + failure = None |
582 | + result = None |
583 | try: |
584 | - yield client.protocol_version() |
585 | - except Exception, e: |
586 | - if client is self.client: |
587 | - logger.error("Protocol version error.") |
588 | - logger.debug('Traceback follows:\n\n' + traceback.format_exc()) |
589 | - if e.message == 'UNSUPPORTED_VERSION': |
590 | - self.event_queue.push('SYS_PROTOCOL_VERSION_ERROR', |
591 | - error=e.message) |
592 | - else: |
593 | - self.event_queue.push('SYS_UNKNOWN_ERROR') |
594 | - # it looks like we won't be authenticating, so hook up the |
595 | - # for-testing deferred now |
596 | - self.deferred.callback(Failure(e)) |
597 | - else: |
598 | - if client is self.client: |
599 | - logger.info("Protocol version OK") |
600 | - self.event_queue.push('SYS_PROTOCOL_VERSION_OK') |
601 | + try: |
602 | + result = yield request(*args, **kwargs) |
603 | + finally: |
604 | + # common handling for all cases |
605 | + if client is not self.client: |
606 | + client_mismatch_msg = "Client mismatch while processing "\ |
607 | + "the request '%s', " \ |
608 | + "client (%r) is not self.client (%r)." |
609 | + logger.warning(client_mismatch_msg, req_name, |
610 | + client, self.client) |
611 | + return |
612 | + except request_error, failure: |
613 | + self.event_queue.push(event_error, error=str(failure)) |
614 | + except errors.AuthenticationRequiredError, failure: |
615 | + # we need to separate this case from the rest because an |
616 | + # AuthenticationRequiredError is an StorageRequestError, |
617 | + # and we treat it differently. |
618 | + self.event_queue.push('SYS_UNKNOWN_ERROR') |
619 | + except errors.StorageRequestError, failure: |
620 | + self.event_queue.push('SYS_SERVER_ERROR', error=str(failure)) |
621 | + except Exception, failure: |
622 | + self.event_queue.push('SYS_UNKNOWN_ERROR') |
623 | + else: |
624 | + logger.info("The request '%s' finished OK.", req_name) |
625 | + if event_ok is not None: |
626 | + self.event_queue.push(event_ok) |
627 | + |
628 | + if failure is not None: |
629 | + logger.error("The request '%s' failed with the error:\n\n%s\n", |
630 | + req_name, failure) |
631 | + # it looks like we won't be authenticating, so hook up the |
632 | + # for-testing deferred now |
633 | + self.deferred.callback(Failure(failure)) |
634 | + else: |
635 | + defer.returnValue(result) |
636 | + |
637 | + def check_version(self): |
638 | + """Check if the client protocol version matches that of the server.""" |
639 | + check_version_d = self._send_request_and_handle_errors( |
640 | + request=self.client.protocol_version, |
641 | + request_error=errors.UnsupportedVersionError, |
642 | + event_error='SYS_PROTOCOL_VERSION_ERROR', |
643 | + event_ok='SYS_PROTOCOL_VERSION_OK' |
644 | + ) |
645 | + return check_version_d |
646 | |
647 | @defer.inlineCallbacks |
648 | def set_capabilities(self, caps): |
649 | - """Set the capabilities with the server""" |
650 | - client = self.client |
651 | - is_failed = None |
652 | - try: |
653 | - req = (yield client.query_caps(caps)) |
654 | - except Exception, e: |
655 | - is_failed = e |
656 | - else: |
657 | + """Set the capabilities with the server.""" |
658 | + |
659 | + @defer.inlineCallbacks |
660 | + def caps_raising_if_not_accepted(capability_method, caps, msg): |
661 | + client_caps = getattr(self.client, capability_method) |
662 | + req = yield client_caps(caps) |
663 | if not req.accepted: |
664 | - is_failed = StandardError("The server doesn't have" |
665 | - " the requested capabilities") |
666 | - |
667 | - if client is self.client: |
668 | - if is_failed is not None: |
669 | - logger.error("Capabilities query failed: %s" % is_failed) |
670 | - # Push the error to the event queue, |
671 | - self.event_queue.push('SYS_SET_CAPABILITIES_ERROR', |
672 | - error=is_failed.message) |
673 | - # it looks like we won't be authenticating, so hook up the |
674 | - # for-testing deferred now |
675 | - self.deferred.callback(Failure(is_failed)) |
676 | - return |
677 | - |
678 | - is_failed = None |
679 | - try: |
680 | - req = (yield client.set_caps(caps)) |
681 | - except Exception, e: |
682 | - if client is self.client: |
683 | - is_failed = e |
684 | - else: |
685 | - if client is self.client: |
686 | - if req.accepted: |
687 | - self.event_queue.push('SYS_SET_CAPABILITIES_OK') |
688 | - defer.returnValue(self.client) |
689 | - else: |
690 | - is_failed = StandardError("The server denied setting " |
691 | - "%s capabilities" % req.caps) |
692 | - if is_failed is not None: |
693 | - logger.error("Capabilities set failed: %s", is_failed) |
694 | - # Push the error to the event queue, |
695 | - self.event_queue.push('SYS_SET_CAPABILITIES_ERROR', |
696 | - error=is_failed.message) |
697 | - # it looks like we won't be authenticating, so hook up the |
698 | - # for-testing deferred now |
699 | - self.deferred.callback(Failure(is_failed)) |
700 | + raise StandardError(msg) |
701 | + |
702 | + error_msg = "The server doesn't have the requested capabilities" |
703 | + query_caps_d = self._send_request_and_handle_errors( |
704 | + request=caps_raising_if_not_accepted, |
705 | + request_error=StandardError, |
706 | + event_error='SYS_SET_CAPABILITIES_ERROR', |
707 | + event_ok=None, |
708 | + args=('query_caps', caps, error_msg) |
709 | + ) |
710 | + yield query_caps_d |
711 | + |
712 | + if self.deferred.called: |
713 | + # the query_caps call finished with failure |
714 | + return |
715 | + |
716 | + error_msg = "The server denied setting '%s' capabilities" % caps |
717 | + set_caps_d = self._send_request_and_handle_errors( |
718 | + request=caps_raising_if_not_accepted, |
719 | + request_error=StandardError, |
720 | + event_error='SYS_SET_CAPABILITIES_ERROR', |
721 | + event_ok='SYS_SET_CAPABILITIES_OK', |
722 | + args=('set_caps', caps, error_msg) |
723 | + ) |
724 | + yield set_caps_d |
725 | |
726 | @defer.inlineCallbacks |
727 | def authenticate(self, oauth_consumer): |
728 | - client = self.client |
729 | - try: |
730 | - yield client.oauth_authenticate(oauth_consumer, self.token) |
731 | - except request.StorageRequestError, e: |
732 | - if client is not self.client: |
733 | - return |
734 | - if e.error_message.error.type == \ |
735 | - protocol_pb2.Error.AUTHENTICATION_FAILED: |
736 | - logger.error("OAuth failed: %s", e) |
737 | - self.event_queue.push('SYS_AUTH_ERROR', error=str(e)) |
738 | - else: |
739 | - logger.error("StorageRequestError during OAuth: %s", e) |
740 | - self.event_queue.push('SYS_UNKNOWN_ERROR') |
741 | - self.deferred.callback(Failure(e)) |
742 | - except Exception, e: |
743 | - if client is not self.client: |
744 | - return |
745 | - logger.error("Generic error during OAuth: %s", e) |
746 | - self.event_queue.push('SYS_UNKNOWN_ERROR') |
747 | - self.deferred.callback(Failure(e)) |
748 | - else: |
749 | - if client is not self.client: |
750 | - return |
751 | - logger.info("Oauth OK") |
752 | - self.event_queue.push('SYS_AUTH_OK') |
753 | - self.deferred.callback(client) |
754 | + """Authenticate the client against the server using oauth_consumer.""" |
755 | + authenticate_d = self._send_request_and_handle_errors( |
756 | + request=self.client.oauth_authenticate, |
757 | + request_error=errors.AuthenticationFailedError, |
758 | + event_error='SYS_AUTH_ERROR', event_ok='SYS_AUTH_OK', |
759 | + args=(oauth_consumer, self.token) |
760 | + ) |
761 | + yield authenticate_d |
762 | + |
763 | + if not self.deferred.called: |
764 | + # callback the deferred if everything went ok |
765 | + self.deferred.callback(self.client) |
766 | |
767 | @defer.inlineCallbacks |
768 | def server_rescan(self, root_mdid, data_gen): |
769 | |
770 | === modified file 'ubuntuone/syncdaemon/event_queue.py' |
771 | --- ubuntuone/syncdaemon/event_queue.py 2010-04-08 21:32:57 +0000 |
772 | +++ ubuntuone/syncdaemon/event_queue.py 2010-04-12 13:34:34 +0000 |
773 | @@ -129,6 +129,7 @@ |
774 | 'SYS_HANDSHAKE_TIMEOUT': (), |
775 | 'SYS_ROOT_RECEIVED': ('root_id',), |
776 | 'SYS_ROOT_MISMATCH': ('root_id', 'new_root_id'), |
777 | + 'SYS_SERVER_ERROR': ('error',), |
778 | |
779 | 'VM_UDF_SUBSCRIBED': ('udf',), |
780 | 'VM_UDF_SUBSCRIBE_ERROR': ('udf_id', 'error'), |
Is this a fix for something that is going into Lucid? If so, please link it. If not, I'm afraid it would be a lot easier if we merge it after the freeze.