Merge lp:~cprov/uci-engine/gk-ubuntucore into lp:uci-engine
- gk-ubuntucore
- Merge into trunk
Status: | Needs review |
---|---|
Proposed branch: | lp:~cprov/uci-engine/gk-ubuntucore |
Merge into: | lp:uci-engine |
Diff against target: |
927 lines (+621/-127) 15 files modified
gatekeeper/gatekeeper/datastore/__init__.py (+2/-1) gatekeeper/gatekeeper/datastore/container.py (+66/-0) gatekeeper/gatekeeper/datastore/tests/test_container.py (+71/-0) gatekeeper/gatekeeper/datastore/tests/test_ticket.py (+3/-51) gatekeeper/gatekeeper/datastore/tests/test_ubuntucore.py (+73/-0) gatekeeper/gatekeeper/datastore/ticket.py (+5/-50) gatekeeper/gatekeeper/datastore/ubuntucore.py (+46/-0) gatekeeper/gatekeeper/resources/root.py (+15/-2) gatekeeper/gatekeeper/resources/tests/test_ubuntucore.py (+88/-0) gatekeeper/gatekeeper/resources/ubuntucore.py (+73/-0) juju-deployer/configs/ubuntucore_http_vhost (+18/-0) juju-deployer/relations.yaml (+10/-0) juju-deployer/services.yaml.tmpl (+31/-1) tests/test_ubuntucore.py (+119/-0) tests/test_webui.py (+1/-22) |
To merge this branch: | bzr merge lp:~cprov/uci-engine/gk-ubuntucore |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Canonical CI Engineering | Pending | ||
Review via email: mp+246495@code.launchpad.net |
Commit message
Extends the common (ci-airline) Gatekeeper service to traverse ubuntucore test results containers (ubuntucore-
Description of the change
TL;DR: Feel free to ignore me because it's not relevant for the first story checkpoint.
Extends the common (ci-airline) Gatekeeper service to traverse ubuntucore test results containers (ubuntucore-
Also wire up ubuntucore-
* / -> <GK>/ubuntucore
* /<ID> -> <GK>/ubuntucore
GK currently renders a very simple HTML with links to swift tempurls for objects inside the test result container (pass|failed and 'tarball'). Until now no template library was necessary, but if we have to get a little more sophisticated, that might be required.
We can actually deploy ci-ubuntucore (`deploy.py --name ci-ubuntucore`) and create test result containers manually (`swift upload ubuntucore-current xxx`).
Unmerged revisions
- 928. By Celso Providelo
-
typo
- 927. By Celso Providelo
-
Rendering links (tempurls) to results container objects.
- 926. By Celso Providelo
-
Deploy and test GK in ci-ubuntucore.
- 925. By Celso Providelo
-
Improving test result page.
- 924. By Celso Providelo
-
Implementing and activating UbuntuCoreResource (/ubuntucore/<id>).
- 923. By Celso Providelo
-
Implementing support for retriving ubuntucore container from the datastore.
- 922. By Celso Providelo
-
Splitting and generalising GK datastore container model.
Preview Diff
1 | === modified file 'gatekeeper/gatekeeper/datastore/__init__.py' |
2 | --- gatekeeper/gatekeeper/datastore/__init__.py 2014-10-07 10:04:01 +0000 |
3 | +++ gatekeeper/gatekeeper/datastore/__init__.py 2015-01-14 23:23:47 +0000 |
4 | @@ -18,8 +18,9 @@ |
5 | from base import BasicDataStore |
6 | from sandbox import SupervisedSandboxesMixin |
7 | from ticket import TicketContainerMixin |
8 | +from ubuntucore import UbuntuCoreContainerMixin |
9 | |
10 | |
11 | class DataStore(SupervisedSandboxesMixin, TicketContainerMixin, |
12 | - BasicDataStore): |
13 | + UbuntuCoreContainerMixin, BasicDataStore): |
14 | """Swift data store utility.""" |
15 | |
16 | === added file 'gatekeeper/gatekeeper/datastore/container.py' |
17 | --- gatekeeper/gatekeeper/datastore/container.py 1970-01-01 00:00:00 +0000 |
18 | +++ gatekeeper/gatekeeper/datastore/container.py 2015-01-14 23:23:47 +0000 |
19 | @@ -0,0 +1,66 @@ |
20 | +# -*- coding: utf-8 -*- |
21 | +# Ubuntu CI Engine |
22 | +# Copyright 2014, 2015 Canonical Ltd. |
23 | + |
24 | +# This program is free software: you can redistribute it and/or modify it |
25 | +# under the terms of the GNU Affero General Public License version 3, as |
26 | +# published by the Free Software Foundation. |
27 | + |
28 | +# This program is distributed in the hope that it will be useful, but |
29 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
30 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
31 | +# PURPOSE. See the GNU Affero General Public License for more details. |
32 | + |
33 | +# You should have received a copy of the GNU Affero General Public License |
34 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
35 | +"""Swift container model.""" |
36 | +from __future__ import unicode_literals |
37 | + |
38 | +import uuid |
39 | + |
40 | +from swiftclient import client |
41 | + |
42 | +from ci_utils import unit_config |
43 | +from tempurl import ( |
44 | + OSTempUrlProvider, |
45 | + HPTempUrlProvider, |
46 | +) |
47 | + |
48 | + |
49 | +class Container(object): |
50 | + """Swift container utility with tempurl support. """ |
51 | + |
52 | + def __init__(self, url, token, options, **kwargs): |
53 | + self.url = url |
54 | + self.token = token |
55 | + self.options = options |
56 | + self.name = kwargs.get('name') |
57 | + self.size = kwargs.get('bytes') |
58 | + self.count = kwargs.get('count') |
59 | + |
60 | + def __unicode__(self): |
61 | + return self.name |
62 | + |
63 | + def _get_tempurl_key(self): |
64 | + """Local wrapper to UUID for facilitating tests.""" |
65 | + return str(uuid.uuid1()) |
66 | + |
67 | + def _get_tempurl_provider(self): |
68 | + """Return the tempurl provider appropriate for this context.""" |
69 | + if unit_config.is_hpcloud(self.options.auth_url): |
70 | + return HPTempUrlProvider(self.url, self.token, self.options) |
71 | + return OSTempUrlProvider(self.url, self.token, self.options) |
72 | + |
73 | + def get_objects_tempurls(self, method='GET'): |
74 | + """Return a tempurls for objects for the given method.""" |
75 | + provider = self._get_tempurl_provider() |
76 | + provider.set_temp_key(self._get_tempurl_key()) |
77 | + header, contents = client.get_container( |
78 | + self.url, self.token, self.name) |
79 | + results = [] |
80 | + for content in contents: |
81 | + object_name = content['name'] |
82 | + temp_url = provider.get_temp_url( |
83 | + self.name, object_name, method=method) |
84 | + results.append((object_name, temp_url)) |
85 | + return dict(results) |
86 | |
87 | === added file 'gatekeeper/gatekeeper/datastore/tests/test_container.py' |
88 | --- gatekeeper/gatekeeper/datastore/tests/test_container.py 1970-01-01 00:00:00 +0000 |
89 | +++ gatekeeper/gatekeeper/datastore/tests/test_container.py 2015-01-14 23:23:47 +0000 |
90 | @@ -0,0 +1,71 @@ |
91 | +# -*- coding: utf-8 -*- |
92 | +# Ubuntu CI Engine |
93 | +# Copyright 2014, 2015 Canonical Ltd. |
94 | + |
95 | +# This program is free software: you can redistribute it and/or modify it |
96 | +# under the terms of the GNU Affero General Public License version 3, as |
97 | +# published by the Free Software Foundation. |
98 | + |
99 | +# This program is distributed in the hope that it will be useful, but |
100 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
101 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
102 | +# PURPOSE. See the GNU Affero General Public License for more details. |
103 | + |
104 | +# You should have received a copy of the GNU Affero General Public License |
105 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
106 | +"""Swift datastore container tests.""" |
107 | +from __future__ import unicode_literals |
108 | + |
109 | +from mock import patch |
110 | +import unittest |
111 | + |
112 | +from gatekeeper.datastore.tests import FakeOptions |
113 | +from gatekeeper.datastore.container import Container |
114 | + |
115 | + |
116 | +class TestContainer(unittest.TestCase): |
117 | + """Tests for `Container`.""" |
118 | + |
119 | + def setUp(self): |
120 | + # Creates a sample `Container`. |
121 | + super(TestContainer, self).setUp() |
122 | + contents = { |
123 | + 'name': 'ticket-3', |
124 | + 'bytes': 17, |
125 | + 'count': 1, |
126 | + } |
127 | + self.url = 'http://fake.com/AUTH_1234' |
128 | + self.token = '4321' |
129 | + self.options = FakeOptions() |
130 | + self.container = Container( |
131 | + self.url, self.token, self.options, **contents) |
132 | + |
133 | + def test_representation(self): |
134 | + # `TicketContainer` representation and base attributes. |
135 | + self.assertEquals('ticket-3', unicode(self.container)) |
136 | + self.assertEquals('ticket-3', self.container.name) |
137 | + self.assertEquals(17, self.container.size) |
138 | + self.assertEquals(1, self.container.count) |
139 | + |
140 | + @patch('swiftclient.client.post_account') |
141 | + @patch('time.time') |
142 | + @patch('gatekeeper.datastore.ticket.Container._get_tempurl_key') |
143 | + @patch('swiftclient.client.get_container') |
144 | + def test_get_objects_tempurls(self, mocked_container, mocked_key, |
145 | + mocked_time, mocked_post): |
146 | + # `TicketContainer.get_objects_tempurls` returns a dictionary |
147 | + # keyed by the ticket objects (artifacts) names and their |
148 | + # corresponding tempurls (for GET) as values. |
149 | + mocked_container.return_value = ( |
150 | + 'ignored', [{'name': 'one'}, {'name': 'two'}]) |
151 | + mocked_key.return_value = b'02c5fc18-ca73-11e3-88d9-001c4229f9ac' |
152 | + mocked_time.return_value = 1397702769 |
153 | + |
154 | + self.assertEquals( |
155 | + {'one': 'http://fake.com/v1/AUTH_1234/ticket-3/one?' |
156 | + 'temp_url_sig=5de01a1853ae94eb824d4c12d2d1dafb0d4f1000&' |
157 | + 'temp_url_expires=1397703069', |
158 | + 'two': 'http://fake.com/v1/AUTH_1234/ticket-3/two?' |
159 | + 'temp_url_sig=27a61b0817e51fdd1a64ca2d71d98d6d4a1adebf&' |
160 | + 'temp_url_expires=1397703069'}, |
161 | + self.container.get_objects_tempurls()) |
162 | |
163 | === modified file 'gatekeeper/gatekeeper/datastore/tests/test_ticket.py' |
164 | --- gatekeeper/gatekeeper/datastore/tests/test_ticket.py 2014-10-07 10:04:01 +0000 |
165 | +++ gatekeeper/gatekeeper/datastore/tests/test_ticket.py 2015-01-14 23:23:47 +0000 |
166 | @@ -1,6 +1,6 @@ |
167 | # -*- coding: utf-8 -*- |
168 | # Ubuntu CI Engine |
169 | -# Copyright 2014 Canonical Ltd. |
170 | +# Copyright 2014, 2015 Canonical Ltd. |
171 | |
172 | # This program is free software: you can redistribute it and/or modify it |
173 | # under the terms of the GNU Affero General Public License version 3, as |
174 | @@ -19,62 +19,14 @@ |
175 | from mock import patch |
176 | import unittest |
177 | |
178 | +from gatekeeper.datastore.container import Container |
179 | from gatekeeper.datastore.tests import FakeOptions |
180 | from gatekeeper.datastore.ticket import ( |
181 | - TicketContainer, |
182 | TicketContainerNotFound, |
183 | TicketContainerMixin, |
184 | ) |
185 | |
186 | |
187 | -class TestTicketContainer(unittest.TestCase): |
188 | - """Tests for `TicketContainer`.""" |
189 | - |
190 | - def setUp(self): |
191 | - # Creates a sample `TicketContainer`. |
192 | - super(TestTicketContainer, self).setUp() |
193 | - contents = { |
194 | - 'name': 'ticket-3', |
195 | - 'bytes': 17, |
196 | - 'count': 1, |
197 | - } |
198 | - self.url = 'http://fake.com/AUTH_1234' |
199 | - self.token = '4321' |
200 | - self.options = FakeOptions() |
201 | - self.container = TicketContainer( |
202 | - self.url, self.token, self.options, **contents) |
203 | - |
204 | - def test_representation(self): |
205 | - # `TicketContainer` representation and base attributes. |
206 | - self.assertEquals('ticket-3', unicode(self.container)) |
207 | - self.assertEquals('ticket-3', self.container.name) |
208 | - self.assertEquals(17, self.container.size) |
209 | - self.assertEquals(1, self.container.count) |
210 | - |
211 | - @patch('swiftclient.client.post_account') |
212 | - @patch('time.time') |
213 | - @patch('gatekeeper.datastore.ticket.TicketContainer._get_tempurl_key') |
214 | - @patch('swiftclient.client.get_container') |
215 | - def test_get_objects_tempurls(self, mocked_container, mocked_key, |
216 | - mocked_time, mocked_post): |
217 | - # `TicketContainer.get_objects_tempurls` returns a dictionary |
218 | - # keyed by the ticket objects (artifacts) names and their |
219 | - # corresponding tempurls (for GET) as values. |
220 | - mocked_container.return_value = ( |
221 | - 'ignored', [{'name': 'one'}, {'name': 'two'}]) |
222 | - mocked_key.return_value = b'02c5fc18-ca73-11e3-88d9-001c4229f9ac' |
223 | - mocked_time.return_value = 1397702769 |
224 | - |
225 | - self.assertEquals( |
226 | - {'one': 'http://fake.com/v1/AUTH_1234/ticket-3/one?' |
227 | - 'temp_url_sig=5de01a1853ae94eb824d4c12d2d1dafb0d4f1000&' |
228 | - 'temp_url_expires=1397703069', |
229 | - 'two': 'http://fake.com/v1/AUTH_1234/ticket-3/two?' |
230 | - 'temp_url_sig=27a61b0817e51fdd1a64ca2d71d98d6d4a1adebf&' |
231 | - 'temp_url_expires=1397703069'}, |
232 | - self.container.get_objects_tempurls()) |
233 | - |
234 | - |
235 | class TestTicketContainerMixin(unittest.TestCase): |
236 | """Tests for `TicketContainerMixin`.""" |
237 | |
238 | @@ -94,7 +46,7 @@ |
239 | container = self.mixin.get_ticket_container('9') |
240 | mocked_account.assert_called_once_with( |
241 | self.mixin.url, self.mixin.token, prefix='ticket-9') |
242 | - self.assertIsInstance(container, TicketContainer) |
243 | + self.assertIsInstance(container, Container) |
244 | self.assertEquals('ticket-9', container.name) |
245 | self.assertEquals(0, container.size) |
246 | self.assertEquals(0, container.count) |
247 | |
248 | === added file 'gatekeeper/gatekeeper/datastore/tests/test_ubuntucore.py' |
249 | --- gatekeeper/gatekeeper/datastore/tests/test_ubuntucore.py 1970-01-01 00:00:00 +0000 |
250 | +++ gatekeeper/gatekeeper/datastore/tests/test_ubuntucore.py 2015-01-14 23:23:47 +0000 |
251 | @@ -0,0 +1,73 @@ |
252 | +# -*- coding: utf-8 -*- |
253 | +# Ubuntu CI Engine |
254 | +# Copyright 2014, 2015 Canonical Ltd. |
255 | + |
256 | +# This program is free software: you can redistribute it and/or modify it |
257 | +# under the terms of the GNU Affero General Public License version 3, as |
258 | +# published by the Free Software Foundation. |
259 | + |
260 | +# This program is distributed in the hope that it will be useful, but |
261 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
262 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
263 | +# PURPOSE. See the GNU Affero General Public License for more details. |
264 | + |
265 | +# You should have received a copy of the GNU Affero General Public License |
266 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
267 | +"""Swift datastore ubuntucore test result container mixin tests.""" |
268 | +from __future__ import unicode_literals |
269 | + |
270 | +from mock import patch |
271 | +import unittest |
272 | + |
273 | +from gatekeeper.datastore.container import Container |
274 | +from gatekeeper.datastore.tests import FakeOptions |
275 | +from gatekeeper.datastore.ubuntucore import ( |
276 | + UbuntuCoreContainerMixin, |
277 | + UbuntuCoreContainerNotFound, |
278 | +) |
279 | + |
280 | + |
281 | +class TestUbuntuCoreContainerMixin(unittest.TestCase): |
282 | + """Tests for `UbuntuCoreContainerMixin`.""" |
283 | + |
284 | + def setUp(self): |
285 | + # Creates a sample `UbuntuCoreContainerMixin` object. |
286 | + super(TestUbuntuCoreContainerMixin, self).setUp() |
287 | + self.mixin = UbuntuCoreContainerMixin() |
288 | + self.mixin.url = 'http://a-url' |
289 | + self.mixin.token = 'a-token' |
290 | + self.mixin.options = FakeOptions() |
291 | + |
292 | + @patch('swiftclient.client.get_account') |
293 | + def test_get_container(self, mocked_account): |
294 | + # `UbuntuCoreContainerMixin` implements a test result container |
295 | + # getter. |
296 | + mocked_account.return_value = ( |
297 | + 'ignored', [{'name': 'ubuntucore-9', 'bytes': 0, 'count': 0}]) |
298 | + container = self.mixin.get_ubuntucore_container('9') |
299 | + mocked_account.assert_called_once_with( |
300 | + self.mixin.url, self.mixin.token, prefix='ubuntucore-9') |
301 | + self.assertIsInstance(container, Container) |
302 | + self.assertEquals('ubuntucore-9', container.name) |
303 | + self.assertEquals(0, container.size) |
304 | + self.assertEquals(0, container.count) |
305 | + |
306 | + @patch('swiftclient.client.get_account') |
307 | + def test_get_container_missing(self, mocked_account): |
308 | + # `UbuntuCoreContainerMixin.get_ubuntucore_container` raises |
309 | + # `UbuntuCoreContainerNotFound` on missing test results. |
310 | + mocked_account.return_value = ('ignored', []) |
311 | + with self.assertRaises(UbuntuCoreContainerNotFound): |
312 | + self.mixin.get_ubuntucore_container('10') |
313 | + mocked_account.assert_called_once_with( |
314 | + self.mixin.url, self.mixin.token, prefix='ubuntucore-10') |
315 | + |
316 | + @patch('swiftclient.client.get_account') |
317 | + def test_get_container_exact(self, mocked_account): |
318 | + # `UbuntuCoreContainerMixin` getter only returns exact matches. |
319 | + mocked_account.return_value = ( |
320 | + 'ignored', [{'name': 'ubuntucore-9999', 'bytes': 0, 'count': 0}]) |
321 | + with self.assertRaises(UbuntuCoreContainerNotFound): |
322 | + self.mixin.get_ubuntucore_container('9') |
323 | + mocked_account.assert_called_once_with( |
324 | + self.mixin.url, self.mixin.token, prefix='ubuntucore-9') |
325 | |
326 | === modified file 'gatekeeper/gatekeeper/datastore/ticket.py' |
327 | --- gatekeeper/gatekeeper/datastore/ticket.py 2014-10-31 14:26:27 +0000 |
328 | +++ gatekeeper/gatekeeper/datastore/ticket.py 2015-01-14 23:23:47 +0000 |
329 | @@ -1,6 +1,6 @@ |
330 | # -*- coding: utf-8 -*- |
331 | # Ubuntu CI Engine |
332 | -# Copyright 2014 Canonical Ltd. |
333 | +# Copyright 2014, 2015 Canonical Ltd. |
334 | |
335 | # This program is free software: you can redistribute it and/or modify it |
336 | # under the terms of the GNU Affero General Public License version 3, as |
337 | @@ -13,68 +13,23 @@ |
338 | |
339 | # You should have received a copy of the GNU Affero General Public License |
340 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
341 | -"""Swift ticket container hanlder mixin.""" |
342 | +"""Swift ticket container handler mixin.""" |
343 | from __future__ import unicode_literals |
344 | |
345 | -import uuid |
346 | - |
347 | from swiftclient import client |
348 | |
349 | -from ci_utils import unit_config |
350 | -from tempurl import ( |
351 | - OSTempUrlProvider, |
352 | - HPTempUrlProvider, |
353 | -) |
354 | +from gatekeeper.datastore.container import Container |
355 | |
356 | |
357 | class TicketContainerNotFound(Exception): |
358 | """Raised when a swift ticket container could not be found.""" |
359 | |
360 | |
361 | -class TicketContainer(object): |
362 | - """Swift ticket container utility with tempurl support. """ |
363 | - |
364 | - def __init__(self, url, token, options, **kwargs): |
365 | - self.url = url |
366 | - self.token = token |
367 | - self.options = options |
368 | - self.name = kwargs.get('name') |
369 | - self.size = kwargs.get('bytes') |
370 | - self.count = kwargs.get('count') |
371 | - |
372 | - def __unicode__(self): |
373 | - return self.name |
374 | - |
375 | - def _get_tempurl_key(self): |
376 | - """Local wrapper to UUID for facilitating tests.""" |
377 | - return str(uuid.uuid1()) |
378 | - |
379 | - def _get_tempurl_provider(self): |
380 | - """Return the tempurl provider appropriate for this context.""" |
381 | - if unit_config.is_hpcloud(self.options.auth_url): |
382 | - return HPTempUrlProvider(self.url, self.token, self.options) |
383 | - return OSTempUrlProvider(self.url, self.token, self.options) |
384 | - |
385 | - def get_objects_tempurls(self, method='GET'): |
386 | - """Return a tempurls for objects for the given method.""" |
387 | - provider = self._get_tempurl_provider() |
388 | - provider.set_temp_key(self._get_tempurl_key()) |
389 | - header, contents = client.get_container( |
390 | - self.url, self.token, self.name) |
391 | - results = [] |
392 | - for content in contents: |
393 | - object_name = content['name'] |
394 | - temp_url = provider.get_temp_url( |
395 | - self.name, object_name, method=method) |
396 | - results.append((object_name, temp_url)) |
397 | - return dict(results) |
398 | - |
399 | - |
400 | class TicketContainerMixin(object): |
401 | """Swift datastore mixin for ticket container utility.""" |
402 | |
403 | def get_ticket_container(self, ticket_id): |
404 | - """Return a corresponding `TicketContainer`. |
405 | + """Return the corresponding `Container`. |
406 | |
407 | Raises `TicketContainerNotFound` if it could not be found. |
408 | """ |
409 | @@ -87,5 +42,5 @@ |
410 | raise TicketContainerNotFound() |
411 | if contents[0].get('name') != container_name: |
412 | raise TicketContainerNotFound() |
413 | - return TicketContainer( |
414 | + return Container( |
415 | self.url, self.token, self.options, **contents[0]) |
416 | |
417 | === added file 'gatekeeper/gatekeeper/datastore/ubuntucore.py' |
418 | --- gatekeeper/gatekeeper/datastore/ubuntucore.py 1970-01-01 00:00:00 +0000 |
419 | +++ gatekeeper/gatekeeper/datastore/ubuntucore.py 2015-01-14 23:23:47 +0000 |
420 | @@ -0,0 +1,46 @@ |
421 | +# -*- coding: utf-8 -*- |
422 | +# Ubuntu CI Engine |
423 | +# Copyright 2014, 2015 Canonical Ltd. |
424 | + |
425 | +# This program is free software: you can redistribute it and/or modify it |
426 | +# under the terms of the GNU Affero General Public License version 3, as |
427 | +# published by the Free Software Foundation. |
428 | + |
429 | +# This program is distributed in the hope that it will be useful, but |
430 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
431 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
432 | +# PURPOSE. See the GNU Affero General Public License for more details. |
433 | + |
434 | +# You should have received a copy of the GNU Affero General Public License |
435 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
436 | +"""Swift ubuntucore testing results container handler mixin.""" |
437 | +from __future__ import unicode_literals |
438 | + |
439 | +from swiftclient import client |
440 | + |
441 | +from gatekeeper.datastore.container import Container |
442 | + |
443 | + |
444 | +class UbuntuCoreContainerNotFound(Exception): |
445 | + """Raised when a swift test result container couldn't be found.""" |
446 | + |
447 | + |
448 | +class UbuntuCoreContainerMixin(object): |
449 | + """Swift datastore mixin for ubuntucore container utility.""" |
450 | + |
451 | + def get_ubuntucore_container(self, result_id): |
452 | + """Return the corresponding `Container`. |
453 | + |
454 | + Raises `UbuntuCoreContainerNotFound` if it could not be found. |
455 | + """ |
456 | + if not result_id: |
457 | + raise UbuntuCoreContainerNotFound() |
458 | + container_name = 'ubuntucore-{}'.format(result_id) |
459 | + headers, contents = client.get_account( |
460 | + self.url, self.token, prefix=container_name) |
461 | + if len(contents) == 0: |
462 | + raise UbuntuCoreContainerNotFound() |
463 | + if contents[0].get('name') != container_name: |
464 | + raise UbuntuCoreContainerNotFound() |
465 | + return Container( |
466 | + self.url, self.token, self.options, **contents[0]) |
467 | |
468 | === modified file 'gatekeeper/gatekeeper/resources/root.py' |
469 | --- gatekeeper/gatekeeper/resources/root.py 2014-04-26 00:03:31 +0000 |
470 | +++ gatekeeper/gatekeeper/resources/root.py 2015-01-14 23:23:47 +0000 |
471 | @@ -1,6 +1,6 @@ |
472 | # -*- coding: utf-8 -*- |
473 | # Ubuntu CI Engine |
474 | -# Copyright 2014 Canonical Ltd. |
475 | +# Copyright 2014, 2015 Canonical Ltd. |
476 | |
477 | # This program is free software: you can redistribute it and/or modify it |
478 | # under the terms of the GNU Affero General Public License version 3, as |
479 | @@ -16,11 +16,16 @@ |
480 | """Restish root resource.""" |
481 | from __future__ import unicode_literals |
482 | |
483 | -from restish import resource |
484 | +from restish import ( |
485 | + http, |
486 | + resource, |
487 | +) |
488 | from restish.app import RestishApp |
489 | |
490 | +from gatekeeper.datastore.ubuntucore import UbuntuCoreContainerNotFound |
491 | from gatekeeper.resources import GatekeeperResource |
492 | from gatekeeper.resources.v1 import API as v1_api |
493 | +from gatekeeper.resources.ubuntucore import UbuntuCoreResource |
494 | |
495 | |
496 | class Root(GatekeeperResource): |
497 | @@ -31,6 +36,14 @@ |
498 | self.logger.debug('[api]: %s %s', request.url, str(segments)) |
499 | return v1_api() |
500 | |
501 | + @resource.child('ubuntucore/{result_id}') |
502 | + def ubuntucore_wrapper(self, request, segments, result_id): |
503 | + try: |
504 | + self.datastore.get_ubuntucore_container(result_id) |
505 | + except UbuntuCoreContainerNotFound: |
506 | + return http.not_found() |
507 | + return UbuntuCoreResource(result_id) |
508 | + |
509 | |
510 | def make_app(datastore_factory): |
511 | """Build the wsgi app object with the given datastore factory.""" |
512 | |
513 | === added file 'gatekeeper/gatekeeper/resources/tests/test_ubuntucore.py' |
514 | --- gatekeeper/gatekeeper/resources/tests/test_ubuntucore.py 1970-01-01 00:00:00 +0000 |
515 | +++ gatekeeper/gatekeeper/resources/tests/test_ubuntucore.py 2015-01-14 23:23:47 +0000 |
516 | @@ -0,0 +1,88 @@ |
517 | +# -*- coding: utf-8 -*- |
518 | +# Ubuntu CI Engine |
519 | +# Copyright 2014, 2015 Canonical Ltd. |
520 | + |
521 | +# This program is free software: you can redistribute it and/or modify it |
522 | +# under the terms of the GNU Affero General Public License version 3, as |
523 | +# published by the Free Software Foundation. |
524 | + |
525 | +# This program is distributed in the hope that it will be useful, but |
526 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
527 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
528 | +# PURPOSE. See the GNU Affero General Public License for more details. |
529 | + |
530 | +# You should have received a copy of the GNU Affero General Public License |
531 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
532 | +"""UbuntuCore test suite.""" |
533 | +from __future__ import unicode_literals |
534 | + |
535 | +from testtools import TestCase |
536 | +from webtest import TestApp |
537 | + |
538 | +from gatekeeper.datastore.ubuntucore import UbuntuCoreContainerNotFound |
539 | +from gatekeeper.resources.root import make_app |
540 | + |
541 | + |
542 | +class FakeUbuntuCoreContainer(object): |
543 | + |
544 | + def get_objects_tempurls(self): |
545 | + return { |
546 | + 'pass': 'http://pass_temp_url', |
547 | + 'tarball': 'http://tarball_temp_url', |
548 | + } |
549 | + |
550 | + |
551 | +class FakeDataStore(object): |
552 | + |
553 | + def get_ubuntucore_container(self, result_id): |
554 | + if result_id in ['', '1000']: |
555 | + raise UbuntuCoreContainerNotFound() |
556 | + return FakeUbuntuCoreContainer() |
557 | + |
558 | + |
559 | +def fake_datastore_factory(klass): |
560 | + return FakeDataStore() |
561 | + |
562 | + |
563 | +class TestUbuntuCoreResource(TestCase): |
564 | + """UbuntuCore resource tests.""" |
565 | + |
566 | + def setUp(self): |
567 | + # Run the restful server. |
568 | + super(TestUbuntuCoreResource, self).setUp() |
569 | + wsgi_app = make_app(fake_datastore_factory) |
570 | + self.app = TestApp(wsgi_app, relative_to='.') |
571 | + |
572 | + def test_get_not_found(self): |
573 | + # Empty GET on ubuntucore/ or pointing to an absent ID returns 404. |
574 | + self.app.get('/ubuntucore/', status=404) |
575 | + self.app.get('/ubuntucore/1000/', status=404) |
576 | + |
577 | + def test_get_returns_test_result_html(self): |
578 | + # GET on a ubuntucore result_id URL returns |
579 | + # Since tempurls expires, this page should not be cached in browsers. |
580 | + resp = self.app.get('/ubuntucore/1') |
581 | + self.assertEqual(200, resp.status_code, resp.body) |
582 | + self.assertEqual( |
583 | + 'max-age=0, no-cache', str(resp.cache_control)) |
584 | + self.assertEqual('text/html', resp.content_type) |
585 | + self.assertEqual([ |
586 | + '<html>', |
587 | + '<head>', |
588 | + '<title>UbuntuCore results for 1</title>', |
589 | + '</head>', |
590 | + '<body>', |
591 | + '<h1>UbuntuCore results for 1</h1>', |
592 | + '<ul>', |
593 | + '<li><a href="http://pass_temp_url">pass</a></li>', |
594 | + '<li><a href="http://tarball_temp_url">tarball</a></li>', |
595 | + '</ul>', |
596 | + '</body>', |
597 | + '</html>' |
598 | + ], resp.body.splitlines()) |
599 | + |
600 | + def test_other_methods_not_allowed(self): |
601 | + # POST, PUT and DELETE are not allowed. |
602 | + self.app.post('/ubuntucore/1', status=405) |
603 | + self.app.put('/ubuntucore/1', status=405) |
604 | + self.app.delete('/ubuntucore/1', status=405) |
605 | |
606 | === added file 'gatekeeper/gatekeeper/resources/ubuntucore.py' |
607 | --- gatekeeper/gatekeeper/resources/ubuntucore.py 1970-01-01 00:00:00 +0000 |
608 | +++ gatekeeper/gatekeeper/resources/ubuntucore.py 2015-01-14 23:23:47 +0000 |
609 | @@ -0,0 +1,73 @@ |
610 | +# -*- coding: utf-8 -*- |
611 | +# Ubuntu CI Engine |
612 | +# Copyright 2014, 2015 Canonical Ltd. |
613 | + |
614 | +# This program is free software: you can redistribute it and/or modify it |
615 | +# under the terms of the GNU Affero General Public License version 3, as |
616 | +# published by the Free Software Foundation. |
617 | + |
618 | +# This program is distributed in the hope that it will be useful, but |
619 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
620 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
621 | +# PURPOSE. See the GNU Affero General Public License for more details. |
622 | + |
623 | +# You should have received a copy of the GNU Affero General Public License |
624 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
625 | +"""UbuntuCore service resource.""" |
626 | +from __future__ import unicode_literals |
627 | + |
628 | +from restish import ( |
629 | + http, |
630 | + resource, |
631 | +) |
632 | + |
633 | +from gatekeeper.datastore.ubuntucore import UbuntuCoreContainerNotFound |
634 | +from gatekeeper.resources import GatekeeperResource |
635 | + |
636 | + |
637 | +class UbuntuCoreResource(GatekeeperResource): |
638 | + """UbuntuCore service resource. |
639 | + |
640 | + - GET '/{'current'|<result_id>}' -> returns a simple HTML page presenting |
641 | + the results. |
642 | + """ |
643 | + |
644 | + def __init__(self, result_id): |
645 | + super(UbuntuCoreResource, self).__init__() |
646 | + self.result_id = result_id |
647 | + |
648 | + @resource.GET() |
649 | + def contents(self, request): |
650 | + try: |
651 | + container = self.datastore.get_ubuntucore_container( |
652 | + self.result_id) |
653 | + except UbuntuCoreContainerNotFound: |
654 | + return http.not_found() |
655 | + # XXX cprov 20140114: expand details presented according to |
656 | + # the customer story specification. |
657 | + obj_list = ['<ul>'] |
658 | + tempurls = container.get_objects_tempurls() |
659 | + for k, v in sorted(tempurls.items()): |
660 | + obj_list.append('<li><a href="{}">{}</a></li>'.format(v, k)) |
661 | + obj_list.append('</ul>') |
662 | + context = { |
663 | + 'result_id': self.result_id, |
664 | + 'obj_list': '\n'.join(obj_list), |
665 | + } |
666 | + payload = '\n'.join([ |
667 | + '<html>', |
668 | + '<head>', |
669 | + '<title>UbuntuCore results for {result_id}</title>', |
670 | + '</head>', |
671 | + '<body>', |
672 | + '<h1>UbuntuCore results for {result_id}</h1>', |
673 | + '{obj_list}', |
674 | + '</body>', |
675 | + '</html>', |
676 | + ]).format(**context) |
677 | + |
678 | + headers = [ |
679 | + (b'Cache-Control', 'max-age=0, no-cache'), |
680 | + (b'Content-Type', b'text/html; charset=utf-8') |
681 | + ] |
682 | + return http.ok(headers, str(payload)) |
683 | |
684 | === added file 'juju-deployer/configs/ubuntucore_http_vhost' |
685 | --- juju-deployer/configs/ubuntucore_http_vhost 1970-01-01 00:00:00 +0000 |
686 | +++ juju-deployer/configs/ubuntucore_http_vhost 2015-01-14 23:23:47 +0000 |
687 | @@ -0,0 +1,18 @@ |
688 | +<VirtualHost *:80> |
689 | + ServerAdmin ci-engineering-private@lists.launchpad.net |
690 | + ErrorLog ${APACHE_LOG_DIR}/webui-error.log |
691 | + LogLevel warn |
692 | + CustomLog ${APACHE_LOG_DIR}/webui-access.log combined |
693 | + |
694 | + DocumentRoot /var/www |
695 | + |
696 | + <Directory /> |
697 | + Options FollowSymLinks |
698 | + AllowOverride None |
699 | + </Directory> |
700 | + |
701 | + RewriteEngine on |
702 | + Include /etc/apache2/juju-*.conf |
703 | + RewriteRule ^/$ http://{{ ciairlinegatekeeperrestish }}/ubuntucore/current [P] |
704 | + RewriteRule ^/(.*)$ http://{{ ciairlinegatekeeperrestish }}/ubuntucore/$1 [P] |
705 | +</VirtualHost> |
706 | |
707 | === modified file 'juju-deployer/relations.yaml' |
708 | --- juju-deployer/relations.yaml 2015-01-13 17:44:27 +0000 |
709 | +++ juju-deployer/relations.yaml 2015-01-14 23:23:47 +0000 |
710 | @@ -121,3 +121,13 @@ |
711 | |
712 | ci-ubuntucore: |
713 | inherits: common |
714 | + services: |
715 | + ci-ubuntucore-webui-apache: |
716 | + charm: apache2 |
717 | + ci-airline-gatekeeper-restish: |
718 | + charm: uci-engine-wsgi-app |
719 | + ci-airline-gatekeeper-gunicorn: |
720 | + charm: gunicorn |
721 | + relations: |
722 | + - ["ci-airline-gatekeeper-restish:wsgi", "ci-airline-gatekeeper-gunicorn:wsgi-file"] |
723 | + - ["ci-airline-gatekeeper-restish:website", "ci-ubuntucore-webui-apache:reverseproxy"] |
724 | |
725 | === modified file 'juju-deployer/services.yaml.tmpl' |
726 | --- juju-deployer/services.yaml.tmpl 2015-01-13 17:44:27 +0000 |
727 | +++ juju-deployer/services.yaml.tmpl 2015-01-14 23:23:47 +0000 |
728 | @@ -428,5 +428,35 @@ |
729 | branch: lp:charms/precise/apache2@54 |
730 | options: |
731 | enable_modules: "rewrite proxy proxy_http ssl" |
732 | - servername: ci_ubuntu_core_webui_apache |
733 | + vhost_http_template: include-base64://configs/ubuntucore_http_vhost |
734 | + servername: ci_ubuntucore_webui_apache |
735 | expose: true |
736 | + ci-airline-gatekeeper-restish: |
737 | + expose: False |
738 | + branch: lp:~canonical-ci-engineering/charms/precise/uci-engine-wsgi-app/trunk@2 |
739 | + charm: uci-engine-wsgi-app |
740 | + constraints: "mem=1024M" |
741 | + options: |
742 | + current_code: ${CI_PAYLOAD_URL} |
743 | + available_code: ${CI_PAYLOAD_URL} |
744 | + python_path: ./gatekeeper:./ci-utils |
745 | + packages: "python-swiftclient python-tz python-dput python-gnupg" |
746 | + unit-config: include-base64://configs/unit_config.yaml |
747 | + json_status_path: api/v1/status |
748 | + install_sources: | |
749 | + - ${CI_PPA} |
750 | + install_keys: | |
751 | + - null |
752 | + nagios_check_http_params: -H gatekeeper -I 127.0.0.1 -e ' |
753 | + 200 OK' --url='/api/v1/' -p 8080 |
754 | + nagios_check_health_params: -t 120 gatekeeper.health |
755 | + nagios_context: ci-airline-staging |
756 | + cron_cmd: ./run-python ./gatekeeper/bin/check_gatekeeper.py |
757 | + cron_schedule: "* * * * *" |
758 | + ci-airline-gatekeeper-gunicorn: |
759 | + charm: gunicorn |
760 | + branch: lp:charms/gunicorn@30 |
761 | + options: |
762 | + wsgi_wsgi_file: gatekeeper.wsgi:app |
763 | + wsgi_log_level: DEBUG |
764 | + wsgi_timeout: 60 |
765 | |
766 | === added file 'tests/test_ubuntucore.py' |
767 | --- tests/test_ubuntucore.py 1970-01-01 00:00:00 +0000 |
768 | +++ tests/test_ubuntucore.py 2015-01-14 23:23:47 +0000 |
769 | @@ -0,0 +1,119 @@ |
770 | +#!/usr/bin/env python |
771 | +# Ubuntu Continuous Integration Engine |
772 | +# Copyright 2015 Canonical Ltd. |
773 | + |
774 | +# This program is free software: you can redistribute it and/or modify it |
775 | +# under the terms of the GNU Affero General Public License version 3, as |
776 | +# published by the Free Software Foundation. |
777 | + |
778 | +# This program is distributed in the hope that it will be useful, but |
779 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
780 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
781 | +# PURPOSE. See the GNU Affero General Public License for more details. |
782 | + |
783 | +# You should have received a copy of the GNU Affero General Public License |
784 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
785 | +from __future__ import unicode_literals |
786 | + |
787 | +import unittest |
788 | + |
789 | +from sst.actions import ( |
790 | + assert_title, |
791 | + go_to, |
792 | + get_element, |
793 | + get_elements, |
794 | + get_link_url, |
795 | + set_base_url, |
796 | + set_window_size, |
797 | +) |
798 | +from sst import ( |
799 | + browsers, |
800 | + cases, |
801 | +) |
802 | + |
803 | +from gatekeeper.datastore import DataStore |
804 | +from gatekeeper.datastore.options import DataStoreOptions |
805 | +from swiftclient import client |
806 | + |
807 | +import deployers |
808 | + |
809 | + |
810 | +class UbuntuCoreWebUITest(cases.SSTTestCase): |
811 | + |
812 | + browser_factory = browsers.PhantomJSFactory() |
813 | + |
814 | + def setUp(self): |
815 | + """Sets browser base_url based on the available deployment.""" |
816 | + super(UbuntuCoreWebUITest, self).setUp() |
817 | + try: |
818 | + set_base_url('http://{}/'.format( |
819 | + deployers.juju_get_ip_and_port( |
820 | + 'ci-ubuntucore-webui-apache')[0])) |
821 | + except KeyError: |
822 | + self.skipTest('UbuntuCore WebUI not available') |
823 | + set_window_size(1280, 1024) |
824 | + self._reset_containers() |
825 | + |
826 | + def _reset_containers(self): |
827 | + """Ensure swift containers expected by tests exist.""" |
828 | + options = DataStoreOptions() |
829 | + ds = DataStore(options) |
830 | + client.put_container(ds.url, ds.token, 'ubuntucore-current') |
831 | + client.put_object(ds.url, ds.token, 'ubuntucore-current', 'pass') |
832 | + client.put_object( |
833 | + ds.url, ds.token, 'ubuntucore-current', 'tarball_667') |
834 | + client.put_container(ds.url, ds.token, 'ubuntucore-666') |
835 | + client.put_object(ds.url, ds.token, 'ubuntucore-666', 'failed') |
836 | + client.put_object(ds.url, ds.token, 'ubuntucore-666', 'tarball_666') |
837 | + |
838 | + def get_object_links(self): |
839 | + """Return all object links available in the page. |
840 | + |
841 | + Currently, simply *all* links, but that might evolve to a dedicated |
842 | + CSS class or something similar. |
843 | + """ |
844 | + return get_elements(tag='a') |
845 | + |
846 | + def assertObjLinksText(self, expected, links): |
847 | + """Assert given links text match 'expected' list. |
848 | + |
849 | + Links text list is sorted (alphabetically) before comparing. |
850 | + """ |
851 | + self.assertEqual(expected, sorted([l.text for l in links])) |
852 | + |
853 | + def assertTempurlLinks(self, links): |
854 | + """Assert the given list of links are swift tempurl.""" |
855 | + for link in links: |
856 | + url = get_link_url(link) |
857 | + if (url is not None and |
858 | + 'temp_url_sig=' in url and |
859 | + 'temp_url_expires=' in url): |
860 | + continue |
861 | + self.fail( |
862 | + "'{}' is not a tempurl link ({})".format(link.text, url)) |
863 | + |
864 | + def test_home(self): |
865 | + # UbuntuCore home presents the contents of the 'current' test |
866 | + # result container linking to its objects tempurls. |
867 | + go_to('/') |
868 | + assert_title('UbuntuCore results for current') |
869 | + h1 = get_element(tag='h1') |
870 | + self.assertEqual('UbuntuCore results for current', h1.text) |
871 | + obj_links = self.get_object_links() |
872 | + self.assertObjLinksText(['pass', 'tarball_667'], obj_links) |
873 | + self.assertTempurlLinks(obj_links) |
874 | + |
875 | + def test_specific_version(self): |
876 | + # UbuntuCore webui allow navigation to a specific test result |
877 | + # (image really) version. |
878 | + go_to('/666') |
879 | + assert_title('UbuntuCore results for 666') |
880 | + h1 = get_element(tag='h1') |
881 | + self.assertEqual('UbuntuCore results for 666', h1.text) |
882 | + obj_links = self.get_object_links() |
883 | + self.assertObjLinksText(['failed', 'tarball_666'], obj_links) |
884 | + self.assertTempurlLinks(obj_links) |
885 | + |
886 | + |
887 | +if __name__ == "__main__": |
888 | + unittest.main() |
889 | |
890 | === modified file 'tests/test_webui.py' |
891 | --- tests/test_webui.py 2015-01-13 17:44:27 +0000 |
892 | +++ tests/test_webui.py 2015-01-14 23:23:47 +0000 |
893 | @@ -1,6 +1,6 @@ |
894 | #!/usr/bin/env python |
895 | # Ubuntu Continuous Integration Engine |
896 | -# Copyright 2014 Canonical Ltd. |
897 | +# Copyright 2014, 2015 Canonical Ltd. |
898 | |
899 | # This program is free software: you can redistribute it and/or modify it |
900 | # under the terms of the GNU Affero General Public License version 3, as |
901 | @@ -538,26 +538,5 @@ |
902 | self.assertEqual('Engine health', active_link.text) |
903 | |
904 | |
905 | -class UbuntuCoreWebUITest(cases.SSTTestCase): |
906 | - |
907 | - browser_factory = browsers.PhantomJSFactory() |
908 | - |
909 | - def setUp(self): |
910 | - """Sets browser base_url based on the available deployment.""" |
911 | - super(UbuntuCoreWebUITest, self).setUp() |
912 | - try: |
913 | - set_base_url('http://{}/'.format( |
914 | - deployers.juju_get_ip_and_port( |
915 | - 'ci-ubuntucore-webui-apache')[0])) |
916 | - except KeyError: |
917 | - self.skipTest('UbuntuCore WebUI not available') |
918 | - set_window_size(1280, 1024) |
919 | - |
920 | - def test_home(self): |
921 | - # Placeholder test, since UbuntuCore apache is still unconfigured. |
922 | - go_to('/') |
923 | - assert_title('403 Forbidden') |
924 | - |
925 | - |
926 | if __name__ == "__main__": |
927 | unittest.main() |