Merge lp:~vila/uci-engine/1302471-fake-rabbit into lp:uci-engine

Proposed by Vincent Ladeuil
Status: Merged
Approved by: Vincent Ladeuil
Approved revision: 454
Merged at revision: 485
Proposed branch: lp:~vila/uci-engine/1302471-fake-rabbit
Merge into: lp:uci-engine
Diff against target: 631 lines (+307/-61)
15 files modified
branch-source-builder/bsbuilder/run_worker.py (+1/-1)
ci-utils/ci_utils/amqp_utils.py (+33/-16)
ci-utils/ci_utils/amqp_worker.py (+11/-5)
ci-utils/ci_utils/gpg.py (+1/-1)
ci-utils/ci_utils/sourcecode.py (+1/-1)
ci-utils/ci_utils/testing/fixtures.py (+19/-0)
ci-utils/ci_utils/tests/test_amqp_worker.py (+2/-2)
image-builder/imagebuilder/run_worker.py (+1/-1)
lander/bin/json_status_cgi.py (+1/-1)
lander/bin/lander_process_ticket.py (+1/-1)
test_runner/tstrun/run_worker.py (+3/-3)
test_runner/tstrun/testbed.py (+3/-2)
test_runner/tstrun/tests/test_worker.py (+47/-0)
tests/deployers.py (+34/-27)
tests/test_rabbit.py (+149/-0)
To merge this branch: bzr merge lp:~vila/uci-engine/1302471-fake-rabbit
Reviewer Review Type Date Requested Status
Andy Doan (community) Needs Fixing
PS Jenkins bot (community) continuous-integration Approve
Review via email: mp+218938@code.launchpad.net

Commit message

Fake rabbit queue and tests against a real rabbit server.

Description of the change

This MP is a first (rough) shot at starting tests for a real rabbit
server and providing a fake queue for test writers.

I've added a *few* tests against a deployed (real) rabbit server with
required explicit setup for credentials so we can run the tests from a
dev host (instead of from a deployed instance with the rabbitmq-worker
charm providing the credentials automagically).

For the isolated tests, I've added a first, narrowed, implementation
of rabbit queue without a real server. For the API there, I aligned
with the terms used in rabbit documentation. A test (as a producer)
can publish messages in a queue and (as a consumer) subscribe to a
queue providing a callback used for each message.

There is no error checking, no nothing, only the happy path: push a
list of messages, subscribe and execute the callback for each message
(no error, no nothing), look at the state of the queue. More data can
be provided by the callback.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:452
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/623/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/623/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Andy Doan (doanac) wrote :

You have a merge conflict in this, and it also seems like you may be combining 3 different MPs, so I'm wondering if this was intentional? I think there are 3-4 change-sets:

1) re-ordering of parameters in functions
I'd separate this so this diff will be small and easy to ack.

2) FakeTaskQueue
I think this plus the change to the test-runner

3) tests/test_rabbit.py

4) amqp_utils split up of send into send/declare_queue

I might be reading too fast, but I think all 4 of these changes are independent of one another (with the expception or #4).

review: Needs Information
Revision history for this message
Vincent Ladeuil (vila) wrote :

> You have a merge conflict in this,

Yeah, that's weird, I resolved it locally but haven't pushed the result, sorry about that.

> and it also seems like you may be combining
> 3 different MPs, so I'm wondering if this was intentional?

Well, yes, all the changes are related to each other so splitting them means the reviewer has to jump around MPs to understand *why* they are done.

> I think there are
> 3-4 change-sets:
>
> 1) re-ordering of parameters in functions
> I'd separate this so this diff will be small and easy to ack.

I can do that but if the related MPs are landed at different times the resulting history will lose the reasoning leading to that change.

>
> 2) FakeTaskQueue
> I think this plus the change to the test-runner
>
> 3) tests/test_rabbit.py
>
> 4) amqp_utils split up of send into send/declare_queue
>
> I might be reading too fast, but I think all 4 of these changes are
> independent of one another (with the expception or #4).

453. By Vincent Ladeuil

Merge trunk fixing conflicts

454. By Vincent Ladeuil

Fix imports.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:454
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/627/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/uci-engine-ci/627/rebuild

review: Approve (continuous-integration)
Revision history for this message
Andy Doan (doanac) wrote :

looks good. at some point we probably should figure out what of:

 http://bazaar.launchpad.net/~canonical-ci-engineering/uci-engine/trunk/view/head:/ci-utils/ci_utils/tests/test_amqp_worker.py

could fit into this new FakeTaskQueue Fixture so that we can really do a thorough testing of each of our runworker handlers.

Revision history for this message
Andy Doan (doanac) :
review: Approve
Revision history for this message
Vincent Ladeuil (vila) wrote :

> looks good. at some point we probably should figure out what of:
>
> http://bazaar.launchpad.net/~canonical-ci-engineering/uci-
> engine/trunk/view/head:/ci-utils/ci_utils/tests/test_amqp_worker.py
>
> could fit into this new FakeTaskQueue Fixture so that we can really do a
> thorough testing of each of our runworker handlers.

You got that perfectly right ;)

But I'm focused on isolated tests for the test runner right now and didn't attempt to do more than strictly necessary.

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

Just realized we did this all wrong. See in-line comment.

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

I missed something else really important in this. I *thought* I added an inline comment last night, but I noticed this morning its not there. I'll just re-explain here:

369 + def test_handle_request(self):
370 + worker = run_worker.TestRunnerWorker()
371 + # FIXME: We need a way to get an image that is available to the nova
372 + # setup we use. -- vila 20140512
373 + params = dict(ticket_id='t1', progress_trigger='progress',
374 + image_id=('ubuntu-released/ubuntu-precise-12.04'
375 + '-amd64-server-20140428-disk1.img'),
376 + package_list=['libpng'])
377 + self.queue.publish(params)
378 + self.queue.subscribe(worker.handle_request, self.logger)

If you look at AMQPWorker[1], you'll see that it isolates classes that extend it from dealing with rabbit already. The real method that handles amqp is _on_message. This pops the message off the queue, does some basic sanity checks and then calls _handle_request with the message converted into a dictionary.

So this test method really doesn't need to try and fake rabbit. You should just call _handle_request directoy with the "params" value you set up on line 373.

The real place to use the FakeTaskQueue would be in test_amqp_worker.py[2]. As you'll see there, I do quite a bit of testing against what rabbit will send the "_on_message" method already. However, I'm sure we could find some ways to incorporate that logic into the FakeTaskQueue more cleanly and add some new tests.

Sorry for missing this after its already been merged. It hit me while walking the dog.

1: http://bazaar.launchpad.net/~canonical-ci-engineering/uci-engine/trunk/view/head:/ci-utils/ci_utils/amqp_worker.py

2: http://bazaar.launchpad.net/~canonical-ci-engineering/uci-engine/trunk/view/head:/ci-utils/ci_utils/tests/test_amqp_worker.py

review: Needs Fixing

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'branch-source-builder/bsbuilder/run_worker.py'
2--- branch-source-builder/bsbuilder/run_worker.py 2014-04-28 14:45:39 +0000
3+++ branch-source-builder/bsbuilder/run_worker.py 2014-05-14 13:40:29 +0000
4@@ -72,7 +72,7 @@
5 def __init__(self):
6 super(BSBuilderWorker, self).__init__('bsbuilder')
7
8- def handle_request(self, log, params):
9+ def handle_request(self, params, log):
10 subtickets = params['subtickets']
11 series = params['series']
12 ppa = params['ppa']
13
14=== modified file 'ci-utils/ci_utils/amqp_utils.py'
15--- ci-utils/ci_utils/amqp_utils.py 2014-04-28 14:45:39 +0000
16+++ ci-utils/ci_utils/amqp_utils.py 2014-05-14 13:40:29 +0000
17@@ -28,12 +28,15 @@
18 log = logging.getLogger(__name__)
19
20
21+HERE = os.path.abspath(os.path.dirname(__file__))
22+
23+
24 def get_config():
25 '''Load the rabbit config created by the charm'''
26 config = None
27 try:
28 # try and find the config file, should be in the root of our bzr branch
29- f = os.path.join(os.path.dirname(__file__), '../../amqp_config.py')
30+ f = os.path.join(HERE, '../../amqp_config.py')
31 if os.path.exists(f):
32 import imp
33 config = imp.load_source('amqp_config', f)
34@@ -53,7 +56,25 @@
35 )
36
37
38-def send(queue, msg, raise_errors=False):
39+def declare_queue(queue_name, config=None):
40+ if config is None:
41+ config = get_config()
42+ conn = channel = None
43+ try:
44+ conn = connection(config)
45+ channel = conn.channel()
46+ channel.queue_declare(queue=queue_name, durable=True,
47+ auto_delete=False)
48+ except Exception as e:
49+ if channel:
50+ channel.close()
51+ if conn:
52+ conn.close()
53+ raise e
54+ return conn, channel
55+
56+
57+def send(queue_name, msg, raise_errors=False):
58 '''send a message to the given queue.
59
60 :param raise_errors: There are two users of this function. Some users are
61@@ -63,14 +84,12 @@
62 config = get_config()
63 if not config:
64 return 'rabbitmq settings not available.'
65- con = channel = None
66+ conn = channel = None
67 try:
68- con = connection(config)
69- channel = con.channel()
70- channel.queue_declare(queue=queue, durable=True, auto_delete=False)
71+ conn, channel = declare_queue(queue_name, config)
72 body = amqp.Message(msg)
73 body.properties['delivery_mode'] = 2 # Persistent
74- channel.basic_publish(body, routing_key=queue)
75+ channel.basic_publish(exchange='', routing_key=queue_name, body=body)
76 except Exception as e:
77 if raise_errors:
78 raise
79@@ -79,12 +98,13 @@
80 finally:
81 if channel:
82 channel.close()
83- if con:
84- con.close()
85+ if conn:
86+ conn.close()
87 return None
88
89
90 def _run_forever(channel, queue, callback, retry_period=120):
91+ logging.info('Waiting for messages. ^C to exit.')
92 tag = channel.basic_consume(callback=callback, queue=queue)
93 try:
94 timeout = time.time()
95@@ -107,19 +127,16 @@
96 channel.basic_cancel(tag)
97
98
99-def process_queue(config, queue, callback, delete=False):
100+def process_queue(config, queue_name, callback, delete=False):
101 conn = channel = None
102 try:
103- conn = connection(config)
104- channel = conn.channel()
105- channel.queue_declare(queue=queue, durable=True, auto_delete=False)
106+ conn, channel = declare_queue(queue_name, config)
107 channel.basic_qos(0, 1, False)
108- logging.info('Waiting for messages. ^C to exit.')
109- _run_forever(channel, queue, callback)
110+ _run_forever(channel, queue_name, callback)
111 finally:
112 if channel:
113 if delete:
114- channel.queue_delete(queue)
115+ channel.queue_delete(queue_name)
116 channel.close()
117 if conn:
118 conn.close()
119
120=== modified file 'ci-utils/ci_utils/amqp_worker.py'
121--- ci-utils/ci_utils/amqp_worker.py 2014-04-29 12:44:36 +0000
122+++ ci-utils/ci_utils/amqp_worker.py 2014-05-14 13:40:29 +0000
123@@ -24,8 +24,14 @@
124 import urllib2
125 import yaml
126
127-from ci_utils import amqp_utils, data_store, dump_stack
128-
129+from ci_utils import (
130+ amqp_utils,
131+ data_store,
132+ dump_stack,
133+)
134+
135+
136+HERE = os.path.abspath(os.path.dirname(__file__))
137 logging.basicConfig(level=logging.INFO)
138 log = logging.getLogger(__name__)
139
140@@ -75,7 +81,7 @@
141 # how often to check if the current task should be cancelled
142 cancel_interval = 120
143
144- def handle_request(self, logger, params):
145+ def handle_request(self, params, logger):
146 '''To be implemented by the subclass to do their work
147
148 amqp_cb should either be one of:
149@@ -110,7 +116,7 @@
150
151 def _create_data_store(self, ticket):
152 # Note that this may be called multiple times
153- fname = os.path.join(os.path.dirname(__file__), '../../unit_config')
154+ fname = os.path.join(HERE, '../../unit_config')
155 with open(fname) as f:
156 config = yaml.safe_load(f)
157 return data_store.create_for_ticket(ticket, config)
158@@ -189,7 +195,7 @@
159 cancel_thread = self._handle_cancel(params)
160
161 logger = self._create_worker_logger()
162- amqp_cb, ret = self.handle_request(logger, params)
163+ amqp_cb, ret = self.handle_request(params, logger)
164 self._store_worker_log(store, logger, ret)
165 amqp_cb(trigger, ret)
166 except (KeyboardInterrupt, Exception) as e:
167
168=== modified file 'ci-utils/ci_utils/gpg.py'
169--- ci-utils/ci_utils/gpg.py 2014-05-08 01:58:36 +0000
170+++ ci-utils/ci_utils/gpg.py 2014-05-14 13:40:29 +0000
171@@ -1,5 +1,5 @@
172 # Ubuntu Continuous Integration Engine
173-# Copyright 2013, 2014 Canonical Ltd.
174+# Copyright 2014 Canonical Ltd.
175
176 # This program is free software: you can redistribute it and/or modify it under
177 # the terms of the GNU General Public License version 3, as published by the
178
179=== modified file 'ci-utils/ci_utils/sourcecode.py'
180--- ci-utils/ci_utils/sourcecode.py 2014-04-30 16:15:05 +0000
181+++ ci-utils/ci_utils/sourcecode.py 2014-05-14 13:40:29 +0000
182@@ -1,5 +1,5 @@
183 #!/usr/bin/python
184-# Copyright 2009 Canonical Ltd. This software is licensed under the
185+# Copyright 2014 Canonical Ltd. This software is licensed under the
186 # GNU Affero General Public License version 3 (see the file LICENSE).
187
188 """Tools for maintaining the Launchpad source code."""
189
190=== modified file 'ci-utils/ci_utils/testing/fixtures.py'
191--- ci-utils/ci_utils/testing/fixtures.py 2014-05-06 14:16:05 +0000
192+++ ci-utils/ci_utils/testing/fixtures.py 2014-05-14 13:40:29 +0000
193@@ -77,3 +77,22 @@
194
195 def file_path(self, filename):
196 return '{}{}/{}'.format('file://', self.container_id, filename)
197+
198+
199+class FakeTaskQueue(object):
200+
201+ def __init__(self, queue_name, config=None):
202+ self.msgs = []
203+ self.queue_name = queue_name
204+ # We don't use the config which contains the credentials to access a
205+ # real rabbit server.
206+
207+ def publish(self, msg):
208+ self.msgs.append(msg)
209+
210+ def subscribe(self, func, *args, **kwargs):
211+ while not self.is_empty():
212+ func(self.msgs.pop(0), *args, **kwargs)
213+
214+ def is_empty(self):
215+ return not self.msgs
216
217=== modified file 'ci-utils/ci_utils/tests/test_amqp_worker.py'
218--- ci-utils/ci_utils/tests/test_amqp_worker.py 2014-03-12 18:20:22 +0000
219+++ ci-utils/ci_utils/tests/test_amqp_worker.py 2014-05-14 13:40:29 +0000
220@@ -38,7 +38,7 @@
221 def _create_data_store(self, ticket):
222 return self.store
223
224- def handle_request(self, log, params):
225+ def handle_request(self, params, log):
226 log.info('called')
227 self.params = params
228 if self.exception:
229@@ -146,7 +146,7 @@
230 cmd = ['/bin/foo', 'bar']
231 output = 'i can\'t believe its not butter'
232
233- def handle_request(self, log, params):
234+ def handle_request(self, params, log):
235 raise subprocess.CalledProcessError(
236 self.rc, self.cmd, self.output)
237
238
239=== modified file 'image-builder/imagebuilder/run_worker.py'
240--- image-builder/imagebuilder/run_worker.py 2014-05-01 08:34:49 +0000
241+++ image-builder/imagebuilder/run_worker.py 2014-05-14 13:40:29 +0000
242@@ -23,7 +23,7 @@
243 def __init__(self):
244 super(ImageBuildWorker, self).__init__('image_build')
245
246- def handle_request(self, log, params):
247+ def handle_request(self, params, log):
248 image = params['image']
249 repos = params['ppa_list']
250 series = params['series']
251
252=== modified file 'lander/bin/json_status_cgi.py'
253--- lander/bin/json_status_cgi.py 2014-04-25 16:54:18 +0000
254+++ lander/bin/json_status_cgi.py 2014-05-14 13:40:29 +0000
255@@ -1,6 +1,6 @@
256 #!/usr/bin/env python
257 # Ubuntu CI Engine
258-# Copyright 2013, 2014 Canonical Ltd.
259+# Copyright 2014 Canonical Ltd.
260
261 # This program is free software: you can redistribute it and/or modify it
262 # under the terms of the GNU Affero General Public License version 3, as
263
264=== modified file 'lander/bin/lander_process_ticket.py'
265--- lander/bin/lander_process_ticket.py 2014-05-07 16:25:13 +0000
266+++ lander/bin/lander_process_ticket.py 2014-05-14 13:40:29 +0000
267@@ -1,6 +1,6 @@
268 #!/usr/bin/env python
269 # Ubuntu CI Engine
270-# Copyright 2013, 2014 Canonical Ltd.
271+# Copyright 2014 Canonical Ltd.
272
273 # This program is free software: you can redistribute it and/or modify it
274 # under the terms of the GNU Affero General Public License version 3, as
275
276=== modified file 'test_runner/tstrun/run_worker.py'
277--- test_runner/tstrun/run_worker.py 2014-04-16 18:36:03 +0000
278+++ test_runner/tstrun/run_worker.py 2014-05-14 13:40:29 +0000
279@@ -33,7 +33,7 @@
280 """Save an artifact catching and reporting exceptions.
281
282 This should only be used to upload artifacts after the pass/fail status
283- has been established to it's safe to fail the upload.
284+ has been established so it's safe to fail the upload.
285
286 :param logger: To report execution.
287
288@@ -81,7 +81,7 @@
289 summary_log_path, test_bed.get_remote_content(summary_log_path),
290 'adt-run summary for {}'.format(package))
291
292- def handle_request(self, logger, params):
293+ def handle_request(self, params, logger):
294 ticket_id = params['ticket_id']
295 progress_queue = params['progress_trigger']
296 image_id = params['image_id']
297@@ -97,7 +97,7 @@
298 try:
299 status_cb('Setting up the testbed for ticket {}'.format(ticket_id))
300 flavors = tstrun.get_auth_config()['tr_flavors']
301- test_bed = testbed.TestBed('testbed-{}'.format(progress_queue),
302+ test_bed = testbed.TestBed('testbed-{}'.format(ticket_id),
303 flavors, image_id, status_cb)
304 except:
305 logger.exception(
306
307=== modified file 'test_runner/tstrun/testbed.py'
308--- test_runner/tstrun/testbed.py 2014-04-16 18:36:03 +0000
309+++ test_runner/tstrun/testbed.py 2014-05-14 13:40:29 +0000
310@@ -184,6 +184,7 @@
311
312 def setup(self):
313 flavor = self.find_flavor()
314+ # FIXME: Better check for NotFound on wrong images -- vila 2014-05-12
315 image = self.nova.images.find(name=self.image)
316 self.create_user_data()
317 self.instance = self.nova.servers.create(
318@@ -332,10 +333,10 @@
319 # whether canonistack or hpcloud is used.
320 test_images = dict(
321 cs_precise=('ubuntu-released/ubuntu-precise-12.04'
322- '-amd64-server-20131205-disk1.img'),
323+ '-amd64-server-20140428-disk1.img'),
324 cs_saucy=('ubuntu-released/ubuntu-saucy-13.10'
325 '-amd64-server-20140212-disk1.img'),
326- hp_precise=('Ubuntu Server 12.04.4 LTS (amd64 20140227)'
327+ hp_precise=('Ubuntu Server 12.04.4 LTS (amd64 20140306)'
328 ' - CI Engineering'),
329 hp_saucy=('Ubuntu Server 13.10 (amd64 20131030) - Partner Image'),
330 )
331
332=== added file 'test_runner/tstrun/tests/test_worker.py'
333--- test_runner/tstrun/tests/test_worker.py 1970-01-01 00:00:00 +0000
334+++ test_runner/tstrun/tests/test_worker.py 2014-05-14 13:40:29 +0000
335@@ -0,0 +1,47 @@
336+# Ubuntu CI Engine
337+# Copyright 2014 Canonical Ltd.
338+
339+# This program is free software: you can redistribute it and/or modify it
340+# under the terms of the GNU Affero General Public License version 3, as
341+# published by the Free Software Foundation.
342+
343+# This program is distributed in the hope that it will be useful, but
344+# WITHOUT ANY WARRANTY; without even the implied warranties of
345+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
346+# PURPOSE. See the GNU Affero General Public License for more details.
347+
348+# You should have received a copy of the GNU Affero General Public License
349+# along with this program. If not, see <http://www.gnu.org/licenses/>.
350+
351+import logging
352+import unittest
353+
354+from ci_utils.testing import (
355+ features,
356+ fixtures,
357+)
358+from tstrun import run_worker
359+
360+
361+@features.requires(features.nova_compute)
362+class TestWorker(unittest.TestCase):
363+
364+ def setUp(self):
365+ super(TestWorker, self).setUp()
366+ self.logger = logging.getLogger()
367+ self.queue = fixtures.FakeTaskQueue('test')
368+
369+ def test_handle_request(self):
370+ worker = run_worker.TestRunnerWorker()
371+ # FIXME: We need a way to get an image that is available to the nova
372+ # setup we use. -- vila 20140512
373+ params = dict(ticket_id='t1', progress_trigger='progress',
374+ image_id=('ubuntu-released/ubuntu-precise-12.04'
375+ '-amd64-server-20140428-disk1.img'),
376+ package_list=['libpng'])
377+ self.queue.publish(params)
378+ self.queue.subscribe(worker.handle_request, self.logger)
379+ self.assertTrue(self.queue.is_empty())
380+ # FIXME: More assertions could be added from the data store content, at
381+ # least on artifacts present there. Revisit once we use the fake data
382+ # store. -- vila 20140512
383
384=== modified file 'tests/deployers.py'
385--- tests/deployers.py 2014-05-12 14:17:51 +0000
386+++ tests/deployers.py 2014-05-14 13:40:29 +0000
387@@ -29,45 +29,33 @@
388 # We need access to ci_utils
389 sys.path.append(os.path.join(HERE, '..', 'ci-utils'))
390
391-from ci_utils.testing import features, TestCaseWithGnupg
392+from ci_utils import testing
393+from ci_utils.testing import features
394
395
396 class AmuletDeployment(object):
397 '''Fixture for a deployed set of juju services from a deployer config'''
398
399- def __init__(self, deployer_cfg, juju_env=None, sentries=False):
400+ def __init__(self, juju_env=None, sentries=False):
401 self.deployment = amulet.Deployment(juju_env, sentries=sentries)
402- with open(deployer_cfg) as f:
403- jd_script = yaml.safe_load(f.read())
404- self.fixup_includes(os.path.dirname(deployer_cfg), jd_script)
405+
406+ def setUp(self, timeout):
407+ self.deployment.setup(timeout)
408+
409+ def load(self, script):
410 # Use juju_env given or defaulted above rather than taking the name
411 # from the deployer config and assuming an env of that name exists
412- script = {self.deployment.juju_env: jd_script.values()[0]}
413- self.deployment.load(script)
414-
415- def setUp(self, timeout):
416- self.deployment.setup(timeout)
417-
418- def fixup_includes(self, path, jd_script):
419- # amulet doesn't deal with "include-base64" options, we have to
420- # patch the config ourselves
421- for deployment in jd_script:
422- for service in jd_script[deployment]['services'].values():
423- for key, val in service.get('options', {}).iteritems():
424- include_token = 'include-base64://'
425- if type(val) is str and val.startswith(include_token):
426- val = val.replace(include_token, '')
427- p = os.path.join(path, val)
428- with open(p) as f:
429- val = base64.b64encode(f.read())
430- service['options'][key] = val
431+ self.deployment.load({self.deployment.juju_env: script.values()[0]})
432+
433+ def status(self):
434+ return amulet.waiter.status(self.deployment.juju_env)
435
436 def tearDown(self):
437 self.deployment.cleanup()
438
439
440 @features.requires(features.bootstrapped_juju)
441-class DeployerTest(TestCaseWithGnupg):
442+class DeployerTest(testing.TestCaseWithGnupg):
443 '''Base class for building juju deployer based tests.'''
444
445 deployer_cfg = None
446@@ -75,10 +63,29 @@
447
448 def setUp(self):
449 super(DeployerTest, self).setUp()
450- self.deployer = AmuletDeployment(
451- os.path.join(os.environ['JUJU_DEPLOYER_DIR'], self.deployer_cfg))
452+ self.deployer = AmuletDeployment()
453+ deployer_dir = os.environ['JUJU_DEPLOYER_DIR']
454+ with open(os.path.join(deployer_dir, self.deployer_cfg)) as f:
455+ script = yaml.safe_load(f.read())
456+ self.fixup_includes(script, deployer_dir)
457+ self.deployer.load(script)
458 self.addCleanup(self.deployer.tearDown)
459 self.deployer.setUp(self.timeout)
460+ self.status = self.deployer.status()
461+
462+ def fixup_includes(self, script, base_dir):
463+ # amulet can only process "include-base64" option if the file is under
464+ # a relative 'tests' dir, so we process the option ourselves.
465+ for deployment in script:
466+ for service in script[deployment]['services'].values():
467+ for key, val in service.get('options', {}).iteritems():
468+ include_token = 'include-base64://'
469+ if type(val) is str and val.startswith(include_token):
470+ val = val.replace(include_token, '')
471+ include_path = os.path.join(base_dir, val)
472+ with open(include_path) as f:
473+ val = base64.b64encode(f.read())
474+ service['options'][key] = val
475
476 def get_ip_and_port(self, service, unit=0):
477 tries = 0
478
479=== added file 'tests/test_rabbit.py'
480--- tests/test_rabbit.py 1970-01-01 00:00:00 +0000
481+++ tests/test_rabbit.py 2014-05-14 13:40:29 +0000
482@@ -0,0 +1,149 @@
483+# Ubuntu CI Engine
484+# Copyright 2014 Canonical Ltd.
485+
486+# This program is free software: you can redistribute it and/or modify it
487+# under the terms of the GNU Affero General Public License version 3, as
488+# published by the Free Software Foundation.
489+
490+# This program is distributed in the hope that it will be useful, but
491+# WITHOUT ANY WARRANTY; without even the implied warranties of
492+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
493+# PURPOSE. See the GNU Affero General Public License for more details.
494+
495+# You should have received a copy of the GNU Affero General Public License
496+# along with this program. If not, see <http://www.gnu.org/licenses/>.
497+
498+import errno
499+import subprocess
500+import unittest
501+import yaml
502+
503+
504+import deployers
505+from ucitests import fixtures
506+
507+
508+from ci_utils import amqp_utils
509+from ci_utils.testing import features
510+
511+
512+def juju(args):
513+ try:
514+ p = subprocess.Popen(['juju'] + args,
515+ stdout=subprocess.PIPE,
516+ stderr=subprocess.PIPE)
517+ except OSError as e:
518+ if e.errno != errno.ENOENT:
519+ raise
520+ raise OSError("juju not found, do you have Juju installed?")
521+ out, err = p.communicate()
522+ if p.returncode:
523+ raise IOError('juju command failed {!r}:\n{}'.format(args, err))
524+ return out
525+
526+
527+class RabbitServer(object):
528+
529+ timeout = 600
530+
531+ def __init__(self):
532+ self.deployer = None
533+
534+ def setUp(self):
535+ just_rabbit ='''
536+ci-airline-staging:
537+ series: precise
538+ services:
539+ rabbit:
540+ to: 0
541+ branch: lp:~canonical-ci-engineering/charms/precise/uci-engine/rabbitmq-server@46
542+ charm: rabbitmq
543+ options:
544+ management_plugin: true
545+ relations: []
546+'''
547+ self.deployer = deployers.AmuletDeployment()
548+ self.deployer.load(yaml.safe_load(just_rabbit))
549+ self.deployer.setUp(self.timeout)
550+ status = self.deployer.status()
551+ units = status['services']['rabbit']['units']
552+ self.unit = 'rabbit/0'
553+ self.public_ip = units[self.unit]['public-address']
554+ # So caller can handle tearDown/cleanup
555+ return self.deployer.tearDown
556+
557+ def run_ctl_cmd(self, args):
558+ cmd = ' '.join(args)
559+ juju(['run', '--unit', self.unit, 'sudo rabbitmqctl {}'.format(cmd)])
560+
561+ def create_user(self, user_name, password=None):
562+ if password is None:
563+ password = user_name
564+ self.run_ctl_cmd(['add_user', user_name, password])
565+ self.run_ctl_cmd(['set_permissions', '-p', '/',
566+ user_name, '".*" ".*" ".*"'])
567+
568+ def delete_user(self, user_name):
569+ self.run_ctl_cmd(['delete_user', user_name])
570+
571+
572+
573+@features.requires(features.bootstrapped_juju)
574+class TestRabbit(unittest.TestCase):
575+
576+ timeout = 600
577+
578+ def setUp(self):
579+ super(TestRabbit, self).setUp()
580+ self.rabbit = RabbitServer()
581+ tearDown = self.rabbit.setUp()
582+ self.addCleanup(tearDown)
583+ self.rabbit.create_user('tester', 's3cr3t')
584+ self.addCleanup(self.rabbit.delete_user, 'tester')
585+
586+ class AmqpConfig(object):
587+
588+ def __init__(inner):
589+ inner.AMQP_HOST = self.rabbit.public_ip
590+ inner.AMQP_VHOST = '/'
591+ inner.AMQP_USER = 'tester'
592+ inner.AMQP_PASSWORD = 's3cr3t'
593+ fixtures.patch(self, amqp_utils, 'get_config', AmqpConfig)
594+
595+ def test_acked_message(self):
596+ amqp_utils.send('myqueue', 'hello')
597+ conn, channel = amqp_utils.declare_queue('myqueue')
598+ self.addCleanup(conn.close)
599+ self.addCleanup(channel.close)
600+ self.addCleanup(channel.queue_delete, 'myqueue')
601+ channel.basic_qos(prefetch_size=0, prefetch_count=1, a_global=False)
602+ def ack(msg):
603+ self.assertEqual('hello', msg.body)
604+ msg.channel.basic_ack(msg.delivery_tag)
605+
606+ channel.basic_consume(callback=ack, queue='myqueue')
607+
608+ def test_nacked_message(self):
609+ amqp_utils.send('myqueue', 'hello')
610+ conn, channel = amqp_utils.declare_queue('myqueue')
611+ self.addCleanup(conn.close)
612+ self.addCleanup(channel.close)
613+ self.addCleanup(channel.queue_delete, 'myqueue')
614+ channel.basic_qos(prefetch_size=0, prefetch_count=1, a_global=False)
615+ def nack(msg):
616+ self.assertEqual('hello', msg.body)
617+ msg.channel.basic_reject(msg.delivery_tag, requeue=True)
618+
619+ channel.basic_consume(callback=nack, queue='myqueue')
620+
621+ # Use a different channel to get the message again
622+ conn, channel = amqp_utils.declare_queue('myqueue')
623+ self.addCleanup(conn.close)
624+ self.addCleanup(channel.close)
625+ # We don't need to delete the queue during cleanup, it's already done
626+ # above.
627+ channel.basic_qos(prefetch_size=0, prefetch_count=1, a_global=False)
628+ def ack(msg):
629+ self.assertEqual('hello', msg.body)
630+ msg.channel.basic_ack(msg.delivery_tag)
631+ channel.basic_consume(callback=ack, queue='myqueue')

Subscribers

People subscribed via source and target branches