106
107
test_case.assertEqual(args, (expected_args,))
110
class FakeControllerState:
113
self.state = 'not-bootstrapped'
116
def add_model(self, name):
117
state = FakeEnvironmentState()
119
self.models[name] = state
120
state.controller = self
121
state.controller.state = 'created'
124
def require_admin(self, operation, name):
125
if name != self.admin_model.name:
126
raise AdminOperation(operation)
128
def bootstrap(self, model_name, config, separate_admin):
129
default_model = self.add_model(model_name)
130
default_model.name = model_name
132
admin_model = default_model.controller.add_model('admin')
134
admin_model = default_model
135
self.admin_model = admin_model
136
admin_model.state_servers.append(admin_model.add_machine())
137
self.state = 'bootstrapped'
138
default_model.model_config = copy.deepcopy(config)
139
self.models[default_model.name] = default_model
143
class FakeEnvironmentState:
144
"""A Fake environment state that can be used by multiple FakeClients."""
146
def __init__(self, controller=None):
148
if controller is not None:
149
self.controller = controller
151
self.controller = FakeControllerState()
155
self.machine_id_iter = count()
156
self.state_servers = []
158
self.machines = set()
163
self.machine_host_names = {}
164
self.current_bundle = None
165
self.model_config = None
169
return self.controller.state
171
def add_machine(self):
172
machine_id = str(self.machine_id_iter.next())
173
self.machines.add(machine_id)
174
self.machine_host_names[machine_id] = '{}.example.com'.format(
178
def add_ssh_machines(self, machines):
179
for machine in machines:
182
def add_container(self, container_type):
183
host = self.add_machine()
184
container_name = '{}/{}/{}'.format(host, container_type, '0')
185
self.containers[host] = {container_name}
187
def remove_container(self, container_id):
188
for containers in self.containers.values():
189
containers.discard(container_id)
191
def remove_machine(self, machine_id):
192
self.machines.remove(machine_id)
193
self.containers.pop(machine_id, None)
195
def remove_state_server(self, machine_id):
196
self.remove_machine(machine_id)
197
self.state_servers.remove(machine_id)
199
def destroy_environment(self):
201
self.controller.state = 'destroyed'
204
def kill_controller(self):
206
self.controller.state = 'controller-killed'
208
def destroy_model(self):
209
del self.controller.models[self.name]
211
self.controller.state = 'model-destroyed'
213
def restore_backup(self):
214
self.controller.require_admin('restore', self.name)
215
if len(self.state_servers) > 0:
216
exc = subprocess.CalledProcessError('Operation not permitted', 1,
218
exc.stderr = 'Operation not permitted'
222
self.controller.require_admin('enable-ha', self.name)
224
self.state_servers.append(self.add_machine())
226
def deploy(self, charm_name, service_name):
227
self.add_unit(service_name)
229
def deploy_bundle(self, bundle_path):
230
self.current_bundle = bundle_path
232
def add_unit(self, service_name):
233
machines = self.services.setdefault(service_name, set())
235
('{}/{}'.format(service_name, str(len(machines))),
238
def remove_unit(self, to_remove):
239
for units in self.services.values():
240
for unit_id, machine_id in units:
241
if unit_id == to_remove:
242
self.remove_machine(machine_id)
243
units.remove((unit_id, machine_id))
246
def destroy_service(self, service_name):
247
for unit, machine_id in self.services.pop(service_name):
248
self.remove_machine(machine_id)
250
def get_status_dict(self):
252
for machine_id in self.machines:
253
machine_dict = {'juju-status': {'current': 'idle'}}
254
hostname = self.machine_host_names.get(machine_id)
255
machine_dict['instance-id'] = machine_id
256
if hostname is not None:
257
machine_dict['dns-name'] = hostname
258
machines[machine_id] = machine_dict
259
if machine_id in self.state_servers:
260
machine_dict['controller-member-status'] = 'has-vote'
261
for host, containers in self.containers.items():
262
machines[host]['containers'] = dict((c, {}) for c in containers)
264
for service, units in self.services.items():
266
for unit_id, machine_id in units:
267
unit_map[unit_id] = {
268
'machine': machine_id,
269
'juju-status': {'current': 'idle'}}
270
services[service] = {
272
'relations': self.relations.get(service, {}),
273
'exposed': service in self.exposed,
275
return {'machines': machines, 'services': services}
279
"""A fake juju backend for tests.
281
This is a partial implementation, but should be suitable for many uses,
284
The state is provided by controller_state, so that multiple clients and
285
backends can manipulate the same state.
288
def __init__(self, controller_state, feature_flags=None, version=None,
289
full_path=None, debug=False):
290
assert isinstance(controller_state, FakeControllerState)
291
self.controller_state = controller_state
292
if feature_flags is None:
293
feature_flags = set()
294
self.feature_flags = feature_flags
295
self.version = version
296
self.full_path = full_path
298
self.juju_timings = {}
300
def clone(self, full_path=None, version=None, debug=None,
303
version = self.version
304
if full_path is None:
305
full_path = self.full_path
308
if feature_flags is None:
309
feature_flags = set(self.feature_flags)
310
controller_state = self.controller_state
311
return self.__class__(controller_state, feature_flags, version,
314
def set_feature(self, feature, enabled):
316
self.feature_flags.add(feature)
318
self.feature_flags.discard(feature)
320
def is_feature_enabled(self, feature):
323
return bool(feature in self.feature_flags)
325
def deploy(self, model_state, charm_name, service_name=None, series=None):
326
if service_name is None:
327
service_name = charm_name.split(':')[-1].split('/')[-1]
328
model_state.deploy(charm_name, service_name)
330
def bootstrap(self, args):
331
parser = ArgumentParser()
332
parser.add_argument('controller_name')
333
parser.add_argument('cloud_name_region')
334
parser.add_argument('--constraints')
335
parser.add_argument('--config')
336
parser.add_argument('--default-model')
337
parser.add_argument('--agent-version')
338
parser.add_argument('--bootstrap-series')
339
parser.add_argument('--upload-tools', action='store_true')
340
parsed = parser.parse_args(args)
341
with open(parsed.config) as config_file:
342
config = yaml.safe_load(config_file)
343
cloud_region = parsed.cloud_name_region.split('/', 1)
344
cloud = cloud_region[0]
345
# Although they are specified with specific arguments instead of as
346
# config, these values are listed by get-model-config:
347
# name, region, type (from cloud).
348
config['type'] = cloud
349
if len(cloud_region) > 1:
350
config['region'] = cloud_region[1]
351
config['name'] = parsed.default_model
352
if parsed.bootstrap_series is not None:
353
config['default-series'] = parsed.bootstrap_series
354
self.controller_state.bootstrap(parsed.default_model, config,
355
self.is_feature_enabled('jes'))
357
def quickstart(self, model_name, config, bundle):
358
default_model = self.controller_state.bootstrap(
359
model_name, config, self.is_feature_enabled('jes'))
360
default_model.deploy_bundle(bundle)
362
def destroy_environment(self, model_name):
364
state = self.controller_state.models[model_name]
367
state.destroy_environment()
370
def add_machines(self, model_state, args):
372
return model_state.add_machine()
373
ssh_machines = [a[4:] for a in args if a.startswith('ssh:')]
374
if len(ssh_machines) == len(args):
375
return model_state.add_ssh_machines(ssh_machines)
376
(container_type,) = args
377
model_state.add_container(container_type)
379
def get_admin_model_name(self):
380
return self.controller_state.admin_model.name
382
def make_controller_dict(self, controller_name):
383
admin_model = self.controller_state.admin_model
384
server_id = list(admin_model.state_servers)[0]
385
server_hostname = admin_model.machine_host_names[server_id]
386
api_endpoint = '{}:23'.format(server_hostname)
387
return {controller_name: {'details': {'api-endpoints': [
390
def list_models(self):
391
model_names = [state.name for state in
392
self.controller_state.models.values()]
393
return {'models': [{'name': n} for n in model_names]}
395
def juju(self, command, args, used_feature_flags,
396
juju_home, model=None, check=True, timeout=None, extra_env=None):
397
if model is not None:
398
model_state = self.controller_state.models[model]
399
if command == 'enable-ha':
400
model_state.enable_ha()
401
if (command, args[:1]) == ('set-config', ('dummy-source',)):
402
name, value = args[1].split('=')
404
model_state.token = value
405
if command == 'deploy':
406
parser = ArgumentParser()
407
parser.add_argument('charm_name')
408
parser.add_argument('service_name', nargs='?')
409
parser.add_argument('--to')
410
parser.add_argument('--series')
411
parsed = parser.parse_args(args)
412
self.deploy(model_state, parsed.charm_name,
413
parsed.service_name, parsed.series)
414
if command == 'destroy-service':
415
model_state.destroy_service(*args)
416
if command == 'remove-service':
417
model_state.destroy_service(*args)
418
if command == 'add-relation':
419
if args[0] == 'dummy-source':
420
model_state.relations[args[1]] = {'source': [args[0]]}
421
if command == 'expose':
423
model_state.exposed.add(service)
424
if command == 'unexpose':
426
model_state.exposed.remove(service)
427
if command == 'add-unit':
429
model_state.add_unit(service)
430
if command == 'remove-unit':
432
model_state.remove_unit(unit_id)
433
if command == 'add-machine':
434
return self.add_machines(model_state, args)
435
if command == 'remove-machine':
436
parser = ArgumentParser()
437
parser.add_argument('machine_id')
438
parser.add_argument('--force', action='store_true')
439
parsed = parser.parse_args(args)
440
machine_id = parsed.machine_id
441
if '/' in machine_id:
442
model_state.remove_container(machine_id)
444
model_state.remove_machine(machine_id)
445
if command == 'quickstart':
446
parser = ArgumentParser()
447
parser.add_argument('--constraints')
448
parser.add_argument('--no-browser', action='store_true')
449
parser.add_argument('bundle')
450
parsed = parser.parse_args(args)
451
# Released quickstart doesn't seem to provide the config via
453
self.quickstart(model, {}, parsed.bundle)
455
if command == 'bootstrap':
457
if command == 'kill-controller':
458
if self.controller_state.state == 'not-bootstrapped':
461
model_state = self.controller_state.models[model]
462
model_state.kill_controller()
463
if command == 'destroy-model':
464
if not self.is_feature_enabled('jes'):
465
raise JESNotSupported()
467
model_state = self.controller_state.models[model]
468
model_state.destroy_model()
469
if command == 'add-model':
470
if not self.is_feature_enabled('jes'):
471
raise JESNotSupported()
472
parser = ArgumentParser()
473
parser.add_argument('-c', '--controller')
474
parser.add_argument('--config')
475
parser.add_argument('model_name')
476
parsed = parser.parse_args(args)
477
self.controller_state.add_model(parsed.model_name)
480
def juju_async(self, command, args, used_feature_flags,
481
juju_home, model=None, timeout=None):
483
self.juju(command, args, used_feature_flags,
484
juju_home, model, timeout=timeout)
486
def get_juju_output(self, command, args, used_feature_flags,
487
juju_home, model=None, timeout=None):
488
if model is not None:
489
model_state = self.controller_state.models[model]
490
if (command, args) == ('ssh', ('dummy-sink/0', GET_TOKEN_SCRIPT)):
491
return model_state.token
492
if (command, args) == ('ssh', ('0', 'lsb_release', '-c')):
493
return 'Codename:\t{}\n'.format(
494
model_state.model_config['default-series'])
495
if command == 'get-model-config':
496
return yaml.safe_dump(model_state.model_config)
497
if command == 'restore-backup':
498
model_state.restore_backup()
499
if command == 'show-controller':
500
return yaml.safe_dump(self.make_controller_dict(args[0]))
501
if command == 'list-models':
502
return yaml.safe_dump(self.list_models())
503
if command == ('add-user'):
505
if set(["--acl", "write"]).issubset(args):
506
permissions = 'write'
509
code = b64encode(sha512(username).digest())
511
'User "{}" added\nUser "{}"granted {} access to model "{}\n"' \
512
.format(username, username, permissions, model)
514
'Please send this command to {}\n juju register {}' \
515
.format(username, code)
516
return info_string + register_string
517
if command == 'show-status':
518
status_dict = model_state.get_status_dict()
519
return yaml.safe_dump(status_dict)
520
if command == 'create-backup':
521
self.controller_state.require_admin('backup', model)
522
return 'juju-backup-0.tar.gz'
525
def pause(self, seconds):
529
class FakeBackendOptionalJES(FakeBackend):
531
def is_feature_enabled(self, feature):
532
return bool(feature in self.feature_flags)
535
def fake_juju_client(env=None, full_path=None, debug=False, version='2.0.0',
536
_backend=None, cls=EnvJujuClient):
538
env = JujuData('name', {
540
'default-series': 'angsty',
543
juju_home = env.juju_home
544
if juju_home is None:
547
backend_state = FakeControllerState()
548
_backend = FakeBackend(
549
backend_state, version=version, full_path=full_path,
551
_backend.set_feature('jes', True)
553
env, version, full_path, juju_home, debug, _backend=_backend)
554
client.bootstrap_replaces = {}
558
def fake_juju_client_optional_jes(env=None, full_path=None, debug=False,
559
jes_enabled=True, version='2.0.0',
562
backend_state = FakeControllerState()
563
_backend = FakeBackendOptionalJES(
564
backend_state, version=version, full_path=full_path,
566
_backend.set_feature('jes', jes_enabled)
567
client = fake_juju_client(env, full_path, debug, version, _backend,
568
cls=FakeJujuClientOptionalJES)
569
client.used_feature_flags = frozenset(['address-allocation', 'jes'])
573
class FakeJujuClientOptionalJES(EnvJujuClient):
575
def get_admin_model_name(self):
576
return self._backend.controller_state.admin_model.name
109
579
class TestErroredUnit(TestCase):
111
581
def test_output(self):
156
626
class TestJuju2Backend(TestCase):
158
test_environ = {'PATH': 'foo:bar'}
160
628
def test_juju2_backend(self):
161
629
backend = Juju2Backend('/bin/path', '2.0', set(), False)
162
630
self.assertEqual('/bin/path', backend.full_path)
163
631
self.assertEqual('2.0', backend.version)
165
def test_clone_retains_soft_deadline(self):
166
soft_deadline = object()
167
backend = Juju2Backend('/bin/path', '2.0', feature_flags=set(),
168
debug=False, soft_deadline=soft_deadline)
169
cloned = backend.clone(full_path=None, version=None, debug=None,
171
self.assertIsNot(cloned, backend)
172
self.assertIs(soft_deadline, cloned.soft_deadline)
174
def test__check_timeouts(self):
175
backend = Juju2Backend('/bin/path', '2.0', set(), debug=False,
176
soft_deadline=datetime(2015, 1, 2, 3, 4, 5))
177
with patch('jujupy.Juju2Backend._now',
178
return_value=backend.soft_deadline):
179
with backend._check_timeouts():
181
now = backend.soft_deadline + timedelta(seconds=1)
182
with patch('jujupy.Juju2Backend._now', return_value=now):
183
with self.assertRaisesRegexp(SoftDeadlineExceeded,
184
'Operation exceeded deadline.'):
185
with backend._check_timeouts():
188
def test__check_timeouts_no_deadline(self):
189
backend = Juju2Backend('/bin/path', '2.0', set(), debug=False,
191
now = datetime(2015, 1, 2, 3, 4, 6)
192
with patch('jujupy.Juju2Backend._now', return_value=now):
193
with backend._check_timeouts():
196
def test_ignore_soft_deadline_check_timeouts(self):
197
backend = Juju2Backend('/bin/path', '2.0', set(), debug=False,
198
soft_deadline=datetime(2015, 1, 2, 3, 4, 5))
199
now = backend.soft_deadline + timedelta(seconds=1)
200
with patch('jujupy.Juju2Backend._now', return_value=now):
201
with backend.ignore_soft_deadline():
202
with backend._check_timeouts():
204
with self.assertRaisesRegexp(SoftDeadlineExceeded,
205
'Operation exceeded deadline.'):
206
with backend._check_timeouts():
209
def test_juju_checks_timeouts(self):
210
backend = Juju2Backend('/bin/path', '2.0', set(), debug=False,
211
soft_deadline=datetime(2015, 1, 2, 3, 4, 5))
212
with patch('subprocess.check_call'):
213
with patch('jujupy.Juju2Backend._now',
214
return_value=backend.soft_deadline):
215
backend.juju('cmd', ('args',), [], 'home')
216
now = backend.soft_deadline + timedelta(seconds=1)
217
with patch('jujupy.Juju2Backend._now', return_value=now):
218
with self.assertRaisesRegexp(SoftDeadlineExceeded,
219
'Operation exceeded deadline.'):
220
backend.juju('cmd', ('args',), [], 'home')
222
def test_juju_async_checks_timeouts(self):
223
backend = Juju2Backend('/bin/path', '2.0', set(), debug=False,
224
soft_deadline=datetime(2015, 1, 2, 3, 4, 5))
225
with patch('subprocess.Popen') as mock_popen:
226
mock_popen.return_value.wait.return_value = 0
227
with patch('jujupy.Juju2Backend._now',
228
return_value=backend.soft_deadline):
229
with backend.juju_async('cmd', ('args',), [], 'home'):
231
now = backend.soft_deadline + timedelta(seconds=1)
232
with patch('jujupy.Juju2Backend._now', return_value=now):
233
with self.assertRaisesRegexp(SoftDeadlineExceeded,
234
'Operation exceeded deadline.'):
235
with backend.juju_async('cmd', ('args',), [], 'home'):
238
def test_get_juju_output_checks_timeouts(self):
239
backend = Juju2Backend('/bin/path', '2.0', set(), debug=False,
240
soft_deadline=datetime(2015, 1, 2, 3, 4, 5))
241
with patch('subprocess.Popen') as mock_popen:
242
mock_popen.return_value.returncode = 0
243
mock_popen.return_value.communicate.return_value = ('', '')
244
with patch('jujupy.Juju2Backend._now',
245
return_value=backend.soft_deadline):
246
backend.get_juju_output('cmd', ('args',), [], 'home')
247
now = backend.soft_deadline + timedelta(seconds=1)
248
with patch('jujupy.Juju2Backend._now', return_value=now):
249
with self.assertRaisesRegexp(SoftDeadlineExceeded,
250
'Operation exceeded deadline.'):
251
backend.get_juju_output('cmd', ('args',), [], 'home')
254
634
class TestEnvJujuClient26(ClientTest, CloudSigmaTest):
544
class TestClientFromConfig(ClientTest):
546
@patch.object(JujuData, 'from_config', return_value=JujuData('', {}))
547
@patch.object(SimpleEnvironment, 'from_config',
548
return_value=SimpleEnvironment('', {}))
549
@patch.object(EnvJujuClient, 'get_full_path', return_value='fake-path')
550
def test_from_config(self, gfp_mock, se_fc_mock, jd_fc_mock):
551
def juju_cmd_iterator():
582
context = patch.object(
583
EnvJujuClient, 'get_version',
584
side_effect=juju_cmd_iterator().send)
586
self.assertIs(EnvJujuClient1X,
587
type(client_from_config('foo', None)))
588
with self.assertRaisesRegexp(Exception, 'Unsupported juju: 1.16'):
589
client_from_config('foo', None)
590
with self.assertRaisesRegexp(Exception,
591
'Unsupported juju: 1.16.1'):
592
client_from_config('foo', None)
594
def test_fc(version, cls):
595
client = client_from_config('foo', None)
596
if isinstance(client, EnvJujuClient2A2):
597
self.assertEqual(se_fc_mock.return_value, client.env)
599
self.assertEqual(jd_fc_mock.return_value, client.env)
600
self.assertIs(cls, type(client))
601
self.assertEqual(version, client.version)
603
test_fc('1.15', EnvJujuClient1X)
604
test_fc('1.22.1', EnvJujuClient22)
605
test_fc('1.24-alpha1', EnvJujuClient24)
606
test_fc('1.24.7', EnvJujuClient24)
607
test_fc('1.25.1', EnvJujuClient25)
608
test_fc('1.26.1', EnvJujuClient26)
609
test_fc('1.27.1', EnvJujuClient1X)
610
test_fc('2.0-alpha1', EnvJujuClient2A1)
611
test_fc('2.0-alpha2', EnvJujuClient2A2)
612
test_fc('2.0-alpha3', EnvJujuClient2B2)
613
test_fc('2.0-beta1', EnvJujuClient2B2)
614
test_fc('2.0-beta2', EnvJujuClient2B2)
615
test_fc('2.0-beta3', EnvJujuClient2B3)
616
test_fc('2.0-beta4', EnvJujuClient2B3)
617
test_fc('2.0-beta5', EnvJujuClient2B3)
618
test_fc('2.0-beta6', EnvJujuClient2B3)
619
test_fc('2.0-beta7', EnvJujuClient2B7)
620
test_fc('2.0-beta8', EnvJujuClient2B8)
621
test_fc('2.0-beta9', EnvJujuClient2B9)
622
test_fc('2.0-beta10', EnvJujuClient2B9)
623
test_fc('2.0-beta11', EnvJujuClient2B9)
624
test_fc('2.0-beta12', EnvJujuClient2B9)
625
test_fc('2.0-beta13', EnvJujuClient2B9)
626
test_fc('2.0-beta14', EnvJujuClient2B9)
627
test_fc('2.0-beta15', EnvJujuClient)
628
test_fc('2.0-delta1', EnvJujuClient)
629
with self.assertRaises(StopIteration):
630
client_from_config('foo', None)
632
def test_client_from_config_path(self):
633
with patch('subprocess.check_output', return_value=' 4.3') as vsn:
634
with patch.object(JujuData, 'from_config'):
635
client = client_from_config('foo', 'foo/bar/qux')
636
vsn.assert_called_once_with(('foo/bar/qux', '--version'))
637
self.assertNotEqual(client.full_path, 'foo/bar/qux')
638
self.assertEqual(client.full_path, os.path.abspath('foo/bar/qux'))
640
def test_client_from_config_keep_home(self):
641
env = JujuData({}, juju_home='/foo/bar')
642
with patch('subprocess.check_output', return_value='2.0-alpha3-a-b'):
643
with patch.object(JujuData, 'from_config',
644
side_effect=lambda x: JujuData(x, {})):
645
client_from_config('foo', 'foo/bar/qux')
646
self.assertEqual('/foo/bar', env.juju_home)
648
def test_client_from_config_deadline(self):
649
deadline = datetime(2012, 11, 10, 9, 8, 7)
650
with patch('subprocess.check_output', return_value='2.0-alpha3-a-b'):
651
with patch.object(JujuData, 'from_config',
652
side_effect=lambda x: JujuData(x, {})):
653
client = client_from_config(
654
'foo', 'foo/bar/qux', soft_deadline=deadline)
655
self.assertEqual(client._backend.soft_deadline, deadline)
659
def client_past_deadline():
660
client = EnvJujuClient(JujuData('local', juju_home=''), None, None)
661
soft_deadline = datetime(2015, 1, 2, 3, 4, 6)
662
now = soft_deadline + timedelta(seconds=1)
663
client._backend.soft_deadline = soft_deadline
664
with patch.object(client._backend, '_now', return_value=now,
669
924
class TestEnvJujuClient(ClientTest):
671
926
def test_no_duplicate_env(self):
708
963
def test_upgrade_juju_nonlocal(self):
709
964
client = EnvJujuClient(
710
JujuData('foo', {'type': 'nonlocal'}), '2.0-betaX', None)
711
with patch.object(client, '_upgrade_juju') as juju_mock:
965
JujuData('foo', {'type': 'nonlocal'}), '1.234-76', None)
966
with patch.object(client, 'juju') as juju_mock:
712
967
client.upgrade_juju()
713
juju_mock.assert_called_with(('--agent-version', '2.0'))
968
juju_mock.assert_called_with(
969
'upgrade-juju', ('--version', '1.234'))
715
971
def test_upgrade_juju_local(self):
716
972
client = EnvJujuClient(
717
JujuData('foo', {'type': 'local'}), '2.0-betaX', None)
718
with patch.object(client, '_upgrade_juju') as juju_mock:
973
JujuData('foo', {'type': 'local'}), '1.234-76', None)
974
with patch.object(client, 'juju') as juju_mock:
719
975
client.upgrade_juju()
720
juju_mock.assert_called_with(('--agent-version', '2.0',))
976
juju_mock.assert_called_with(
977
'upgrade-juju', ('--version', '1.234', '--upload-tools',))
722
979
def test_upgrade_juju_no_force_version(self):
723
980
client = EnvJujuClient(
724
JujuData('foo', {'type': 'local'}), '2.0-betaX', None)
725
with patch.object(client, '_upgrade_juju') as juju_mock:
981
JujuData('foo', {'type': 'local'}), '1.234-76', None)
982
with patch.object(client, 'juju') as juju_mock:
726
983
client.upgrade_juju(force_version=False)
727
juju_mock.assert_called_with(())
984
juju_mock.assert_called_with(
985
'upgrade-juju', ('--upload-tools',))
987
@patch.object(EnvJujuClient, 'get_full_path', return_value='fake-path')
988
def test_by_version(self, gfp_mock):
989
def juju_cmd_iterator():
1012
context = patch.object(
1013
EnvJujuClient, 'get_version',
1014
side_effect=juju_cmd_iterator().send)
1016
self.assertIs(EnvJujuClient1X,
1017
type(EnvJujuClient.by_version(None)))
1018
with self.assertRaisesRegexp(Exception, 'Unsupported juju: 1.16'):
1019
EnvJujuClient.by_version(None)
1020
with self.assertRaisesRegexp(Exception,
1021
'Unsupported juju: 1.16.1'):
1022
EnvJujuClient.by_version(None)
1023
client = EnvJujuClient.by_version(None)
1024
self.assertIs(EnvJujuClient1X, type(client))
1025
self.assertEqual('1.15', client.version)
1026
client = EnvJujuClient.by_version(None)
1027
self.assertIs(type(client), EnvJujuClient22)
1028
client = EnvJujuClient.by_version(None)
1029
self.assertIs(type(client), EnvJujuClient24)
1030
self.assertEqual(client.version, '1.24-alpha1')
1031
client = EnvJujuClient.by_version(None)
1032
self.assertIs(type(client), EnvJujuClient24)
1033
self.assertEqual(client.version, '1.24.7')
1034
client = EnvJujuClient.by_version(None)
1035
self.assertIs(type(client), EnvJujuClient25)
1036
self.assertEqual(client.version, '1.25.1')
1037
client = EnvJujuClient.by_version(None)
1038
self.assertIs(type(client), EnvJujuClient26)
1039
self.assertEqual(client.version, '1.26.1')
1040
client = EnvJujuClient.by_version(None)
1041
self.assertIs(type(client), EnvJujuClient1X)
1042
self.assertEqual(client.version, '1.27.1')
1043
client = EnvJujuClient.by_version(None)
1044
self.assertIs(type(client), EnvJujuClient2A1)
1045
self.assertEqual(client.version, '2.0-alpha1')
1046
client = EnvJujuClient.by_version(None)
1047
self.assertIs(type(client), EnvJujuClient2A2)
1048
self.assertEqual(client.version, '2.0-alpha2')
1049
client = EnvJujuClient.by_version(None)
1050
self.assertIs(type(client), EnvJujuClient2B2)
1051
self.assertEqual(client.version, '2.0-alpha3')
1052
client = EnvJujuClient.by_version(None)
1053
self.assertIs(type(client), EnvJujuClient2B2)
1054
self.assertEqual(client.version, '2.0-beta1')
1055
client = EnvJujuClient.by_version(None)
1056
self.assertIs(type(client), EnvJujuClient2B2)
1057
self.assertEqual(client.version, '2.0-beta2')
1058
client = EnvJujuClient.by_version(None)
1059
self.assertIs(type(client), EnvJujuClient2B3)
1060
self.assertEqual(client.version, '2.0-beta3')
1061
client = EnvJujuClient.by_version(None)
1062
self.assertIs(type(client), EnvJujuClient2B3)
1063
self.assertEqual(client.version, '2.0-beta4')
1064
client = EnvJujuClient.by_version(None)
1065
self.assertIs(type(client), EnvJujuClient2B3)
1066
self.assertEqual(client.version, '2.0-beta5')
1067
client = EnvJujuClient.by_version(None)
1068
self.assertIs(type(client), EnvJujuClient2B3)
1069
self.assertEqual(client.version, '2.0-beta6')
1070
client = EnvJujuClient.by_version(None)
1071
self.assertIs(type(client), EnvJujuClient)
1072
self.assertEqual(client.version, '2.0-beta7')
1073
client = EnvJujuClient.by_version(None)
1074
self.assertIs(type(client), EnvJujuClient)
1075
self.assertEqual(client.version, '2.0-delta1')
1076
with self.assertRaises(StopIteration):
1077
EnvJujuClient.by_version(None)
1079
def test_by_version_path(self):
1080
with patch('subprocess.check_output', return_value=' 4.3') as vsn:
1081
client = EnvJujuClient.by_version(None, 'foo/bar/qux')
1082
vsn.assert_called_once_with(('foo/bar/qux', '--version'))
1083
self.assertNotEqual(client.full_path, 'foo/bar/qux')
1084
self.assertEqual(client.full_path, os.path.abspath('foo/bar/qux'))
1086
def test_by_version_keep_home(self):
1087
env = JujuData({}, juju_home='/foo/bar')
1088
with patch('subprocess.check_output', return_value='2.0-alpha3-a-b'):
1089
EnvJujuClient.by_version(env, 'foo/bar/qux')
1090
self.assertEqual('/foo/bar', env.juju_home)
729
1092
def test_clone_unchanged(self):
730
1093
client1 = EnvJujuClient(JujuData('foo'), '1.27', 'full/path',
762
1125
env = JujuData('foo')
763
1126
client = EnvJujuClient(env, None, 'my/juju/bin')
764
1127
full = client._full_args('bar', False, ('baz', 'qux'))
765
self.assertEqual(('bin', '--show-log', 'bar', '-m', 'foo:foo', 'baz',
1128
self.assertEqual(('juju', '--show-log', 'bar', '-m', 'foo', 'baz',
767
1130
full = client._full_args('bar', True, ('baz', 'qux'))
768
1131
self.assertEqual((
769
'bin', '--show-log', 'bar', '-m', 'foo:foo', 'baz', 'qux'), full)
770
full = client._full_args('bar', True, ('baz', 'qux'), controller=True)
772
('bin', '--show-log', 'bar', '-m', 'foo:controller', 'baz', 'qux'),
1132
'juju', '--show-log', 'bar', '-m', 'foo',
1133
'baz', 'qux'), full)
774
1134
client.env = None
775
1135
full = client._full_args('bar', False, ('baz', 'qux'))
776
self.assertEqual(('bin', '--show-log', 'bar', 'baz', 'qux'), full)
1136
self.assertEqual(('juju', '--show-log', 'bar', 'baz', 'qux'), full)
778
1138
def test_full_args_debug(self):
779
1139
env = JujuData('foo')
780
1140
client = EnvJujuClient(env, None, 'my/juju/bin', debug=True)
781
1141
full = client._full_args('bar', False, ('baz', 'qux'))
782
1142
self.assertEqual((
783
'bin', '--debug', 'bar', '-m', 'foo:foo', 'baz', 'qux'), full)
1143
'juju', '--debug', 'bar', '-m', 'foo', 'baz', 'qux'), full)
785
1145
def test_full_args_action(self):
786
1146
env = JujuData('foo')
787
1147
client = EnvJujuClient(env, None, 'my/juju/bin')
788
1148
full = client._full_args('action bar', False, ('baz', 'qux'))
790
('bin', '--show-log', 'action', 'bar', '-m', 'foo:foo',
1150
'juju', '--show-log', 'action', 'bar', '-m', 'foo', 'baz', 'qux'),
794
def test_full_args_controller(self):
1153
def test_full_args_admin(self):
795
1154
env = JujuData('foo')
796
1155
client = EnvJujuClient(env, None, 'my/juju/bin')
797
with patch.object(client, 'get_controller_model_name',
798
return_value='controller') as gamn_mock:
799
full = client._full_args('bar', False, ('baz', 'qux'),
1156
with patch.object(client, 'get_admin_model_name',
1157
return_value='admin') as gamn_mock:
1158
full = client._full_args('bar', False, ('baz', 'qux'), admin=True)
801
1159
self.assertEqual((
802
'bin', '--show-log', 'bar', '-m', 'foo:controller', 'baz', 'qux'),
1160
'juju', '--show-log', 'bar', '-m', 'admin', 'baz', 'qux'), full)
804
1161
gamn_mock.assert_called_once_with()
806
def test_make_model_config_prefers_agent_metadata_url(self):
807
env = JujuData('qux', {
808
'agent-metadata-url': 'foo',
809
'tools-metadata-url': 'bar',
812
client = EnvJujuClient(env, None, 'my/juju/bin')
814
'agent-metadata-url': 'foo',
816
}, client.make_model_config())
818
1163
def test__bootstrap_config(self):
819
1164
env = JujuData('foo', {
820
1165
'access-key': 'foo',
821
1166
'admin-secret': 'foo',
1167
'agent-metadata-url': 'frank',
822
1168
'agent-stream': 'foo',
823
1169
'application-id': 'foo',
824
1170
'application-password': 'foo',
1123
1393
kill_command, ('foo', '-y'), check=False, include_e=False,
1126
def do_kill_controller_azure(self, jes_command, kill_command):
1127
client = EnvJujuClient(JujuData('foo', {'type': 'azure'}), None, None)
1128
with patch.object(client, 'get_jes_command',
1129
return_value=jes_command):
1130
with patch.object(client, 'juju') as juju_mock:
1131
client.kill_controller()
1132
juju_mock.assert_called_once_with(
1133
kill_command, ('foo', '-y'), check=False, include_e=False,
1136
1396
def test_get_juju_output(self):
1137
1397
env = JujuData('foo')
1138
client = EnvJujuClient(env, None, 'juju')
1398
client = EnvJujuClient(env, None, None)
1139
1399
fake_popen = FakePopen('asdf', None, 0)
1140
1400
with patch('subprocess.Popen', return_value=fake_popen) as mock:
1141
1401
result = client.get_juju_output('bar')
1142
1402
self.assertEqual('asdf', result)
1143
self.assertEqual((('juju', '--show-log', 'bar', '-m', 'foo:foo'),),
1403
self.assertEqual((('juju', '--show-log', 'bar', '-m', 'foo'),),
1144
1404
mock.call_args[0])
1146
1406
def test_get_juju_output_accepts_varargs(self):
1147
1407
env = JujuData('foo')
1148
1408
fake_popen = FakePopen('asdf', None, 0)
1149
client = EnvJujuClient(env, None, 'juju')
1409
client = EnvJujuClient(env, None, None)
1150
1410
with patch('subprocess.Popen', return_value=fake_popen) as mock:
1151
1411
result = client.get_juju_output('bar', 'baz', '--qux')
1152
1412
self.assertEqual('asdf', result)
1153
self.assertEqual((('juju', '--show-log', 'bar', '-m', 'foo:foo', 'baz',
1413
self.assertEqual((('juju', '--show-log', 'bar', '-m', 'foo', 'baz',
1154
1414
'--qux'),), mock.call_args[0])
1156
1416
def test_get_juju_output_stderr(self):
1157
1417
env = JujuData('foo')
1158
1418
fake_popen = FakePopen(None, 'Hello!', 1)
1159
client = EnvJujuClient(env, None, 'juju')
1419
client = EnvJujuClient(env, None, None)
1160
1420
with self.assertRaises(subprocess.CalledProcessError) as exc:
1161
1421
with patch('subprocess.Popen', return_value=fake_popen):
1162
1422
client.get_juju_output('bar')
1163
1423
self.assertEqual(exc.exception.stderr, 'Hello!')
1165
def test_get_juju_output_merge_stderr(self):
1166
env = JujuData('foo')
1167
fake_popen = FakePopen('Err on out', None, 0)
1168
client = EnvJujuClient(env, None, 'juju')
1169
with patch('subprocess.Popen', return_value=fake_popen) as mock_popen:
1170
result = client.get_juju_output('bar', merge_stderr=True)
1171
self.assertEqual(result, 'Err on out')
1172
mock_popen.assert_called_once_with(
1173
('juju', '--show-log', 'bar', '-m', 'foo:foo'),
1174
stdin=subprocess.PIPE, stderr=subprocess.STDOUT,
1175
stdout=subprocess.PIPE)
1177
1425
def test_get_juju_output_full_cmd(self):
1178
1426
env = JujuData('foo')
1179
1427
fake_popen = FakePopen(None, 'Hello!', 1)
1180
client = EnvJujuClient(env, None, 'juju')
1428
client = EnvJujuClient(env, None, None)
1181
1429
with self.assertRaises(subprocess.CalledProcessError) as exc:
1182
1430
with patch('subprocess.Popen', return_value=fake_popen):
1183
1431
client.get_juju_output('bar', '--baz', 'qux')
1184
1432
self.assertEqual(
1185
('juju', '--show-log', 'bar', '-m', 'foo:foo', '--baz', 'qux'),
1433
('juju', '--show-log', 'bar', '-m', 'foo', '--baz', 'qux'),
1186
1434
exc.exception.cmd)
1188
1436
def test_get_juju_output_accepts_timeout(self):
1189
1437
env = JujuData('foo')
1190
1438
fake_popen = FakePopen('asdf', None, 0)
1191
client = EnvJujuClient(env, None, 'juju')
1439
client = EnvJujuClient(env, None, None)
1192
1440
with patch('subprocess.Popen', return_value=fake_popen) as po_mock:
1193
1441
client.get_juju_output('bar', timeout=5)
1194
1442
self.assertEqual(
1195
1443
po_mock.call_args[0][0],
1196
1444
(sys.executable, get_timeout_path(), '5.00', '--', 'juju',
1197
'--show-log', 'bar', '-m', 'foo:foo'))
1445
'--show-log', 'bar', '-m', 'foo'))
1199
1447
def test__shell_environ_juju_data(self):
1200
1448
client = EnvJujuClient(
1489
1699
self.assertEqual(ut_mock.mock_calls,
1490
1700
[call(60), call(30, start=70), call(60), call(60)])
1492
def test_status_until_suppresses_deadline(self):
1493
with self.only_status_checks() as client:
1494
list(client.status_until(0))
1496
def test_status_until_checks_deadline(self):
1497
with self.status_does_not_check() as client:
1498
with self.assertRaises(SoftDeadlineExceeded):
1499
list(client.status_until(0))
1501
1702
def test_add_ssh_machines(self):
1502
client = EnvJujuClient(JujuData('foo'), None, 'juju')
1703
client = EnvJujuClient(JujuData('foo'), None, '')
1503
1704
with patch('subprocess.check_call', autospec=True) as cc_mock:
1504
1705
client.add_ssh_machines(['m-foo', 'm-bar', 'm-baz'])
1509
('juju', '--show-log', 'add-machine',
1510
'-m', 'foo:foo', 'ssh:m-foo'),
1516
('juju', '--show-log', 'add-machine',
1517
'-m', 'foo:foo', 'ssh:m-bar'),
1523
('juju', '--show-log', 'add-machine',
1524
'-m', 'foo:foo', 'ssh:m-baz'),
1706
assert_juju_call(self, cc_mock, client, (
1707
'juju', '--show-log', 'add-machine', '-m', 'foo', 'ssh:m-foo'), 0)
1708
assert_juju_call(self, cc_mock, client, (
1709
'juju', '--show-log', 'add-machine', '-m', 'foo', 'ssh:m-bar'), 1)
1710
assert_juju_call(self, cc_mock, client, (
1711
'juju', '--show-log', 'add-machine', '-m', 'foo', 'ssh:m-baz'), 2)
1526
1712
self.assertEqual(cc_mock.call_count, 3)
1528
1714
def test_add_ssh_machines_retry(self):
1529
client = EnvJujuClient(JujuData('foo'), None, 'juju')
1715
client = EnvJujuClient(JujuData('foo'), None, '')
1530
1716
with patch('subprocess.check_call', autospec=True,
1531
1717
side_effect=[subprocess.CalledProcessError(None, None),
1532
1718
None, None, None]) as cc_mock:
1533
1719
client.add_ssh_machines(['m-foo', 'm-bar', 'm-baz'])
1538
('juju', '--show-log', 'add-machine',
1539
'-m', 'foo:foo', 'ssh:m-foo'),
1720
assert_juju_call(self, cc_mock, client, (
1721
'juju', '--show-log', 'add-machine', '-m', 'foo', 'ssh:m-foo'), 0)
1541
1722
self.pause_mock.assert_called_once_with(30)
1546
('juju', '--show-log', 'add-machine',
1547
'-m', 'foo:foo', 'ssh:m-foo'),
1553
('juju', '--show-log', 'add-machine',
1554
'-m', 'foo:foo', 'ssh:m-bar'),
1560
('juju', '--show-log', 'add-machine',
1561
'-m', 'foo:foo', 'ssh:m-baz'),
1723
assert_juju_call(self, cc_mock, client, (
1724
'juju', '--show-log', 'add-machine', '-m', 'foo', 'ssh:m-foo'), 1)
1725
assert_juju_call(self, cc_mock, client, (
1726
'juju', '--show-log', 'add-machine', '-m', 'foo', 'ssh:m-bar'), 2)
1727
assert_juju_call(self, cc_mock, client, (
1728
'juju', '--show-log', 'add-machine', '-m', 'foo', 'ssh:m-baz'), 3)
1563
1729
self.assertEqual(cc_mock.call_count, 4)
1565
1731
def test_add_ssh_machines_fail_on_second_machine(self):
1566
client = EnvJujuClient(JujuData('foo'), None, 'juju')
1732
client = EnvJujuClient(JujuData('foo'), None, '')
1567
1733
with patch('subprocess.check_call', autospec=True, side_effect=[
1568
1734
None, subprocess.CalledProcessError(None, None), None, None
1570
1736
with self.assertRaises(subprocess.CalledProcessError):
1571
1737
client.add_ssh_machines(['m-foo', 'm-bar', 'm-baz'])
1576
('juju', '--show-log', 'add-machine',
1577
'-m', 'foo:foo', 'ssh:m-foo'),
1583
('juju', '--show-log', 'add-machine',
1584
'-m', 'foo:foo', 'ssh:m-bar'),
1738
assert_juju_call(self, cc_mock, client, (
1739
'juju', '--show-log', 'add-machine', '-m', 'foo', 'ssh:m-foo'), 0)
1740
assert_juju_call(self, cc_mock, client, (
1741
'juju', '--show-log', 'add-machine', '-m', 'foo', 'ssh:m-bar'), 1)
1586
1742
self.assertEqual(cc_mock.call_count, 2)
1588
1744
def test_add_ssh_machines_fail_on_second_attempt(self):
1589
client = EnvJujuClient(JujuData('foo'), None, 'juju')
1745
client = EnvJujuClient(JujuData('foo'), None, '')
1590
1746
with patch('subprocess.check_call', autospec=True, side_effect=[
1591
1747
subprocess.CalledProcessError(None, None),
1592
1748
subprocess.CalledProcessError(None, None)]) as cc_mock:
1593
1749
with self.assertRaises(subprocess.CalledProcessError):
1594
1750
client.add_ssh_machines(['m-foo', 'm-bar', 'm-baz'])
1599
('juju', '--show-log', 'add-machine',
1600
'-m', 'foo:foo', 'ssh:m-foo'),
1606
('juju', '--show-log', 'add-machine',
1607
'-m', 'foo:foo', 'ssh:m-foo'),
1751
assert_juju_call(self, cc_mock, client, (
1752
'juju', '--show-log', 'add-machine', '-m', 'foo', 'ssh:m-foo'), 0)
1753
assert_juju_call(self, cc_mock, client, (
1754
'juju', '--show-log', 'add-machine', '-m', 'foo', 'ssh:m-foo'), 1)
1609
1755
self.assertEqual(cc_mock.call_count, 2)
1611
1757
def test_wait_for_started(self):
2469
2412
def test_get_model_config(self):
2470
2413
env = JujuData('foo', None)
2471
2414
fake_popen = FakePopen(yaml.safe_dump({'bar': 'baz'}), None, 0)
2472
client = EnvJujuClient(env, None, 'juju')
2415
client = EnvJujuClient(env, None, None)
2473
2416
with patch('subprocess.Popen', return_value=fake_popen) as po_mock:
2474
2417
result = client.get_model_config()
2475
2418
assert_juju_call(
2476
2419
self, po_mock, client, (
2477
'juju', '--show-log',
2478
'model-config', '-m', 'foo:foo', '--format', 'yaml'))
2420
'juju', '--show-log', 'get-model-config', '-m', 'foo'))
2479
2421
self.assertEqual({'bar': 'baz'}, result)
2481
2423
def test_get_env_option(self):
2482
2424
env = JujuData('foo', None)
2483
2425
fake_popen = FakePopen('https://example.org/juju/tools', None, 0)
2484
client = EnvJujuClient(env, None, 'juju')
2426
client = EnvJujuClient(env, None, None)
2485
2427
with patch('subprocess.Popen', return_value=fake_popen) as mock:
2486
2428
result = client.get_env_option('tools-metadata-url')
2487
2429
self.assertEqual(
2488
2430
mock.call_args[0][0],
2489
('juju', '--show-log', 'model-config', '-m', 'foo:foo',
2431
('juju', '--show-log', 'get-model-config', '-m', 'foo',
2490
2432
'tools-metadata-url'))
2491
2433
self.assertEqual('https://example.org/juju/tools', result)
2493
2435
def test_set_env_option(self):
2494
2436
env = JujuData('foo')
2495
client = EnvJujuClient(env, None, 'juju')
2437
client = EnvJujuClient(env, None, None)
2496
2438
with patch('subprocess.check_call') as mock:
2497
2439
client.set_env_option(
2498
2440
'tools-metadata-url', 'https://example.org/juju/tools')
2499
2441
environ = dict(os.environ)
2500
2442
environ['JUJU_HOME'] = client.env.juju_home
2501
2443
mock.assert_called_with(
2502
('juju', '--show-log', 'model-config', '-m', 'foo:foo',
2444
('juju', '--show-log', 'set-model-config', '-m', 'foo',
2503
2445
'tools-metadata-url=https://example.org/juju/tools'))
2505
def test_unset_env_option(self):
2506
env = JujuData('foo')
2507
client = EnvJujuClient(env, None, 'juju')
2508
with patch('subprocess.check_call') as mock:
2509
client.unset_env_option('tools-metadata-url')
2510
environ = dict(os.environ)
2511
environ['JUJU_HOME'] = client.env.juju_home
2512
mock.assert_called_with(
2513
('juju', '--show-log', 'model-config', '-m', 'foo:foo',
2514
'--reset', 'tools-metadata-url'))
2516
def test_set_testing_agent_metadata_url(self):
2447
def test_set_testing_tools_metadata_url(self):
2517
2448
env = JujuData(None, {'type': 'foo'})
2518
2449
client = EnvJujuClient(env, None, None)
2519
2450
with patch.object(client, 'get_env_option') as mock_get:
2520
2451
mock_get.return_value = 'https://example.org/juju/tools'
2521
2452
with patch.object(client, 'set_env_option') as mock_set:
2522
client.set_testing_agent_metadata_url()
2523
mock_get.assert_called_with('agent-metadata-url')
2453
client.set_testing_tools_metadata_url()
2454
mock_get.assert_called_with('tools-metadata-url')
2524
2455
mock_set.assert_called_with(
2525
'agent-metadata-url',
2456
'tools-metadata-url',
2526
2457
'https://example.org/juju/testing/tools')
2528
def test_set_testing_agent_metadata_url_noop(self):
2459
def test_set_testing_tools_metadata_url_noop(self):
2529
2460
env = JujuData(None, {'type': 'foo'})
2530
2461
client = EnvJujuClient(env, None, None)
2531
2462
with patch.object(client, 'get_env_option') as mock_get:
2532
2463
mock_get.return_value = 'https://example.org/juju/testing/tools'
2533
2464
with patch.object(client, 'set_env_option') as mock_set:
2534
client.set_testing_agent_metadata_url()
2535
mock_get.assert_called_with('agent-metadata-url',)
2465
client.set_testing_tools_metadata_url()
2466
mock_get.assert_called_with('tools-metadata-url')
2536
2467
self.assertEqual(0, mock_set.call_count)
2538
2469
def test_juju(self):
2539
2470
env = JujuData('qux')
2540
client = EnvJujuClient(env, None, 'juju')
2471
client = EnvJujuClient(env, None, None)
2541
2472
with patch('subprocess.check_call') as mock:
2542
2473
client.juju('foo', ('bar', 'baz'))
2543
2474
environ = dict(os.environ)
2544
2475
environ['JUJU_HOME'] = client.env.juju_home
2545
mock.assert_called_with(('juju', '--show-log', 'foo', '-m', 'qux:qux',
2476
mock.assert_called_with(('juju', '--show-log', 'foo', '-m', 'qux',
2548
2479
def test_expect_returns_pexpect_spawn_object(self):
2549
2480
env = JujuData('qux')
2550
client = EnvJujuClient(env, None, 'juju')
2481
client = EnvJujuClient(env, None, None)
2551
2482
with patch('pexpect.spawn') as mock:
2552
2483
process = client.expect('foo', ('bar', 'baz'))
2554
2485
self.assertIs(process, mock.return_value)
2555
mock.assert_called_once_with('juju --show-log foo -m qux:qux bar baz')
2486
mock.assert_called_once_with('juju --show-log foo -m qux bar baz')
2557
2488
def test_expect_uses_provided_envvar_path(self):
2558
2489
from pexpect import ExceptionPexpect
2559
2490
env = JujuData('qux')
2560
client = EnvJujuClient(env, None, 'juju')
2491
client = EnvJujuClient(env, None, None)
2562
2493
with temp_dir() as empty_path:
2563
2494
broken_envvars = dict(PATH=empty_path)
3066
2972
fake_client.revoke(username)
3067
2973
fake_client.juju.assert_called_with('revoke',
3068
2974
('-c', default_controller,
3069
username, default_permissions,
2975
username, default_model,
2976
'--acl', default_permissions),
3071
2977
include_e=False)
3073
2979
fake_client.revoke(username, model)
3074
2980
fake_client.juju.assert_called_with('revoke',
3075
2981
('-c', default_controller,
3076
username, default_permissions,
2983
'--acl', default_permissions),
3078
2984
include_e=False)
3080
2986
fake_client.revoke(username, model, permissions='write')
3081
2987
fake_client.juju.assert_called_with('revoke',
3082
2988
('-c', default_controller,
3083
username, 'write', model),
3084
2991
include_e=False)
3086
def test_add_user_perms(self):
3087
fake_client = fake_juju_client()
3088
username = 'fakeuser'
3090
# Ensure add_user returns expected value.
3092
fake_client.add_user_perms(username),
3093
get_user_register_token(username))
3096
def assert_add_user_perms(model, permissions):
3097
fake_client = fake_juju_client()
3098
username = 'fakeuser'
3099
output = get_user_register_command_info(username)
3100
if permissions is None:
3101
permissions = 'login'
3102
expected_args = [username, '-c', fake_client.env.controller.name]
3103
with patch.object(fake_client, 'get_juju_output',
3104
return_value=output) as get_output:
3105
with patch.object(fake_client, 'juju') as mock_juju:
3106
fake_client.add_user_perms(username, model, permissions)
3108
model = fake_client.env.environment
3109
get_output.assert_called_with(
3110
'add-user', *expected_args, include_e=False)
3111
if permissions == 'login':
3112
mock_juju.assert_called_once_with(
3114
('fakeuser', permissions,
3115
'-c', fake_client.env.controller.name),
3118
mock_juju.assert_called_once_with(
3120
('fakeuser', permissions,
3122
'-c', fake_client.env.controller.name),
3125
def test_assert_add_user_permissions(self):
3127
permissions = 'write'
3129
# Check using default model and permissions
3130
self.assert_add_user_perms(None, None)
3132
# Check explicit model & default permissions
3133
self.assert_add_user_perms(model, None)
3135
# Check explicit model & permissions
3136
self.assert_add_user_perms(model, permissions)
3138
# Check default model & explicit permissions
3139
self.assert_add_user_perms(None, permissions)
3141
def test_disable_user(self):
3142
env = JujuData('foo')
3143
username = 'fakeuser'
3144
client = EnvJujuClient(env, None, None)
3145
with patch.object(client, 'juju') as mock:
3146
client.disable_user(username)
3147
mock.assert_called_with(
3148
'disable-user', ('-c', 'foo', 'fakeuser'), include_e=False)
3150
def test_enable_user(self):
3151
env = JujuData('foo')
3152
username = 'fakeuser'
3153
client = EnvJujuClient(env, None, None)
3154
with patch.object(client, 'juju') as mock:
3155
client.enable_user(username)
3156
mock.assert_called_with(
3157
'enable-user', ('-c', 'foo', 'fakeuser'), include_e=False)
3159
def test_logout(self):
3160
env = JujuData('foo')
3161
client = EnvJujuClient(env, None, None)
3162
with patch.object(client, 'juju') as mock:
3164
mock.assert_called_with(
3165
'logout', ('-c', 'foo'), include_e=False)
3167
def test_create_cloned_environment(self):
3168
fake_client = fake_juju_client()
3169
fake_client.bootstrap()
3170
# fake_client_environ = fake_client._shell_environ()
3171
controller_name = 'user_controller'
3172
cloned = fake_client.create_cloned_environment(
3176
self.assertIs(fake_client.__class__, type(cloned))
3177
self.assertEqual(cloned.env.juju_home, 'fakehome')
3178
self.assertEqual(cloned.env.controller.name, controller_name)
3179
self.assertEqual(fake_client.env.controller.name, 'name')
3181
def test_list_clouds(self):
3182
env = JujuData('foo')
3183
client = EnvJujuClient(env, None, None)
3184
with patch.object(client, 'get_juju_output') as mock:
3185
client.list_clouds()
3186
mock.assert_called_with(
3187
'list-clouds', '--format', 'json', include_e=False)
3189
def test_show_controller(self):
3190
env = JujuData('foo')
3191
client = EnvJujuClient(env, None, None)
3192
with patch.object(client, 'get_juju_output') as mock:
3193
client.show_controller()
3194
mock.assert_called_with(
3195
'show-controller', '--format', 'json', include_e=False)
3197
def test_ssh_keys(self):
3198
client = EnvJujuClient(JujuData('foo'), None, None)
3199
given_output = 'ssh keys output'
3200
with patch.object(client, 'get_juju_output', autospec=True,
3201
return_value=given_output) as mock:
3202
output = client.ssh_keys()
3203
self.assertEqual(output, given_output)
3204
mock.assert_called_once_with('ssh-keys')
3206
def test_ssh_keys_full(self):
3207
client = EnvJujuClient(JujuData('foo'), None, None)
3208
given_output = 'ssh keys full output'
3209
with patch.object(client, 'get_juju_output', autospec=True,
3210
return_value=given_output) as mock:
3211
output = client.ssh_keys(full=True)
3212
self.assertEqual(output, given_output)
3213
mock.assert_called_once_with('ssh-keys', '--full')
3215
def test_add_ssh_key(self):
3216
client = EnvJujuClient(JujuData('foo'), None, None)
3217
with patch.object(client, 'get_juju_output', autospec=True,
3218
return_value='') as mock:
3219
output = client.add_ssh_key('ak', 'bk')
3220
self.assertEqual(output, '')
3221
mock.assert_called_once_with(
3222
'add-ssh-key', 'ak', 'bk', merge_stderr=True)
3224
def test_remove_ssh_key(self):
3225
client = EnvJujuClient(JujuData('foo'), None, None)
3226
with patch.object(client, 'get_juju_output', autospec=True,
3227
return_value='') as mock:
3228
output = client.remove_ssh_key('ak', 'bk')
3229
self.assertEqual(output, '')
3230
mock.assert_called_once_with(
3231
'remove-ssh-key', 'ak', 'bk', merge_stderr=True)
3233
def test_import_ssh_key(self):
3234
client = EnvJujuClient(JujuData('foo'), None, None)
3235
with patch.object(client, 'get_juju_output', autospec=True,
3236
return_value='') as mock:
3237
output = client.import_ssh_key('gh:au', 'lp:bu')
3238
self.assertEqual(output, '')
3239
mock.assert_called_once_with(
3240
'import-ssh-key', 'gh:au', 'lp:bu', merge_stderr=True)
3242
def test_list_disabled_commands(self):
3243
client = EnvJujuClient(JujuData('foo'), None, None)
3244
with patch.object(client, 'get_juju_output', autospec=True,
3245
return_value=dedent("""\
3246
- command-set: destroy-model
3247
message: Lock Models
3248
- command-set: remove-object""")) as mock:
3249
output = client.list_disabled_commands()
3250
self.assertEqual([{'command-set': 'destroy-model',
3251
'message': 'Lock Models'},
3252
{'command-set': 'remove-object'}], output)
3253
mock.assert_called_once_with('list-disabled-commands',
3256
def test_disable_command(self):
3257
client = EnvJujuClient(JujuData('foo'), None, None)
3258
with patch.object(client, 'juju', autospec=True) as mock:
3259
client.disable_command(('all', 'message'))
3260
mock.assert_called_once_with('disable-command', ('all', 'message'))
3262
def test_enable_command(self):
3263
client = EnvJujuClient(JujuData('foo'), None, None)
3264
with patch.object(client, 'juju', autospec=True) as mock:
3265
client.enable_command('all')
3266
mock.assert_called_once_with('enable-command', 'all')
3268
def test_sync_tools(self):
3269
client = EnvJujuClient(JujuData('foo'), None, None)
3270
with patch.object(client, 'juju', autospec=True) as mock:
3272
mock.assert_called_once_with('sync-tools', ())
3274
def test_sync_tools_local_dir(self):
3275
client = EnvJujuClient(JujuData('foo'), None, None)
3276
with patch.object(client, 'juju', autospec=True) as mock:
3277
client.sync_tools('/agents')
3278
mock.assert_called_once_with('sync-tools', ('--local-dir', '/agents'),
3282
class TestEnvJujuClient2B8(ClientTest):
3284
def test_remove_service(self):
3285
env = EnvJujuClient2B7(
3286
JujuData('foo', {'type': 'local'}), '1.234-76', None)
3287
with patch.object(env, 'juju') as mock_juju:
3288
env.remove_service('mondogb')
3289
mock_juju.assert_called_with('remove-service', ('mondogb',))
3291
def test_deployer(self):
3292
client = EnvJujuClient2B8(JujuData('foo', {'type': 'local'}),
3293
'1.23-series-arch', None)
3294
with patch.object(EnvJujuClient, 'juju') as mock:
3295
client.deployer('bundle:~juju-qa/some-bundle')
3296
mock.assert_called_with(
3297
'deployer', ('-e', 'local.foo:foo', '--debug', '--deploy-delay',
3298
'10', '--timeout', '3600', '--config',
3299
'bundle:~juju-qa/some-bundle'),
3300
True, include_e=False
3303
def test_deployer_with_bundle_name(self):
3304
client = EnvJujuClient2B8(JujuData('foo', {'type': 'local'}),
3305
'2.0.0-series-arch', None)
3306
with patch.object(EnvJujuClient, 'juju') as mock:
3307
client.deployer('bundle:~juju-qa/some-bundle', 'name')
3308
mock.assert_called_with(
3309
'deployer', ('-e', 'local.foo:foo', '--debug', '--deploy-delay',
3310
'10', '--timeout', '3600', '--config',
3311
'bundle:~juju-qa/some-bundle', 'name'),
3312
True, include_e=False
3316
class TestEnvJujuClient2B9(ClientTest):
3318
def test_get_model_uuid_returns_uuid(self):
3319
model_uuid = '9ed1bde9-45c6-4d41-851d-33fdba7fa194'
3320
yaml_string = dedent("""\
3324
controller-uuid: eb67e1eb-6c54-45f5-8b6a-b6243be97202
3337
last-connection: just now
3338
""".format(uuid=model_uuid))
3339
client = EnvJujuClient2B9(JujuData('foo'), None, None)
3340
with patch.object(client, 'get_juju_output') as m_get_juju_output:
3341
m_get_juju_output.return_value = yaml_string
3343
client.get_model_uuid(),
3346
m_get_juju_output.assert_called_once_with(
3347
'show-model', '--format', 'yaml')
3349
def test_add_user_perms(self):
3350
fake_client = fake_juju_client(cls=EnvJujuClient2B9)
3351
username = 'fakeuser'
3353
permissions = 'write'
3354
output = get_user_register_command_info(username)
3356
def _get_expected_args(model=None, permissions=None):
3359
'--models', model or fake_client.env.environment,
3360
'--acl', permissions or 'read',
3361
'-c', fake_client.env.controller.name]
3363
# Ensure add_user_perms returns expected value.
3365
fake_client.add_user_perms(username),
3366
get_user_register_token(username))
3368
with patch.object(fake_client, 'get_juju_output',
3369
return_value=output) as get_output:
3370
# Check using default model and permissions
3371
fake_client.add_user_perms(username)
3372
expected_args = _get_expected_args()
3373
get_output.assert_called_with(
3374
'add-user', *expected_args, include_e=False)
3376
# Check explicit model & default permissions
3377
fake_client.add_user_perms(username, model)
3378
expected_args = _get_expected_args(model)
3379
get_output.assert_called_with(
3380
'add-user', *expected_args, include_e=False)
3382
# Check explicit model & permissions
3383
fake_client.add_user_perms(username, model, permissions)
3384
expected_args = _get_expected_args(model, permissions)
3385
get_output.assert_called_with(
3386
'add-user', *expected_args, include_e=False)
3388
# Check default model & explicit permissions
3389
fake_client.add_user_perms(username, permissions=permissions)
3390
expected_args = _get_expected_args(permissions=permissions)
3391
get_output.assert_called_with(
3392
'add-user', *expected_args, include_e=False)
3394
def test_set_config(self):
3395
client = EnvJujuClient2B9(JujuData('bar', {}), None, '/foo')
3396
with patch.object(client, 'juju') as juju_mock:
3397
client.set_config('foo', {'bar': 'baz'})
3398
juju_mock.assert_called_once_with('set-config', ('foo', 'bar=baz'))
3400
def test_get_config(self):
3401
def output(*args, **kwargs):
3402
return yaml.safe_dump({
3408
'description': 'bla bla',
3410
'value': '/tmp/charm-dir',
3414
expected = yaml.safe_load(output())
3415
client = EnvJujuClient2B9(JujuData('bar', {}), None, '/foo')
3416
with patch.object(client, 'get_juju_output',
3417
side_effect=output) as gjo_mock:
3418
results = client.get_config('foo')
3419
self.assertEqual(expected, results)
3420
gjo_mock.assert_called_once_with('get-config', 'foo')
3422
def test_get_model_config(self):
3423
env = JujuData('foo', None)
3424
fake_popen = FakePopen(yaml.safe_dump({'bar': 'baz'}), None, 0)
3425
client = EnvJujuClient2B9(env, None, 'juju')
3426
with patch('subprocess.Popen', return_value=fake_popen) as po_mock:
3427
result = client.get_model_config()
3429
self, po_mock, client, (
3430
'juju', '--show-log',
3433
'--format', 'yaml'))
3434
self.assertEqual({'bar': 'baz'}, result)
3436
def test_get_env_option(self):
3437
env = JujuData('foo', None)
3438
fake_popen = FakePopen('https://example.org/juju/tools', None, 0)
3439
client = EnvJujuClient2B9(env, None, 'juju')
3440
with patch('subprocess.Popen', return_value=fake_popen) as mock:
3441
result = client.get_env_option('tools-metadata-url')
3443
mock.call_args[0][0],
3444
('juju', '--show-log', 'get-model-config', '-m', 'foo:foo',
3445
'tools-metadata-url'))
3446
self.assertEqual('https://example.org/juju/tools', result)
3448
def test_set_env_option(self):
3449
env = JujuData('foo')
3450
client = EnvJujuClient2B9(env, None, 'juju')
3451
with patch('subprocess.check_call') as mock:
3452
client.set_env_option(
3453
'tools-metadata-url', 'https://example.org/juju/tools')
3454
environ = dict(os.environ)
3455
environ['JUJU_HOME'] = client.env.juju_home
3456
mock.assert_called_with(
3457
('juju', '--show-log', 'set-model-config', '-m', 'foo:foo',
3458
'tools-metadata-url=https://example.org/juju/tools'))
3460
def test_unset_env_option(self):
3461
env = JujuData('foo')
3462
client = EnvJujuClient2B9(env, None, 'juju')
3463
with patch('subprocess.check_call') as mock:
3464
client.unset_env_option('tools-metadata-url')
3465
environ = dict(os.environ)
3466
environ['JUJU_HOME'] = client.env.juju_home
3467
mock.assert_called_with(
3468
('juju', '--show-log', 'unset-model-config', '-m', 'foo:foo',
3469
'tools-metadata-url'))
3471
def test_list_disabled_commands(self):
3472
client = EnvJujuClient2B9(JujuData('foo'), None, None)
3473
with patch.object(client, 'get_juju_output', autospec=True,
3474
return_value=dedent("""\
3475
- command-set: destroy-model
3476
message: Lock Models
3477
- command-set: remove-object""")) as mock:
3478
output = client.list_disabled_commands()
3479
self.assertEqual([{'command-set': 'destroy-model',
3480
'message': 'Lock Models'},
3481
{'command-set': 'remove-object'}], output)
3482
mock.assert_called_once_with('block list',
3485
def test_disable_command(self):
3486
client = EnvJujuClient2B9(JujuData('foo'), None, None)
3487
with patch.object(client, 'juju', autospec=True) as mock:
3488
client.disable_command(('all', 'message'))
3489
mock.assert_called_once_with('block', ('all', 'message'))
3491
def test_enable_command(self):
3492
client = EnvJujuClient2B9(JujuData('foo'), None, None)
3493
with patch.object(client, 'juju', autospec=True) as mock:
3494
client.enable_command('all')
3495
mock.assert_called_once_with('unblock', 'all')
3498
class TestEnvJujuClient2B7(ClientTest):
3500
def test_get_controller_model_name(self):
3503
{'name': 'admin', 'model-uuid': 'aaaa'},
3504
{'name': 'bar', 'model-uuid': 'bbbb'}],
3505
'current-model': 'bar'
3507
client = EnvJujuClient2B7(JujuData('foo'), None, None)
3508
with patch.object(client, 'get_models',
3509
return_value=models) as gm_mock:
3510
controller_name = client.get_controller_model_name()
3511
self.assertEqual(0, gm_mock.call_count)
3512
self.assertEqual('admin', controller_name)
3514
def test_get_controller_model_name_without_controller(self):
3517
{'name': 'bar', 'model-uuid': 'aaaa'},
3518
{'name': 'baz', 'model-uuid': 'bbbb'}],
3519
'current-model': 'bar'
3521
client = EnvJujuClient2B7(JujuData('foo'), None, None)
3522
with patch.object(client, 'get_models', return_value=models):
3523
controller_name = client.get_controller_model_name()
3524
self.assertEqual('admin', controller_name)
3526
def test_get_controller_model_name_no_models(self):
3527
client = EnvJujuClient2B7(JujuData('foo'), None, None)
3528
with patch.object(client, 'get_models', return_value={}):
3529
controller_name = client.get_controller_model_name()
3530
self.assertEqual('admin', controller_name)
3532
def test_get_controller_client(self):
3533
client = EnvJujuClient2B7(
3534
JujuData('foo', {'bar': 'baz'}, 'myhome'), None, None)
3535
controller_client = client.get_controller_client()
3536
controller_env = controller_client.env
3537
self.assertEqual('admin', controller_env.environment)
3538
self.assertEqual({'bar': 'baz', 'name': 'admin'},
3539
controller_env.config)
2993
def test_add_user(self):
2994
fake_client = fake_juju_client()
2995
username = 'fakeuser'
2997
permissions = 'write'
2999
output = fake_client.add_user(username)
3000
self.assertTrue(output.startswith('juju register'))
3002
output = fake_client.add_user(username, model)
3003
self.assertTrue(output.startswith('juju register'))
3005
output = fake_client.add_user(username, model, permissions)
3006
self.assertTrue(output.startswith('juju register'))
3542
3009
class TestEnvJujuClient2B3(ClientTest):
3798
3249
with self.assertRaises(UpgradeMongoNotSupported):
3799
3250
client.upgrade_mongo()
3252
@patch.object(EnvJujuClient1X, 'get_full_path', return_value='fake-path')
3253
def test_by_version(self, gfp_mock):
3254
def juju_cmd_iterator():
3277
context = patch.object(
3278
EnvJujuClient1X, 'get_version',
3279
side_effect=juju_cmd_iterator().send)
3281
self.assertIs(EnvJujuClient1X,
3282
type(EnvJujuClient1X.by_version(None)))
3283
with self.assertRaisesRegexp(Exception, 'Unsupported juju: 1.16'):
3284
EnvJujuClient1X.by_version(None)
3285
with self.assertRaisesRegexp(Exception,
3286
'Unsupported juju: 1.16.1'):
3287
EnvJujuClient1X.by_version(None)
3288
client = EnvJujuClient1X.by_version(None)
3289
self.assertIs(EnvJujuClient1X, type(client))
3290
self.assertEqual('1.15', client.version)
3291
client = EnvJujuClient1X.by_version(None)
3292
self.assertIs(type(client), EnvJujuClient22)
3293
client = EnvJujuClient1X.by_version(None)
3294
self.assertIs(type(client), EnvJujuClient24)
3295
self.assertEqual(client.version, '1.24-alpha1')
3296
client = EnvJujuClient1X.by_version(None)
3297
self.assertIs(type(client), EnvJujuClient24)
3298
self.assertEqual(client.version, '1.24.7')
3299
client = EnvJujuClient1X.by_version(None)
3300
self.assertIs(type(client), EnvJujuClient25)
3301
self.assertEqual(client.version, '1.25.1')
3302
client = EnvJujuClient1X.by_version(None)
3303
self.assertIs(type(client), EnvJujuClient26)
3304
self.assertEqual(client.version, '1.26.1')
3305
client = EnvJujuClient1X.by_version(None)
3306
self.assertIs(type(client), EnvJujuClient1X)
3307
self.assertEqual(client.version, '1.27.1')
3308
client = EnvJujuClient1X.by_version(None)
3309
self.assertIs(type(client), EnvJujuClient2A1)
3310
self.assertEqual(client.version, '2.0-alpha1')
3311
client = EnvJujuClient1X.by_version(None)
3312
self.assertIs(type(client), EnvJujuClient2A2)
3313
self.assertEqual(client.version, '2.0-alpha2')
3314
client = EnvJujuClient1X.by_version(None)
3315
self.assertIs(type(client), EnvJujuClient2B2)
3316
self.assertEqual(client.version, '2.0-alpha3')
3317
client = EnvJujuClient1X.by_version(None)
3318
self.assertIs(type(client), EnvJujuClient2B2)
3319
self.assertEqual(client.version, '2.0-beta1')
3320
client = EnvJujuClient1X.by_version(None)
3321
self.assertIs(type(client), EnvJujuClient2B2)
3322
self.assertEqual(client.version, '2.0-beta2')
3323
client = EnvJujuClient1X.by_version(None)
3324
self.assertIs(type(client), EnvJujuClient2B3)
3325
self.assertEqual(client.version, '2.0-beta3')
3326
client = EnvJujuClient1X.by_version(None)
3327
self.assertIs(type(client), EnvJujuClient2B3)
3328
self.assertEqual(client.version, '2.0-beta4')
3329
client = EnvJujuClient1X.by_version(None)
3330
self.assertIs(type(client), EnvJujuClient2B3)
3331
self.assertEqual(client.version, '2.0-beta5')
3332
client = EnvJujuClient1X.by_version(None)
3333
self.assertIs(type(client), EnvJujuClient2B3)
3334
self.assertEqual(client.version, '2.0-beta6')
3335
client = EnvJujuClient1X.by_version(None)
3336
self.assertIs(type(client), EnvJujuClient)
3337
self.assertEqual(client.version, '2.0-beta7')
3338
client = EnvJujuClient1X.by_version(None)
3339
self.assertIs(type(client), EnvJujuClient)
3340
self.assertEqual(client.version, '2.0-delta1')
3341
with self.assertRaises(StopIteration):
3342
EnvJujuClient1X.by_version(None)
3344
def test_by_version_path(self):
3345
with patch('subprocess.check_output', return_value=' 4.3') as vsn:
3346
client = EnvJujuClient1X.by_version(None, 'foo/bar/qux')
3347
vsn.assert_called_once_with(('foo/bar/qux', '--version'))
3348
self.assertNotEqual(client.full_path, 'foo/bar/qux')
3349
self.assertEqual(client.full_path, os.path.abspath('foo/bar/qux'))
3351
def test_by_version_keep_home(self):
3352
env = SimpleEnvironment({}, juju_home='/foo/bar')
3353
with patch('subprocess.check_output', return_value=' 1.27'):
3354
EnvJujuClient1X.by_version(env, 'foo/bar/qux')
3355
self.assertEqual('/foo/bar', env.juju_home)
3801
3357
def test_get_cache_path(self):
3802
3358
client = EnvJujuClient1X(SimpleEnvironment('foo', juju_home='/foo/'),
3803
3359
'1.27', 'full/path', debug=True)