Merge lp:~vila/uci-engine/1302471-fake-rabbit into lp:uci-engine
- 1302471-fake-rabbit
- Merge into trunk
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 |
Related bugs: |
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.
PS Jenkins bot (ps-jenkins) wrote : | # |
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_
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).
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_
>
> 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.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:454
http://
Executed test runs:
Click here to trigger a rebuild:
http://
Andy Doan (doanac) wrote : | # |
looks good. at some point we probably should figure out what of:
could fit into this new FakeTaskQueue Fixture so that we can really do a thorough testing of each of our runworker handlers.
Andy Doan (doanac) : | # |
Vincent Ladeuil (vila) wrote : | # |
> looks good. at some point we probably should figure out what of:
>
> http://
> engine/
>
> 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.
Andy Doan (doanac) wrote : | # |
Just realized we did this all wrong. See in-line comment.
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_
370 + worker = run_worker.
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_
374 + image_id=
375 + '-amd64-
376 + package_
377 + self.queue.
378 + self.queue.
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_
Sorry for missing this after its already been merged. It hit me while walking the dog.
Preview Diff
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') |
FAILED: Continuous integration, rev:452 s-jenkins. ubuntu- ci:8080/ job/uci- engine- ci/623/
http://
Executed test runs:
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/uci- engine- ci/623/ rebuild
http://