Merge lp:~nataliabidart/ubuntuone-client/improve-error-handling into lp:ubuntuone-client

Proposed by Natalia Bidart
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
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.

To post a comment you must log in.
Revision history for this message
John Lenton (chipaca) wrote :

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.

review: Needs Information
Revision history for this message
John Lenton (chipaca) wrote :

OK, brown paper bag for me. I promise I *looked* for the bugs.
Getting more coffee, now.

Revision history for this message
John Lenton (chipaca) :
review: Abstain
Revision history for this message
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.

review: Approve
Revision history for this message
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 ().

Revision history for this message
Facundo Batista (facundo) wrote :

It's ok!

review: Approve
497. By Natalia Bidart

Adding improvements suggested by facundobatista.

498. By Natalia Bidart

Fixing typos.

Preview Diff

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

Subscribers

People subscribed via source and target branches