Merge lp:~doanac/ubuntu-ci-services-itself/bsbuilder into lp:ubuntu-ci-services-itself

Proposed by Andy Doan
Status: Merged
Approved by: Francis Ginther
Approved revision: 25
Merged at revision: 28
Proposed branch: lp:~doanac/ubuntu-ci-services-itself/bsbuilder
Merge into: lp:ubuntu-ci-services-itself
Diff against target: 800 lines (+720/-2)
12 files modified
README (+31/-2)
branch-source-builder/bsbuilder/__init__.py (+17/-0)
branch-source-builder/bsbuilder/amqp_utils.py (+108/-0)
branch-source-builder/bsbuilder/resources/root.py (+30/-0)
branch-source-builder/bsbuilder/resources/v1.py (+53/-0)
branch-source-builder/bsbuilder/tests/test_utils.py (+165/-0)
branch-source-builder/bsbuilder/tests/test_v1.py (+70/-0)
branch-source-builder/bsbuilder/utils.py (+78/-0)
branch-source-builder/bsbuilder/wsgi.py (+48/-0)
branch-source-builder/run_worker (+47/-0)
branch-source-builder/setup.py (+44/-0)
juju-deployer/branch-source-builder.yaml (+29/-0)
To merge this branch: bzr merge lp:~doanac/ubuntu-ci-services-itself/bsbuilder
Reviewer Review Type Date Requested Status
Francis Ginther Approve
Review via email: mp+198308@code.launchpad.net

Commit message

Implements the webservice portion of the branch source builder

This creates a simple app using restish that takes a build_source request and sends it to rabbitmq. On the other end we have a simple worker script, branch-source-builder/run_worker that will wait for messages and print a TODO.

The MP itself is bigger than the service because of the groundwork I did for the restish service.

Description of the change

Implements the webservice portion of the branch source builder

This creates a simple app using restish that takes a build_source request and sends it to rabbitmq. On the other end we have a simple worker script, branch-source-builder/run_worker that will wait for messages and print a TODO.

The MP itself is bigger than the service because of the groundwork I did for the restish service.

To post a comment you must log in.
Revision history for this message
Andy Doan (doanac) wrote :

There are some caveats to getting the juju-deployer portion of this working:

1) the rabbitmq-server charm is broke. It seems to be the .deb itself, but you have to edit /etc/hosts and add the host name to the 127.0.0.1 entry so the install will work. If you are fast and do that before juju-deployer gets to the bsb-rabbit unit the deployer will work. Otherwise you have to make that change, run "juju resolved --retry bsb-rabbit/0". then re-run the deployer script.

2) the bsb-worker script doesn't work out the first time. For some reason even with upstart respawing it, it won't start until you run: sudo stop bsb_worker; sudo start bsb_worker

At this point the service should be running and you can test with:

 curl --dump-header - -H "Content-Type: application/json" -X POST --data '{"source_packages": "todo_source", "ppa": "todo_ppa", "progress_trigger": "todo_trigger"}' http://<BSB RESTISH IP>:8080/api/v1/build_source

then on the bsb_worker node, you'll see a TODO printed in /var/log/upstart/bsb_worker.log

23. By Andy Doan

work around a canonistack bug with seen with rabbitmq-server

Revision history for this message
Andy Doan (doanac) wrote :

I've fixed the rabbitmq-server issue mentioned above by creating our own rabbitmq-server charm with a fix.

I've identified the solution to the 2nd problem and should have a minor fix for that ready soon.

24. By Andy Doan

move queue helpers to their own module

This makes the queue helpers more accessible by the run_worker
script w/o making it have to pull in python-restish dependencies.

It also uses the new common configuration file mechanism thats
been changed in the rabbitmq-worker charm to be like the
restish charm.

Revision history for this message
Andy Doan (doanac) wrote :

as of revno 24, things "just work".

Revision history for this message
Francis Ginther (fginther) wrote :

In addition to eventually refactoring the queue helpers into a common utils directory as you mentioned:

 - when refactoring, be sure to split the tests in branch-source-builder/bsbuilder/tests/test_utils.py.
 - branch-source-builder/setup.py is not executable
 - wsgi.py starts the server on port 8080, but prints '8000'. Is there a guideline for choosing ports? My local jenkins runs on 8080 also so I can't start this locally as is.

Not having done a REST app before, I don't have a lot of input. I'm in favor of how you're doing this, but not familiar with any alternatives. I did spend extra time reviewing the queue setup and like what's going on there.

Want to do some local testing before approving.

25. By Andy Doan

address comments from fginther

make setup.py executable
use argparse for local wsgi server

Revision history for this message
Francis Ginther (fginther) wrote :

Was able to deploy successfully after adjusting the juju-deployer file to point to this branch.

Let's go with this and tweak it over time.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'README'
--- README 2013-11-25 17:45:20 +0000
+++ README 2013-12-11 16:52:29 +0000
@@ -4,8 +4,14 @@
4Development environments can be set of using the setup.py files in the4Development environments can be set of using the setup.py files in the
5projects you wish to work on. The easiest approach is to use python-virtualenv.5projects you wish to work on. The easiest approach is to use python-virtualenv.
6Since most projects require the ci-utils project, that should almost always6Since most projects require the ci-utils project, that should almost always
7get setup first::7get setup first.
88
9There are two types of services written in this project, Django and Restish.
10Development varies slightly between the two.
11
12Django
13~~~~~~
14::
9 # setup the ppa-assigner project15 # setup the ppa-assigner project
10 virtualenv /tmp/venv16 virtualenv /tmp/venv
11 . /tmp/venv/bin/activate17 . /tmp/venv/bin/activate
@@ -25,6 +31,29 @@
25 ./ppa-assigner/manage.py test ppa_assigner.TestApi #test one class31 ./ppa-assigner/manage.py test ppa_assigner.TestApi #test one class
26 ./ppa-assigner/manage.py test ppa_assigner.TestApi.testFree # test one method32 ./ppa-assigner/manage.py test ppa_assigner.TestApi.testFree # test one method
2733
34Restish
35~~~~~~~
36::
37 virtualenv /tmp/venv
38 . /tmp/venv/bin/activate
39 ./branch-source-builder/setup.py develop
40
41Unit-testing can be done with::
42
43 python -m unittest bsbuilder.tests.test_example
44
45Running under the python wsgi server can be done with::
46
47 ./branch-source-builder/bsbuilder/wsgi.py
48
49Running under gunicorn can be done with::
50
51 # need gunicorn:
52 pip install gunicorn
53
54 # TIP: by setting max-requests=1 you can make changes to the source code
55 # and they'll be picked up in the next http request you make!!
56 gunicorn --max-requests 1 bsbuilder.wsgi:app
2857
29Juju Testing58Juju Testing
30------------59------------
3160
=== added directory 'branch-source-builder'
=== added directory 'branch-source-builder/bsbuilder'
=== added file 'branch-source-builder/bsbuilder/__init__.py'
--- branch-source-builder/bsbuilder/__init__.py 1970-01-01 00:00:00 +0000
+++ branch-source-builder/bsbuilder/__init__.py 2013-12-11 16:52:29 +0000
@@ -0,0 +1,17 @@
1# Ubuntu CI Services
2# Copyright 2013 Canonical Ltd.
3
4# This program is free software: you can redistribute it and/or modify it
5# under the terms of the GNU Affero General Public License version 3, as
6# published by the Free Software Foundation.
7
8# This program is distributed in the hope that it will be useful, but
9# WITHOUT ANY WARRANTY; without even the implied warranties of
10# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11# PURPOSE. See the GNU Affero General Public License for more details.
12
13# You should have received a copy of the GNU Affero General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16# TODO make this probe from changelog or bzr
17__version__ = '0.1'
018
=== added file 'branch-source-builder/bsbuilder/amqp_utils.py'
--- branch-source-builder/bsbuilder/amqp_utils.py 1970-01-01 00:00:00 +0000
+++ branch-source-builder/bsbuilder/amqp_utils.py 2013-12-11 16:52:29 +0000
@@ -0,0 +1,108 @@
1# Ubuntu CI Services
2# Copyright 2013 Canonical Ltd.
3
4# This program is free software: you can redistribute it and/or modify it
5# under the terms of the GNU Affero General Public License version 3, as
6# published by the Free Software Foundation.
7
8# This program is distributed in the hope that it will be useful, but
9# WITHOUT ANY WARRANTY; without even the implied warranties of
10# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11# PURPOSE. See the GNU Affero General Public License for more details.
12
13# You should have received a copy of the GNU Affero General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16import logging
17import os
18import socket
19import time
20
21from amqplib import client_0_8 as amqp
22
23log = logging.getLogger(__name__)
24
25
26def get_config():
27 '''Load the rabbit config created by the restish charm'''
28 config = None
29 try:
30 # try and find the config file, should be in the root of our bzr branch
31 f = os.path.join(os.path.dirname(__file__), '../../amqp_config.py')
32 if os.path.exists(f):
33 import imp
34 config = imp.load_source('amqp_config', f)
35 else:
36 log.warn('No amqp_config found at: %s' % os.path.abspath(f))
37 except:
38 log.exception('ERROR detecting rabbit args')
39 return config
40
41
42def connection(rabbit_config):
43 return amqp.Connection(
44 userid=rabbit_config.AMQP_USER,
45 virtual_host=rabbit_config.AMQP_VHOST,
46 host=rabbit_config.AMQP_HOST,
47 password=rabbit_config.AMQP_PASSWORD
48 )
49
50
51def send(queue, msg):
52 config = get_config()
53 if not config:
54 return 'rabbitmq settings not available.'
55 con = channel = None
56 try:
57 con = connection(config)
58 channel = con.channel()
59 channel.queue_declare(queue=queue, durable=True, auto_delete=False)
60 body = amqp.Message(msg)
61 body.properties['delivery_mode'] = 2 # Persistent
62 channel.basic_publish(body, routing_key=queue)
63 except Exception as e:
64 logging.exception('unable to queue up build_source request')
65 return str(e)
66 finally:
67 if channel:
68 channel.close()
69 if con:
70 con.close()
71
72
73def _run_forever(channel, queue, callback, retry_period=120):
74 tag = channel.basic_consume(callback=callback, queue=queue)
75 try:
76 timeout = time.time()
77 while time.time() < timeout + retry_period:
78 try:
79 channel.wait()
80 timeout = time.time()
81 except (amqp.AMQPConnectionException, socket.error):
82 logging.error('lost connection to Rabbit')
83 # TODO metrics.meter('lost_rabbit_connection')
84 # Don't probe immediately, give the network/process
85 # time to come back.
86 time.sleep(0.1)
87 logging.error('Rabbit did not reappear quickly enough.')
88 except KeyboardInterrupt:
89 pass
90 finally:
91 if channel and channel.is_open:
92 channel.basic_cancel(tag)
93
94
95def process_queue(config, queue, callback):
96 conn = channel = None
97 try:
98 conn = connection(config)
99 channel = conn.channel()
100 channel.queue_declare(queue=queue, durable=True, auto_delete=False)
101 channel.basic_qos(0, 1, False)
102 logging.info('Waiting for messages. ^C to exit.')
103 _run_forever(channel, queue, callback)
104 finally:
105 if channel:
106 channel.close()
107 if conn:
108 conn.close()
0109
=== added directory 'branch-source-builder/bsbuilder/resources'
=== added file 'branch-source-builder/bsbuilder/resources/__init__.py'
=== added file 'branch-source-builder/bsbuilder/resources/root.py'
--- branch-source-builder/bsbuilder/resources/root.py 1970-01-01 00:00:00 +0000
+++ branch-source-builder/bsbuilder/resources/root.py 2013-12-11 16:52:29 +0000
@@ -0,0 +1,30 @@
1# Ubuntu CI Services
2# Copyright 2013 Canonical Ltd.
3
4# This program is free software: you can redistribute it and/or modify it
5# under the terms of the GNU Affero General Public License version 3, as
6# published by the Free Software Foundation.
7
8# This program is distributed in the hope that it will be useful, but
9# WITHOUT ANY WARRANTY; without even the implied warranties of
10# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11# PURPOSE. See the GNU Affero General Public License for more details.
12
13# You should have received a copy of the GNU Affero General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16import logging
17
18from restish import resource
19
20from bsbuilder.resources import v1
21
22
23log = logging.getLogger(__name__)
24
25
26class Root(resource.Resource):
27 @resource.child('api/v1')
28 def api(self, request, segments):
29 log.debug('[api]: %s %s', request.url, str(segments))
30 return v1.API()
031
=== added file 'branch-source-builder/bsbuilder/resources/v1.py'
--- branch-source-builder/bsbuilder/resources/v1.py 1970-01-01 00:00:00 +0000
+++ branch-source-builder/bsbuilder/resources/v1.py 2013-12-11 16:52:29 +0000
@@ -0,0 +1,53 @@
1# Ubuntu CI Services
2# Copyright 2013 Canonical Ltd.
3
4# This program is free software: you can redistribute it and/or modify it
5# under the terms of the GNU Affero General Public License version 3, as
6# published by the Free Software Foundation.
7
8# This program is distributed in the hope that it will be useful, but
9# WITHOUT ANY WARRANTY; without even the implied warranties of
10# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11# PURPOSE. See the GNU Affero General Public License for more details.
12
13# You should have received a copy of the GNU Affero General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16import json
17import logging
18
19from restish import http, resource
20
21from bsbuilder import amqp_utils, utils
22
23log = logging.getLogger(__name__)
24
25
26def _status():
27 return utils.json_ok({
28 'rabbit_configured': amqp_utils.get_config() is not None,
29 })
30
31
32def _build_source(source_packages, ppa, progress_trigger):
33 msg = json.dumps({
34 'source_packages': source_packages,
35 'ppa': ppa,
36 'progress_trigger': progress_trigger,
37 })
38 r = amqp_utils.send('bsbuilder', msg)
39 if r:
40 # send only returns something if it an error message
41 r = http.service_unavailable(body=r)
42 return r
43
44
45class API(resource.Resource):
46 @resource.child()
47 def status(self, request, segments):
48 log.debug('[status]: %s %s', request.url, str(segments))
49 return utils.http_get_resource(_status)
50
51 @resource.child()
52 def build_source(self, request, segments):
53 return utils.http_post_resource(_build_source)
054
=== added directory 'branch-source-builder/bsbuilder/tests'
=== added file 'branch-source-builder/bsbuilder/tests/__init__.py'
=== added file 'branch-source-builder/bsbuilder/tests/test_utils.py'
--- branch-source-builder/bsbuilder/tests/test_utils.py 1970-01-01 00:00:00 +0000
+++ branch-source-builder/bsbuilder/tests/test_utils.py 2013-12-11 16:52:29 +0000
@@ -0,0 +1,165 @@
1# Ubuntu CI Services
2# Copyright 2013 Canonical Ltd.
3
4# This program is free software: you can redistribute it and/or modify it
5# under the terms of the GNU Affero General Public License version 3, as
6# published by the Free Software Foundation.
7
8# This program is distributed in the hope that it will be useful, but
9# WITHOUT ANY WARRANTY; without even the implied warranties of
10# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11# PURPOSE. See the GNU Affero General Public License for more details.
12
13# You should have received a copy of the GNU Affero General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16import json
17import socket
18import time
19import unittest
20
21import mock
22import webtest
23
24from restish import resource
25from restish.app import RestishApp
26
27from bsbuilder import amqp_utils, utils
28
29
30def _no_param_action():
31 return utils.json_ok({'func': _no_param_action.__name__})
32
33
34def _two_param_action(p1, p2):
35 return utils.json_ok({'func': _two_param_action.__name__})
36
37
38class TestGetResource(unittest.TestCase):
39 '''Test to ensure the http_get_resource works properly'''
40
41 def setUp(self):
42 super(TestGetResource, self).setUp()
43
44 class TR(resource.Resource):
45 @resource.child()
46 def empty(self, request, segments):
47 return utils.http_get_resource(_no_param_action)
48
49 @resource.child()
50 def two_param_get(self, request, segments):
51 return utils.http_get_resource(_two_param_action)
52
53 app = RestishApp(TR())
54 self.app = webtest.TestApp(app, relative_to='.')
55
56 def testGetNoParam(self):
57 resp = self.app.get('/empty', status=200)
58 data = json.loads(resp.body)
59 self.assertEqual(data['func'], _no_param_action.__name__)
60
61 def testGetTwoParam(self):
62 resp = self.app.get('/two_param_get', {'p1': 1, 'p2': 2}, status=200)
63 data = json.loads(resp.body)
64 self.assertEqual(data['func'], _two_param_action.__name__)
65
66 # ensure a missing parameter returns a 400 error
67 resp = self.app.get('/two_param_get', {'p1': 1}, status=400)
68 self.assertTrue('p2' in resp.body)
69
70 def testLeafEnforced(self):
71 '''ensure we only respond to the leaf and not additional segments'''
72 self.app.get('/empty/', status=404)
73
74 def testAllowedMethods(self):
75 self.app.post_json('/empty', {'key': 'val'}, status=405)
76
77
78class TestPostResource(unittest.TestCase):
79 '''Test to ensure the http_post_resource works properly'''
80
81 def setUp(self):
82 super(TestPostResource, self).setUp()
83
84 class TR(resource.Resource):
85 @resource.child()
86 def empty(self, request, segments):
87 return utils.http_post_resource(_no_param_action)
88
89 @resource.child()
90 def two_param_get(self, request, segments):
91 return utils.http_post_resource(_two_param_action)
92
93 app = RestishApp(TR())
94 self.app = webtest.TestApp(app, relative_to='.')
95
96 def testPostNoParam(self):
97 resp = self.app.post_json('/empty', {}, status=200)
98 data = json.loads(resp.body)
99 self.assertEqual(data['func'], _no_param_action.__name__)
100
101 def testPostTwoParam(self):
102 resp = self.app.post_json('/two_param_get', {'p1': 1, 'p2': 2})
103 data = json.loads(resp.body)
104 self.assertEqual(data['func'], _two_param_action.__name__)
105
106 # ensure a missing parameter returns a 400 error
107 resp = self.app.post_json('/two_param_get', {'p1': 1}, status=400)
108 self.assertTrue('p2' in resp.body)
109
110 def testLeafEnforced(self):
111 '''ensure we only respond to the leaf and not additional segments'''
112 self.app.post_json('/empty/', {}, status=404)
113
114 def testAllowedMethods(self):
115 self.app.get('/empty', {'p1': 1, 'p2': 2}, status=405)
116
117
118class TestAMQP(unittest.TestCase):
119 @mock.patch('bsbuilder.amqp_utils.connection')
120 @mock.patch('bsbuilder.amqp_utils.get_config')
121 def testConnectFailed(self, get_config, connect):
122 '''Ensure a failed queue connection returns an HTTP 503 error'''
123 get_config.return_value = mock.Mock()
124 error = 'mocked test exception'
125 connect.side_effect = RuntimeError(error)
126 r = amqp_utils.send('fake_queue', 'fake_message')
127 self.assertIsNotNone(r)
128 self.assertEqual(error, r)
129
130 @mock.patch('bsbuilder.amqp_utils.connection')
131 @mock.patch('bsbuilder.amqp_utils.get_config')
132 def testSent(self, get_config, connect):
133 '''Test a successful send returns nothing
134
135 There's not much you can test in isolation here, but this gives
136 a bit of a sanity check.
137 '''
138 get_config.return_value = mock.Mock()
139 connect.return_value = mock.Mock()
140 r = amqp_utils.send('fake_queue', 'fake_message')
141 self.assertIsNone(r)
142
143 @mock.patch('bsbuilder.amqp_utils._run_forever')
144 @mock.patch('bsbuilder.amqp_utils.connection')
145 def testProcessQueue(self, connection, run_forever):
146 '''Ensure we close the connection if something fails'''
147 conn = mock.Mock()
148 connection.return_value = conn
149 run_forever.side_effect = RuntimeError
150 with self.assertRaises(RuntimeError):
151 amqp_utils.process_queue(None, None, None)
152 self.assertTrue(conn.close.called)
153
154 def testRunForever(self):
155 '''Ensure this times out after the right amount of time'''
156 channel = mock.Mock()
157 callback = mock.Mock()
158 #wait = mock.Mock()
159 #wait.side_effect = socket.error
160 channel.wait.side_effect = socket.error()
161 start = time.time()
162 retry_period = 2
163 amqp_utils._run_forever(channel, 'foo', callback, retry_period)
164 # see if it was within 1/10 of a second of the retry_period
165 self.assertAlmostEqual(start + retry_period, time.time(), places=1)
0166
=== added file 'branch-source-builder/bsbuilder/tests/test_v1.py'
--- branch-source-builder/bsbuilder/tests/test_v1.py 1970-01-01 00:00:00 +0000
+++ branch-source-builder/bsbuilder/tests/test_v1.py 2013-12-11 16:52:29 +0000
@@ -0,0 +1,70 @@
1# Ubuntu CI Services
2# Copyright 2013 Canonical Ltd.
3
4# This program is free software: you can redistribute it and/or modify it
5# under the terms of the GNU Affero General Public License version 3, as
6# published by the Free Software Foundation.
7
8# This program is distributed in the hope that it will be useful, but
9# WITHOUT ANY WARRANTY; without even the implied warranties of
10# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11# PURPOSE. See the GNU Affero General Public License for more details.
12
13# You should have received a copy of the GNU Affero General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16import json
17import unittest
18
19import mock
20import webtest
21
22from bsbuilder import wsgi
23
24
25class TestAPI(unittest.TestCase):
26 '''Test to ensure the v1 API works.'''
27
28 def setUp(self):
29 super(TestAPI, self).setUp()
30 self.app = webtest.TestApp(wsgi.app, relative_to='.')
31
32 @mock.patch('bsbuilder.amqp_utils.get_config')
33 def testStatus(self, get_config):
34 get_config.return_value = None
35 resp = self.app.get('/api/v1/status', status=200)
36 data = json.loads(resp.body)
37 self.assertEqual(False, data['rabbit_configured'])
38
39 get_config.return_value = {'foo': 'bar'}
40 resp = self.app.get('/api/v1/status', status=200)
41 data = json.loads(resp.body)
42 self.assertEqual(True, data['rabbit_configured'])
43
44 @mock.patch('bsbuilder.amqp_utils.get_config')
45 def testBuildSourceUnconfigured(self, get_config):
46 # ensure it fails when not configured
47 get_config.return_value = None
48 params = {
49 'source_packages': 'foo',
50 'ppa': 'foo',
51 'progress_trigger': 'foo',
52 }
53 resp = self.app.post_json('/api/v1/build_source', params, status=503)
54 self.assertTrue('rabbitmq settings not available' in resp.body)
55
56 def testBuildSourceBadParams(self):
57 '''Ensure proper error message is returned for incorrect params.'''
58 resp = self.app.post_json('/api/v1/build_source', {}, status=400)
59 self.assertTrue('Missing required parameters' in resp.body)
60
61 @mock.patch('bsbuilder.amqp_utils.send')
62 def testBuildSource(self, send):
63 send.return_value = None
64 params = {
65 'source_packages': 'foo',
66 'ppa': 'foo',
67 'progress_trigger': 'foo',
68 }
69 r = self.app.post_json('/api/v1/build_source', params, status=204)
70 self.assertEqual('', r.body)
071
=== added file 'branch-source-builder/bsbuilder/utils.py'
--- branch-source-builder/bsbuilder/utils.py 1970-01-01 00:00:00 +0000
+++ branch-source-builder/bsbuilder/utils.py 2013-12-11 16:52:29 +0000
@@ -0,0 +1,78 @@
1# Ubuntu CI Services
2# Copyright 2013 Canonical Ltd.
3
4# This program is free software: you can redistribute it and/or modify it
5# under the terms of the GNU Affero General Public License version 3, as
6# published by the Free Software Foundation.
7
8# This program is distributed in the hope that it will be useful, but
9# WITHOUT ANY WARRANTY; without even the implied warranties of
10# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11# PURPOSE. See the GNU Affero General Public License for more details.
12
13# You should have received a copy of the GNU Affero General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16import inspect
17import logging
18import json
19
20from restish import http, resource
21
22log = logging.getLogger(__name__)
23
24
25def json_ok(data):
26 '''Simple wrapper to return JSON data as an OK response'''
27 data = json.dumps(data) + '\n' # \n is handy when using curl from CLI
28 return http.ok([('Content-Type', 'application/json')], data)
29
30
31class _BaseResource(resource.Resource):
32 '''Creates a resource that validates arguments by introspcection.
33
34 This class makes it easy to supply a single function with parameters
35 to handle HTTP operations. The GET/POST parameters will be compared to
36 the callback function's parameter list to validate it can be called.
37 '''
38 def __init__(self, callback_func, allowed):
39 super(_BaseResource, self).__init__()
40 self._cb = callback_func
41 self._allowed = allowed
42
43 def _handle(self, req_args):
44 args, vargs, keywords, defaults = inspect.getargspec(self._cb)
45 missing = set(args) - set(req_args.keys())
46 unknown = set(req_args.keys()) - set(args)
47 if unknown:
48 log.warning('Unsupported arguments: %r' % unknown)
49 if missing:
50 body = 'Missing required parameters\n %s\n' % '\n '.join(missing)
51 return http.bad_request([('Content-Type', 'text/plain')], body)
52
53 params = {k: req_args[k] for k in args}
54 return self._cb(**params)
55
56 @resource.GET()
57 def _do_get(self, request):
58 if resource.GET not in self._allowed:
59 return http.method_not_allowed([request.method])
60 return self._handle(request.GET)
61
62 @resource.POST(accept='json')
63 def _do_post(self, request):
64 if resource.POST not in self._allowed:
65 return http.method_not_allowed([request.method])
66 params = json.loads(request.body)
67 resp = self._handle(params)
68 if not resp:
69 resp = http.Response('204 No Content', [], None)
70 return resp
71
72
73def http_get_resource(callback_func):
74 return _BaseResource(callback_func, (resource.GET,))
75
76
77def http_post_resource(callback_func):
78 return _BaseResource(callback_func, (resource.POST,))
079
=== added file 'branch-source-builder/bsbuilder/wsgi.py'
--- branch-source-builder/bsbuilder/wsgi.py 1970-01-01 00:00:00 +0000
+++ branch-source-builder/bsbuilder/wsgi.py 2013-12-11 16:52:29 +0000
@@ -0,0 +1,48 @@
1#!/usr/bin/env python
2# Ubuntu CI Services
3# Copyright 2013 Canonical Ltd.
4
5# This program is free software: you can redistribute it and/or modify it
6# under the terms of the GNU Affero General Public License version 3, as
7# published by the Free Software Foundation.
8
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranties of
11# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12# PURPOSE. See the GNU Affero General Public License for more details.
13
14# You should have received a copy of the GNU Affero General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17import logging
18from restish.app import RestishApp
19
20from bsbuilder.resources.root import Root
21
22logging.basicConfig(level=logging.DEBUG)
23
24
25def make_app():
26 """Build the wsgi app object."""
27 app = RestishApp(Root())
28 return app
29
30app = make_app()
31
32if __name__ == '__main__':
33 import argparse
34 from wsgiref.simple_server import make_server
35
36 parser = argparse.ArgumentParser(
37 description='Run webservice in python\'s wsgi reference server.')
38 parser.add_argument('-p', '--port', type=int, default=8080,
39 help='Port to use. Default=%(default)d')
40 args = parser.parse_args()
41
42 httpd = make_server('', args.port, app)
43 try:
44 print('Running server on port %d...' % args.port)
45 httpd.serve_forever()
46 except KeyboardInterrupt:
47 print('exiting')
48 pass
049
=== added file 'branch-source-builder/run_worker'
--- branch-source-builder/run_worker 1970-01-01 00:00:00 +0000
+++ branch-source-builder/run_worker 2013-12-11 16:52:29 +0000
@@ -0,0 +1,47 @@
1#!/usr/bin/env python
2# Ubuntu CI Services
3# Copyright 2013 Canonical Ltd.
4
5# This program is free software: you can redistribute it and/or modify it
6# under the terms of the GNU Affero General Public License version 3, as
7# published by the Free Software Foundation.
8
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranties of
11# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12# PURPOSE. See the GNU Affero General Public License for more details.
13
14# You should have received a copy of the GNU Affero General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17import json
18import logging
19import os
20import sys
21
22
23logging.basicConfig(level=logging.INFO)
24log = logging.getLogger(__name__)
25
26# the worker might not have installed this module, so determine the path
27# and add it, so we can always safely import stuff
28sys.path.append(os.path.join(os.path.dirname(__file__), 'bsbuilder'))
29import amqp_utils
30
31
32def on_message(msg):
33 params = json.loads(msg.body)
34 sources = params['source_packages']
35 ppa = params['ppa']
36 trigger = params['progress_trigger']
37 print('TODO handler message: sources(%s) ppa(%s) trigger(%s)' % (
38 sources, ppa, trigger))
39 # remove from queue so request becomes completed
40 msg.channel.basic_ack(msg.delivery_tag)
41
42
43if __name__ == '__main__':
44 config = amqp_utils.get_config()
45 if not config:
46 exit(1) # the get_config code prints an error
47 amqp_utils.process_queue(config, 'bsbuilder', on_message)
048
=== added file 'branch-source-builder/setup.py'
--- branch-source-builder/setup.py 1970-01-01 00:00:00 +0000
+++ branch-source-builder/setup.py 2013-12-11 16:52:29 +0000
@@ -0,0 +1,44 @@
1#!/usr/bin/env python
2# Ubuntu CI Services
3# Copyright 2013 Canonical Ltd.
4
5# This program is free software: you can redistribute it and/or modify it
6# under the terms of the GNU Affero General Public License version 3, as
7# published by the Free Software Foundation.
8
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranties of
11# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12# PURPOSE. See the GNU Affero General Public License for more details.
13
14# You should have received a copy of the GNU Affero General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17import os
18
19from setuptools import find_packages, setup
20
21# ensure find_packages works if our current directory isn't this project
22basedir = os.path.abspath(os.path.dirname(__file__))
23os.chdir(basedir)
24packages = find_packages(basedir)
25
26import bsbuilder
27
28requires = [
29 'restish==0.12.1',
30 'amqplib==1.0.0',
31 'mock==1.0.1',
32 'WebTest==2.0.10',
33]
34
35setup(
36 name='branch-source-builder',
37 version=bsbuilder.__version__,
38 description='Branch/Source Builder component of Ubuntu CI Services',
39 author='Canonical CI Engineering Team',
40 license='AGPL',
41 packages=packages,
42 test_suite='tests',
43 install_requires=requires,
44)
045
=== added file 'juju-deployer/branch-source-builder.yaml'
--- juju-deployer/branch-source-builder.yaml 1970-01-01 00:00:00 +0000
+++ juju-deployer/branch-source-builder.yaml 2013-12-11 16:52:29 +0000
@@ -0,0 +1,29 @@
1branch-source-builder-staging:
2 series: precise
3 services:
4 bsb-restish:
5 charm: restish
6 branch: lp:~canonical-ci-engineering/charms/precise/ubuntu-ci-services-itself/restish
7 options:
8 branch: lp:ubuntu-ci-services-itself
9 python_path: ./branch-source-builder
10 # need non-default package python-amqplib for this service
11 packages: "python-webtest python-mock python-jinja2 python-amqplib"
12 bsb-gunicorn:
13 charm: gunicorn
14 branch: lp:charms/precise/gunicorn
15 options:
16 wsgi_wsgi_file: bsbuilder.wsgi:app
17 bsb-worker:
18 charm: rabbitmq-worker
19 branch: lp:~canonical-ci-engineering/charms/precise/ubuntu-ci-services-itself/rabbitmq-worker
20 options:
21 branch: lp:ubuntu-ci-services-itself
22 main: ./branch-source-builder/run_worker
23 bsb-rabbit:
24 branch: lp:~canonical-ci-engineering/charms/precise/ubuntu-ci-services-itself/rabbitmq-server
25 charm: rabbitmq
26 relations:
27 - [bsb-restish, bsb-gunicorn]
28 - [bsb-worker, bsb-rabbit]
29 - [bsb-rabbit, bsb-restish]

Subscribers

People subscribed via source and target branches