~andrewjbeach/juju-ci-tools/make-local-patcher

« back to all changes in this revision

Viewing changes to tests/test_chaos.py

  • Committer: John George
  • Date: 2015-01-14 22:03:47 UTC
  • mto: This revision was merged to the branch mainline in revision 798.
  • Revision ID: john.george@canonical.com-20150114220347-e8q5wezs1qg9a00u
Added support for setting the juju path, series and agent_url.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
import os
2
 
from shutil import rmtree
3
 
import stat
4
 
from tempfile import (
5
 
    NamedTemporaryFile,
6
 
    mkdtemp,
7
 
    )
8
 
import unittest
9
 
 
10
 
from mock import (
11
 
    call,
12
 
    patch,
13
 
)
14
 
import yaml
15
 
 
16
 
from chaos import (
17
 
    background_chaos,
18
 
    MonkeyRunner,
19
 
    )
20
 
from jujupy import (
21
 
    EnvJujuClient,
22
 
    JujuData,
23
 
    SimpleEnvironment,
24
 
    )
25
 
from remote import SSHRemote
26
 
from tests import FakeHomeTestCase
27
 
from test_jujupy import (
28
 
    assert_juju_call,
29
 
    )
30
 
 
31
 
 
32
 
def fake_EnvJujuClient_by_version(env, path=None, debug=None):
33
 
    return EnvJujuClient(env=env, version='1.2.3.4', full_path=path)
34
 
 
35
 
 
36
 
def fake_SimpleEnvironment_from_config(name):
37
 
    return SimpleEnvironment(name, {})
38
 
 
39
 
 
40
 
class TestBackgroundChaos(FakeHomeTestCase):
41
 
 
42
 
    def test_background_chaos(self):
43
 
        client = EnvJujuClient(JujuData('foo', {}), None, '/foo/juju')
44
 
        with patch('chaos.MonkeyRunner.deploy_chaos_monkey',
45
 
                   autospec=True) as d_mock:
46
 
            with patch('chaos.MonkeyRunner.unleash_once',
47
 
                       return_value=['1'], autospec=True) as u_mock:
48
 
                with patch('chaos.MonkeyRunner.wait_for_chaos',
49
 
                           autospec=True) as w_mock:
50
 
                    with patch('chaos.remote_from_unit',
51
 
                               return_value=SSHRemote(None, None, 'foo')
52
 
                               ) as ru_mock:
53
 
                        with patch('remote.SSHRemote.copy') as rc_mock:
54
 
                            log_dir = mkdtemp()
55
 
                            with background_chaos('foo', client, log_dir, 1):
56
 
                                pass
57
 
                            rmtree(log_dir)
58
 
        self.assertEqual(1, d_mock.call_count)
59
 
        self.assertEqual(1, u_mock.call_count)
60
 
        self.assertEqual(1, ru_mock.call_count)
61
 
        self.assertEqual(1, rc_mock.call_count)
62
 
        self.assertEqual({'state': 'start'}, w_mock.mock_calls[0][2])
63
 
        self.assertEqual({'state': 'complete', 'timeout': 1},
64
 
                         w_mock.mock_calls[1][2])
65
 
 
66
 
    def test_background_chaos_exits(self):
67
 
        client = EnvJujuClient(JujuData('foo', {}), None, '/foo/juju')
68
 
        with patch('chaos.MonkeyRunner.deploy_chaos_monkey',
69
 
                   autospec=True):
70
 
            with patch('chaos.MonkeyRunner.unleash_once',
71
 
                       autospec=True):
72
 
                with patch('chaos.MonkeyRunner.wait_for_chaos',
73
 
                           autospec=True):
74
 
                    with patch('chaos.remote_from_unit'):
75
 
                        with patch('remote.SSHRemote.copy'):
76
 
                            with patch('logging.exception') as le_mock:
77
 
                                with patch('sys.exit', autospec=True) as sm:
78
 
                                    log_dir = mkdtemp()
79
 
                                    with background_chaos('foo', client,
80
 
                                                          log_dir, 1):
81
 
                                        raise Exception()
82
 
                            rmtree(log_dir)
83
 
        self.assertEqual(1, le_mock.call_count)
84
 
        sm.assert_called_once_with(1)
85
 
 
86
 
 
87
 
class TestRunChaosMonkey(FakeHomeTestCase):
88
 
 
89
 
    def test_deploy_chaos_monkey(self):
90
 
        def output(*args, **kwargs):
91
 
            status = yaml.safe_dump({
92
 
                'machines': {
93
 
                    '0': {'agent-state': 'started'}
94
 
                },
95
 
                'services': {
96
 
                    'ser1': {
97
 
                        'units': {
98
 
                            'bar': {
99
 
                                'agent-state': 'started',
100
 
                                'subordinates': {
101
 
                                    'chaos-monkey/1': {
102
 
                                        'agent-state': 'started'
103
 
                                    }
104
 
                                }
105
 
                            }
106
 
                        }
107
 
                    }
108
 
                }
109
 
            })
110
 
            output = {
111
 
                ('show-status', '--format', 'yaml'): status,
112
 
                }
113
 
            return output[args]
114
 
        client = EnvJujuClient(JujuData('foo', {}), '1.25.0', '/foo/juju')
115
 
        with patch.object(client, 'get_juju_output', side_effect=output,
116
 
                          autospec=True) as gjo_mock:
117
 
            with patch('subprocess.check_call', autospec=True) as cc_mock:
118
 
                monkey_runner = MonkeyRunner('foo', client, service='ser1')
119
 
                with patch('jujupy.GroupReporter._write', autospec=True):
120
 
                    monkey_runner.deploy_chaos_monkey()
121
 
        assert_juju_call(self, cc_mock, client, (
122
 
            'juju', '--show-log', 'deploy', '-m', 'foo', 'local:chaos-monkey'),
123
 
            0)
124
 
        assert_juju_call(self, cc_mock, client, (
125
 
            'juju', '--show-log', 'add-relation', '-m', 'foo', 'ser1',
126
 
            'chaos-monkey'), 1)
127
 
        self.assertEqual(cc_mock.call_count, 2)
128
 
        self.assertEqual(gjo_mock.call_count, 2)
129
 
 
130
 
    def test_iter_chaos_monkey_units(self):
131
 
        def output(*args, **kwargs):
132
 
            status = yaml.safe_dump({
133
 
                'machines': {
134
 
                    '0': {'agent-state': 'started'}
135
 
                },
136
 
                'services': {
137
 
                    'jenkins': {
138
 
                        'units': {
139
 
                            'foo': {
140
 
                                'subordinates': {
141
 
                                    'chaos-monkey/0': {'baz': 'qux'},
142
 
                                    'not-chaos/0': {'qwe': 'rty'},
143
 
                                }
144
 
                            },
145
 
                            'bar': {
146
 
                                'subordinates': {
147
 
                                    'chaos-monkey/1': {'abc': '123'},
148
 
                                }
149
 
                            }
150
 
                        }
151
 
                    }
152
 
                }
153
 
            })
154
 
            output = {
155
 
                ('show-status', '--format', 'yaml'): status,
156
 
                }
157
 
            return output[args]
158
 
        client = EnvJujuClient(JujuData('foo', {}), None, '/foo/juju')
159
 
        runner = MonkeyRunner('foo', client, service='jenkins')
160
 
        with patch.object(client, 'get_juju_output', side_effect=output,
161
 
                          autospec=True):
162
 
            monkey_units = dict((k, v) for k, v in
163
 
                                runner.iter_chaos_monkey_units())
164
 
        expected = {
165
 
            'chaos-monkey/0': {'baz': 'qux'},
166
 
            'chaos-monkey/1': {'abc': '123'}
167
 
        }
168
 
        self.assertEqual(expected, monkey_units)
169
 
 
170
 
    def test_get_unit_status(self):
171
 
        def output(*args, **kwargs):
172
 
            status = yaml.safe_dump({
173
 
                'machines': {
174
 
                    '0': {'agent-state': 'started'}
175
 
                },
176
 
                'services': {
177
 
                    'jenkins': {
178
 
                        'units': {
179
 
                            'foo': {
180
 
                                'subordinates': {
181
 
                                    'chaos-monkey/0': {'baz': 'qux'},
182
 
                                    'not-chaos/0': {'qwe': 'rty'},
183
 
                                }
184
 
                            },
185
 
                            'bar': {
186
 
                                'subordinates': {
187
 
                                    'chaos-monkey/1': {'abc': '123'},
188
 
                                }
189
 
                            }
190
 
                        }
191
 
                    }
192
 
                }
193
 
            })
194
 
            charm_config = yaml.safe_dump({
195
 
                'charm': {'jenkins'},
196
 
                'service': {'jenkins'},
197
 
                'settings': {
198
 
                    'chaos-dir': {
199
 
                        'default': 'true',
200
 
                        'description': 'bla bla',
201
 
                        'type': 'string',
202
 
                        'value': '/tmp/charm-dir',
203
 
                    }
204
 
                }
205
 
            })
206
 
            output = {
207
 
                ('status',): status,
208
 
                ('get-config', 'chaos-monkey'): charm_config,
209
 
                }
210
 
            return output[args]
211
 
        client = EnvJujuClient(JujuData('foo', {}), None, '/foo')
212
 
        monkey_runner = MonkeyRunner('foo', client, service='jenkins')
213
 
        monkey_runner.monkey_ids = {
214
 
            'chaos-monkey/0': 'workspace0',
215
 
            'chaos-monkey/1': 'workspace1'
216
 
        }
217
 
        with patch.object(client, 'get_juju_output', side_effect=output,
218
 
                          autospec=True):
219
 
            with patch('subprocess.call', autospec=True,
220
 
                       return_value=0) as call_mock:
221
 
                for unit_name in ['chaos-monkey/1', 'chaos-monkey/0']:
222
 
                    self.assertEqual(monkey_runner.get_unit_status(unit_name),
223
 
                                     'running')
224
 
            self.assertEqual(call_mock.call_count, 2)
225
 
        with patch.object(client, 'get_juju_output', side_effect=output,
226
 
                          autospec=True):
227
 
            with patch('subprocess.call', autospec=True,
228
 
                       return_value=1) as call_mock:
229
 
                for unit_name in ['chaos-monkey/1', 'chaos-monkey/0']:
230
 
                    self.assertEqual(monkey_runner.get_unit_status(unit_name),
231
 
                                     'done')
232
 
            self.assertEqual(call_mock.call_count, 2)
233
 
 
234
 
 
235
 
class TestUnleashOnce(FakeHomeTestCase):
236
 
 
237
 
    def test_unleash_once(self):
238
 
        def output(*args, **kwargs):
239
 
            status = yaml.safe_dump({
240
 
                'machines': {
241
 
                    '0': {'agent-state': 'started'}
242
 
                },
243
 
                'services': {
244
 
                    'jenkins': {
245
 
                        'units': {
246
 
                            'foo': {
247
 
                                'subordinates': {
248
 
                                    'chaos-monkey/0': {'baz': 'qux'},
249
 
                                    'not-chaos/0': {'qwe': 'rty'},
250
 
                                }
251
 
                            },
252
 
                            'bar': {
253
 
                                'subordinates': {
254
 
                                    'chaos-monkey/1': {'abc': '123'},
255
 
                                }
256
 
                            }
257
 
                        }
258
 
                    }
259
 
                }
260
 
            })
261
 
            charm_config = yaml.safe_dump({
262
 
                'charm': {'jenkins'},
263
 
                'service': {'jenkins'},
264
 
                'settings': {
265
 
                    'chaos-dir': {
266
 
                        'default': 'true',
267
 
                        'description': 'bla bla',
268
 
                        'type': 'string',
269
 
                        'value': '/tmp/charm-dir',
270
 
                    }
271
 
                }
272
 
            })
273
 
            output = {
274
 
                ('show-status', '--format', 'yaml'): status,
275
 
                ('get', 'jenkins'): charm_config,
276
 
                ('run-action', 'chaos-monkey/0', 'start', 'mode=single',
277
 
                 'enablement-timeout=120'
278
 
                 ): ('Action queued with id: '
279
 
                     'abcdabcdabcdabcdabcdabcdabcdabcdabcd'),
280
 
                ('run-action', 'chaos-monkey/0', 'start', 'mode=single',
281
 
                 'enablement-timeout=120',
282
 
                 'monkey-id=abcdabcdabcdabcdabcdabcdabcdabcdabcd'
283
 
                 ): ('Action queued with id: '
284
 
                     'efabefabefabefabefabefabefabefabefab'),
285
 
                ('run-action', 'chaos-monkey/1', 'start', 'mode=single',
286
 
                 'enablement-timeout=120'
287
 
                 ): ('Action queued with id: '
288
 
                     '123412341234123412341234123412341234'),
289
 
                ('run-action', 'chaos-monkey/1', 'start', 'mode=single',
290
 
                 'enablement-timeout=120',
291
 
                 'monkey-id=123412341234123412341234123412341234'
292
 
                 ): ('Action queued with id: '
293
 
                     '567856785678567856785678567856785678'),
294
 
                }
295
 
            return output[args]
296
 
 
297
 
        def output2(*args, **kwargs):
298
 
            status = yaml.safe_dump({
299
 
                'machines': {
300
 
                    '0': {'agent-state': 'started'}
301
 
                },
302
 
                'services': {
303
 
                    'jenkins': {
304
 
                        'units': {
305
 
                            'bar': {
306
 
                                'subordinates': {
307
 
                                    'chaos-monkey/1': {'abc': '123'},
308
 
                                }
309
 
                            }
310
 
                        }
311
 
                    }
312
 
                }
313
 
            })
314
 
            output = {
315
 
                ('show-status', '--format', 'yaml'): status,
316
 
                ('run-action', 'chaos-monkey/1', 'start', 'mode=single',
317
 
                 'enablement-timeout=120',
318
 
                 'monkey-id=123412341234123412341234123412341234'
319
 
                 ): ('Action queued with id: '
320
 
                     'abcdabcdabcdabcdabcdabcdabcdabcdabcd'),
321
 
                }
322
 
            return output[args]
323
 
        client = EnvJujuClient(JujuData('foo', {}), None, '/foo/juju')
324
 
        monkey_runner = MonkeyRunner('foo', client, service='jenkins')
325
 
        with patch.object(client, 'get_juju_output', side_effect=output,
326
 
                          autospec=True) as gjo_mock:
327
 
            returned = monkey_runner.unleash_once()
328
 
        expected = ['abcd' * 9, '1234' * 9]
329
 
        self.assertEqual(
330
 
            [
331
 
                call('show-status', '--format', 'yaml', admin=False),
332
 
                call('run-action', 'chaos-monkey/1', 'start', 'mode=single',
333
 
                     'enablement-timeout=120'),
334
 
                call('run-action', 'chaos-monkey/0', 'start', 'mode=single',
335
 
                     'enablement-timeout=120'),
336
 
            ],
337
 
            gjo_mock.call_args_list)
338
 
        self.assertEqual(['chaos-monkey/1', 'chaos-monkey/0'],
339
 
                         monkey_runner.monkey_ids.keys())
340
 
        self.assertEqual(len(monkey_runner.monkey_ids), 2)
341
 
        self.assertItemsEqual(returned, expected)
342
 
        with patch.object(client, 'get_juju_output', side_effect=output,
343
 
                          autospec=True) as gjo_mock:
344
 
            monkey_runner.unleash_once()
345
 
        self.assertEqual(
346
 
            [
347
 
                call('show-status', '--format', 'yaml', admin=False),
348
 
                call('run-action',
349
 
                     'chaos-monkey/1', 'start', 'mode=single',
350
 
                     'enablement-timeout=120',
351
 
                     'monkey-id=123412341234123412341234123412341234'),
352
 
                call('run-action',
353
 
                     'chaos-monkey/0', 'start', 'mode=single',
354
 
                     'enablement-timeout=120',
355
 
                     'monkey-id=abcdabcdabcdabcdabcdabcdabcdabcdabcd'),
356
 
            ],
357
 
            gjo_mock.call_args_list)
358
 
        self.assertTrue('1234', monkey_runner.monkey_ids['chaos-monkey/1'])
359
 
        # Test monkey_ids.get(unit_name) does not change on second call to
360
 
        # unleash_once()
361
 
        with patch.object(client, 'get_juju_output', side_effect=output2,
362
 
                          autospec=True):
363
 
            monkey_runner.unleash_once()
364
 
        self.assertEqual('1234' * 9,
365
 
                         monkey_runner.monkey_ids['chaos-monkey/1'])
366
 
 
367
 
    def test_unleash_once_raises_for_unexpected_action_output(self):
368
 
        def output(*args, **kwargs):
369
 
            status = yaml.safe_dump({
370
 
                'machines': {
371
 
                    '0': {'agent-state': 'started'}
372
 
                },
373
 
                'services': {
374
 
                    'jenkins': {
375
 
                        'units': {
376
 
                            'foo': {
377
 
                                'subordinates': {
378
 
                                    'chaos-monkey/0': {'baz': 'qux'},
379
 
                                }
380
 
                            }
381
 
                        }
382
 
                    }
383
 
                }
384
 
            })
385
 
            output = {
386
 
                ('show-status', '--format', 'yaml'): status,
387
 
                ('run-action', 'chaos-monkey/0', 'start', 'mode=single',
388
 
                 'enablement-timeout=120'
389
 
                 ): 'Action fail',
390
 
                }
391
 
            return output[args]
392
 
        client = EnvJujuClient(JujuData('foo', {}), None, '/foo/juju')
393
 
        monkey_runner = MonkeyRunner('foo', client, service='jenkins')
394
 
        with patch.object(client, 'get_juju_output', side_effect=output,
395
 
                          autospec=True):
396
 
            with self.assertRaisesRegexp(
397
 
                    Exception, 'Action id not found in output: Action fail'):
398
 
                monkey_runner.unleash_once()
399
 
 
400
 
 
401
 
class TestIsHealthy(unittest.TestCase):
402
 
 
403
 
    def test_is_healthy(self):
404
 
        SCRIPT = """#!/bin/bash\necho -n 'PASS'\nexit 0"""
405
 
        client = EnvJujuClient(JujuData('foo', {}), None, '/foo/juju')
406
 
        with NamedTemporaryFile(delete=False) as health_script:
407
 
            health_script.write(SCRIPT)
408
 
            os.fchmod(health_script.fileno(), stat.S_IEXEC | stat.S_IREAD)
409
 
            health_script.close()
410
 
            monkey_runner = MonkeyRunner('foo', client,
411
 
                                         health_checker=health_script.name)
412
 
            with patch('logging.info') as lo_mock:
413
 
                result = monkey_runner.is_healthy()
414
 
            os.unlink(health_script.name)
415
 
            self.assertTrue(result)
416
 
            self.assertEqual(lo_mock.call_args[0][0],
417
 
                             'Health check output: PASS')
418
 
 
419
 
    def test_is_healthy_fail(self):
420
 
        SCRIPT = """#!/bin/bash\necho -n 'FAIL'\nexit 1"""
421
 
        client = EnvJujuClient(JujuData('foo', {}), None, '/foo/juju')
422
 
        with NamedTemporaryFile(delete=False) as health_script:
423
 
            health_script.write(SCRIPT)
424
 
            os.fchmod(health_script.fileno(), stat.S_IEXEC | stat.S_IREAD)
425
 
            health_script.close()
426
 
            monkey_runner = MonkeyRunner('foo', client,
427
 
                                         health_checker=health_script.name)
428
 
            with patch('logging.error') as le_mock:
429
 
                result = monkey_runner.is_healthy()
430
 
            os.unlink(health_script.name)
431
 
            self.assertFalse(result)
432
 
            self.assertEqual(le_mock.call_args[0][0], 'FAIL')
433
 
 
434
 
    def test_is_healthy_with_no_execute_perms(self):
435
 
        SCRIPT = """#!/bin/bash\nexit 0"""
436
 
        client = EnvJujuClient(JujuData('foo', {}), None, '/foo/juju')
437
 
        with NamedTemporaryFile(delete=False) as health_script:
438
 
            health_script.write(SCRIPT)
439
 
            os.fchmod(health_script.fileno(), stat.S_IREAD)
440
 
            health_script.close()
441
 
            monkey_runner = MonkeyRunner('foo', client,
442
 
                                         health_checker=health_script.name)
443
 
            with patch('logging.error') as le_mock:
444
 
                with self.assertRaises(OSError):
445
 
                    monkey_runner.is_healthy()
446
 
            os.unlink(health_script.name)
447
 
        self.assertRegexpMatches(
448
 
            le_mock.call_args[0][0],
449
 
            r'The health check failed to execute with: \[Errno 13\].*')
450
 
 
451
 
 
452
 
class TestWaitForChaos(FakeHomeTestCase):
453
 
 
454
 
    def test_wait_for_chaos_complete(self):
455
 
        client = EnvJujuClient(JujuData('foo', {}), None, '/foo')
456
 
        runner = MonkeyRunner('foo', client)
457
 
        units = [('blib', 'blab')]
458
 
        with patch.object(runner, 'iter_chaos_monkey_units', autospec=True,
459
 
                          return_value=units) as ic_mock:
460
 
            with patch.object(runner, 'get_unit_status',
461
 
                              autospec=True, return_value='done') as us_mock:
462
 
                returned = runner.wait_for_chaos()
463
 
        self.assertEqual(returned, None)
464
 
        self.assertEqual(ic_mock.call_count, 1)
465
 
        self.assertEqual(us_mock.call_count, 1)
466
 
 
467
 
    def test_wait_for_chaos_complete_timesout(self):
468
 
        client = EnvJujuClient(JujuData('foo', {}), None, '/foo')
469
 
        runner = MonkeyRunner('foo', client)
470
 
        with self.assertRaisesRegexp(
471
 
                Exception, 'Chaos operations did not complete.'):
472
 
            runner.wait_for_chaos(timeout=0)
473
 
 
474
 
    def test_wait_for_chaos_started(self):
475
 
        client = EnvJujuClient(JujuData('foo', {}), None, '/foo')
476
 
        runner = MonkeyRunner('foo', client)
477
 
        units = [('blib', 'blab')]
478
 
        with patch.object(runner, 'iter_chaos_monkey_units', autospec=True,
479
 
                          return_value=units) as ic_mock:
480
 
            with patch.object(runner, 'get_unit_status',
481
 
                              autospec=True,
482
 
                              return_value='running') as us_mock:
483
 
                returned = runner.wait_for_chaos(state='start')
484
 
        self.assertEqual(returned, None)
485
 
        self.assertEqual(ic_mock.call_count, 1)
486
 
        self.assertEqual(us_mock.call_count, 1)
487
 
 
488
 
    def test_wait_for_chaos_unexpected_state(self):
489
 
        client = EnvJujuClient(JujuData('foo', {}), None, '/foo')
490
 
        runner = MonkeyRunner('foo', client)
491
 
        with self.assertRaisesRegexp(
492
 
                Exception, 'Unexpected state value: foo'):
493
 
            runner.wait_for_chaos(state='foo')