Merge lp:~cprov/uci-engine/gk-ubuntucore into lp:uci-engine

Proposed by Celso Providelo
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
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-<'current'|<image|tr ID>>) under <GK>/ubuntucore/<ID> and wire up Apache.

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-<'current'|<image|tr ID>>) under <GK>/ubuntucore/<ID>.

Also wire up ubuntucore-webui-apache to proxy requests to GK on:

* / -> <GK>/ubuntucore/current
* /<ID> -> <GK>/ubuntucore/<ID> (suffices for browsing if we propagate the image ID)

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`).

To post a comment you must log in.

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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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()

Subscribers

People subscribed via source and target branches