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
1=== modified file 'lib/lp/bugs/doc/externalbugtracker-bugzilla.txt'
2--- lib/lp/bugs/doc/externalbugtracker-bugzilla.txt 2009-06-15 11:10:49 +0000
3+++ lib/lp/bugs/doc/externalbugtracker-bugzilla.txt 2009-09-23 12:24:10 +0000
4@@ -67,14 +67,18 @@
5
6 == Launchpad plugin ==
7
8-Some Bugzillas have a Launchpad plugin installed. For these bugtrackers,
9-we use the BugzillaLPPlugin ExternalBugTracker class. The Bugzilla
10+Some Bugzillas offer the Bugzilla 3.4 XML-RPC API or have a Launchpad
11+plugin installed. For these bugtrackers, we use the BugzillaAPI
12+ExternalBugTracker or its subclass, BugzillaLPPlugin, depending upon
13+which type of Bugzilla we're dealing with. The Bugzilla
14 ExternalBugTracker class has a getExternalBugTrackerToUse() method which
15-will return a BugzillaLPPlugin instance if the remote Bugzilla has the
16-plugin installed or a standard Bugzilla ExternalBugTracker if not.
17+will return a BugzillaAPI instance if the remote Bugzilla offers the 3.4
18+API or a BugzillaLPPlugin instance if the remote Bugzilla has the
19+plugin installed. If neither of these is offered, a standard Bugzilla
20+ExternalBugTracker will be returned.
21
22-The Bugzilla ExternalBugTracker has an xmlrpc_proxy property which we
23-override for the purpose of this test.
24+The Bugzilla ExternalBugTracker has a _test_xmlrpc_proxy property which
25+we override for the purpose of this test.
26
27 >>> import xmlrpclib
28 >>> class FailingXMLRPCTransport(xmlrpclib.Transport):
29@@ -103,14 +107,14 @@
30 Bugzilla instance.
31
32 >>> from lp.bugs.externalbugtracker import (
33- ... BugzillaLPPlugin)
34+ ... BugzillaAPI, BugzillaLPPlugin)
35 >>> bugzilla_to_use = bugzilla.getExternalBugTrackerToUse()
36
37 The returned bugtracker will be a Bugzilla instance bug not a
38-BugzillaLPPlugin instance.
39+BugzillaAPI instance.
40
41 >>> (isinstance(bugzilla_to_use, Bugzilla) and
42- ... not isinstance(bugzilla_to_use, BugzillaLPPlugin))
43+ ... not isinstance(bugzilla_to_use, BugzillaAPI))
44 True
45
46 The same is true if getExternalBugTrackerToUse() receives a 404 error
47@@ -122,7 +126,7 @@
48 >>> bugzilla_to_use = bugzilla.getExternalBugTrackerToUse()
49
50 >>> (isinstance(bugzilla_to_use, Bugzilla) and
51- ... not isinstance(bugzilla_to_use, BugzillaLPPlugin))
52+ ... not isinstance(bugzilla_to_use, BugzillaAPI))
53 True
54
55 Some Bugzillas respond to an invalid XML-RPC method call by returning a
56@@ -134,7 +138,7 @@
57 >>> bugzilla_to_use = bugzilla.getExternalBugTrackerToUse()
58
59 >>> (isinstance(bugzilla_to_use, Bugzilla) and
60- ... not isinstance(bugzilla_to_use, BugzillaLPPlugin))
61+ ... not isinstance(bugzilla_to_use, BugzillaAPI))
62 True
63
64 Some other Bugzillas generate an unparsable response, causing
65@@ -144,9 +148,59 @@
66 >>> bugzilla_to_use = bugzilla.getExternalBugTrackerToUse()
67
68 >>> (isinstance(bugzilla_to_use, Bugzilla) and
69+ ... not isinstance(bugzilla_to_use, BugzillaAPI))
70+ True
71+
72+If the remote Bugzilla offers the Bugzilla 3.4 API, an instance of
73+BuzillaAPI will be returned. To test this, we use a specially-crafted
74+XML-RPC proxy that behaves like a Bugzilla 3.4 instance.
75+
76+ >>> class APIXMLRPCTransport(xmlrpclib.Transport):
77+ ...
78+ ... version = '3.4.2'
79+ ...
80+ ... def request(self, host, handler, request, verbose=None):
81+ ... args, method_name = xmlrpclib.loads(request)
82+ ...
83+ ... if method_name == 'Bugzilla.version':
84+ ... return [{'version': self.version}]
85+ ... else:
86+ ... raise xmlrpclib.Fault('Client', 'No such method')
87+ ...
88+ >>> test_transport = APIXMLRPCTransport()
89+
90+ >>> bugzilla._test_xmlrpc_proxy = xmlrpclib.ServerProxy(
91+ ... 'http://example.com/xmlrpc.cgi',
92+ ... transport=test_transport)
93+
94+ >>> bugzilla_to_use = bugzilla.getExternalBugTrackerToUse()
95+ >>> (isinstance(bugzilla_to_use, BugzillaAPI) and
96 ... not isinstance(bugzilla_to_use, BugzillaLPPlugin))
97 True
98
99+If the remote system has the Launchpad plugin installed, an
100+getExternalBugTrackerToUse() will return a BugzillaLPPlugin instance.
101+
102+ >>> class PluginXMLRPCTransport(xmlrpclib.Transport):
103+ ...
104+ ... def request(self, host, handler, request, verbose=None):
105+ ... args, method_name = xmlrpclib.loads(request)
106+ ...
107+ ... if method_name == 'Launchpad.plugin_version':
108+ ... return [{'version': '0.2'}]
109+ ... else:
110+ ... raise xmlrpclib.Fault('Client', 'No such method')
111+ ...
112+ >>> test_transport = PluginXMLRPCTransport()
113+
114+ >>> bugzilla._test_xmlrpc_proxy = xmlrpclib.ServerProxy(
115+ ... 'http://example.com/xmlrpc.cgi',
116+ ... transport=test_transport)
117+
118+ >>> bugzilla_to_use = bugzilla.getExternalBugTrackerToUse()
119+ >>> isinstance(bugzilla_to_use, BugzillaLPPlugin)
120+ True
121+
122
123 == Status Conversion ==
124
125
126=== modified file 'lib/lp/bugs/externalbugtracker/bugzilla.py'
127--- lib/lp/bugs/externalbugtracker/bugzilla.py 2009-09-02 08:32:27 +0000
128+++ lib/lp/bugs/externalbugtracker/bugzilla.py 2009-09-23 12:24:10 +0000
129@@ -54,23 +54,61 @@
130 self.remote_bug_status = {}
131 self.remote_bug_product = {}
132
133- def getExternalBugTrackerToUse(self):
134- """Return the correct `Bugzilla` subclass for the current bugtracker.
135-
136- See `IExternalBugTracker`.
137+ def _remoteSystemHasBugzillaAPI(self):
138+ """Return True if the remote host offers the Bugzilla API.
139+
140+ :return: True if the remote host offers an XML-RPC API and its
141+ version is > 3.4. Return False otherwise.
142+ """
143+ api = BugzillaAPI(self.baseurl)
144+ if self._test_xmlrpc_proxy is not None:
145+ proxy = self._test_xmlrpc_proxy
146+ else:
147+ proxy = api.xmlrpc_proxy
148+
149+ try:
150+ # We try calling Bugzilla.version() on the remote
151+ # server because it's the most lightweight method there is.
152+ remote_version_dict = proxy.Bugzilla.version()
153+ except xmlrpclib.Fault, fault:
154+ if fault.faultCode == 'Client':
155+ return False
156+ else:
157+ raise
158+ except xmlrpclib.ProtocolError, error:
159+ # We catch 404s, which occur when xmlrpc.cgi doesn't exist
160+ # on the remote server, and 500s, which sometimes occur when
161+ # an invalid request is made to the remote server. We allow
162+ # any other error types to propagate upward.
163+ if error.errcode in (404, 500):
164+ return False
165+ else:
166+ raise
167+ except xmlrpclib.ResponseError:
168+ # The server returned an unparsable response.
169+ return False
170+ else:
171+ if remote_version_dict['version'] >= '3.4':
172+ return True
173+ else:
174+ return False
175+
176+ def _remoteSystemHasPluginAPI(self):
177+ """Return True if the remote host has the Launchpad plugin installed.
178 """
179 plugin = BugzillaLPPlugin(self.baseurl)
180+ if self._test_xmlrpc_proxy is not None:
181+ proxy = self._test_xmlrpc_proxy
182+ else:
183+ proxy = plugin.xmlrpc_proxy
184+
185 try:
186 # We try calling Launchpad.plugin_version() on the remote
187 # server because it's the most lightweight method there is.
188- if self._test_xmlrpc_proxy is not None:
189- proxy = self._test_xmlrpc_proxy
190- else:
191- proxy = plugin.xmlrpc_proxy
192 proxy.Launchpad.plugin_version()
193 except xmlrpclib.Fault, fault:
194 if fault.faultCode == 'Client':
195- return self
196+ return False
197 else:
198 raise
199 except xmlrpclib.ProtocolError, error:
200@@ -80,14 +118,26 @@
201 # can consider to be a problem, so we let it travel up the
202 # stack for the error log.
203 if error.errcode in (404, 500):
204- return self
205+ return False
206 else:
207 raise
208 except xmlrpclib.ResponseError:
209 # The server returned an unparsable response.
210+ return False
211+ else:
212+ return True
213+
214+ def getExternalBugTrackerToUse(self):
215+ """Return the correct `Bugzilla` subclass for the current bugtracker.
216+
217+ See `IExternalBugTracker`.
218+ """
219+ if self._remoteSystemHasPluginAPI():
220+ return BugzillaLPPlugin(self.baseurl)
221+ elif self._remoteSystemHasBugzillaAPI():
222+ return BugzillaAPI(self.baseurl)
223+ else:
224 return self
225- else:
226- return plugin
227
228 def _parseDOMString(self, contents):
229 """Return a minidom instance representing the XML contents supplied"""

Subscribers

People subscribed via source and target branches

to status/vote changes: