224
259
def get_status_dict(self):
226
261
for machine_id in self.machines:
262
machine_dict = {'juju-status': {'current': 'idle'}}
228
263
hostname = self.machine_host_names.get(machine_id)
264
machine_dict['instance-id'] = machine_id
229
265
if hostname is not None:
230
266
machine_dict['dns-name'] = hostname
231
267
machines[machine_id] = machine_dict
268
if machine_id in self.state_servers:
269
machine_dict['controller-member-status'] = 'has-vote'
232
270
for host, containers in self.containers.items():
233
271
machines[host]['containers'] = dict((c, {}) for c in containers)
235
273
for service, units in self.services.items():
237
275
for unit_id, machine_id in units:
238
unit_map[unit_id] = {'machine': machine_id}
276
unit_map[unit_id] = {
277
'machine': machine_id,
278
'juju-status': {'current': 'idle'}}
239
279
services[service] = {
240
280
'units': unit_map,
241
281
'relations': self.relations.get(service, {}),
242
282
'exposed': service in self.exposed,
244
return {'machines': machines, 'services': services}
247
class FakeJujuClient:
248
"""A fake juju client for tests.
284
return {'machines': machines, 'applications': services}
288
"""A fake juju backend for tests.
250
290
This is a partial implementation, but should be suitable for many uses,
251
291
and can be extended.
253
The state is provided by _backing_state, so that multiple clients can
254
manipulate the same state.
293
The state is provided by controller_state, so that multiple clients and
294
backends can manipulate the same state.
256
def __init__(self, env=None, full_path=None, debug=False,
257
jes_enabled=False, version='2.0.0'):
258
self._backing_state = FakeEnvironmentState()
260
env = SimpleEnvironment('name', {
262
'default-series': 'angsty',
297
def __init__(self, controller_state, feature_flags=None, version=None,
298
full_path=None, debug=False):
299
assert isinstance(controller_state, FakeControllerState)
300
self.controller_state = controller_state
301
if feature_flags is None:
302
feature_flags = set()
303
self.feature_flags = feature_flags
304
self.version = version
265
305
self.full_path = full_path
266
306
self.debug = debug
267
self.bootstrap_replaces = {}
268
self._jes_enabled = jes_enabled
269
self._separate_admin = jes_enabled
270
self.version = version
307
self.juju_timings = {}
272
def clone(self, env, full_path=None, debug=None):
309
def clone(self, full_path=None, version=None, debug=None,
312
version = self.version
273
313
if full_path is None:
274
314
full_path = self.full_path
275
315
if debug is None:
276
316
debug = self.debug
277
client = self.__class__(env, full_path, debug,
278
jes_enabled=self._jes_enabled)
279
client._backing_state = self._backing_state
282
def by_version(self, env, path, debug):
283
return FakeJujuClient(env, path, debug)
285
def get_matching_agent_version(self):
288
def _acquire_state_client(self, state):
289
if state.name == self.env.environment:
291
new_env = self.env.clone(model_name=state.name)
292
new_client = self.clone(new_env)
293
new_client._backing_state = state
296
def get_admin_client(self):
297
admin_model = self._backing_state.controller.admin_model
298
return self._acquire_state_client(admin_model)
300
def iter_model_clients(self):
301
if not self._jes_enabled:
302
raise JESNotSupported()
303
for state in self._backing_state.controller.models.values():
304
yield self._acquire_state_client(state)
306
def is_jes_enabled(self):
307
return self._jes_enabled
309
def get_cache_path(self):
310
return get_cache_path(self.env.juju_home, models=True)
312
def get_juju_output(self, command, *args, **kwargs):
317
if feature_flags is None:
318
feature_flags = set(self.feature_flags)
319
controller_state = self.controller_state
320
return self.__class__(controller_state, feature_flags, version,
323
def set_feature(self, feature, enabled):
325
self.feature_flags.add(feature)
327
self.feature_flags.discard(feature)
329
def is_feature_enabled(self, feature):
332
return bool(feature in self.feature_flags)
334
def deploy(self, model_state, charm_name, service_name=None, series=None):
335
if service_name is None:
336
service_name = charm_name.split(':')[-1].split('/')[-1]
337
model_state.deploy(charm_name, service_name)
339
def bootstrap(self, args):
340
parser = ArgumentParser()
341
parser.add_argument('controller_name')
342
parser.add_argument('cloud_name_region')
343
parser.add_argument('--constraints')
344
parser.add_argument('--config')
345
parser.add_argument('--default-model')
346
parser.add_argument('--agent-version')
347
parser.add_argument('--bootstrap-series')
348
parser.add_argument('--upload-tools', action='store_true')
349
parsed = parser.parse_args(args)
350
with open(parsed.config) as config_file:
351
config = yaml.safe_load(config_file)
352
cloud_region = parsed.cloud_name_region.split('/', 1)
353
cloud = cloud_region[0]
354
# Although they are specified with specific arguments instead of as
355
# config, these values are listed by get-model-config:
356
# name, region, type (from cloud).
357
config['type'] = cloud
358
if len(cloud_region) > 1:
359
config['region'] = cloud_region[1]
360
config['name'] = parsed.default_model
361
if parsed.bootstrap_series is not None:
362
config['default-series'] = parsed.bootstrap_series
363
self.controller_state.bootstrap(parsed.default_model, config,
364
self.is_feature_enabled('jes'))
366
def quickstart(self, model_name, config, bundle):
367
default_model = self.controller_state.bootstrap(
368
model_name, config, self.is_feature_enabled('jes'))
369
default_model.deploy_bundle(bundle)
371
def destroy_environment(self, model_name):
373
state = self.controller_state.models[model_name]
376
state.destroy_environment()
379
def add_machines(self, model_state, args):
381
return model_state.add_machine()
382
ssh_machines = [a[4:] for a in args if a.startswith('ssh:')]
383
if len(ssh_machines) == len(args):
384
return model_state.add_ssh_machines(ssh_machines)
385
(container_type,) = args
386
model_state.add_container(container_type)
388
def get_admin_model_name(self):
389
return self.controller_state.admin_model.name
391
def make_controller_dict(self, controller_name):
392
admin_model = self.controller_state.admin_model
393
server_id = list(admin_model.state_servers)[0]
394
server_hostname = admin_model.machine_host_names[server_id]
395
api_endpoint = '{}:23'.format(server_hostname)
396
return {controller_name: {'details': {'api-endpoints': [
399
def list_models(self):
400
model_names = [state.name for state in
401
self.controller_state.models.values()]
402
return {'models': [{'name': n} for n in model_names]}
404
def juju(self, command, args, used_feature_flags,
405
juju_home, model=None, check=True, timeout=None, extra_env=None):
406
if 'service' in command:
407
raise Exception('No service')
408
if model is not None:
409
model_state = self.controller_state.models[model]
410
if command == 'enable-ha':
411
model_state.enable_ha()
412
if (command, args[:1]) == ('set-config', ('dummy-source',)):
413
name, value = args[1].split('=')
415
model_state.token = value
416
if command == 'deploy':
417
parser = ArgumentParser()
418
parser.add_argument('charm_name')
419
parser.add_argument('service_name', nargs='?')
420
parser.add_argument('--to')
421
parser.add_argument('--series')
422
parsed = parser.parse_args(args)
423
self.deploy(model_state, parsed.charm_name,
424
parsed.service_name, parsed.series)
425
if command == 'remove-application':
426
model_state.destroy_service(*args)
427
if command == 'add-relation':
428
if args[0] == 'dummy-source':
429
model_state.relations[args[1]] = {'source': [args[0]]}
430
if command == 'expose':
432
model_state.exposed.add(service)
433
if command == 'unexpose':
435
model_state.exposed.remove(service)
436
if command == 'add-unit':
438
model_state.add_unit(service)
439
if command == 'remove-unit':
441
model_state.remove_unit(unit_id)
442
if command == 'add-machine':
443
return self.add_machines(model_state, args)
444
if command == 'remove-machine':
445
parser = ArgumentParser()
446
parser.add_argument('machine_id')
447
parser.add_argument('--force', action='store_true')
448
parsed = parser.parse_args(args)
449
machine_id = parsed.machine_id
450
if '/' in machine_id:
451
model_state.remove_container(machine_id)
453
model_state.remove_machine(machine_id)
454
if command == 'quickstart':
455
parser = ArgumentParser()
456
parser.add_argument('--constraints')
457
parser.add_argument('--no-browser', action='store_true')
458
parser.add_argument('bundle')
459
parsed = parser.parse_args(args)
460
# Released quickstart doesn't seem to provide the config via
462
self.quickstart(model, {}, parsed.bundle)
464
if command == 'bootstrap':
466
if command == 'kill-controller':
467
if self.controller_state.state == 'not-bootstrapped':
470
model_state = self.controller_state.models[model]
471
model_state.kill_controller()
472
if command == 'destroy-model':
473
if not self.is_feature_enabled('jes'):
474
raise JESNotSupported()
476
model_state = self.controller_state.models[model]
477
model_state.destroy_model()
478
if command == 'add-model':
479
if not self.is_feature_enabled('jes'):
480
raise JESNotSupported()
481
parser = ArgumentParser()
482
parser.add_argument('-c', '--controller')
483
parser.add_argument('--config')
484
parser.add_argument('model_name')
485
parsed = parser.parse_args(args)
486
self.controller_state.add_model(parsed.model_name)
489
def juju_async(self, command, args, used_feature_flags,
490
juju_home, model=None, timeout=None):
492
self.juju(command, args, used_feature_flags,
493
juju_home, model, timeout=timeout)
496
def get_juju_output(self, command, args, used_feature_flags,
497
juju_home, model=None, timeout=None):
498
if 'service' in command:
499
raise Exception('No service')
500
if model is not None:
501
model_state = self.controller_state.models[model]
313
502
if (command, args) == ('ssh', ('dummy-sink/0', GET_TOKEN_SCRIPT)):
314
return self._backing_state.token
503
return model_state.token
315
504
if (command, args) == ('ssh', ('0', 'lsb_release', '-c')):
316
return 'Codename:\t{}\n'.format(self.env.config['default-series'])
318
def juju(self, cmd, args, include_e=True):
319
# TODO: Use argparse or change all call sites to use functions.
320
if (cmd, args[:1]) == ('set', ('dummy-source',)):
321
name, value = args[1].split('=')
323
self._backing_state.token = value
326
if cmd == 'destroy-service':
327
self._backing_state.destroy_service(*args)
328
if cmd == 'add-relation':
329
if args[0] == 'dummy-source':
330
self._backing_state.relations[args[1]] = {'source': [args[0]]}
333
self._backing_state.exposed.add(service)
334
if cmd == 'unexpose':
336
self._backing_state.exposed.remove(service)
337
if cmd == 'add-unit':
339
self._backing_state.add_unit(service)
340
if cmd == 'remove-unit':
342
self._backing_state.remove_unit(unit_id)
343
if cmd == 'add-machine':
344
(container_type,) = args
345
self._backing_state.add_container(container_type)
346
if cmd == 'remove-machine':
348
if '/' in machine_id:
349
self._backing_state.remove_container(machine_id)
351
self._backing_state.remove_machine(machine_id)
353
def bootstrap(self, upload_tools=False, bootstrap_series=None):
354
commandline_config = {}
355
if bootstrap_series is not None:
356
commandline_config['default-series'] = bootstrap_series
357
self._backing_state.bootstrap(self.env, commandline_config,
358
self._separate_admin)
361
def bootstrap_async(self, upload_tools=False):
364
def quickstart(self, bundle):
365
self._backing_state.bootstrap(self.env, {}, self._separate_admin)
366
self._backing_state.deploy_bundle(bundle)
368
def create_environment(self, controller_client, config_file):
369
if not self._jes_enabled:
370
raise JESNotSupported()
371
model_state = controller_client._backing_state.controller.create_model(
372
self.env.environment)
373
self._backing_state = model_state
375
def destroy_model(self):
376
if not self._jes_enabled:
377
raise JESNotSupported()
378
self._backing_state.destroy_model()
380
def destroy_environment(self, force=True, delete_jenv=False):
381
self._backing_state.destroy_environment()
384
def kill_controller(self):
385
self._backing_state.kill_controller()
387
def add_ssh_machines(self, machines):
388
self._backing_state.add_ssh_machines(machines)
390
def deploy(self, charm_name, service_name=None, series=None):
391
if service_name is None:
392
service_name = charm_name.split(':')[-1].split('/')[-1]
393
self._backing_state.deploy(charm_name, service_name)
395
def remove_service(self, service):
396
self._backing_state.destroy_service(service)
398
def wait_for_started(self, timeout=1200, start=None):
399
return self.get_status()
401
def wait_for_deploy_started(self):
404
def show_status(self):
407
def get_status(self, admin=False):
408
status_dict = self._backing_state.get_status_dict()
409
return Status(status_dict, yaml.safe_dump(status_dict))
411
def status_until(self, timeout):
412
yield self.get_status()
414
def set_config(self, service, options):
415
option_strings = ['{}={}'.format(*item) for item in options.items()]
416
self.juju('set', (service,) + tuple(option_strings))
418
def get_config(self, service):
421
def get_model_config(self):
422
return copy.deepcopy(self._backing_state.model_config)
424
def deployer(self, bundle, name=None):
427
def wait_for_workloads(self, timeout=600):
430
def get_juju_timings(self):
433
def _require_admin(self, operation):
434
if self.get_admin_client() != self:
435
raise AdminOperation(operation)
438
self._require_admin('backup')
440
def restore_backup(self, backup_file):
441
self._require_admin('restore')
442
if len(self._backing_state.state_servers) > 0:
443
exc = subprocess.CalledProcessError('Operation not permitted', 1,
445
exc.stderr = 'Operation not permitted'
449
self._require_admin('enable-ha')
451
def wait_for_ha(self):
452
self._require_admin('wait-for-ha')
454
def get_controller_leader(self):
455
return self.get_controller_members()[0]
457
def get_controller_members(self):
458
return [Machine(s, {'instance-id': s})
459
for s in self._backing_state.state_servers]
505
return 'Codename:\t{}\n'.format(
506
model_state.model_config['default-series'])
507
if command == 'get-model-config':
508
return yaml.safe_dump(model_state.model_config)
509
if command == 'restore-backup':
510
model_state.restore_backup()
511
if command == 'show-controller':
512
return yaml.safe_dump(self.make_controller_dict(args[0]))
513
if command == 'list-models':
514
return yaml.safe_dump(self.list_models())
515
if command == ('add-user'):
517
if set(["--acl", "write"]).issubset(args):
518
permissions = 'write'
521
code = b64encode(sha512(username).digest())
523
'User "{}" added\nUser "{}"granted {} access to model "{}\n"' \
524
.format(username, username, permissions, model)
526
'Please send this command to {}\n juju register {}' \
527
.format(username, code)
528
return info_string + register_string
529
if command == 'show-status':
530
status_dict = model_state.get_status_dict()
531
return yaml.safe_dump(status_dict)
532
if command == 'create-backup':
533
self.controller_state.require_admin('backup', model)
534
return 'juju-backup-0.tar.gz'
537
def pause(self, seconds):
541
class FakeBackend2B7(FakeBackend):
543
def juju(self, command, args, used_feature_flags,
544
juju_home, model=None, check=True, timeout=None, extra_env=None):
545
if model is not None:
546
model_state = self.controller_state.models[model]
547
if command == 'destroy-service':
548
model_state.destroy_service(*args)
549
if command == 'remove-service':
550
model_state.destroy_service(*args)
551
return super(FakeBackend2B7).juju(command, args, used_feature_flags,
552
juju_home, model, check, timeout,
556
class FakeBackendOptionalJES(FakeBackend):
558
def is_feature_enabled(self, feature):
559
return bool(feature in self.feature_flags)
562
def fake_juju_client(env=None, full_path=None, debug=False, version='2.0.0',
563
_backend=None, cls=EnvJujuClient):
565
env = JujuData('name', {
567
'default-series': 'angsty',
570
juju_home = env.juju_home
571
if juju_home is None:
574
backend_state = FakeControllerState()
575
_backend = FakeBackend(
576
backend_state, version=version, full_path=full_path,
578
_backend.set_feature('jes', True)
580
env, version, full_path, juju_home, debug, _backend=_backend)
581
client.bootstrap_replaces = {}
585
def fake_juju_client_optional_jes(env=None, full_path=None, debug=False,
586
jes_enabled=True, version='2.0.0',
589
backend_state = FakeControllerState()
590
_backend = FakeBackendOptionalJES(
591
backend_state, version=version, full_path=full_path,
593
_backend.set_feature('jes', jes_enabled)
594
client = fake_juju_client(env, full_path, debug, version, _backend,
595
cls=FakeJujuClientOptionalJES)
596
client.used_feature_flags = frozenset(['address-allocation', 'jes'])
600
class FakeJujuClientOptionalJES(EnvJujuClient):
602
def get_admin_model_name(self):
603
return self._backend.controller_state.admin_model.name
462
606
class TestErroredUnit(TestCase):
5276
5676
self.assertEqual(status.status_text, text)
5277
5677
self.assertEqual(status.status, {
5278
5678
'machines': {'0': {'agent-state': 'pending'}},
5679
'applications': {'jenkins': {'units': {'jenkins/0': {
5680
'agent-state': 'horsefeathers'}}}}
5683
def test_iter_units(self):
5684
started_unit = {'agent-state': 'started'}
5685
unit_with_subordinates = {
5686
'agent-state': 'started',
5688
'ntp/0': started_unit,
5689
'nrpe/0': started_unit,
5694
'1': {'agent-state': 'started'},
5699
'jenkins/0': unit_with_subordinates,
5704
'application/0': started_unit,
5705
'application/1': started_unit,
5711
('application/0', started_unit),
5712
('application/1', started_unit),
5713
('jenkins/0', unit_with_subordinates),
5714
('nrpe/0', started_unit),
5715
('ntp/0', started_unit),
5717
gen = status.iter_units()
5718
self.assertIsInstance(gen, types.GeneratorType)
5719
self.assertEqual(expected, list(gen))
5722
class TestServiceStatus(FakeHomeTestCase):
5724
def test_iter_machines_no_containers(self):
5725
status = ServiceStatus({
5727
'1': {'foo': 'bar', 'containers': {'1/lxc/0': {'baz': 'qux'}}}
5729
'services': {}}, '')
5730
self.assertEqual(list(status.iter_machines()),
5731
[('1', status.status['machines']['1'])])
5733
def test_iter_machines_containers(self):
5734
status = ServiceStatus({
5736
'1': {'foo': 'bar', 'containers': {'1/lxc/0': {'baz': 'qux'}}}
5738
'services': {}}, '')
5739
self.assertEqual(list(status.iter_machines(containers=True)), [
5740
('1', status.status['machines']['1']),
5741
('1/lxc/0', {'baz': 'qux'}),
5744
def test_agent_items_empty(self):
5745
status = ServiceStatus({'machines': {}, 'services': {}}, '')
5746
self.assertItemsEqual([], status.agent_items())
5748
def test_agent_items(self):
5749
status = ServiceStatus({
5758
'sub': {'baz': 'qux'}
5766
('1', {'foo': 'bar'}),
5767
('jenkins/1', {'subordinates': {'sub': {'baz': 'qux'}}}),
5768
('sub', {'baz': 'qux'})]
5769
self.assertItemsEqual(expected, status.agent_items())
5771
def test_agent_items_containers(self):
5772
status = ServiceStatus({
5774
'1': {'foo': 'bar', 'containers': {
5775
'2': {'qux': 'baz'},
5781
('1', {'foo': 'bar', 'containers': {'2': {'qux': 'baz'}}}),
5782
('2', {'qux': 'baz'})
5784
self.assertItemsEqual(expected, status.agent_items())
5786
def test_get_service_count_zero(self):
5787
status = ServiceStatus({
5789
'1': {'agent-state': 'good'},
5793
self.assertEqual(0, status.get_service_count())
5795
def test_get_service_count(self):
5796
status = ServiceStatus({
5798
'1': {'agent-state': 'good'},
5804
'jenkins/1': {'agent-state': 'bad'},
5809
'dummy-sink/0': {'agent-state': 'started'},
5814
'juju-reports/0': {'agent-state': 'pending'},
5819
self.assertEqual(3, status.get_service_count())
5821
def test_get_service_unit_count_zero(self):
5822
status = ServiceStatus({
5824
'1': {'agent-state': 'good'},
5828
self.assertEqual(0, status.get_service_unit_count('jenkins'))
5830
def test_get_service_unit_count(self):
5831
status = ServiceStatus({
5833
'1': {'agent-state': 'good'},
5839
'jenkins/1': {'agent-state': 'bad'},
5840
'jenkins/2': {'agent-state': 'bad'},
5841
'jenkins/3': {'agent-state': 'bad'},
5846
self.assertEqual(3, status.get_service_unit_count('jenkins'))
5848
def test_get_unit(self):
5849
status = ServiceStatus({
5856
'jenkins/1': {'agent-state': 'bad'},
5861
'jenkins/2': {'agent-state': 'started'},
5867
status.get_unit('jenkins/1'), {'agent-state': 'bad'})
5869
status.get_unit('jenkins/2'), {'agent-state': 'started'})
5870
with self.assertRaisesRegexp(KeyError, 'jenkins/3'):
5871
status.get_unit('jenkins/3')
5873
def test_service_subordinate_units(self):
5874
status = ServiceStatus({
5884
'chaos-monkey/0': {'agent-state': 'started'},
5893
'chaos-monkey/1': {'agent-state': 'started'}
5898
'chaos-monkey/2': {'agent-state': 'started'}
5905
self.assertItemsEqual(
5906
status.service_subordinate_units('ubuntu'),
5908
self.assertItemsEqual(
5909
status.service_subordinate_units('jenkins'),
5910
[('chaos-monkey/0', {'agent-state': 'started'},)])
5911
self.assertItemsEqual(
5912
status.service_subordinate_units('dummy-sink'), [
5913
('chaos-monkey/1', {'agent-state': 'started'}),
5914
('chaos-monkey/2', {'agent-state': 'started'})]
5917
def test_get_open_ports(self):
5918
status = ServiceStatus({
5925
'jenkins/1': {'agent-state': 'bad'},
5930
'jenkins/2': {'open-ports': ['42/tcp']},
5935
self.assertEqual(status.get_open_ports('jenkins/1'), [])
5936
self.assertEqual(status.get_open_ports('jenkins/2'), ['42/tcp'])
5938
def test_agent_states_with_agent_state(self):
5939
status = ServiceStatus({
5941
'1': {'agent-state': 'good'},
5947
'jenkins/1': {'agent-state': 'bad'},
5948
'jenkins/2': {'agent-state': 'good'},
5954
'good': ['1', 'jenkins/2'],
5955
'bad': ['jenkins/1'],
5958
self.assertEqual(expected, status.agent_states())
5960
def test_agent_states_with_agent_status(self):
5961
status = ServiceStatus({
5963
'1': {'agent-state': 'good'},
5969
'jenkins/1': {'agent-status': {'current': 'bad'}},
5970
'jenkins/2': {'agent-status': {'current': 'good'}},
5977
'good': ['1', 'jenkins/2'],
5978
'bad': ['jenkins/1'],
5979
'no-agent': ['2', 'jenkins/3'],
5981
self.assertEqual(expected, status.agent_states())
5983
def test_agent_states_with_juju_status(self):
5984
status = ServiceStatus({
5986
'1': {'juju-status': {'current': 'good'}},
5992
'jenkins/1': {'juju-status': {'current': 'bad'}},
5993
'jenkins/2': {'juju-status': {'current': 'good'}},
6000
'good': ['1', 'jenkins/2'],
6001
'bad': ['jenkins/1'],
6002
'no-agent': ['2', 'jenkins/3'],
6004
self.assertEqual(expected, status.agent_states())
6006
def test_check_agents_started_not_started(self):
6007
status = ServiceStatus({
6009
'1': {'agent-state': 'good'},
6015
'jenkins/1': {'agent-state': 'bad'},
6016
'jenkins/2': {'agent-state': 'good'},
6021
self.assertEqual(status.agent_states(),
6022
status.check_agents_started('env1'))
6024
def test_check_agents_started_all_started_with_agent_state(self):
6025
status = ServiceStatus({
6027
'1': {'agent-state': 'started'},
6028
'2': {'agent-state': 'started'},
6034
'agent-state': 'started',
6037
'agent-state': 'started'
6041
'jenkins/2': {'agent-state': 'started'},
6046
self.assertIs(None, status.check_agents_started('env1'))
6048
def test_check_agents_started_all_started_with_agent_status(self):
6049
status = ServiceStatus({
6051
'1': {'agent-state': 'started'},
6052
'2': {'agent-state': 'started'},
6058
'agent-status': {'current': 'idle'},
6061
'agent-status': {'current': 'idle'}
6065
'jenkins/2': {'agent-status': {'current': 'idle'}},
6070
self.assertIs(None, status.check_agents_started('env1'))
6072
def test_check_agents_started_agent_error(self):
6073
status = ServiceStatus({
6075
'1': {'agent-state': 'any-error'},
6079
with self.assertRaisesRegexp(ErroredUnit,
6080
'1 is in state any-error'):
6081
status.check_agents_started('env1')
6083
def do_check_agents_started_failure(self, failure):
6084
status = ServiceStatus({
6086
'agent-state-info': failure}},
6089
with self.assertRaises(ErroredUnit) as e_cxt:
6090
status.check_agents_started()
6093
str(e), '0 is in state {}'.format(failure))
6094
self.assertEqual(e.unit_name, '0')
6095
self.assertEqual(e.state, failure)
6097
def test_check_agents_cannot_set_up_groups(self):
6098
self.do_check_agents_started_failure('cannot set up groups foobar')
6100
def test_check_agents_error(self):
6101
self.do_check_agents_started_failure('error executing "lxc-start"')
6103
def test_check_agents_cannot_run_instances(self):
6104
self.do_check_agents_started_failure('cannot run instances')
6106
def test_check_agents_cannot_run_instance(self):
6107
self.do_check_agents_started_failure('cannot run instance')
6109
def test_check_agents_started_agent_info_error(self):
6110
# Sometimes the error is indicated in a special 'agent-state-info'
6112
status = ServiceStatus({
6114
'1': {'agent-state-info': 'any-error'},
6118
with self.assertRaisesRegexp(ErroredUnit,
6119
'1 is in state any-error'):
6120
status.check_agents_started('env1')
6122
def test_get_agent_versions_1x(self):
6123
status = ServiceStatus({
6125
'1': {'agent-version': '1.6.2'},
6126
'2': {'agent-version': '1.6.1'},
6132
'agent-version': '1.6.1'},
6140
'1.6.1': {'jenkins/0', '2'},
6141
'unknown': {'jenkins/1'},
6142
}, status.get_agent_versions())
6144
def test_get_agent_versions_2x(self):
6145
status = ServiceStatus({
6147
'1': {'juju-status': {'version': '1.6.2'}},
6148
'2': {'juju-status': {'version': '1.6.1'}},
6154
'juju-status': {'version': '1.6.1'}},
6162
'1.6.1': {'jenkins/0', '2'},
6163
'unknown': {'jenkins/1'},
6164
}, status.get_agent_versions())
6166
def test_iter_new_machines(self):
6167
old_status = ServiceStatus({
6172
new_status = ServiceStatus({
6178
self.assertItemsEqual(new_status.iter_new_machines(old_status),
6179
[('foo', 'foo_info')])
6181
def test_get_instance_id(self):
6182
status = ServiceStatus({
6184
'0': {'instance-id': 'foo-bar'},
6188
self.assertEqual(status.get_instance_id('0'), 'foo-bar')
6189
with self.assertRaises(KeyError):
6190
status.get_instance_id('1')
6191
with self.assertRaises(KeyError):
6192
status.get_instance_id('2')
6194
def test_from_text(self):
6195
text = TestEnvJujuClient1X.make_status_yaml(
6196
'agent-state', 'pending', 'horsefeathers')
6197
status = ServiceStatus.from_text(text)
6198
self.assertEqual(status.status_text, text)
6199
self.assertEqual(status.status, {
6200
'machines': {'0': {'agent-state': 'pending'}},
5279
6201
'services': {'jenkins': {'units': {'jenkins/0': {
5280
6202
'agent-state': 'horsefeathers'}}}}