Merge lp:~gmb/launchpad/bugzilla-3.4-dbtime into lp:launchpad

Proposed by Graham Binns
Status: Merged
Merged at revision: not available
Proposed branch: lp:~gmb/launchpad/bugzilla-3.4-dbtime
Merge into: lp:launchpad
Diff against target: None lines
To merge this branch: bzr merge lp:~gmb/launchpad/bugzilla-3.4-dbtime
Reviewer Review Type Date Requested Status
Celso Providelo (community) code Approve
Review via email: mp+10464@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Graham Binns (gmb) wrote :

This branch adds the ability to get the time from the remote server to
the BugzillaAPI ExternalBugTracker.

I've added a local implementation of the API method that we call so that
we can test properly without having to connect to an actual instance as
well as the implementation of getCurrentDBTime() itself. I've made some
minor changes to TestBugzillaXMLRPCTransport.time() to make it a little
more readable.

= Launchpad lint =

Checking for conflicts. and issues in doctests and templates.
Running jslint, xmllint, pyflakes, and pylint.
Using normal rules.

Linting changed files:
  lib/lp/bugs/doc/externalbugtracker-bugzilla-api.txt
  lib/lp/bugs/externalbugtracker/bugzilla.py
  lib/lp/bugs/tests/bugzilla-xmlrpc-transport.txt
  lib/lp/bugs/tests/externalbugtracker.py

Revision history for this message
Celso Providelo (cprov) wrote :

Hi Graham,

The change is flawless!

Thanks for improving the testing infrastructure readability while you were there.

r=me

review: Approve (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-api.txt'
2--- lib/lp/bugs/doc/externalbugtracker-bugzilla-api.txt 2009-08-19 13:12:30 +0000
3+++ lib/lp/bugs/doc/externalbugtracker-bugzilla-api.txt 2009-08-20 15:43:37 +0000
4@@ -75,3 +75,39 @@
5 ...
6 BugTrackerAuthenticationError: http://thiswillfail.example.com:
7 Fault 300: The username or password you entered is not valid.
8+
9+
10+Getting the server time
11+-----------------------
12+
13+To be able to accurately sync with a bug tracker, we need to be able to
14+check the time on the remote server. We use BugzillaAPI.getCurrentDBTime()
15+to get the current time on the remote server.
16+
17+ # There's no way to create a UTC timestamp without monkey-patching
18+ # the TZ environment variable. Rather than do that, we create our
19+ # own datetime and work with that.
20+ >>> from datetime import datetime
21+ >>> remote_time = datetime(2009, 8, 19, 17, 2, 2)
22+
23+ >>> test_transport.local_datetime = remote_time
24+ >>> bugzilla.getCurrentDBTime()
25+ CALLED Bugzilla.time()
26+ datetime.datetime(2009, 8, 19, 17, 2, 2, tzinfo=<UTC>)
27+
28+If the remote system is in a different timezone, getCurrentDBTime() will
29+convert its time to UTC before returning it.
30+
31+ >>> test_transport.utc_offset = 60**2
32+ >>> test_transport.timezone = 'CET'
33+ >>> bugzilla.getCurrentDBTime()
34+ CALLED Bugzilla.time()
35+ datetime.datetime(2009, 8, 19, 16, 2, 2, tzinfo=<UTC>)
36+
37+This works whether the UTC offset is positive or negative.
38+
39+ >>> test_transport.utc_offset = -5 * 60**2
40+ >>> test_transport.timezone = 'US/Eastern'
41+ >>> bugzilla.getCurrentDBTime()
42+ CALLED Bugzilla.time()
43+ datetime.datetime(2009, 8, 19, 22, 2, 2, tzinfo=<UTC>)
44
45=== modified file 'lib/lp/bugs/externalbugtracker/bugzilla.py'
46--- lib/lp/bugs/externalbugtracker/bugzilla.py 2009-08-19 13:19:22 +0000
47+++ lib/lp/bugs/externalbugtracker/bugzilla.py 2009-08-20 15:43:37 +0000
48@@ -430,6 +430,34 @@
49 self.baseurl,
50 "Fault %s: %s" % (fault.faultCode, fault.faultString))
51
52+ def getCurrentDBTime(self):
53+ """See `IExternalBugTracker`."""
54+ time_dict = self.xmlrpc_proxy.Bugzilla.time()
55+
56+ # Convert the XML-RPC DateTime we get back into a regular Python
57+ # datetime.
58+ server_db_timetuple = time.strptime(
59+ str(time_dict['db_time']), '%Y%m%dT%H:%M:%S')
60+ server_db_datetime = datetime(*server_db_timetuple[:6])
61+
62+ # The server's DB time is the one that we want to use. However,
63+ # this may not be in UTC, so we need to convert it. Since we
64+ # can't guarantee that the timezone data returned by the server
65+ # is sane, we work out the server's offset from UTC by looking
66+ # at the difference between the web_time and the web_time_utc
67+ # values.
68+ server_web_time = time.strptime(
69+ str(time_dict['web_time']), '%Y%m%dT%H:%M:%S')
70+ server_web_datetime = datetime(*server_web_time[:6])
71+ server_web_time_utc = time.strptime(
72+ str(time_dict['web_time_utc']), '%Y%m%dT%H:%M:%S')
73+ server_web_datetime_utc = datetime(*server_web_time_utc[:6])
74+
75+ server_utc_offset = server_web_datetime - server_web_datetime_utc
76+ server_utc_datetime = server_db_datetime - server_utc_offset
77+
78+ return server_utc_datetime.replace(tzinfo=pytz.timezone('UTC'))
79+
80
81 class BugzillaLPPlugin(BugzillaAPI):
82 """An `ExternalBugTracker` to handle Bugzillas using the LP Plugin."""
83
84=== modified file 'lib/lp/bugs/tests/bugzilla-xmlrpc-transport.txt'
85--- lib/lp/bugs/tests/bugzilla-xmlrpc-transport.txt 2009-08-18 16:39:17 +0000
86+++ lib/lp/bugs/tests/bugzilla-xmlrpc-transport.txt 2009-08-19 23:17:09 +0000
87@@ -576,3 +576,36 @@
88 Traceback (most recent call last):
89 ...
90 Fault: <Fault 300: 'The username or password you entered is not valid.'>
91+
92+
93+Getting the current time
94+------------------------
95+
96+The Bugzilla.time() method allows us to retrieve a dict of the time on
97+the remote server.
98+
99+ >>> time_dict = server.Bugzilla.time()
100+ >>> for key in sorted(time_dict):
101+ ... print "%s: %s" % (key, time_dict[key])
102+ db_time: 20080501T01:01:01
103+ tz_name: UTC
104+ tz_offset: +0000
105+ tz_short_name: UTC
106+ web_time: 20080501T01:01:01
107+ web_time_utc: 20080501T01:01:01
108+
109+If the remote server is in a different timezone, the db_time and
110+web_time items will be in the server's local timezone whilst
111+web_time_utc will be in UTC.
112+
113+ >>> bugzilla_transport.utc_offset = 60**2
114+ >>> bugzilla_transport.timezone = 'CET'
115+ >>> time_dict = server.Bugzilla.time()
116+ >>> for key in sorted(time_dict):
117+ ... print "%s: %s" % (key, time_dict[key])
118+ db_time: 20080501T01:01:01
119+ tz_name: CET
120+ tz_offset: +0100
121+ tz_short_name: CET
122+ web_time: 20080501T01:01:01
123+ web_time_utc: 20080501T00:01:01
124
125=== modified file 'lib/lp/bugs/tests/externalbugtracker.py'
126--- lib/lp/bugs/tests/externalbugtracker.py 2009-08-18 16:41:18 +0000
127+++ lib/lp/bugs/tests/externalbugtracker.py 2009-08-20 15:43:37 +0000
128@@ -522,7 +522,9 @@
129 # what BugZilla will return.
130 local_time = xmlrpclib.DateTime(local_datetime.timetuple())
131
132- utc_date_time = local_datetime - timedelta(seconds=self.utc_offset)
133+ utc_offset_delta = timedelta(seconds=self.utc_offset)
134+ utc_date_time = local_datetime - utc_offset_delta
135+
136 utc_time = xmlrpclib.DateTime(utc_date_time.timetuple())
137 return {
138 'local_time': local_time,
139@@ -757,7 +759,10 @@
140
141 # Map namespaces onto method names.
142 methods = {
143- 'Bugzilla': ['version'],
144+ 'Bugzilla': [
145+ 'time',
146+ 'version',
147+ ],
148 'Test': ['login_required'],
149 'User': ['login'],
150 }
151@@ -794,6 +799,24 @@
152 300,
153 "The username or password you entered is not valid.")
154
155+ def time(self):
156+ """Return a dict of the local time and associated data."""
157+ # We cheat slightly by calling the superclass to get the time
158+ # data. We do this the old fashioned way because XML-RPC
159+ # Transports don't support new-style classes.
160+ time_dict = TestBugzillaXMLRPCTransport.time(self)
161+ offset_hours = (self.utc_offset / 60) / 60
162+ offset_string = '+%02d00' % offset_hours
163+
164+ return {
165+ 'db_time': time_dict['local_time'],
166+ 'tz_name': time_dict['tz_name'],
167+ 'tz_offset': offset_string,
168+ 'tz_short_name': time_dict['tz_name'],
169+ 'web_time': time_dict['local_time'],
170+ 'web_time_utc': time_dict['utc_time'],
171+ }
172+
173
174 class TestMantis(Mantis):
175 """Mantis ExternalSystem for use in tests.