2
from shutil import rmtree
25
from remote import SSHRemote
26
from tests import FakeHomeTestCase
27
from test_jujupy import (
32
def fake_EnvJujuClient_by_version(env, path=None, debug=None):
33
return EnvJujuClient(env=env, version='1.2.3.4', full_path=path)
36
def fake_SimpleEnvironment_from_config(name):
37
return SimpleEnvironment(name, {})
40
class TestBackgroundChaos(FakeHomeTestCase):
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')
53
with patch('remote.SSHRemote.copy') as rc_mock:
55
with background_chaos('foo', client, log_dir, 1):
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])
66
def test_background_chaos_exits(self):
67
client = EnvJujuClient(JujuData('foo', {}), None, '/foo/juju')
68
with patch('chaos.MonkeyRunner.deploy_chaos_monkey',
70
with patch('chaos.MonkeyRunner.unleash_once',
72
with patch('chaos.MonkeyRunner.wait_for_chaos',
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:
79
with background_chaos('foo', client,
83
self.assertEqual(1, le_mock.call_count)
84
sm.assert_called_once_with(1)
87
class TestRunChaosMonkey(FakeHomeTestCase):
89
def test_deploy_chaos_monkey(self):
90
def output(*args, **kwargs):
91
status = yaml.safe_dump({
93
'0': {'agent-state': 'started'}
99
'agent-state': 'started',
102
'agent-state': 'started'
111
('show-status', '--format', 'yaml'): status,
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'),
124
assert_juju_call(self, cc_mock, client, (
125
'juju', '--show-log', 'add-relation', '-m', 'foo', 'ser1',
127
self.assertEqual(cc_mock.call_count, 2)
128
self.assertEqual(gjo_mock.call_count, 2)
130
def test_iter_chaos_monkey_units(self):
131
def output(*args, **kwargs):
132
status = yaml.safe_dump({
134
'0': {'agent-state': 'started'}
141
'chaos-monkey/0': {'baz': 'qux'},
142
'not-chaos/0': {'qwe': 'rty'},
147
'chaos-monkey/1': {'abc': '123'},
155
('show-status', '--format', 'yaml'): status,
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,
162
monkey_units = dict((k, v) for k, v in
163
runner.iter_chaos_monkey_units())
165
'chaos-monkey/0': {'baz': 'qux'},
166
'chaos-monkey/1': {'abc': '123'}
168
self.assertEqual(expected, monkey_units)
170
def test_get_unit_status(self):
171
def output(*args, **kwargs):
172
status = yaml.safe_dump({
174
'0': {'agent-state': 'started'}
181
'chaos-monkey/0': {'baz': 'qux'},
182
'not-chaos/0': {'qwe': 'rty'},
187
'chaos-monkey/1': {'abc': '123'},
194
charm_config = yaml.safe_dump({
195
'charm': {'jenkins'},
196
'service': {'jenkins'},
200
'description': 'bla bla',
202
'value': '/tmp/charm-dir',
208
('get-config', 'chaos-monkey'): charm_config,
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'
217
with patch.object(client, 'get_juju_output', side_effect=output,
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),
224
self.assertEqual(call_mock.call_count, 2)
225
with patch.object(client, 'get_juju_output', side_effect=output,
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),
232
self.assertEqual(call_mock.call_count, 2)
235
class TestUnleashOnce(FakeHomeTestCase):
237
def test_unleash_once(self):
238
def output(*args, **kwargs):
239
status = yaml.safe_dump({
241
'0': {'agent-state': 'started'}
248
'chaos-monkey/0': {'baz': 'qux'},
249
'not-chaos/0': {'qwe': 'rty'},
254
'chaos-monkey/1': {'abc': '123'},
261
charm_config = yaml.safe_dump({
262
'charm': {'jenkins'},
263
'service': {'jenkins'},
267
'description': 'bla bla',
269
'value': '/tmp/charm-dir',
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'),
297
def output2(*args, **kwargs):
298
status = yaml.safe_dump({
300
'0': {'agent-state': 'started'}
307
'chaos-monkey/1': {'abc': '123'},
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'),
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]
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'),
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()
347
call('show-status', '--format', 'yaml', admin=False),
349
'chaos-monkey/1', 'start', 'mode=single',
350
'enablement-timeout=120',
351
'monkey-id=123412341234123412341234123412341234'),
353
'chaos-monkey/0', 'start', 'mode=single',
354
'enablement-timeout=120',
355
'monkey-id=abcdabcdabcdabcdabcdabcdabcdabcdabcd'),
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
361
with patch.object(client, 'get_juju_output', side_effect=output2,
363
monkey_runner.unleash_once()
364
self.assertEqual('1234' * 9,
365
monkey_runner.monkey_ids['chaos-monkey/1'])
367
def test_unleash_once_raises_for_unexpected_action_output(self):
368
def output(*args, **kwargs):
369
status = yaml.safe_dump({
371
'0': {'agent-state': 'started'}
378
'chaos-monkey/0': {'baz': 'qux'},
386
('show-status', '--format', 'yaml'): status,
387
('run-action', 'chaos-monkey/0', 'start', 'mode=single',
388
'enablement-timeout=120'
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,
396
with self.assertRaisesRegexp(
397
Exception, 'Action id not found in output: Action fail'):
398
monkey_runner.unleash_once()
401
class TestIsHealthy(unittest.TestCase):
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')
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')
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\].*')
452
class TestWaitForChaos(FakeHomeTestCase):
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)
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)
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',
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)
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')