Merge lp:~robru/cupstream2distro/add-ticket-system-support into lp:cupstream2distro

Proposed by Robert Bruce Park on 2014-12-17
Status: Rejected
Rejected by: Robert Bruce Park on 2015-04-14
Proposed branch: lp:~robru/cupstream2distro/add-ticket-system-support
Merge into: lp:cupstream2distro
Diff against target: 299 lines (+125/-1)
2 files modified
cupstream2distro/silomanager.py (+41/-0)
tests/unit/test_silomanager.py (+84/-1)
To merge this branch: bzr merge lp:~robru/cupstream2distro/add-ticket-system-support
Reviewer Review Type Date Requested Status
Robert Bruce Park (community) Disapprove on 2015-04-14
PS Jenkins bot continuous-integration 2014-12-17 Approve on 2014-12-19
Ursula Junque (community) 2014-12-18 Approve on 2014-12-19
Francis Ginther 2014-12-18 Pending
Review via email: mp+245045@code.launchpad.net

Commit Message

Teach CI Train how to push statuses to the ticket system.

Description of the Change

This can be tested locally in an interactive python session by pasting the following lines one by one into a terminal after cd'ing into the branch dir:

    BUILD_URL=http://example.com DEBUG=true python
    from cupstream2distro.silomanager import SiloState
    s = SiloState.new_blank('ubuntu/some-ppa', requestid='e107b2fa-8625-11e4-98f1-fa163eb82757')
    s.set_ticket_status('Silo building', 'In progress')
    s.set_ticket_status('0', '0')

The output is expected to look something like this:

http://paste.ubuntu.com/9564610/

(the env spew at the import step is expected, don't worry about that)

And you can see the results reflected at:

http://172.19.1.177/ticket/e107b2fa-8625-11e4-98f1-fa163eb82757/

To post a comment you must log in.
Robert Bruce Park (robru) wrote :

We don't seem to have access to bootstack from production:

https://ci-train.ubuntu.com/job/cyphermox-test/511/console

Robert Bruce Park (robru) wrote :

Hey guys, so most of this is pretty uncontroversial, however the ticket system seems to be missing a good analog for 'Silo merging', so I substituted 'Completed' instead, which is probably wrong but I'm not sure what else to do there.

Also, during the next phase when the ticket system grows to absorb the dashboard, there'll need to be a way to inject arbitrary status messages for the ticket system to display. As you can see in this merge there are many failure messages that get saved into the dashboard but not pushed into the ticket system, which is fine for now, but will need to get fixed later on.

865. By Robert Bruce Park on 2014-12-18

Better mocks.

Robert Bruce Park (robru) wrote :

One thing we should probably discuss is the potential for redundancy here since there isn't a clear distinction between our steps and our statuses. So we've got two kinds of methods here, some go 'self.set_config_status(...); self.set_ticket_step(...)' and some go 'self.config_step = ...; self.set_ticket_step(...)'

I suspect that those two are always called in pairs, I'll have to check the jobs for any cases where a config_step might get called without a set_config_status equivalent at the same time. If that's the case then I'm really making two PATCH requests every time something changes, so I should probably check if those can be removed.

866. By Robert Bruce Park on 2014-12-18

Tweak variable names.

Robert Bruce Park (robru) wrote :

Ok yeah, I audited the citrain/ scripts and the steps are totally redundant to the statuses. I'll drop those in order to reduce redundant calls into the ticket system.

867. By Robert Bruce Park on 2014-12-18

Drop redundant calls to set_ticket_step().

868. By Robert Bruce Park on 2014-12-18

Rename set_ticket_step to set_ticket_status for consistency.

PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:
http://jenkins.qa.ubuntu.com/job/cu2d-choo-choo-ci/392/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/cu2d-choo-choo-ci/392/rebuild

review: Needs Fixing (continuous-integration)
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:868
http://jenkins.qa.ubuntu.com/job/cu2d-choo-choo-ci/393/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/cu2d-choo-choo-ci/393/rebuild

review: Needs Fixing (continuous-integration)
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:868
http://jenkins.qa.ubuntu.com/job/cu2d-choo-choo-ci/394/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/cu2d-choo-choo-ci/394/rebuild

review: Needs Fixing (continuous-integration)
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:868
http://jenkins.qa.ubuntu.com/job/cu2d-choo-choo-ci/395/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/cu2d-choo-choo-ci/395/rebuild

review: Approve (continuous-integration)
869. By Robert Bruce Park on 2014-12-18

Use new Silo merging ticket step.

870. By Robert Bruce Park on 2014-12-18

Ignore exceptions from requests.patch in case ticket system unreachable.

PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:869
http://jenkins.qa.ubuntu.com/job/cu2d-choo-choo-ci/396/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/cu2d-choo-choo-ci/396/rebuild

review: Approve (continuous-integration)
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:870
http://jenkins.qa.ubuntu.com/job/cu2d-choo-choo-ci/397/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/cu2d-choo-choo-ci/397/rebuild

review: Approve (continuous-integration)
871. By Robert Bruce Park on 2014-12-18

Refactor set_ticket_status() into patch() for better reusability.

PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:871
http://jenkins.qa.ubuntu.com/job/cu2d-choo-choo-ci/398/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/cu2d-choo-choo-ci/398/rebuild

review: Approve (continuous-integration)
872. By Robert Bruce Park on 2014-12-18

HTTP PATCH ticket system once build_ppa is known.

PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:872
http://jenkins.qa.ubuntu.com/job/cu2d-choo-choo-ci/399/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/cu2d-choo-choo-ci/399/rebuild

review: Approve (continuous-integration)
873. By Robert Bruce Park on 2014-12-18

Include job_url with step & status info.

PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:873
http://jenkins.qa.ubuntu.com/job/cu2d-choo-choo-ci/400/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/cu2d-choo-choo-ci/400/rebuild

review: Approve (continuous-integration)
Ursula Junque (ursinha) wrote :

Looks good! Thanks for adding the silo PPA and job url, it's more useful now.

review: Approve
Robert Bruce Park (robru) wrote :

I'm also quite happy with this, I just don't want to merge it (or deploy it to production) until we get a public IP for the ticket system.

review: Approve
874. By Robert Bruce Park on 2014-12-19

Rebase on trunk.

PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:874
http://jenkins.qa.ubuntu.com/job/cu2d-choo-choo-ci/405/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/cu2d-choo-choo-ci/405/rebuild

review: Approve (continuous-integration)
Robert Bruce Park (robru) wrote :

Rejecting because we decided in Austin not to proceed with "CI Engine as Spreadsheet Replacement"

review: Disapprove

Unmerged revisions

874. By Robert Bruce Park on 2014-12-19

Rebase on trunk.

873. By Robert Bruce Park on 2014-12-18

Include job_url with step & status info.

872. By Robert Bruce Park on 2014-12-18

HTTP PATCH ticket system once build_ppa is known.

871. By Robert Bruce Park on 2014-12-18

Refactor set_ticket_status() into patch() for better reusability.

870. By Robert Bruce Park on 2014-12-18

Ignore exceptions from requests.patch in case ticket system unreachable.

869. By Robert Bruce Park on 2014-12-18

Use new Silo merging ticket step.

868. By Robert Bruce Park on 2014-12-18

Rename set_ticket_step to set_ticket_status for consistency.

867. By Robert Bruce Park on 2014-12-18

Drop redundant calls to set_ticket_step().

866. By Robert Bruce Park on 2014-12-18

Tweak variable names.

865. By Robert Bruce Park on 2014-12-18

Better mocks.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'cupstream2distro/silomanager.py'
2--- cupstream2distro/silomanager.py 2014-12-08 18:43:01 +0000
3+++ cupstream2distro/silomanager.py 2014-12-19 08:25:20 +0000
4@@ -19,6 +19,7 @@
5
6 import json
7 import logging
8+import requests
9 import os
10 import shutil
11 import signal
12@@ -29,6 +30,7 @@
13
14 from cupstream2distro.settings import (
15 ALL_SILO_NAMES,
16+ SILO_BUILDPPA_SCHEME,
17 SILO_NAME_LIST,
18 SILO_STATUS_RSYNCDIR,
19 SILOS_DIR,
20@@ -51,9 +53,27 @@
21 SILO_STATE_CLEANING = 11
22
23
24+# FIXME: Using bootstack for now, should switch to production once it exists.
25+API_URL = 'http://172.19.1.177/api/v1/'
26+TICKET_URL = '{}ticket/{{}}/'.format(API_URL)
27+
28 default_excepthook = sys.excepthook
29
30
31+def patch(requestid, data):
32+ """Submit an HTTP PATCH to the UCI Engine Ticket System."""
33+ try:
34+ error = requests.patch(
35+ TICKET_URL.format(requestid),
36+ headers={'content-type': 'application/json'},
37+ data=json.dumps(data)).content
38+ if error:
39+ raise Exception(error)
40+ except Exception as e:
41+ logging.error(e.message)
42+ return e.message
43+
44+
45 class SiloState(object):
46 """This class stores and manipulates the silo config json blob."""
47 CONFIG_FILENAME = 'config'
48@@ -90,6 +110,8 @@
49 'sources': [],
50 'mps': {},
51 }
52+ patch(requestid, dict(build_ppa='ppa:' + SILO_BUILDPPA_SCHEME.format(
53+ *siloname.split('/'))))
54 return silo_state
55
56 @staticmethod
57@@ -152,6 +174,13 @@
58 logging.debug('Deleting! ' + self.rsyncfile)
59 os.unlink(self.rsyncfile)
60
61+ def set_ticket_status(self, step, status):
62+ """Communicate step and state info to Ticket System."""
63+ patch(self.requestid, dict(
64+ citrain_overlay=dict(job_url=env.BUILD_URL),
65+ current_workflow_step=step,
66+ status=status))
67+
68 def set_config_status(self, state, status, ping=True):
69 """Update to reflect the latest status."""
70 self._config['global']['status'] = {
71@@ -208,46 +237,58 @@
72
73 def set_building(self):
74 self.set_config_status(SILO_STATE_BUILDING, 'Building')
75+ self.set_ticket_status('Silo building', 'In progress')
76
77 def set_build_failed(self, message):
78 self.set_config_status(SILO_STATE_BUILD_FAILED, message)
79+ self.set_ticket_status('Silo building', 'Failed')
80
81 def set_build_successful(self):
82 self.set_config_status(SILO_STATE_BUILD_SUCCESSFUL, 'Packages built')
83+ self.set_ticket_status('Silo building', 'Completed')
84
85 def set_publishing(self):
86 self.set_config_status(SILO_STATE_PUBLISHING, 'Publishing')
87+ self.set_ticket_status('Silo publishing', 'In progress')
88
89 def set_publish_failed(self, message):
90 self.set_config_status(SILO_STATE_PUBLISH_FAILED,
91 "Can't publish: " + message)
92+ self.set_ticket_status('Silo publishing', 'Failed')
93
94 def set_cleaning(self, message):
95 self.set_config_status(SILO_STATE_CLEANING, message)
96+ self.set_ticket_status('Silo merging', 'In progress')
97
98 def set_empty(self):
99 self.config_step = SILO_EMPTY
100
101 def set_merging(self):
102 self.set_config_status(SILO_STATE_MERGING, 'Merging')
103+ self.set_ticket_status('Silo merging', 'In progress')
104
105 def set_merge_failed(self, message):
106 self.set_config_status(SILO_STATE_MERGE_FAILED, message)
107+ self.set_ticket_status('Silo merging', 'Failed')
108
109 def set_migrating(self, message='Packages migrating to destination'):
110 self.set_config_status(SILO_STATE_MIGRATING, message)
111+ self.set_ticket_status('Silo publishing', 'In progress')
112
113 def set_preparing(self):
114 self.set_config_status(SILO_STATE_BUILDING, 'Preparing packages')
115+ self.set_ticket_status('Silo building', 'In progress')
116
117 def set_ready(self):
118 self.set_config_status(SILO_STATE_NONE, 'Silo ready to build')
119+ self.set_ticket_status('Silo creating', 'Completed')
120 self.config_step = SILO_EMPTY
121
122 def set_reconfigure_failed(self, message):
123 self.config_step = -1
124 self.set_config_status(
125 SILO_STATE_RECONFIGURE_FAILED, 'Reconfigure failed: ' + message)
126+ self.set_ticket_status('Silo creating', 'Failed')
127
128 def set_built_checked(self):
129 self.config_step = SILO_BUILTCHECKED
130
131=== modified file 'tests/unit/test_silomanager.py'
132--- tests/unit/test_silomanager.py 2014-12-08 18:43:01 +0000
133+++ tests/unit/test_silomanager.py 2014-12-19 08:25:20 +0000
134@@ -2,6 +2,7 @@
135 import sys
136 import time
137 import signal
138+import requests
139
140 from mock import Mock, patch, call
141 from collections import defaultdict
142@@ -35,6 +36,8 @@
143 self.state._configfile = os_path_join_safe(
144 self.data_dir, 'landing-123', 'config')
145 self.state.load_config()
146+ self.set_ticket_status = self.state.set_ticket_status
147+ self.state.set_ticket_status = Mock()
148
149 @patch('cupstream2distro.silomanager.SiloState._configfile',
150 os_path_join_safe(
151@@ -206,7 +209,8 @@
152 mkdirMock.assert_called_once_with(
153 os.path.expanduser('~/silos/ubuntu-rtm/landing-000'))
154
155- def test_silostate_new_blank(self):
156+ @patch('cupstream2distro.silomanager.patch')
157+ def test_silostate_new_blank(self, patchMock):
158 """Create a new SiloState instance for a fresh silo assignment."""
159 silo_state = SiloState.new_blank(
160 siloname='ubuntu/landing-789',
161@@ -214,6 +218,9 @@
162 dest='mars',
163 series='parallel',
164 landers=['curiosity', 'spirit', 'opportunity'])
165+ patchMock.assert_called_once_with(
166+ 'zippy',
167+ {'build_ppa': 'ppa:ci-train-staging-area/ubuntu/landing-789'})
168 self.assertEqual(silo_state.siloname, 'ubuntu/landing-789')
169 self.assertEqual(
170 silo_state.configfile,
171@@ -327,24 +334,44 @@
172 setMock = self.state.set_config_status = Mock()
173 self.state.set_build_successful()
174 setMock.assert_called_once_with(3, 'Packages built')
175+ self.state.set_ticket_status.assert_called_once_with(
176+ 'Silo building', 'Completed')
177 self.state.set_cleaning('Foo')
178 setMock.assert_called_with(11, 'Foo')
179+ self.state.set_ticket_status.assert_called_with(
180+ 'Silo merging', 'In progress')
181 self.state.set_merging()
182 setMock.assert_called_with(9, 'Merging')
183+ self.state.set_ticket_status.assert_called_with(
184+ 'Silo merging', 'In progress')
185 self.state.set_merge_failed('Bar')
186 setMock.assert_called_with(10, 'Bar')
187+ self.state.set_ticket_status.assert_called_with(
188+ 'Silo merging', 'Failed')
189 self.state.set_migrating('Grill')
190 setMock.assert_called_with(8, 'Grill')
191+ self.state.set_ticket_status.assert_called_with(
192+ 'Silo publishing', 'In progress')
193 self.state.set_building()
194 setMock.assert_called_with(2, 'Building')
195+ self.state.set_ticket_status.assert_called_with(
196+ 'Silo building', 'In progress')
197 self.state.set_build_failed('It dun broke')
198 setMock.assert_called_with(4, 'It dun broke')
199+ self.state.set_ticket_status.assert_called_with(
200+ 'Silo building', 'Failed')
201 self.state.set_preparing()
202 setMock.assert_called_with(2, 'Preparing packages')
203+ self.state.set_ticket_status.assert_called_with(
204+ 'Silo building', 'In progress')
205 self.state.set_publishing()
206 setMock.assert_called_with(6, 'Publishing')
207+ self.state.set_ticket_status.assert_called_with(
208+ 'Silo publishing', 'In progress')
209 self.state.set_publish_failed('Doh!')
210 setMock.assert_called_with(7, "Can't publish: Doh!")
211+ self.state.set_ticket_status.assert_called_with(
212+ 'Silo publishing', 'Failed')
213
214 def test_silostate_step_setters(self):
215 """Set various silo steps."""
216@@ -356,6 +383,7 @@
217 self.assertEqual(self.state._config['global']['step'], 2)
218 self.state.set_done()
219 self.assertEqual(self.state._config['global']['step'], 3)
220+ self.assertEqual(self.state.set_ticket_status.call_count, 0)
221
222 def test_silostate_getters(self):
223 """Read various state properties."""
224@@ -450,6 +478,8 @@
225 self.state.set_ready()
226 statusMock.assert_called_once_with(1, 'Silo ready to build')
227 self.assertEqual(self.state.config_step, 0)
228+ self.state.set_ticket_status.assert_called_once_with(
229+ 'Silo creating', 'Completed')
230
231 def test_silostate_set_reconfigure_failed(self):
232 """Set reconfigure failed status."""
233@@ -458,6 +488,8 @@
234 statusMock.assert_called_once_with(
235 5, 'Reconfigure failed: You did a bad thing.')
236 self.assertEqual(self.state.config_step, -1)
237+ self.state.set_ticket_status.assert_called_once_with(
238+ 'Silo creating', 'Failed')
239
240 def test_silostate_append_mp(self):
241 """Add an MP to the list of that project's MPs."""
242@@ -514,6 +546,57 @@
243 self.assertEqual(
244 self.state._config['additional_targets'], ['a', 'b', 'c'])
245
246+ @patch('cupstream2distro.silomanager.json')
247+ @patch('cupstream2distro.silomanager.requests')
248+ def test_set_ticket_status(self, requestsMock, jsonMock):
249+ """Report statuses to the ticket system correctly."""
250+ env.BUILD_URL = 'http://example.com/zoinks'
251+ requestsMock.patch.return_value.content = ''
252+ self.set_ticket_status('Hi', 'there')
253+ jsonMock.dumps.assert_called_once_with(
254+ dict(current_workflow_step='Hi',
255+ status='there',
256+ citrain_overlay=dict(job_url='http://example.com/zoinks')))
257+ requestsMock.patch.assert_called_once_with(
258+ 'http://172.19.1.177/api/v1/ticket/1411123362781/',
259+ headers={'content-type': 'application/json'},
260+ data=jsonMock.dumps())
261+
262+ @patch('cupstream2distro.silomanager.logging')
263+ @patch('cupstream2distro.silomanager.json')
264+ @patch('cupstream2distro.silomanager.requests')
265+ def test_set_ticket_status_fail(self, requestsMock, jsonMock, logMock):
266+ """Report errors from the ticket system correctly."""
267+ env.BUILD_URL = 'http://example.com/zoinks'
268+ requestsMock.patch.return_value.content = '{"errors":"you fail"}'
269+ self.set_ticket_status('Hi', 'there')
270+ jsonMock.dumps.assert_called_once_with(
271+ dict(current_workflow_step='Hi',
272+ status='there',
273+ citrain_overlay=dict(job_url='http://example.com/zoinks')))
274+ logMock.error.assert_called_once_with(
275+ '{"errors":"you fail"}')
276+ requestsMock.patch.assert_called_once_with(
277+ 'http://172.19.1.177/api/v1/ticket/1411123362781/',
278+ headers={'content-type': 'application/json'},
279+ data=jsonMock.dumps())
280+
281+ @patch('cupstream2distro.silomanager.logging')
282+ @patch('cupstream2distro.silomanager.json')
283+ @patch('cupstream2distro.silomanager.requests')
284+ def test_set_ticket_status_except(self, requestsMock, jsonMock, logMock):
285+ """Report errors from the requests system correctly."""
286+ requestsMock.patch.return_value.content = ''
287+ requestsMock.patch.side_effect = requests.ConnectionError(
288+ '[Errno 110] Connection timed out')
289+ self.set_ticket_status('Hi', 'there')
290+ logMock.error.assert_called_once_with(
291+ '[Errno 110] Connection timed out')
292+ requestsMock.patch.assert_called_once_with(
293+ 'http://172.19.1.177/api/v1/ticket/1411123362781/',
294+ headers={'content-type': 'application/json'},
295+ data=jsonMock.dumps())
296+
297 def test_signal_handler(self):
298 """Our signal handler is installed globally."""
299 self.assertTrue(

Subscribers

People subscribed via source and target branches