Merge lp:~jml/launchpad/more-services into lp:launchpad

Proposed by Jonathan Lange
Status: Merged
Merged at revision: not available
Proposed branch: lp:~jml/launchpad/more-services
Merge into: lp:launchpad
Diff against target: 800 lines (+208/-237)
15 files modified
lib/canonical/launchpad/scripts/__init__.py (+0/-3)
lib/canonical/launchpad/xmlrpc/faults.py (+1/-35)
lib/canonical/launchpad/xmlrpc/tests/test_authserver.py (+2/-8)
lib/canonical/launchpad/xmlrpc/tests/test_faults.py (+0/-65)
lib/lp/code/xmlrpc/tests/test_branch.py (+2/-1)
lib/lp/code/xmlrpc/tests/test_codehosting.py (+20/-34)
lib/lp/codehosting/sshserver/accesslog.py (+1/-15)
lib/lp/codehosting/sshserver/auth.py (+1/-1)
lib/lp/codehosting/vfs/branchfs.py (+3/-2)
lib/lp/codehosting/vfs/branchfsclient.py (+0/-18)
lib/lp/codehosting/vfs/tests/test_branchfsclient.py (+1/-54)
lib/lp/services/twistedsupport/tests/test_xmlrpc.py (+90/-0)
lib/lp/services/twistedsupport/xmlrpc.py (+27/-0)
lib/lp/services/utils.py (+20/-1)
lib/lp/services/xmlrpc.py (+40/-0)
To merge this branch: bzr merge lp:~jml/launchpad/more-services
Reviewer Review Type Date Requested Status
Abel Deuring (community) code Approve
Review via email: mp+23107@code.launchpad.net

Commit message

Move Twisted fault support and the base LaunchpadFault to lp.services. Move 'synchronize' helper to lp.services.

Description of the change

This branch extracts some drive-by-cleanups from my uber-branch lp:~jml/launchpad/ssh-key-auth.

Specifically:
  1. The higher-order function "synchronize" is moved to lp.services
  2. The "trap_fault" helper for XMLRPC Twisted code is moved to lp.services.twistedsupport
  3. The "LaunchpadFault" base class for all of our XMLRPC faults is moved to lp.services.xmlrpc
  4. A XXX comment from 2005 is deleted

I know it's a bit of a grab-bag of changes, but they are all conceptually quite simple, so I hope it's an easy enough branch to review.

To post a comment you must log in.
Revision history for this message
Abel Deuring (adeuring) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/canonical/launchpad/scripts/__init__.py'
--- lib/canonical/launchpad/scripts/__init__.py 2010-03-18 21:30:51 +0000
+++ lib/canonical/launchpad/scripts/__init__.py 2010-04-10 13:58:26 +0000
@@ -46,9 +46,6 @@
46# We should probably split out all the stuff in this directory that46# We should probably split out all the stuff in this directory that
47# doesn't rely on Zope and migrate it to canonical/scripts.47# doesn't rely on Zope and migrate it to canonical/scripts.
4848
49# XXX SteveAlexander 2005-04-11:
50# This is a total mess. I need to work out what this all means.
51
52class NullItem:49class NullItem:
53 def __init__(self, context, handler, info, *argdata):50 def __init__(self, context, handler, info, *argdata):
54 newcontext = GroupingContextDecorator(context)51 newcontext = GroupingContextDecorator(context)
5552
=== modified file 'lib/canonical/launchpad/xmlrpc/faults.py'
--- lib/canonical/launchpad/xmlrpc/faults.py 2010-03-14 20:15:34 +0000
+++ lib/canonical/launchpad/xmlrpc/faults.py 2010-04-10 13:58:26 +0000
@@ -15,7 +15,6 @@
15 'BranchNameInUse',15 'BranchNameInUse',
16 'BranchUniqueNameConflict',16 'BranchUniqueNameConflict',
17 'CannotHaveLinkedBranch',17 'CannotHaveLinkedBranch',
18 'check_fault',
19 'FileBugGotProductAndDistro',18 'FileBugGotProductAndDistro',
20 'FileBugMissingProductOrDistribution',19 'FileBugMissingProductOrDistribution',
21 'InvalidBranchIdentifier',20 'InvalidBranchIdentifier',
@@ -42,41 +41,8 @@
42 ]41 ]
4342
4443
45import xmlrpclib
46
47
48from lp.registry.interfaces.projectgroup import IProjectGroup44from lp.registry.interfaces.projectgroup import IProjectGroup
4945from lp.services.xmlrpc import LaunchpadFault
50
51def check_fault(fault, *fault_classes):
52 """Check if 'fault's faultCode matches any of 'fault_classes'.
53
54 :param fault: An instance of `xmlrpclib.Fault`.
55 :param fault_classes: Any number of `LaunchpadFault` subclasses.
56 """
57 for cls in fault_classes:
58 if fault.faultCode == cls.error_code:
59 return True
60 return False
61
62
63class LaunchpadFault(xmlrpclib.Fault):
64 """Base class for a Launchpad XMLRPC fault.
65
66 Subclasses should define a unique error_code and a msg_template,
67 which will be interpolated with the given keyword arguments.
68 """
69
70 error_code = None
71 msg_template = None
72
73 def __init__(self, **kw):
74 assert self.error_code is not None, (
75 "Subclasses must define error_code.")
76 assert self.msg_template is not None, (
77 "Subclasses must define msg_template.")
78 msg = self.msg_template % kw
79 xmlrpclib.Fault.__init__(self, self.error_code, msg)
8046
8147
82class NoSuchProduct(LaunchpadFault):48class NoSuchProduct(LaunchpadFault):
8349
=== modified file 'lib/canonical/launchpad/xmlrpc/tests/test_authserver.py'
--- lib/canonical/launchpad/xmlrpc/tests/test_authserver.py 2009-06-25 05:30:52 +0000
+++ lib/canonical/launchpad/xmlrpc/tests/test_authserver.py 2010-04-10 13:58:26 +0000
@@ -16,6 +16,7 @@
16from canonical.launchpad.xmlrpc.authserver import AuthServerAPIView16from canonical.launchpad.xmlrpc.authserver import AuthServerAPIView
17from canonical.testing.layers import DatabaseFunctionalLayer17from canonical.testing.layers import DatabaseFunctionalLayer
1818
19
19class GetUserAndSSHKeysTests(TestCaseWithFactory):20class GetUserAndSSHKeysTests(TestCaseWithFactory):
20 """Tests for the implementation of `IAuthServer.getUserAndSSHKeys`.21 """Tests for the implementation of `IAuthServer.getUserAndSSHKeys`.
21 """22 """
@@ -28,17 +29,10 @@
28 self.authserver = AuthServerAPIView(29 self.authserver = AuthServerAPIView(
29 private_root.authserver, TestRequest())30 private_root.authserver, TestRequest())
3031
31 def assertFaultEqual(self, expected_fault, observed_fault):
32 """Assert that `expected_fault` equals `observed_fault`."""
33 self.assertIsInstance(observed_fault, faults.LaunchpadFault)
34 self.assertEqual(expected_fault.faultCode, observed_fault.faultCode)
35 self.assertEqual(
36 expected_fault.faultString, observed_fault.faultString)
37
38 def test_user_not_found(self):32 def test_user_not_found(self):
39 # getUserAndSSHKeys returns the NoSuchPersonWithName fault if there is33 # getUserAndSSHKeys returns the NoSuchPersonWithName fault if there is
40 # no Person of the given name.34 # no Person of the given name.
41 self.assertFaultEqual(35 self.assertEqual(
42 faults.NoSuchPersonWithName('no-one'),36 faults.NoSuchPersonWithName('no-one'),
43 self.authserver.getUserAndSSHKeys('no-one'))37 self.authserver.getUserAndSSHKeys('no-one'))
4438
4539
=== removed file 'lib/canonical/launchpad/xmlrpc/tests/test_faults.py'
--- lib/canonical/launchpad/xmlrpc/tests/test_faults.py 2009-06-25 05:30:52 +0000
+++ lib/canonical/launchpad/xmlrpc/tests/test_faults.py 1970-01-01 00:00:00 +0000
@@ -1,65 +0,0 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Tests for `canonical.launchpad.xmlrpc.faults`."""
5
6__metaclass__ = type
7
8import unittest
9
10from lp.testing import TestCase
11from canonical.launchpad.xmlrpc import faults
12
13
14class TestFaultOne(faults.LaunchpadFault):
15 """An arbitrary subclass of `LaunchpadFault`.
16
17 This class and `TestFaultTwo` are a pair of distinct `LaunchpadFault`
18 subclasses to use in tests.
19 """
20
21 error_code = 1001
22 msg_template = "Fault one."
23
24
25class TestFaultTwo(faults.LaunchpadFault):
26 """Another arbitrary subclass of `LaunchpadFault`.
27
28 This class and `TestFaultOne` are a pair of distinct `LaunchpadFault`
29 subclasses to use in tests.
30 """
31
32 error_code = 1002
33 msg_template = "Fault two."
34
35
36class TestTrapFault(TestCase):
37 """Tests for `check_fault`."""
38
39 def test_wrong_fault(self):
40 # check_fault returns False if the passed fault does not have the code
41 # of one of the passed classes.
42 self.assertFalse(
43 faults.check_fault(TestFaultOne(), TestFaultTwo))
44
45 def test_no_fault_classes(self):
46 # check_fault returns False if there are no passed classes.
47 self.assertFalse(
48 faults.check_fault(TestFaultOne()))
49
50 def test_matches(self):
51 # check_fault returns True if the passed fault has the code of the
52 # passed class.
53 self.assertTrue(
54 faults.check_fault(TestFaultOne(), TestFaultOne))
55
56 def test_matches_one_of_set(self):
57 # check_fault returns True if the passed fault has the code of one of
58 # the passed classes.
59 self.assertTrue(faults.check_fault(
60 TestFaultOne(), TestFaultOne, TestFaultTwo))
61
62
63def test_suite():
64 return unittest.TestLoader().loadTestsFromName(__name__)
65
660
=== modified file 'lib/lp/code/xmlrpc/tests/test_branch.py'
--- lib/lp/code/xmlrpc/tests/test_branch.py 2010-02-22 06:16:47 +0000
+++ lib/lp/code/xmlrpc/tests/test_branch.py 2010-04-10 13:58:26 +0000
@@ -21,6 +21,7 @@
21from lp.testing import TestCaseWithFactory21from lp.testing import TestCaseWithFactory
22from lazr.uri import URI22from lazr.uri import URI
23from lp.code.xmlrpc.branch import PublicCodehostingAPI23from lp.code.xmlrpc.branch import PublicCodehostingAPI
24from lp.services.xmlrpc import LaunchpadFault
24from canonical.launchpad.xmlrpc import faults25from canonical.launchpad.xmlrpc import faults
25from canonical.testing import DatabaseFunctionalLayer26from canonical.testing import DatabaseFunctionalLayer
2627
@@ -62,7 +63,7 @@
62 """Assert that `lp_url_path` path expands to `unique_name`."""63 """Assert that `lp_url_path` path expands to `unique_name`."""
63 results = self.api.resolve_lp_path(lp_url_path)64 results = self.api.resolve_lp_path(lp_url_path)
64 # This improves the error message if results happens to be a fault.65 # This improves the error message if results happens to be a fault.
65 if isinstance(results, faults.LaunchpadFault):66 if isinstance(results, LaunchpadFault):
66 raise results67 raise results
67 for url in results['urls']:68 for url in results['urls']:
68 self.assertEqual('/' + unique_name, URI(url).path)69 self.assertEqual('/' + unique_name, URI(url).path)
6970
=== modified file 'lib/lp/code/xmlrpc/tests/test_codehosting.py'
--- lib/lp/code/xmlrpc/tests/test_codehosting.py 2010-02-24 19:01:25 +0000
+++ lib/lp/code/xmlrpc/tests/test_codehosting.py 2010-04-10 13:58:26 +0000
@@ -137,13 +137,6 @@
137 self.branch_lookup = frontend.getBranchLookup()137 self.branch_lookup = frontend.getBranchLookup()
138 self.getLastActivity = frontend.getLastActivity138 self.getLastActivity = frontend.getLastActivity
139139
140 def assertFaultEqual(self, expected_fault, observed_fault):
141 """Assert that `expected_fault` equals `observed_fault`."""
142 self.assertIsInstance(observed_fault, faults.LaunchpadFault)
143 self.assertEqual(expected_fault.faultCode, observed_fault.faultCode)
144 self.assertEqual(
145 expected_fault.faultString, observed_fault.faultString)
146
147 def assertMirrorFailed(self, branch, failure_message, num_failures=1):140 def assertMirrorFailed(self, branch, failure_message, num_failures=1):
148 """Assert that `branch` failed to mirror.141 """Assert that `branch` failed to mirror.
149142
@@ -204,7 +197,7 @@
204 # exist.197 # exist.
205 invalid_id = self.getUnusedBranchID()198 invalid_id = self.getUnusedBranchID()
206 fault = self.storage.startMirroring(invalid_id)199 fault = self.storage.startMirroring(invalid_id)
207 self.assertFaultEqual(faults.NoBranchWithID(invalid_id), fault)200 self.assertEqual(faults.NoBranchWithID(invalid_id), fault)
208201
209 def test_mirrorFailed(self):202 def test_mirrorFailed(self):
210 branch = self.factory.makeAnyBranch()203 branch = self.factory.makeAnyBranch()
@@ -220,7 +213,7 @@
220 branch_id = self.getUnusedBranchID()213 branch_id = self.getUnusedBranchID()
221 failure_message = self.factory.getUniqueString()214 failure_message = self.factory.getUniqueString()
222 fault = self.storage.mirrorFailed(branch_id, failure_message)215 fault = self.storage.mirrorFailed(branch_id, failure_message)
223 self.assertFaultEqual(faults.NoBranchWithID(branch_id), fault)216 self.assertEqual(faults.NoBranchWithID(branch_id), fault)
224217
225 def test_mirrorComplete(self):218 def test_mirrorComplete(self):
226 # mirrorComplete marks the branch as having been successfully219 # mirrorComplete marks the branch as having been successfully
@@ -240,7 +233,7 @@
240 branch_id = self.getUnusedBranchID()233 branch_id = self.getUnusedBranchID()
241 fault = self.storage.mirrorComplete(234 fault = self.storage.mirrorComplete(
242 branch_id, self.factory.getUniqueString())235 branch_id, self.factory.getUniqueString())
243 self.assertFaultEqual(faults.NoBranchWithID(branch_id), fault)236 self.assertEqual(faults.NoBranchWithID(branch_id), fault)
244237
245 def test_mirrorComplete_resets_failure_count(self):238 def test_mirrorComplete_resets_failure_count(self):
246 # mirrorComplete marks the branch as successfully mirrored and removes239 # mirrorComplete marks the branch as successfully mirrored and removes
@@ -388,7 +381,7 @@
388 stacked_branch = self.factory.makeAnyBranch()381 stacked_branch = self.factory.makeAnyBranch()
389 url = self.factory.getUniqueURL()382 url = self.factory.getUniqueURL()
390 fault = self.storage.setStackedOn(stacked_branch.id, url)383 fault = self.storage.setStackedOn(stacked_branch.id, url)
391 self.assertFaultEqual(faults.NoSuchBranch(url), fault)384 self.assertEqual(faults.NoSuchBranch(url), fault)
392385
393 def test_setStackedOnNoBranchWithID(self):386 def test_setStackedOnNoBranchWithID(self):
394 # If setStackedOn is called for a branch that doesn't exist, it will387 # If setStackedOn is called for a branch that doesn't exist, it will
@@ -397,7 +390,7 @@
397 branch_type=BranchType.MIRRORED)390 branch_type=BranchType.MIRRORED)
398 branch_id = self.getUnusedBranchID()391 branch_id = self.getUnusedBranchID()
399 fault = self.storage.setStackedOn(branch_id, stacked_on_branch.url)392 fault = self.storage.setStackedOn(branch_id, stacked_on_branch.url)
400 self.assertFaultEqual(faults.NoBranchWithID(branch_id), fault)393 self.assertEqual(faults.NoBranchWithID(branch_id), fault)
401394
402395
403class AcquireBranchToPullTestsViaEndpoint(TestCaseWithFactory,396class AcquireBranchToPullTestsViaEndpoint(TestCaseWithFactory,
@@ -499,13 +492,6 @@
499 self.factory = frontend.getLaunchpadObjectFactory()492 self.factory = frontend.getLaunchpadObjectFactory()
500 self.branch_lookup = frontend.getBranchLookup()493 self.branch_lookup = frontend.getBranchLookup()
501494
502 def assertFaultEqual(self, expected_fault, observed_fault):
503 """Assert that `expected_fault` equals `observed_fault`."""
504 self.assertIsInstance(observed_fault, faults.LaunchpadFault)
505 self.assertEqual(expected_fault.faultCode, observed_fault.faultCode)
506 self.assertEqual(
507 expected_fault.faultString, observed_fault.faultString)
508
509 def test_createBranch(self):495 def test_createBranch(self):
510 # createBranch creates a branch with the supplied details and the496 # createBranch creates a branch with the supplied details and the
511 # caller as registrant.497 # caller as registrant.
@@ -527,7 +513,7 @@
527 path = escape(u'invalid')513 path = escape(u'invalid')
528 fault = self.branchfs.createBranch(requester.id, path)514 fault = self.branchfs.createBranch(requester.id, path)
529 login(ANONYMOUS)515 login(ANONYMOUS)
530 self.assertFaultEqual(faults.InvalidPath(path), fault)516 self.assertEqual(faults.InvalidPath(path), fault)
531517
532 def test_createBranch_junk(self):518 def test_createBranch_junk(self):
533 # createBranch can create +junk branches.519 # createBranch can create +junk branches.
@@ -565,7 +551,7 @@
565 message = "Project 'no-such-product' does not exist."551 message = "Project 'no-such-product' does not exist."
566 fault = self.branchfs.createBranch(552 fault = self.branchfs.createBranch(
567 owner.id, escape('/~%s/no-such-product/%s' % (owner.name, name)))553 owner.id, escape('/~%s/no-such-product/%s' % (owner.name, name)))
568 self.assertFaultEqual(faults.NotFound(message), fault)554 self.assertEqual(faults.NotFound(message), fault)
569555
570 def test_createBranch_other_user(self):556 def test_createBranch_other_user(self):
571 # Creating a branch under another user's directory fails.557 # Creating a branch under another user's directory fails.
@@ -578,7 +564,7 @@
578 fault = self.branchfs.createBranch(564 fault = self.branchfs.createBranch(
579 creator.id,565 creator.id,
580 escape('/~%s/%s/%s' % (other_person.name, product.name, name)))566 escape('/~%s/%s/%s' % (other_person.name, product.name, name)))
581 self.assertFaultEqual(faults.PermissionDenied(message), fault)567 self.assertEqual(faults.PermissionDenied(message), fault)
582568
583 def test_createBranch_bad_name(self):569 def test_createBranch_bad_name(self):
584 # Creating a branch with an invalid name fails.570 # Creating a branch with an invalid name fails.
@@ -590,7 +576,7 @@
590 fault = self.branchfs.createBranch(576 fault = self.branchfs.createBranch(
591 owner.id, escape(577 owner.id, escape(
592 '/~%s/%s/%s' % (owner.name, product.name, invalid_name)))578 '/~%s/%s/%s' % (owner.name, product.name, invalid_name)))
593 self.assertFaultEqual(faults.PermissionDenied(message), fault)579 self.assertEqual(faults.PermissionDenied(message), fault)
594580
595 def test_createBranch_unicode_name(self):581 def test_createBranch_unicode_name(self):
596 # Creating a branch with an invalid name fails.582 # Creating a branch with an invalid name fails.
@@ -603,7 +589,7 @@
603 fault = self.branchfs.createBranch(589 fault = self.branchfs.createBranch(
604 owner.id, escape(590 owner.id, escape(
605 '/~%s/%s/%s' % (owner.name, product.name, invalid_name)))591 '/~%s/%s/%s' % (owner.name, product.name, invalid_name)))
606 self.assertFaultEqual(592 self.assertEqual(
607 faults.PermissionDenied(message), fault)593 faults.PermissionDenied(message), fault)
608594
609 def test_createBranch_bad_user(self):595 def test_createBranch_bad_user(self):
@@ -614,7 +600,7 @@
614 message = "User/team 'no-one' does not exist."600 message = "User/team 'no-one' does not exist."
615 fault = self.branchfs.createBranch(601 fault = self.branchfs.createBranch(
616 owner.id, escape('/~no-one/%s/%s' % (product.name, name)))602 owner.id, escape('/~no-one/%s/%s' % (product.name, name)))
617 self.assertFaultEqual(faults.NotFound(message), fault)603 self.assertEqual(faults.NotFound(message), fault)
618604
619 def test_createBranch_bad_user_bad_product(self):605 def test_createBranch_bad_user_bad_product(self):
620 # If both the user and the product are not found, then the missing606 # If both the user and the product are not found, then the missing
@@ -625,7 +611,7 @@
625 message = "User/team 'no-one' does not exist."611 message = "User/team 'no-one' does not exist."
626 fault = self.branchfs.createBranch(612 fault = self.branchfs.createBranch(
627 owner.id, escape('/~no-one/no-product/%s' % (name,)))613 owner.id, escape('/~no-one/no-product/%s' % (name,)))
628 self.assertFaultEqual(faults.NotFound(message), fault)614 self.assertEqual(faults.NotFound(message), fault)
629615
630 def test_createBranch_not_branch(self):616 def test_createBranch_not_branch(self):
631 # Trying to create a branch at a path that's not valid for branches617 # Trying to create a branch at a path that's not valid for branches
@@ -634,7 +620,7 @@
634 path = escape('/~%s' % owner.name)620 path = escape('/~%s' % owner.name)
635 fault = self.branchfs.createBranch(owner.id, path)621 fault = self.branchfs.createBranch(owner.id, path)
636 message = "Cannot create branch at '%s'" % path622 message = "Cannot create branch at '%s'" % path
637 self.assertFaultEqual(faults.PermissionDenied(message), fault)623 self.assertEqual(faults.PermissionDenied(message), fault)
638624
639 def test_createBranch_source_package(self):625 def test_createBranch_source_package(self):
640 # createBranch can take the path to a source package branch and create626 # createBranch can take the path to a source package branch and create
@@ -671,7 +657,7 @@
671 branch_name)657 branch_name)
672 fault = self.branchfs.createBranch(owner.id, escape(unique_name))658 fault = self.branchfs.createBranch(owner.id, escape(unique_name))
673 message = "No such distribution: 'ningnangnong'."659 message = "No such distribution: 'ningnangnong'."
674 self.assertFaultEqual(faults.NotFound(message), fault)660 self.assertEqual(faults.NotFound(message), fault)
675661
676 def test_createBranch_invalid_distroseries(self):662 def test_createBranch_invalid_distroseries(self):
677 # If createBranch is called with the path to a non-existent663 # If createBranch is called with the path to a non-existent
@@ -685,7 +671,7 @@
685 branch_name)671 branch_name)
686 fault = self.branchfs.createBranch(owner.id, escape(unique_name))672 fault = self.branchfs.createBranch(owner.id, escape(unique_name))
687 message = "No such distribution series: 'ningnangnong'."673 message = "No such distribution series: 'ningnangnong'."
688 self.assertFaultEqual(faults.NotFound(message), fault)674 self.assertEqual(faults.NotFound(message), fault)
689675
690 def test_createBranch_invalid_sourcepackagename(self):676 def test_createBranch_invalid_sourcepackagename(self):
691 # If createBranch is called with the path to an invalid source677 # If createBranch is called with the path to an invalid source
@@ -698,7 +684,7 @@
698 branch_name)684 branch_name)
699 fault = self.branchfs.createBranch(owner.id, escape(unique_name))685 fault = self.branchfs.createBranch(owner.id, escape(unique_name))
700 message = "No such source package: 'ningnangnong'."686 message = "No such source package: 'ningnangnong'."
701 self.assertFaultEqual(faults.NotFound(message), fault)687 self.assertEqual(faults.NotFound(message), fault)
702688
703 def test_initialMirrorRequest(self):689 def test_initialMirrorRequest(self):
704 # The default 'next_mirror_time' for a newly created hosted branch690 # The default 'next_mirror_time' for a newly created hosted branch
@@ -727,21 +713,21 @@
727 def assertCannotTranslate(self, requester, path):713 def assertCannotTranslate(self, requester, path):
728 """Assert that we cannot translate 'path'."""714 """Assert that we cannot translate 'path'."""
729 fault = self.branchfs.translatePath(requester.id, path)715 fault = self.branchfs.translatePath(requester.id, path)
730 self.assertFaultEqual(faults.PathTranslationError(path), fault)716 self.assertEqual(faults.PathTranslationError(path), fault)
731717
732 def assertNotFound(self, requester, path):718 def assertNotFound(self, requester, path):
733 """Assert that the given path cannot be found."""719 """Assert that the given path cannot be found."""
734 if requester not in [LAUNCHPAD_ANONYMOUS, LAUNCHPAD_SERVICES]:720 if requester not in [LAUNCHPAD_ANONYMOUS, LAUNCHPAD_SERVICES]:
735 requester = requester.id721 requester = requester.id
736 fault = self.branchfs.translatePath(requester, path)722 fault = self.branchfs.translatePath(requester, path)
737 self.assertFaultEqual(faults.PathTranslationError(path), fault)723 self.assertEqual(faults.PathTranslationError(path), fault)
738724
739 def assertPermissionDenied(self, requester, path):725 def assertPermissionDenied(self, requester, path):
740 """Assert that looking at the given path gives permission denied."""726 """Assert that looking at the given path gives permission denied."""
741 if requester not in [LAUNCHPAD_ANONYMOUS, LAUNCHPAD_SERVICES]:727 if requester not in [LAUNCHPAD_ANONYMOUS, LAUNCHPAD_SERVICES]:
742 requester = requester.id728 requester = requester.id
743 fault = self.branchfs.translatePath(requester, path)729 fault = self.branchfs.translatePath(requester, path)
744 self.assertFaultEqual(faults.PermissionDenied(), fault)730 self.assertEqual(faults.PermissionDenied(), fault)
745731
746 def _makeProductWithDevFocus(self, private=False):732 def _makeProductWithDevFocus(self, private=False):
747 """Make a stacking-enabled product with a development focus.733 """Make a stacking-enabled product with a development focus.
@@ -769,7 +755,7 @@
769 requester = self.factory.makePerson()755 requester = self.factory.makePerson()
770 path = escape(u'invalid')756 path = escape(u'invalid')
771 fault = self.branchfs.translatePath(requester.id, path)757 fault = self.branchfs.translatePath(requester.id, path)
772 self.assertFaultEqual(faults.InvalidPath(path), fault)758 self.assertEqual(faults.InvalidPath(path), fault)
773759
774 def test_translatePath_branch(self):760 def test_translatePath_branch(self):
775 requester = self.factory.makePerson()761 requester = self.factory.makePerson()
776762
=== modified file 'lib/lp/codehosting/sshserver/accesslog.py'
--- lib/lp/codehosting/sshserver/accesslog.py 2010-03-19 10:43:51 +0000
+++ lib/lp/codehosting/sshserver/accesslog.py 2010-04-10 13:58:26 +0000
@@ -29,24 +29,10 @@
2929
30from canonical.config import config30from canonical.config import config
31from canonical.launchpad.scripts import WatchedFileHandler31from canonical.launchpad.scripts import WatchedFileHandler
32from lp.services.utils import synchronize
32from lp.services.twistedsupport.loggingsupport import set_up_oops_reporting33from lp.services.twistedsupport.loggingsupport import set_up_oops_reporting
3334
3435
35def synchronize(source, target, add, remove):
36 """Update 'source' to match 'target' using 'add' and 'remove'.
37
38 Changes the container 'source' so that it equals 'target', calling 'add'
39 with any object in 'target' not in 'source' and 'remove' with any object
40 not in 'target' but in 'source'.
41 """
42 need_to_add = [obj for obj in target if obj not in source]
43 need_to_remove = [obj for obj in source if obj not in target]
44 for obj in need_to_add:
45 add(obj)
46 for obj in need_to_remove:
47 remove(obj)
48
49
50class LoggingManager:36class LoggingManager:
51 """Class for managing codehosting logging."""37 """Class for managing codehosting logging."""
5238
5339
=== modified file 'lib/lp/codehosting/sshserver/auth.py'
--- lib/lp/codehosting/sshserver/auth.py 2009-06-25 04:06:00 +0000
+++ lib/lp/codehosting/sshserver/auth.py 2010-04-10 13:58:26 +0000
@@ -42,7 +42,7 @@
42from lp.codehosting.sshserver import accesslog42from lp.codehosting.sshserver import accesslog
43from lp.codehosting.sshserver.session import (43from lp.codehosting.sshserver.session import (
44 launch_smart_server, PatchedSSHSession)44 launch_smart_server, PatchedSSHSession)
45from lp.codehosting.vfs.branchfsclient import trap_fault45from lp.services.twistedsupport.xmlrpc import trap_fault
46from canonical.config import config46from canonical.config import config
47from canonical.launchpad.xmlrpc import faults47from canonical.launchpad.xmlrpc import faults
4848
4949
=== modified file 'lib/lp/codehosting/vfs/branchfs.py'
--- lib/lp/codehosting/vfs/branchfs.py 2010-03-23 03:35:13 +0000
+++ lib/lp/codehosting/vfs/branchfs.py 2010-04-10 13:58:26 +0000
@@ -83,16 +83,17 @@
83from zope.interface import implements, Interface83from zope.interface import implements, Interface
8484
85from lp.codehosting.vfs.branchfsclient import (85from lp.codehosting.vfs.branchfsclient import (
86 BlockingProxy, BranchFileSystemClient, trap_fault)86 BlockingProxy, BranchFileSystemClient)
87from lp.codehosting.vfs.transport import (87from lp.codehosting.vfs.transport import (
88 AsyncVirtualServer, AsyncVirtualTransport, _MultiServer,88 AsyncVirtualServer, AsyncVirtualTransport, _MultiServer,
89 get_chrooted_transport, get_readonly_transport, TranslationError)89 get_chrooted_transport, get_readonly_transport, TranslationError)
90from canonical.config import config90from canonical.config import config
91from canonical.launchpad.xmlrpc import faults
91from lp.code.enums import BranchType92from lp.code.enums import BranchType
92from lp.code.interfaces.branchlookup import IBranchLookup93from lp.code.interfaces.branchlookup import IBranchLookup
93from lp.code.interfaces.codehosting import (94from lp.code.interfaces.codehosting import (
94 BRANCH_TRANSPORT, CONTROL_TRANSPORT, LAUNCHPAD_SERVICES)95 BRANCH_TRANSPORT, CONTROL_TRANSPORT, LAUNCHPAD_SERVICES)
95from canonical.launchpad.xmlrpc import faults96from lp.services.twistedsupport.xmlrpc import trap_fault
9697
9798
98class BadUrl(Exception):99class BadUrl(Exception):
99100
=== modified file 'lib/lp/codehosting/vfs/branchfsclient.py'
--- lib/lp/codehosting/vfs/branchfsclient.py 2010-03-23 02:34:35 +0000
+++ lib/lp/codehosting/vfs/branchfsclient.py 2010-04-10 13:58:26 +0000
@@ -11,13 +11,11 @@
11 'BlockingProxy',11 'BlockingProxy',
12 'BranchFileSystemClient',12 'BranchFileSystemClient',
13 'NotInCache',13 'NotInCache',
14 'trap_fault',
15 ]14 ]
1615
17import time16import time
1817
19from twisted.internet import defer18from twisted.internet import defer
20from twisted.web.xmlrpc import Fault
2119
22from lp.code.interfaces.codehosting import BRANCH_TRANSPORT20from lp.code.interfaces.codehosting import BRANCH_TRANSPORT
2321
@@ -136,19 +134,3 @@
136 'translatePath', self._user_id, path)134 'translatePath', self._user_id, path)
137 deferred.addCallback(self._addToCache, path)135 deferred.addCallback(self._addToCache, path)
138 return deferred136 return deferred
139
140
141def trap_fault(failure, *fault_classes):
142 """Trap a fault, based on fault code.
143
144 :param failure: A Twisted L{Failure}.
145 :param *fault_codes: `LaunchpadFault` subclasses.
146 :raise Failure: if 'failure' is not a Fault failure, or if the fault code
147 does not match the given codes.
148 :return: The Fault if it matches one of the codes.
149 """
150 failure.trap(Fault)
151 fault = failure.value
152 if fault.faultCode in [cls.error_code for cls in fault_classes]:
153 return fault
154 raise failure
155137
=== modified file 'lib/lp/codehosting/vfs/tests/test_branchfsclient.py'
--- lib/lp/codehosting/vfs/tests/test_branchfsclient.py 2010-03-23 02:34:35 +0000
+++ lib/lp/codehosting/vfs/tests/test_branchfsclient.py 2010-04-10 13:58:26 +0000
@@ -9,16 +9,13 @@
99
10import unittest10import unittest
1111
12from twisted.python.failure import Failure
13from twisted.trial.unittest import TestCase12from twisted.trial.unittest import TestCase
1413
15from lp.codehosting.vfs.branchfsclient import (14from lp.codehosting.vfs.branchfsclient import (
16 BranchFileSystemClient, NotInCache, trap_fault)15 BranchFileSystemClient, NotInCache)
17from lp.codehosting.inmemory import InMemoryFrontend, XMLRPCWrapper16from lp.codehosting.inmemory import InMemoryFrontend, XMLRPCWrapper
18from lp.code.interfaces.codehosting import BRANCH_TRANSPORT17from lp.code.interfaces.codehosting import BRANCH_TRANSPORT
19from lp.testing import FakeTime18from lp.testing import FakeTime
20from canonical.launchpad.xmlrpc.tests.test_faults import (
21 TestFaultOne, TestFaultTwo)
2219
2320
24class TestBranchFileSystemClient(TestCase):21class TestBranchFileSystemClient(TestCase):
@@ -250,56 +247,6 @@
250 [branch1.unique_name, branch2.unique_name], seen_branches)247 [branch1.unique_name, branch2.unique_name], seen_branches)
251248
252249
253class TestTrapFault(TestCase):
254 """Tests for `trap_fault`."""
255
256 def makeFailure(self, exception_factory, *args, **kwargs):
257 """Make a `Failure` from the given exception factory."""
258 try:
259 raise exception_factory(*args, **kwargs)
260 except:
261 return Failure()
262
263 def assertRaisesFailure(self, failure, function, *args, **kwargs):
264 try:
265 function(*args, **kwargs)
266 except Failure, raised_failure:
267 self.assertEqual(failure, raised_failure)
268
269 def test_raises_non_faults(self):
270 # trap_fault re-raises any failures it gets that aren't faults.
271 failure = self.makeFailure(RuntimeError, 'example failure')
272 self.assertRaisesFailure(failure, trap_fault, failure, TestFaultOne)
273
274 def test_raises_faults_with_wrong_code(self):
275 # trap_fault re-raises any failures it gets that are faults but have
276 # the wrong fault code.
277 failure = self.makeFailure(TestFaultOne)
278 self.assertRaisesFailure(failure, trap_fault, failure, TestFaultTwo)
279
280 def test_raises_faults_if_no_codes_given(self):
281 # If trap_fault is not given any fault codes, it re-raises the fault
282 # failure.
283 failure = self.makeFailure(TestFaultOne)
284 self.assertRaisesFailure(failure, trap_fault, failure)
285
286 def test_returns_fault_if_code_matches(self):
287 # trap_fault returns the Fault inside the Failure if the fault code
288 # matches what's given.
289 failure = self.makeFailure(TestFaultOne)
290 fault = trap_fault(failure, TestFaultOne)
291 self.assertEqual(TestFaultOne.error_code, fault.faultCode)
292 self.assertEqual(TestFaultOne.msg_template, fault.faultString)
293
294 def test_returns_fault_if_code_matches_one_of_set(self):
295 # trap_fault returns the Fault inside the Failure if the fault code
296 # matches even one of the given fault codes.
297 failure = self.makeFailure(TestFaultOne)
298 fault = trap_fault(failure, TestFaultOne, TestFaultTwo)
299 self.assertEqual(TestFaultOne.error_code, fault.faultCode)
300 self.assertEqual(TestFaultOne.msg_template, fault.faultString)
301
302
303def test_suite():250def test_suite():
304 return unittest.TestLoader().loadTestsFromName(__name__)251 return unittest.TestLoader().loadTestsFromName(__name__)
305252
306253
=== added file 'lib/lp/services/twistedsupport/tests/test_xmlrpc.py'
--- lib/lp/services/twistedsupport/tests/test_xmlrpc.py 1970-01-01 00:00:00 +0000
+++ lib/lp/services/twistedsupport/tests/test_xmlrpc.py 2010-04-10 13:58:26 +0000
@@ -0,0 +1,90 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Tests for Twisted XML-RPC support."""
5
6__metaclass__ = type
7
8import unittest
9
10from twisted.python.failure import Failure
11from twisted.trial.unittest import TestCase
12
13from lp.services.xmlrpc import LaunchpadFault
14from lp.services.twistedsupport.xmlrpc import trap_fault
15
16
17class TestFaultOne(LaunchpadFault):
18 """An arbitrary subclass of `LaunchpadFault`.
19
20 This class and `TestFaultTwo` are a pair of distinct `LaunchpadFault`
21 subclasses to use in tests.
22 """
23
24 error_code = 1001
25 msg_template = "Fault one."
26
27
28class TestFaultTwo(LaunchpadFault):
29 """Another arbitrary subclass of `LaunchpadFault`.
30
31 This class and `TestFaultOne` are a pair of distinct `LaunchpadFault`
32 subclasses to use in tests.
33 """
34
35 error_code = 1002
36 msg_template = "Fault two."
37
38
39class TestTrapFault(TestCase):
40 """Tests for `trap_fault`."""
41
42 def makeFailure(self, exception_factory, *args, **kwargs):
43 """Make a `Failure` from the given exception factory."""
44 try:
45 raise exception_factory(*args, **kwargs)
46 except:
47 return Failure()
48
49 def assertRaisesFailure(self, failure, function, *args, **kwargs):
50 try:
51 function(*args, **kwargs)
52 except Failure, raised_failure:
53 self.assertEqual(failure, raised_failure)
54
55 def test_raises_non_faults(self):
56 # trap_fault re-raises any failures it gets that aren't faults.
57 failure = self.makeFailure(RuntimeError, 'example failure')
58 self.assertRaisesFailure(failure, trap_fault, failure, TestFaultOne)
59
60 def test_raises_faults_with_wrong_code(self):
61 # trap_fault re-raises any failures it gets that are faults but have
62 # the wrong fault code.
63 failure = self.makeFailure(TestFaultOne)
64 self.assertRaisesFailure(failure, trap_fault, failure, TestFaultTwo)
65
66 def test_raises_faults_if_no_codes_given(self):
67 # If trap_fault is not given any fault codes, it re-raises the fault
68 # failure.
69 failure = self.makeFailure(TestFaultOne)
70 self.assertRaisesFailure(failure, trap_fault, failure)
71
72 def test_returns_fault_if_code_matches(self):
73 # trap_fault returns the Fault inside the Failure if the fault code
74 # matches what's given.
75 failure = self.makeFailure(TestFaultOne)
76 fault = trap_fault(failure, TestFaultOne)
77 self.assertEqual(TestFaultOne.error_code, fault.faultCode)
78 self.assertEqual(TestFaultOne.msg_template, fault.faultString)
79
80 def test_returns_fault_if_code_matches_one_of_set(self):
81 # trap_fault returns the Fault inside the Failure if the fault code
82 # matches even one of the given fault codes.
83 failure = self.makeFailure(TestFaultOne)
84 fault = trap_fault(failure, TestFaultOne, TestFaultTwo)
85 self.assertEqual(TestFaultOne.error_code, fault.faultCode)
86 self.assertEqual(TestFaultOne.msg_template, fault.faultString)
87
88
89def test_suite():
90 return unittest.TestLoader().loadTestsFromName(__name__)
091
=== added file 'lib/lp/services/twistedsupport/xmlrpc.py'
--- lib/lp/services/twistedsupport/xmlrpc.py 1970-01-01 00:00:00 +0000
+++ lib/lp/services/twistedsupport/xmlrpc.py 2010-04-10 13:58:26 +0000
@@ -0,0 +1,27 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Support for XML-RPC stuff with Twisted."""
5
6__metaclass__ = type
7__all__ = [
8 'trap_fault',
9 ]
10
11from twisted.web.xmlrpc import Fault
12
13
14def trap_fault(failure, *fault_classes):
15 """Trap a fault, based on fault code.
16
17 :param failure: A Twisted L{Failure}.
18 :param *fault_codes: `LaunchpadFault` subclasses.
19 :raise Failure: if 'failure' is not a Fault failure, or if the fault code
20 does not match the given codes.
21 :return: The Fault if it matches one of the codes.
22 """
23 failure.trap(Fault)
24 fault = failure.value
25 if fault.faultCode in [cls.error_code for cls in fault_classes]:
26 return fault
27 raise failure
028
=== modified file 'lib/lp/services/utils.py'
--- lib/lp/services/utils.py 2010-03-18 22:53:50 +0000
+++ lib/lp/services/utils.py 2010-04-10 13:58:26 +0000
@@ -1,12 +1,16 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the1# Copyright 2009 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Generic Python utilities.
45
5"""Implementations of the XML-RPC APIs for codehosting."""6Functions, lists and so forth. Nothing here that does system calls or network
7stuff.
8"""
69
7__metaclass__ = type10__metaclass__ = type
8__all__ = [11__all__ = [
9 'iter_split',12 'iter_split',
13 'synchronize',
10 'text_delta',14 'text_delta',
11 'value_string',15 'value_string',
12 ]16 ]
@@ -35,6 +39,21 @@
35 yield splitter.join(tokens[:i]), splitter.join(tokens[i:])39 yield splitter.join(tokens[:i]), splitter.join(tokens[i:])
3640
3741
42def synchronize(source, target, add, remove):
43 """Update 'source' to match 'target' using 'add' and 'remove'.
44
45 Changes the container 'source' so that it equals 'target', calling 'add'
46 with any object in 'target' not in 'source' and 'remove' with any object
47 not in 'target' but in 'source'.
48 """
49 need_to_add = [obj for obj in target if obj not in source]
50 need_to_remove = [obj for obj in source if obj not in target]
51 for obj in need_to_add:
52 add(obj)
53 for obj in need_to_remove:
54 remove(obj)
55
56
38def value_string(item):57def value_string(item):
39 """Return a unicode string representing value.58 """Return a unicode string representing value.
4059
4160
=== added file 'lib/lp/services/xmlrpc.py'
--- lib/lp/services/xmlrpc.py 1970-01-01 00:00:00 +0000
+++ lib/lp/services/xmlrpc.py 2010-04-10 13:58:26 +0000
@@ -0,0 +1,40 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Generic code for XML-RPC in Launchpad."""
5
6__metaclass__ = type
7__all__ = [
8 'LaunchpadFault',
9 ]
10
11import xmlrpclib
12
13
14class LaunchpadFault(xmlrpclib.Fault):
15 """Base class for a Launchpad XMLRPC fault.
16
17 Subclasses should define a unique error_code and a msg_template,
18 which will be interpolated with the given keyword arguments.
19 """
20
21 error_code = None
22 msg_template = None
23
24 def __init__(self, **kw):
25 assert self.error_code is not None, (
26 "Subclasses must define error_code.")
27 assert self.msg_template is not None, (
28 "Subclasses must define msg_template.")
29 msg = self.msg_template % kw
30 xmlrpclib.Fault.__init__(self, self.error_code, msg)
31
32 def __eq__(self, other):
33 if not isinstance(other, LaunchpadFault):
34 return False
35 return (
36 self.faultCode == other.faultCode
37 and self.faultString == other.faultString)
38
39 def __ne__(self, other):
40 return not (self == other)