Merge lp:~gmb/launchpad/enable-bz-3.4-bug-415779 into lp:launchpad/db-devel

Proposed by Graham Binns
Status: Merged
Approved by: Brad Crittenden
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~gmb/launchpad/enable-bz-3.4-bug-415779
Merge into: lp:launchpad/db-devel
Diff against target: None lines
To merge this branch: bzr merge lp:~gmb/launchpad/enable-bz-3.4-bug-415779
Reviewer Review Type Date Requested Status
Brad Crittenden (community) release-critical code Approve
Canonical Launchpad Engineering code Pending
Review via email: mp+12276@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Graham Binns (gmb) wrote :

This branch fixes bug 415779 by making it possible for Bugzilla.getExternalBugTrackerToUse() to recognise Bugzillas which offer an API.

I've done this by creating two methods: one which can be used to check whether the remote system has an API of the right version and one which can be used to check if the remote system has the LP plugin installed. There's a bit of duplication in these methods' try:except blocks, but I decided not to try and refactor these since doing so would make things a lot less easy to understand.

Revision history for this message
Brad Crittenden (bac) wrote :

The code looks good Graham.

review: Approve (release-critical code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/bugs/doc/externalbugtracker-bugzilla.txt'
--- lib/lp/bugs/doc/externalbugtracker-bugzilla.txt 2009-06-15 11:10:49 +0000
+++ lib/lp/bugs/doc/externalbugtracker-bugzilla.txt 2009-09-23 12:24:10 +0000
@@ -67,14 +67,18 @@
6767
68== Launchpad plugin ==68== Launchpad plugin ==
6969
70Some Bugzillas have a Launchpad plugin installed. For these bugtrackers,70Some Bugzillas offer the Bugzilla 3.4 XML-RPC API or have a Launchpad
71we use the BugzillaLPPlugin ExternalBugTracker class. The Bugzilla71plugin installed. For these bugtrackers, we use the BugzillaAPI
72ExternalBugTracker or its subclass, BugzillaLPPlugin, depending upon
73which type of Bugzilla we're dealing with. The Bugzilla
72ExternalBugTracker class has a getExternalBugTrackerToUse() method which74ExternalBugTracker class has a getExternalBugTrackerToUse() method which
73will return a BugzillaLPPlugin instance if the remote Bugzilla has the75will return a BugzillaAPI instance if the remote Bugzilla offers the 3.4
74plugin installed or a standard Bugzilla ExternalBugTracker if not.76API or a BugzillaLPPlugin instance if the remote Bugzilla has the
77plugin installed. If neither of these is offered, a standard Bugzilla
78ExternalBugTracker will be returned.
7579
76The Bugzilla ExternalBugTracker has an xmlrpc_proxy property which we80The Bugzilla ExternalBugTracker has a _test_xmlrpc_proxy property which
77override for the purpose of this test.81we override for the purpose of this test.
7882
79 >>> import xmlrpclib83 >>> import xmlrpclib
80 >>> class FailingXMLRPCTransport(xmlrpclib.Transport):84 >>> class FailingXMLRPCTransport(xmlrpclib.Transport):
@@ -103,14 +107,14 @@
103Bugzilla instance.107Bugzilla instance.
104108
105 >>> from lp.bugs.externalbugtracker import (109 >>> from lp.bugs.externalbugtracker import (
106 ... BugzillaLPPlugin)110 ... BugzillaAPI, BugzillaLPPlugin)
107 >>> bugzilla_to_use = bugzilla.getExternalBugTrackerToUse()111 >>> bugzilla_to_use = bugzilla.getExternalBugTrackerToUse()
108112
109The returned bugtracker will be a Bugzilla instance bug not a113The returned bugtracker will be a Bugzilla instance bug not a
110BugzillaLPPlugin instance.114BugzillaAPI instance.
111115
112 >>> (isinstance(bugzilla_to_use, Bugzilla) and116 >>> (isinstance(bugzilla_to_use, Bugzilla) and
113 ... not isinstance(bugzilla_to_use, BugzillaLPPlugin))117 ... not isinstance(bugzilla_to_use, BugzillaAPI))
114 True118 True
115119
116The same is true if getExternalBugTrackerToUse() receives a 404 error120The same is true if getExternalBugTrackerToUse() receives a 404 error
@@ -122,7 +126,7 @@
122 >>> bugzilla_to_use = bugzilla.getExternalBugTrackerToUse()126 >>> bugzilla_to_use = bugzilla.getExternalBugTrackerToUse()
123127
124 >>> (isinstance(bugzilla_to_use, Bugzilla) and128 >>> (isinstance(bugzilla_to_use, Bugzilla) and
125 ... not isinstance(bugzilla_to_use, BugzillaLPPlugin))129 ... not isinstance(bugzilla_to_use, BugzillaAPI))
126 True130 True
127131
128Some Bugzillas respond to an invalid XML-RPC method call by returning a132Some Bugzillas respond to an invalid XML-RPC method call by returning a
@@ -134,7 +138,7 @@
134 >>> bugzilla_to_use = bugzilla.getExternalBugTrackerToUse()138 >>> bugzilla_to_use = bugzilla.getExternalBugTrackerToUse()
135139
136 >>> (isinstance(bugzilla_to_use, Bugzilla) and140 >>> (isinstance(bugzilla_to_use, Bugzilla) and
137 ... not isinstance(bugzilla_to_use, BugzillaLPPlugin))141 ... not isinstance(bugzilla_to_use, BugzillaAPI))
138 True142 True
139143
140Some other Bugzillas generate an unparsable response, causing144Some other Bugzillas generate an unparsable response, causing
@@ -144,9 +148,59 @@
144 >>> bugzilla_to_use = bugzilla.getExternalBugTrackerToUse()148 >>> bugzilla_to_use = bugzilla.getExternalBugTrackerToUse()
145149
146 >>> (isinstance(bugzilla_to_use, Bugzilla) and150 >>> (isinstance(bugzilla_to_use, Bugzilla) and
151 ... not isinstance(bugzilla_to_use, BugzillaAPI))
152 True
153
154If the remote Bugzilla offers the Bugzilla 3.4 API, an instance of
155BuzillaAPI will be returned. To test this, we use a specially-crafted
156XML-RPC proxy that behaves like a Bugzilla 3.4 instance.
157
158 >>> class APIXMLRPCTransport(xmlrpclib.Transport):
159 ...
160 ... version = '3.4.2'
161 ...
162 ... def request(self, host, handler, request, verbose=None):
163 ... args, method_name = xmlrpclib.loads(request)
164 ...
165 ... if method_name == 'Bugzilla.version':
166 ... return [{'version': self.version}]
167 ... else:
168 ... raise xmlrpclib.Fault('Client', 'No such method')
169 ...
170 >>> test_transport = APIXMLRPCTransport()
171
172 >>> bugzilla._test_xmlrpc_proxy = xmlrpclib.ServerProxy(
173 ... 'http://example.com/xmlrpc.cgi',
174 ... transport=test_transport)
175
176 >>> bugzilla_to_use = bugzilla.getExternalBugTrackerToUse()
177 >>> (isinstance(bugzilla_to_use, BugzillaAPI) and
147 ... not isinstance(bugzilla_to_use, BugzillaLPPlugin))178 ... not isinstance(bugzilla_to_use, BugzillaLPPlugin))
148 True179 True
149180
181If the remote system has the Launchpad plugin installed, an
182getExternalBugTrackerToUse() will return a BugzillaLPPlugin instance.
183
184 >>> class PluginXMLRPCTransport(xmlrpclib.Transport):
185 ...
186 ... def request(self, host, handler, request, verbose=None):
187 ... args, method_name = xmlrpclib.loads(request)
188 ...
189 ... if method_name == 'Launchpad.plugin_version':
190 ... return [{'version': '0.2'}]
191 ... else:
192 ... raise xmlrpclib.Fault('Client', 'No such method')
193 ...
194 >>> test_transport = PluginXMLRPCTransport()
195
196 >>> bugzilla._test_xmlrpc_proxy = xmlrpclib.ServerProxy(
197 ... 'http://example.com/xmlrpc.cgi',
198 ... transport=test_transport)
199
200 >>> bugzilla_to_use = bugzilla.getExternalBugTrackerToUse()
201 >>> isinstance(bugzilla_to_use, BugzillaLPPlugin)
202 True
203
150204
151== Status Conversion ==205== Status Conversion ==
152206
153207
=== modified file 'lib/lp/bugs/externalbugtracker/bugzilla.py'
--- lib/lp/bugs/externalbugtracker/bugzilla.py 2009-09-02 08:32:27 +0000
+++ lib/lp/bugs/externalbugtracker/bugzilla.py 2009-09-23 12:24:10 +0000
@@ -54,23 +54,61 @@
54 self.remote_bug_status = {}54 self.remote_bug_status = {}
55 self.remote_bug_product = {}55 self.remote_bug_product = {}
5656
57 def getExternalBugTrackerToUse(self):57 def _remoteSystemHasBugzillaAPI(self):
58 """Return the correct `Bugzilla` subclass for the current bugtracker.58 """Return True if the remote host offers the Bugzilla API.
5959
60 See `IExternalBugTracker`.60 :return: True if the remote host offers an XML-RPC API and its
61 version is > 3.4. Return False otherwise.
62 """
63 api = BugzillaAPI(self.baseurl)
64 if self._test_xmlrpc_proxy is not None:
65 proxy = self._test_xmlrpc_proxy
66 else:
67 proxy = api.xmlrpc_proxy
68
69 try:
70 # We try calling Bugzilla.version() on the remote
71 # server because it's the most lightweight method there is.
72 remote_version_dict = proxy.Bugzilla.version()
73 except xmlrpclib.Fault, fault:
74 if fault.faultCode == 'Client':
75 return False
76 else:
77 raise
78 except xmlrpclib.ProtocolError, error:
79 # We catch 404s, which occur when xmlrpc.cgi doesn't exist
80 # on the remote server, and 500s, which sometimes occur when
81 # an invalid request is made to the remote server. We allow
82 # any other error types to propagate upward.
83 if error.errcode in (404, 500):
84 return False
85 else:
86 raise
87 except xmlrpclib.ResponseError:
88 # The server returned an unparsable response.
89 return False
90 else:
91 if remote_version_dict['version'] >= '3.4':
92 return True
93 else:
94 return False
95
96 def _remoteSystemHasPluginAPI(self):
97 """Return True if the remote host has the Launchpad plugin installed.
61 """98 """
62 plugin = BugzillaLPPlugin(self.baseurl)99 plugin = BugzillaLPPlugin(self.baseurl)
100 if self._test_xmlrpc_proxy is not None:
101 proxy = self._test_xmlrpc_proxy
102 else:
103 proxy = plugin.xmlrpc_proxy
104
63 try:105 try:
64 # We try calling Launchpad.plugin_version() on the remote106 # We try calling Launchpad.plugin_version() on the remote
65 # server because it's the most lightweight method there is.107 # server because it's the most lightweight method there is.
66 if self._test_xmlrpc_proxy is not None:
67 proxy = self._test_xmlrpc_proxy
68 else:
69 proxy = plugin.xmlrpc_proxy
70 proxy.Launchpad.plugin_version()108 proxy.Launchpad.plugin_version()
71 except xmlrpclib.Fault, fault:109 except xmlrpclib.Fault, fault:
72 if fault.faultCode == 'Client':110 if fault.faultCode == 'Client':
73 return self111 return False
74 else:112 else:
75 raise113 raise
76 except xmlrpclib.ProtocolError, error:114 except xmlrpclib.ProtocolError, error:
@@ -80,14 +118,26 @@
80 # can consider to be a problem, so we let it travel up the118 # can consider to be a problem, so we let it travel up the
81 # stack for the error log.119 # stack for the error log.
82 if error.errcode in (404, 500):120 if error.errcode in (404, 500):
83 return self121 return False
84 else:122 else:
85 raise123 raise
86 except xmlrpclib.ResponseError:124 except xmlrpclib.ResponseError:
87 # The server returned an unparsable response.125 # The server returned an unparsable response.
126 return False
127 else:
128 return True
129
130 def getExternalBugTrackerToUse(self):
131 """Return the correct `Bugzilla` subclass for the current bugtracker.
132
133 See `IExternalBugTracker`.
134 """
135 if self._remoteSystemHasPluginAPI():
136 return BugzillaLPPlugin(self.baseurl)
137 elif self._remoteSystemHasBugzillaAPI():
138 return BugzillaAPI(self.baseurl)
139 else:
88 return self140 return self
89 else:
90 return plugin
91141
92 def _parseDOMString(self, contents):142 def _parseDOMString(self, contents):
93 """Return a minidom instance representing the XML contents supplied"""143 """Return a minidom instance representing the XML contents supplied"""

Subscribers

People subscribed via source and target branches

to status/vote changes: