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

« back to all changes in this revision

Viewing changes to tests/test_jujupy.py

  • Committer: Curtis Hovey
  • Date: 2016-05-25 20:59:08 UTC
  • Revision ID: curtis@canonical.com-20160525205908-1yndw393rgkmxxcc
Revert git_gate.py change.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
from argparse import ArgumentParser
 
2
from base64 import b64encode
1
3
from contextlib import contextmanager
2
4
import copy
3
5
from datetime import (
5
7
    timedelta,
6
8
)
7
9
import errno
8
 
import json
 
10
from hashlib import sha512
 
11
from itertools import count
9
12
import logging
10
13
import os
11
14
import socket
24
27
)
25
28
import yaml
26
29
 
27
 
from fakejuju import (
28
 
    fake_juju_client,
29
 
    get_user_register_command_info,
30
 
    get_user_register_token,
31
 
)
 
30
from deploy_stack import GET_TOKEN_SCRIPT
32
31
from jujuconfig import (
33
32
    get_environments_path,
34
33
    get_jenv_path,
37
36
from jujupy import (
38
37
    BootstrapMismatch,
39
38
    CannotConnectEnv,
40
 
    client_from_config,
41
39
    CONTROLLER,
42
40
    Controller,
43
41
    EnvJujuClient,
50
48
    EnvJujuClient2A2,
51
49
    EnvJujuClient2B2,
52
50
    EnvJujuClient2B3,
53
 
    EnvJujuClient2B7,
54
 
    EnvJujuClient2B8,
55
 
    EnvJujuClient2B9,
56
51
    ErroredUnit,
57
52
    GroupReporter,
58
53
    get_cache_path,
59
54
    get_local_root,
60
55
    get_machine_dns_name,
61
56
    get_timeout_path,
62
 
    IncompatibleConfigClass,
63
57
    jes_home_path,
64
58
    JESByDefault,
65
59
    JESNotSupported,
68
62
    JUJU_DEV_FEATURE_FLAGS,
69
63
    KILL_CONTROLLER,
70
64
    Machine,
 
65
    make_client,
71
66
    make_safe_config,
72
67
    parse_new_state_server_from_error,
73
68
    SimpleEnvironment,
74
 
    ServiceStatus,
75
 
    SoftDeadlineExceeded,
76
69
    Status,
77
70
    SYSTEM,
78
71
    tear_down,
97
90
__metaclass__ = type
98
91
 
99
92
 
 
93
class AdminOperation(Exception):
 
94
 
 
95
    def __init__(self, operation):
 
96
        super(AdminOperation, self).__init__(
 
97
            'Operation "{}" can only be performed on admin models.'.format(
 
98
                operation))
 
99
 
 
100
 
100
101
def assert_juju_call(test_case, mock_method, client, expected_args,
101
102
                     call_index=None):
102
103
    if call_index is None:
106
107
    test_case.assertEqual(args, (expected_args,))
107
108
 
108
109
 
 
110
class FakeControllerState:
 
111
 
 
112
    def __init__(self):
 
113
        self.state = 'not-bootstrapped'
 
114
        self.models = {}
 
115
 
 
116
    def add_model(self, name):
 
117
        state = FakeEnvironmentState()
 
118
        state.name = name
 
119
        self.models[name] = state
 
120
        state.controller = self
 
121
        state.controller.state = 'created'
 
122
        return state
 
123
 
 
124
    def require_admin(self, operation, name):
 
125
        if name != self.admin_model.name:
 
126
            raise AdminOperation(operation)
 
127
 
 
128
    def bootstrap(self, model_name, config, separate_admin):
 
129
        default_model = self.add_model(model_name)
 
130
        default_model.name = model_name
 
131
        if separate_admin:
 
132
            admin_model = default_model.controller.add_model('admin')
 
133
        else:
 
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
 
140
        return default_model
 
141
 
 
142
 
 
143
class FakeEnvironmentState:
 
144
    """A Fake environment state that can be used by multiple FakeClients."""
 
145
 
 
146
    def __init__(self, controller=None):
 
147
        self._clear()
 
148
        if controller is not None:
 
149
            self.controller = controller
 
150
        else:
 
151
            self.controller = FakeControllerState()
 
152
 
 
153
    def _clear(self):
 
154
        self.name = None
 
155
        self.machine_id_iter = count()
 
156
        self.state_servers = []
 
157
        self.services = {}
 
158
        self.machines = set()
 
159
        self.containers = {}
 
160
        self.relations = {}
 
161
        self.token = None
 
162
        self.exposed = set()
 
163
        self.machine_host_names = {}
 
164
        self.current_bundle = None
 
165
        self.model_config = None
 
166
 
 
167
    @property
 
168
    def state(self):
 
169
        return self.controller.state
 
170
 
 
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(
 
175
            machine_id)
 
176
        return machine_id
 
177
 
 
178
    def add_ssh_machines(self, machines):
 
179
        for machine in machines:
 
180
            self.add_machine()
 
181
 
 
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}
 
186
 
 
187
    def remove_container(self, container_id):
 
188
        for containers in self.containers.values():
 
189
            containers.discard(container_id)
 
190
 
 
191
    def remove_machine(self, machine_id):
 
192
        self.machines.remove(machine_id)
 
193
        self.containers.pop(machine_id, None)
 
194
 
 
195
    def remove_state_server(self, machine_id):
 
196
        self.remove_machine(machine_id)
 
197
        self.state_servers.remove(machine_id)
 
198
 
 
199
    def destroy_environment(self):
 
200
        self._clear()
 
201
        self.controller.state = 'destroyed'
 
202
        return 0
 
203
 
 
204
    def kill_controller(self):
 
205
        self._clear()
 
206
        self.controller.state = 'controller-killed'
 
207
 
 
208
    def destroy_model(self):
 
209
        del self.controller.models[self.name]
 
210
        self._clear()
 
211
        self.controller.state = 'model-destroyed'
 
212
 
 
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,
 
217
                                                2)
 
218
            exc.stderr = 'Operation not permitted'
 
219
            raise exc
 
220
 
 
221
    def enable_ha(self):
 
222
        self.controller.require_admin('enable-ha', self.name)
 
223
        for n in range(2):
 
224
            self.state_servers.append(self.add_machine())
 
225
 
 
226
    def deploy(self, charm_name, service_name):
 
227
        self.add_unit(service_name)
 
228
 
 
229
    def deploy_bundle(self, bundle_path):
 
230
        self.current_bundle = bundle_path
 
231
 
 
232
    def add_unit(self, service_name):
 
233
        machines = self.services.setdefault(service_name, set())
 
234
        machines.add(
 
235
            ('{}/{}'.format(service_name, str(len(machines))),
 
236
             self.add_machine()))
 
237
 
 
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))
 
244
                    break
 
245
 
 
246
    def destroy_service(self, service_name):
 
247
        for unit, machine_id in self.services.pop(service_name):
 
248
            self.remove_machine(machine_id)
 
249
 
 
250
    def get_status_dict(self):
 
251
        machines = {}
 
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)
 
263
        services = {}
 
264
        for service, units in self.services.items():
 
265
            unit_map = {}
 
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] = {
 
271
                'units': unit_map,
 
272
                'relations': self.relations.get(service, {}),
 
273
                'exposed': service in self.exposed,
 
274
                }
 
275
        return {'machines': machines, 'services': services}
 
276
 
 
277
 
 
278
class FakeBackend:
 
279
    """A fake juju backend for tests.
 
280
 
 
281
    This is a partial implementation, but should be suitable for many uses,
 
282
    and can be extended.
 
283
 
 
284
    The state is provided by controller_state, so that multiple clients and
 
285
    backends can manipulate the same state.
 
286
    """
 
287
 
 
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
 
297
        self.debug = debug
 
298
        self.juju_timings = {}
 
299
 
 
300
    def clone(self, full_path=None,  version=None, debug=None,
 
301
              feature_flags=None):
 
302
        if version is None:
 
303
            version = self.version
 
304
        if full_path is None:
 
305
            full_path = self.full_path
 
306
        if debug is None:
 
307
            debug = self.debug
 
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,
 
312
                              full_path, debug)
 
313
 
 
314
    def set_feature(self, feature, enabled):
 
315
        if enabled:
 
316
            self.feature_flags.add(feature)
 
317
        else:
 
318
            self.feature_flags.discard(feature)
 
319
 
 
320
    def is_feature_enabled(self, feature):
 
321
        if feature == 'jes':
 
322
            return True
 
323
        return bool(feature in self.feature_flags)
 
324
 
 
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)
 
329
 
 
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'))
 
356
 
 
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)
 
361
 
 
362
    def destroy_environment(self, model_name):
 
363
        try:
 
364
            state = self.controller_state.models[model_name]
 
365
        except KeyError:
 
366
            return 0
 
367
        state.destroy_environment()
 
368
        return 0
 
369
 
 
370
    def add_machines(self, model_state, args):
 
371
        if len(args) == 0:
 
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)
 
378
 
 
379
    def get_admin_model_name(self):
 
380
        return self.controller_state.admin_model.name
 
381
 
 
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': [
 
388
            api_endpoint]}}}
 
389
 
 
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]}
 
394
 
 
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('=')
 
403
                if name == 'token':
 
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':
 
422
                (service,) = args
 
423
                model_state.exposed.add(service)
 
424
            if command == 'unexpose':
 
425
                (service,) = args
 
426
                model_state.exposed.remove(service)
 
427
            if command == 'add-unit':
 
428
                (service,) = args
 
429
                model_state.add_unit(service)
 
430
            if command == 'remove-unit':
 
431
                (unit_id,) = args
 
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)
 
443
                else:
 
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
 
452
                # the commandline.
 
453
                self.quickstart(model, {}, parsed.bundle)
 
454
        else:
 
455
            if command == 'bootstrap':
 
456
                self.bootstrap(args)
 
457
            if command == 'kill-controller':
 
458
                if self.controller_state.state == 'not-bootstrapped':
 
459
                    return
 
460
                model = args[0]
 
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()
 
466
                model = args[0]
 
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)
 
478
 
 
479
    @contextmanager
 
480
    def juju_async(self, command, args, used_feature_flags,
 
481
                   juju_home, model=None, timeout=None):
 
482
        yield
 
483
        self.juju(command, args, used_feature_flags,
 
484
                  juju_home, model, timeout=timeout)
 
485
 
 
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'):
 
504
            permissions = 'read'
 
505
            if set(["--acl", "write"]).issubset(args):
 
506
                permissions = 'write'
 
507
            username = args[0]
 
508
            model = args[2]
 
509
            code = b64encode(sha512(username).digest())
 
510
            info_string = \
 
511
                'User "{}" added\nUser "{}"granted {} access to model "{}\n"' \
 
512
                .format(username, username, permissions, model)
 
513
            register_string = \
 
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'
 
523
        return ''
 
524
 
 
525
    def pause(self, seconds):
 
526
        pass
 
527
 
 
528
 
 
529
class FakeBackendOptionalJES(FakeBackend):
 
530
 
 
531
    def is_feature_enabled(self, feature):
 
532
        return bool(feature in self.feature_flags)
 
533
 
 
534
 
 
535
def fake_juju_client(env=None, full_path=None, debug=False, version='2.0.0',
 
536
                     _backend=None, cls=EnvJujuClient):
 
537
    if env is None:
 
538
        env = JujuData('name', {
 
539
            'type': 'foo',
 
540
            'default-series': 'angsty',
 
541
            'region': 'bar',
 
542
            }, juju_home='foo')
 
543
    juju_home = env.juju_home
 
544
    if juju_home is None:
 
545
        juju_home = 'foo'
 
546
    if _backend is None:
 
547
        backend_state = FakeControllerState()
 
548
        _backend = FakeBackend(
 
549
            backend_state, version=version, full_path=full_path,
 
550
            debug=debug)
 
551
        _backend.set_feature('jes', True)
 
552
    client = cls(
 
553
        env, version, full_path, juju_home, debug, _backend=_backend)
 
554
    client.bootstrap_replaces = {}
 
555
    return client
 
556
 
 
557
 
 
558
def fake_juju_client_optional_jes(env=None, full_path=None, debug=False,
 
559
                                  jes_enabled=True, version='2.0.0',
 
560
                                  _backend=None):
 
561
    if _backend is None:
 
562
        backend_state = FakeControllerState()
 
563
        _backend = FakeBackendOptionalJES(
 
564
            backend_state, version=version, full_path=full_path,
 
565
            debug=debug)
 
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'])
 
570
    return client
 
571
 
 
572
 
 
573
class FakeJujuClientOptionalJES(EnvJujuClient):
 
574
 
 
575
    def get_admin_model_name(self):
 
576
        return self._backend.controller_state.admin_model.name
 
577
 
 
578
 
109
579
class TestErroredUnit(TestCase):
110
580
 
111
581
    def test_output(self):
155
625
 
156
626
class TestJuju2Backend(TestCase):
157
627
 
158
 
    test_environ = {'PATH': 'foo:bar'}
159
 
 
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)
164
632
 
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,
170
 
                               feature_flags=None)
171
 
        self.assertIsNot(cloned, backend)
172
 
        self.assertIs(soft_deadline, cloned.soft_deadline)
173
 
 
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():
180
 
                pass
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():
186
 
                    pass
187
 
 
188
 
    def test__check_timeouts_no_deadline(self):
189
 
        backend = Juju2Backend('/bin/path', '2.0', set(), debug=False,
190
 
                               soft_deadline=None)
191
 
        now = datetime(2015, 1, 2, 3, 4, 6)
192
 
        with patch('jujupy.Juju2Backend._now', return_value=now):
193
 
            with backend._check_timeouts():
194
 
                pass
195
 
 
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():
203
 
                    pass
204
 
            with self.assertRaisesRegexp(SoftDeadlineExceeded,
205
 
                                         'Operation exceeded deadline.'):
206
 
                with backend._check_timeouts():
207
 
                    pass
208
 
 
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')
221
 
 
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'):
230
 
                    pass
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'):
236
 
                        pass
237
 
 
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')
252
 
 
253
633
 
254
634
class TestEnvJujuClient26(ClientTest, CloudSigmaTest):
255
635
 
266
646
                client.enable_jes()
267
647
        self.assertNotIn('jes', client.feature_flags)
268
648
        assert_juju_call(
269
 
            self, po_mock, client, ('path', '--show-log', 'help', 'commands'))
 
649
            self, po_mock, client, ('juju', '--show-log', 'help', 'commands'))
270
650
 
271
651
    def test_enable_jes_unsupported(self):
272
652
        client = self.client_class(
278
658
                client.enable_jes()
279
659
        self.assertNotIn('jes', client.feature_flags)
280
660
        assert_juju_call(
281
 
            self, po_mock, client, ('path', '--show-log', 'help', 'commands'),
 
661
            self, po_mock, client, ('juju', '--show-log', 'help', 'commands'),
282
662
            0)
283
663
        assert_juju_call(
284
 
            self, po_mock, client, ('path', '--show-log', 'help', 'commands'),
 
664
            self, po_mock, client, ('juju', '--show-log', 'help', 'commands'),
285
665
            1)
286
666
        self.assertEqual(po_mock.call_count, 2)
287
667
 
296
676
            client.enable_jes()
297
677
        self.assertIn('jes', client.feature_flags)
298
678
        assert_juju_call(
299
 
            self, po_mock, client, ('path', '--show-log', 'help', 'commands'),
 
679
            self, po_mock, client, ('juju', '--show-log', 'help', 'commands'),
300
680
            0)
301
681
        # GZ 2015-10-26: Should assert that env has feature flag at call time.
302
682
        assert_juju_call(
303
 
            self, po_mock, client, ('path', '--show-log', 'help', 'commands'),
 
683
            self, po_mock, client, ('juju', '--show-log', 'help', 'commands'),
304
684
            1)
305
685
        self.assertEqual(po_mock.call_count, 2)
306
686
 
422
802
        self.assertNotIn('jes', env[JUJU_DEV_FEATURE_FLAGS].split(","))
423
803
 
424
804
    def test_add_ssh_machines(self):
425
 
        client = self.client_class(SimpleEnvironment('foo', {}), None, 'juju')
 
805
        client = self.client_class(SimpleEnvironment('foo', {}), None, '')
426
806
        with patch('subprocess.check_call', autospec=True) as cc_mock:
427
807
            client.add_ssh_machines(['m-foo', 'm-bar', 'm-baz'])
428
808
        assert_juju_call(self, cc_mock, client, (
434
814
        self.assertEqual(cc_mock.call_count, 3)
435
815
 
436
816
    def test_add_ssh_machines_no_retry(self):
437
 
        client = self.client_class(SimpleEnvironment('foo', {}), None, 'juju')
 
817
        client = self.client_class(SimpleEnvironment('foo', {}), None, '')
438
818
        with patch('subprocess.check_call', autospec=True,
439
819
                   side_effect=[subprocess.CalledProcessError(None, None),
440
820
                                None, None, None]) as cc_mock:
541
921
                raise
542
922
 
543
923
 
544
 
class TestClientFromConfig(ClientTest):
545
 
 
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():
552
 
            yield '1.17'
553
 
            yield '1.16'
554
 
            yield '1.16.1'
555
 
            yield '1.15'
556
 
            yield '1.22.1'
557
 
            yield '1.24-alpha1'
558
 
            yield '1.24.7'
559
 
            yield '1.25.1'
560
 
            yield '1.26.1'
561
 
            yield '1.27.1'
562
 
            yield '2.0-alpha1'
563
 
            yield '2.0-alpha2'
564
 
            yield '2.0-alpha3'
565
 
            yield '2.0-beta1'
566
 
            yield '2.0-beta2'
567
 
            yield '2.0-beta3'
568
 
            yield '2.0-beta4'
569
 
            yield '2.0-beta5'
570
 
            yield '2.0-beta6'
571
 
            yield '2.0-beta7'
572
 
            yield '2.0-beta8'
573
 
            yield '2.0-beta9'
574
 
            yield '2.0-beta10'
575
 
            yield '2.0-beta11'
576
 
            yield '2.0-beta12'
577
 
            yield '2.0-beta13'
578
 
            yield '2.0-beta14'
579
 
            yield '2.0-beta15'
580
 
            yield '2.0-delta1'
581
 
 
582
 
        context = patch.object(
583
 
            EnvJujuClient, 'get_version',
584
 
            side_effect=juju_cmd_iterator().send)
585
 
        with context:
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)
593
 
 
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)
598
 
                else:
599
 
                    self.assertEqual(jd_fc_mock.return_value, client.env)
600
 
                self.assertIs(cls, type(client))
601
 
                self.assertEqual(version, client.version)
602
 
 
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)
631
 
 
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'))
639
 
 
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)
647
 
 
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)
656
 
 
657
 
 
658
 
@contextmanager
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,
665
 
                      autospec=True):
666
 
        yield client
667
 
 
668
 
 
669
924
class TestEnvJujuClient(ClientTest):
670
925
 
671
926
    def test_no_duplicate_env(self):
707
962
 
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'))
714
970
 
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',))
721
978
 
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',))
 
986
 
 
987
    @patch.object(EnvJujuClient, 'get_full_path', return_value='fake-path')
 
988
    def test_by_version(self, gfp_mock):
 
989
        def juju_cmd_iterator():
 
990
            yield '1.17'
 
991
            yield '1.16'
 
992
            yield '1.16.1'
 
993
            yield '1.15'
 
994
            yield '1.22.1'
 
995
            yield '1.24-alpha1'
 
996
            yield '1.24.7'
 
997
            yield '1.25.1'
 
998
            yield '1.26.1'
 
999
            yield '1.27.1'
 
1000
            yield '2.0-alpha1'
 
1001
            yield '2.0-alpha2'
 
1002
            yield '2.0-alpha3'
 
1003
            yield '2.0-beta1'
 
1004
            yield '2.0-beta2'
 
1005
            yield '2.0-beta3'
 
1006
            yield '2.0-beta4'
 
1007
            yield '2.0-beta5'
 
1008
            yield '2.0-beta6'
 
1009
            yield '2.0-beta7'
 
1010
            yield '2.0-delta1'
 
1011
 
 
1012
        context = patch.object(
 
1013
            EnvJujuClient, 'get_version',
 
1014
            side_effect=juju_cmd_iterator().send)
 
1015
        with context:
 
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)
 
1078
 
 
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'))
 
1085
 
 
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)
728
1091
 
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',
766
1129
                          'qux'), full)
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)
771
 
        self.assertEqual(
772
 
            ('bin', '--show-log', 'bar', '-m', 'foo:controller', 'baz', 'qux'),
773
 
            full)
 
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)
777
1137
 
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)
784
1144
 
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'))
789
 
        self.assertEqual(
790
 
            ('bin', '--show-log', 'action', 'bar', '-m', 'foo:foo',
791
 
             'baz', 'qux'),
 
1149
        self.assertEqual((
 
1150
            'juju', '--show-log', 'action', 'bar', '-m', 'foo', 'baz', 'qux'),
792
1151
            full)
793
1152
 
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'),
800
 
                                     controller=True)
 
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'),
803
 
            full)
 
1160
            'juju', '--show-log', 'bar', '-m', 'admin', 'baz', 'qux'), full)
804
1161
        gamn_mock.assert_called_once_with()
805
1162
 
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',
810
 
            'type': 'baz',
811
 
            })
812
 
        client = EnvJujuClient(env, None, 'my/juju/bin')
813
 
        self.assertEqual({
814
 
            'agent-metadata-url': 'foo',
815
 
            'test-mode': True,
816
 
            }, client.make_model_config())
817
 
 
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',
865
1211
        with client._bootstrap_config() as config_filename:
866
1212
            with open(config_filename) as f:
867
1213
                self.assertEqual({
868
 
                    'agent-metadata-url': 'steve',
 
1214
                    'agent-metadata-url': 'frank',
869
1215
                    'agent-stream': 'foo',
870
1216
                    'authorized-keys': 'foo',
871
1217
                    'availability-sets-enabled': 'foo',
878
1224
                    'image-metadata-url': 'foo',
879
1225
                    'prefer-ipv6': 'foo',
880
1226
                    'test-mode': True,
 
1227
                    'tools-metadata-url': 'steve',
881
1228
                    }, yaml.safe_load(f))
882
1229
 
883
1230
    def test_get_cloud_region(self):
895
1242
                    client.bootstrap()
896
1243
            mock.assert_called_with(
897
1244
                'bootstrap', (
898
 
                    '--constraints', 'mem=2G', 'maas', 'foo/asdf',
 
1245
                    '--constraints', 'mem=2G arch=amd64', 'maas', 'foo/asdf',
899
1246
                    '--config', config_file.name, '--default-model', 'maas',
900
1247
                    '--agent-version', '2.0'),
901
1248
                include_e=False)
944
1291
                    'foo/baz', '--config', config_file.name,
945
1292
                    '--default-model', 'foo'), include_e=False)
946
1293
 
947
 
    def test_bootstrap_credential(self):
948
 
        env = JujuData('foo', {'type': 'foo', 'region': 'baz'})
949
 
        client = EnvJujuClient(env, '2.0-zeta1', None)
950
 
        with observable_temp_file() as config_file:
951
 
            with patch.object(client, 'juju') as mock:
952
 
                client.bootstrap(credential='credential_name')
953
 
        mock.assert_called_with(
954
 
            'bootstrap', (
955
 
                '--constraints', 'mem=2G', 'foo',
956
 
                'foo/baz', '--config', config_file.name,
957
 
                '--default-model', 'foo', '--agent-version', '2.0',
958
 
                '--credential', 'credential_name'), include_e=False)
959
 
 
960
 
    def test_bootstrap_bootstrap_series(self):
 
1294
    def test_bootstrap_args(self):
961
1295
        env = JujuData('foo', {'type': 'bar', 'region': 'baz'})
962
1296
        client = EnvJujuClient(env, '2.0-zeta1', None)
963
1297
        with patch.object(client, 'juju') as mock:
970
1304
                '--agent-version', '2.0',
971
1305
                '--bootstrap-series', 'angsty'), include_e=False)
972
1306
 
973
 
    def test_bootstrap_auto_upgade(self):
974
 
        env = JujuData('foo', {'type': 'bar', 'region': 'baz'})
975
 
        client = EnvJujuClient(env, '2.0-zeta1', None)
976
 
        with patch.object(client, 'juju') as mock:
977
 
            with observable_temp_file() as config_file:
978
 
                client.bootstrap(auto_upgrade=True)
979
 
        mock.assert_called_with(
980
 
            'bootstrap', (
981
 
                '--constraints', 'mem=2G', 'foo', 'bar/baz',
982
 
                '--config', config_file.name, '--default-model', 'foo',
983
 
                '--agent-version', '2.0', '--auto-upgrade'), include_e=False)
984
 
 
985
 
    def test_bootstrap_metadata(self):
986
 
        env = JujuData('foo', {'type': 'bar', 'region': 'baz'})
987
 
        client = EnvJujuClient(env, '2.0-zeta1', None)
988
 
        with patch.object(client, 'juju') as mock:
989
 
            with observable_temp_file() as config_file:
990
 
                client.bootstrap(metadata_source='/var/test-source')
991
 
        mock.assert_called_with(
992
 
            'bootstrap', (
993
 
                '--constraints', 'mem=2G', 'foo', 'bar/baz',
994
 
                '--config', config_file.name, '--default-model', 'foo',
995
 
                '--agent-version', '2.0',
996
 
                '--metadata-source', '/var/test-source'), include_e=False)
997
 
 
998
 
    def test_bootstrap_to(self):
999
 
        env = JujuData('foo', {'type': 'bar', 'region': 'baz'})
1000
 
        client = EnvJujuClient(env, '2.0-zeta1', None)
1001
 
        with patch.object(client, 'juju') as mock:
1002
 
            with observable_temp_file() as config_file:
1003
 
                client.bootstrap(to='target')
1004
 
        mock.assert_called_with(
1005
 
            'bootstrap', (
1006
 
                '--constraints', 'mem=2G', 'foo', 'bar/baz',
1007
 
                '--config', config_file.name, '--default-model', 'foo',
1008
 
                '--agent-version', '2.0', '--to', 'target'), include_e=False)
1009
 
 
1010
1307
    def test_bootstrap_async(self):
1011
1308
        env = JujuData('foo', {'type': 'bar', 'region': 'baz'})
1012
1309
        with patch.object(EnvJujuClient, 'juju_async', autospec=True) as mock:
1046
1343
            '--config', 'config', '--default-model', 'foo',
1047
1344
            '--bootstrap-series', 'angsty'))
1048
1345
 
1049
 
    def test_get_bootstrap_args_agent_version(self):
1050
 
        env = JujuData('foo', {'type': 'bar', 'region': 'baz'})
1051
 
        client = EnvJujuClient(env, '2.0-zeta1', None)
1052
 
        args = client.get_bootstrap_args(upload_tools=False,
1053
 
                                         config_filename='config',
1054
 
                                         agent_version='2.0-lambda1')
1055
 
        self.assertEqual(('--constraints', 'mem=2G', 'foo', 'bar/baz',
1056
 
                          '--config', 'config', '--default-model', 'foo',
1057
 
                          '--agent-version', '2.0-lambda1'), args)
1058
 
 
1059
 
    def test_get_bootstrap_args_upload_tools_and_agent_version(self):
1060
 
        env = JujuData('foo', {'type': 'bar', 'region': 'baz'})
1061
 
        client = EnvJujuClient(env, '2.0-zeta1', None)
1062
 
        with self.assertRaises(ValueError):
1063
 
            client.get_bootstrap_args(upload_tools=True,
1064
 
                                      config_filename='config',
1065
 
                                      agent_version='2.0-lambda1')
1066
 
 
1067
1346
    def test_add_model_hypenated_controller(self):
1068
1347
        self.do_add_model(
1069
1348
            'kill-controller', 'add-model', ('-c', 'foo'))
1087
1366
        self.assertIs(False, hasattr(client, 'destroy_environment'))
1088
1367
 
1089
1368
    def test_destroy_model(self):
1090
 
        env = JujuData('foo', {'type': 'gce'})
1091
 
        client = EnvJujuClient(env, None, None)
1092
 
        with patch.object(client, 'juju') as mock:
1093
 
            client.destroy_model()
1094
 
        mock.assert_called_with(
1095
 
            'destroy-model', ('foo', '-y'),
1096
 
            include_e=False, timeout=600)
1097
 
 
1098
 
    def test_destroy_model_azure(self):
1099
 
        env = JujuData('foo', {'type': 'azure'})
1100
 
        client = EnvJujuClient(env, None, None)
1101
 
        with patch.object(client, 'juju') as mock:
1102
 
            client.destroy_model()
1103
 
        mock.assert_called_with(
1104
 
            'destroy-model', ('foo', '-y'),
1105
 
            include_e=False, timeout=1800)
 
1369
        env = JujuData('foo')
 
1370
        client = EnvJujuClient(env, None, None)
 
1371
        with patch.object(client, 'juju') as mock:
 
1372
            client.destroy_model()
 
1373
        mock.assert_called_with(
 
1374
            'destroy-model', ('foo', '-y'),
 
1375
            include_e=False, timeout=600.0)
1106
1376
 
1107
1377
    def test_kill_controller_system(self):
1108
1378
        self.do_kill_controller('system', 'system kill')
1114
1384
        self.do_kill_controller('kill-controller', 'kill-controller')
1115
1385
 
1116
1386
    def do_kill_controller(self, jes_command, kill_command):
1117
 
        client = EnvJujuClient(JujuData('foo', {'type': 'gce'}), None, None)
 
1387
        client = EnvJujuClient(JujuData('foo'), None, None)
1118
1388
        with patch.object(client, 'get_jes_command',
1119
1389
                          return_value=jes_command):
1120
1390
            with patch.object(client, 'juju') as juju_mock:
1123
1393
            kill_command, ('foo', '-y'), check=False, include_e=False,
1124
1394
            timeout=600)
1125
1395
 
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,
1134
 
            timeout=1800)
1135
 
 
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])
1145
1405
 
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])
1155
1415
 
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!')
1164
1424
 
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)
1176
 
 
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)
1187
1435
 
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'))
1198
1446
 
1199
1447
    def test__shell_environ_juju_data(self):
1200
1448
        client = EnvJujuClient(
1232
1480
                          return_value=output_text) as gjo_mock:
1233
1481
            result = client.get_status()
1234
1482
        gjo_mock.assert_called_once_with(
1235
 
            'show-status', '--format', 'yaml', controller=False)
 
1483
            'show-status', '--format', 'yaml', admin=False)
1236
1484
        self.assertEqual(Status, type(result))
1237
1485
        self.assertEqual(['a', 'b', 'c'], result.status)
1238
1486
 
1280
1528
            machines:
1281
1529
              "0":
1282
1530
                {0}: {1}
1283
 
            applications:
 
1531
            services:
1284
1532
              jenkins:
1285
1533
                units:
1286
1534
                  jenkins/0:
1347
1595
        mock_juju.assert_called_with(
1348
1596
            'deploy', ('local:blah', '--resource', 'foo=/path/dir'))
1349
1597
 
1350
 
    def test_deploy_storage(self):
1351
 
        env = EnvJujuClient1X(
1352
 
            SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None)
1353
 
        with patch.object(env, 'juju') as mock_juju:
1354
 
            env.deploy('mondogb', storage='rootfs,1G')
1355
 
        mock_juju.assert_called_with(
1356
 
            'deploy', ('mondogb', '--storage', 'rootfs,1G'))
1357
 
 
1358
 
    def test_deploy_constraints(self):
1359
 
        env = EnvJujuClient1X(
1360
 
            SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None)
1361
 
        with patch.object(env, 'juju') as mock_juju:
1362
 
            env.deploy('mondogb', constraints='virt-type=kvm')
1363
 
        mock_juju.assert_called_with(
1364
 
            'deploy', ('mondogb', '--constraints', 'virt-type=kvm'))
1365
 
 
1366
1598
    def test_attach(self):
1367
1599
        env = EnvJujuClient(JujuData('foo', {'type': 'local'}), None, None)
1368
1600
        with patch.object(env, 'juju') as mock_juju:
1406
1638
        self.assertEqual(mock_ts.mock_calls, [call(.1), call(.1)])
1407
1639
        self.assertEqual(mock_ju.mock_calls, [call(60)])
1408
1640
 
1409
 
    def test_wait_for_resource_suppresses_deadline(self):
1410
 
        with client_past_deadline() as client:
1411
 
            real_check_timeouts = client.check_timeouts
1412
 
 
1413
 
            def list_resources(service_or_unit):
1414
 
                with real_check_timeouts():
1415
 
                    return make_resource_list()
1416
 
 
1417
 
            with patch.object(client, 'check_timeouts', autospec=True):
1418
 
                with patch.object(client, 'list_resources', autospec=True,
1419
 
                                  side_effect=list_resources):
1420
 
                        client.wait_for_resource('dummy-resource/foo',
1421
 
                                                 'app_unit')
1422
 
 
1423
 
    def test_wait_for_resource_checks_deadline(self):
1424
 
        resource_list = make_resource_list()
1425
 
        with client_past_deadline() as client:
1426
 
            with patch.object(client, 'list_resources', autospec=True,
1427
 
                              return_value=resource_list):
1428
 
                with self.assertRaises(SoftDeadlineExceeded):
1429
 
                    client.wait_for_resource('dummy-resource/foo', 'app_unit')
1430
 
 
1431
1641
    def test_deploy_bundle_2x(self):
1432
1642
        client = EnvJujuClient(JujuData('an_env', None),
1433
1643
                               '1.23-series-arch', None)
1459
1669
            JujuData('foo', {'type': 'local'}), '1.234-76', None)
1460
1670
        with patch.object(env, 'juju') as mock_juju:
1461
1671
            env.remove_service('mondogb')
1462
 
        mock_juju.assert_called_with('remove-application', ('mondogb',))
 
1672
        mock_juju.assert_called_with('remove-service', ('mondogb',))
1463
1673
 
1464
1674
    def test_status_until_always_runs_once(self):
1465
1675
        client = EnvJujuClient(
1489
1699
        self.assertEqual(ut_mock.mock_calls,
1490
1700
                         [call(60), call(30, start=70), call(60), call(60)])
1491
1701
 
1492
 
    def test_status_until_suppresses_deadline(self):
1493
 
        with self.only_status_checks() as client:
1494
 
            list(client.status_until(0))
1495
 
 
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))
1500
 
 
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'])
1505
 
        assert_juju_call(
1506
 
            self,
1507
 
            cc_mock,
1508
 
            client,
1509
 
            ('juju', '--show-log', 'add-machine',
1510
 
             '-m', 'foo:foo', 'ssh:m-foo'),
1511
 
            0)
1512
 
        assert_juju_call(
1513
 
            self,
1514
 
            cc_mock,
1515
 
            client,
1516
 
            ('juju', '--show-log', 'add-machine',
1517
 
             '-m', 'foo:foo', 'ssh:m-bar'),
1518
 
            1)
1519
 
        assert_juju_call(
1520
 
            self,
1521
 
            cc_mock,
1522
 
            client,
1523
 
            ('juju', '--show-log', 'add-machine',
1524
 
             '-m', 'foo:foo', 'ssh:m-baz'),
1525
 
            2)
 
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)
1527
1713
 
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'])
1534
 
        assert_juju_call(
1535
 
            self,
1536
 
            cc_mock,
1537
 
            client,
1538
 
            ('juju', '--show-log', 'add-machine',
1539
 
             '-m', 'foo:foo', 'ssh:m-foo'),
1540
 
            0)
 
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)
1542
 
        assert_juju_call(
1543
 
            self,
1544
 
            cc_mock,
1545
 
            client,
1546
 
            ('juju', '--show-log', 'add-machine',
1547
 
             '-m', 'foo:foo', 'ssh:m-foo'),
1548
 
            1)
1549
 
        assert_juju_call(
1550
 
            self,
1551
 
            cc_mock,
1552
 
            client,
1553
 
            ('juju', '--show-log', 'add-machine',
1554
 
             '-m', 'foo:foo', 'ssh:m-bar'),
1555
 
            2)
1556
 
        assert_juju_call(
1557
 
            self,
1558
 
            cc_mock,
1559
 
            client,
1560
 
            ('juju', '--show-log', 'add-machine',
1561
 
             '-m', 'foo:foo', 'ssh:m-baz'),
1562
 
            3)
 
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)
1564
1730
 
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
1569
1735
                ]) as cc_mock:
1570
1736
            with self.assertRaises(subprocess.CalledProcessError):
1571
1737
                client.add_ssh_machines(['m-foo', 'm-bar', 'm-baz'])
1572
 
        assert_juju_call(
1573
 
            self,
1574
 
            cc_mock,
1575
 
            client,
1576
 
            ('juju', '--show-log', 'add-machine',
1577
 
             '-m', 'foo:foo', 'ssh:m-foo'),
1578
 
            0)
1579
 
        assert_juju_call(
1580
 
            self,
1581
 
            cc_mock,
1582
 
            client,
1583
 
            ('juju', '--show-log', 'add-machine',
1584
 
             '-m', 'foo:foo', 'ssh:m-bar'),
1585
 
            1)
 
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)
1587
1743
 
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'])
1595
 
        assert_juju_call(
1596
 
            self,
1597
 
            cc_mock,
1598
 
            client,
1599
 
            ('juju', '--show-log', 'add-machine',
1600
 
             '-m', 'foo:foo', 'ssh:m-foo'),
1601
 
            0)
1602
 
        assert_juju_call(
1603
 
            self,
1604
 
            cc_mock,
1605
 
            client,
1606
 
            ('juju', '--show-log', 'add-machine',
1607
 
             '-m', 'foo:foo', 'ssh:m-foo'),
1608
 
            1)
 
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)
1610
1756
 
1611
1757
    def test_wait_for_started(self):
1643
1789
                        client.wait_for_started(start=now - timedelta(1200))
1644
1790
                self.assertEqual(writes, ['pending: jenkins/0', '\n'])
1645
1791
 
1646
 
    def make_ha_status(self):
1647
 
        return {'machines': {
1648
 
            '0': {'controller-member-status': 'has-vote'},
1649
 
            '1': {'controller-member-status': 'has-vote'},
1650
 
            '2': {'controller-member-status': 'has-vote'},
1651
 
            }}
1652
 
 
1653
 
    @contextmanager
1654
 
    def only_status_checks(self, status=None):
1655
 
        """This context manager ensure only get_status calls check_timeouts.
1656
 
 
1657
 
        Everything else will get a mock object.
1658
 
 
1659
 
        Also, the client is patched so that the soft_deadline has been hit.
1660
 
        """
1661
 
        with client_past_deadline() as client:
1662
 
            # This will work even after we patch check_timeouts below.
1663
 
            real_check_timeouts = client.check_timeouts
1664
 
 
1665
 
            def check(timeout=60, controller=False):
1666
 
                with real_check_timeouts():
1667
 
                    return client.status_class(status, '')
1668
 
 
1669
 
            with patch.object(client, 'get_status', autospec=True,
1670
 
                              side_effect=check):
1671
 
                with patch.object(client, 'check_timeouts', autospec=True):
1672
 
                    yield client
1673
 
 
1674
 
    def test__wait_for_status_suppresses_deadline(self):
1675
 
 
1676
 
        def translate(x):
1677
 
            return None
1678
 
 
1679
 
        with self.only_status_checks() as client:
1680
 
            client._wait_for_status(Mock(), translate)
1681
 
 
1682
 
    @contextmanager
1683
 
    def status_does_not_check(self, status=None):
1684
 
        """This context manager ensure get_status never calls check_timeouts.
1685
 
 
1686
 
        Also, the client is patched so that the soft_deadline has been hit.
1687
 
        """
1688
 
        with client_past_deadline() as client:
1689
 
            status_obj = client.status_class(status, '')
1690
 
            with patch.object(client, 'get_status', autospec=True,
1691
 
                              return_value=status_obj):
1692
 
                yield client
1693
 
 
1694
 
    def test__wait_for_status_checks_deadline(self):
1695
 
 
1696
 
        def translate(x):
1697
 
            return None
1698
 
 
1699
 
        with self.status_does_not_check() as client:
1700
 
            with self.assertRaises(SoftDeadlineExceeded):
1701
 
                client._wait_for_status(Mock(), translate)
1702
 
 
1703
1792
    def test_wait_for_started_logs_status(self):
1704
1793
        value = self.make_status_yaml('agent-state', 'pending', 'started')
1705
1794
        client = EnvJujuClient(JujuData('local'), None, None)
1814
1903
            machines:
1815
1904
              "0":
1816
1905
                agent-state: started
1817
 
            applications:
 
1906
            services:
1818
1907
              jenkins:
1819
1908
                units:
1820
1909
                  jenkins/0:
1837
1926
            machines:
1838
1927
              "0":
1839
1928
                agent-state: started
1840
 
            applications:
 
1929
            services:
1841
1930
              jenkins:
1842
1931
                units:
1843
1932
                  jenkins/0:
1855
1944
 
1856
1945
    def test_wait_for_workload(self):
1857
1946
        initial_status = Status.from_text("""\
1858
 
            applications:
 
1947
            services:
1859
1948
              jenkins:
1860
1949
                units:
1861
1950
                  jenkins/0:
1867
1956
                        current: unknown
1868
1957
        """)
1869
1958
        final_status = Status(copy.deepcopy(initial_status.status), None)
1870
 
        final_status.status['applications']['jenkins']['units']['jenkins/0'][
 
1959
        final_status.status['services']['jenkins']['units']['jenkins/0'][
1871
1960
            'workload-status']['current'] = 'active'
1872
1961
        client = EnvJujuClient(JujuData('local'), None, None)
1873
1962
        writes = []
1938
2027
              owner: admin@local
1939
2028
            current-model: foo
1940
2029
        """
1941
 
        client = EnvJujuClient(JujuData('baz'), None, None)
 
2030
        client = EnvJujuClient(JujuData('foo'), None, None)
1942
2031
        with patch.object(client, 'get_juju_output',
1943
2032
                          return_value=data) as gjo_mock:
1944
2033
            models = client.get_models()
1945
2034
        gjo_mock.assert_called_once_with(
1946
 
            'list-models', '-c', 'baz', '--format', 'yaml',
1947
 
            include_e=False, timeout=120)
 
2035
            'list-models', '-c', 'foo', '--format', 'yaml', include_e=False)
1948
2036
        expected_models = {
1949
2037
            'models': [
1950
2038
                {'name': 'foo', 'model-uuid': 'aaaa', 'owner': 'admin@local'},
1971
2059
        self.assertIs(client, model_clients[0])
1972
2060
        self.assertEqual('bar', model_clients[1].env.environment)
1973
2061
 
1974
 
    def test_get_controller_model_name(self):
 
2062
    def test_get_admin_model_name(self):
1975
2063
        models = {
1976
2064
            'models': [
1977
 
                {'name': 'controller', 'model-uuid': 'aaaa'},
 
2065
                {'name': 'admin', 'model-uuid': 'aaaa'},
1978
2066
                {'name': 'bar', 'model-uuid': 'bbbb'}],
1979
2067
            'current-model': 'bar'
1980
2068
        }
1981
2069
        client = EnvJujuClient(JujuData('foo'), None, None)
1982
2070
        with patch.object(client, 'get_models',
1983
2071
                          return_value=models) as gm_mock:
1984
 
            controller_name = client.get_controller_model_name()
 
2072
            admin_name = client.get_admin_model_name()
1985
2073
        self.assertEqual(0, gm_mock.call_count)
1986
 
        self.assertEqual('controller', controller_name)
 
2074
        self.assertEqual('admin', admin_name)
1987
2075
 
1988
 
    def test_get_controller_model_name_without_controller(self):
 
2076
    def test_get_admin_model_name_without_admin(self):
1989
2077
        models = {
1990
2078
            'models': [
1991
2079
                {'name': 'bar', 'model-uuid': 'aaaa'},
1994
2082
        }
1995
2083
        client = EnvJujuClient(JujuData('foo'), None, None)
1996
2084
        with patch.object(client, 'get_models', return_value=models):
1997
 
            controller_name = client.get_controller_model_name()
1998
 
        self.assertEqual('controller', controller_name)
 
2085
            admin_name = client.get_admin_model_name()
 
2086
        self.assertEqual('admin', admin_name)
1999
2087
 
2000
 
    def test_get_controller_model_name_no_models(self):
 
2088
    def test_get_admin_model_name_no_models(self):
2001
2089
        client = EnvJujuClient(JujuData('foo'), None, None)
2002
2090
        with patch.object(client, 'get_models', return_value={}):
2003
 
            controller_name = client.get_controller_model_name()
2004
 
        self.assertEqual('controller', controller_name)
2005
 
 
2006
 
    def test_get_model_uuid_returns_uuid(self):
2007
 
        model_uuid = '9ed1bde9-45c6-4d41-851d-33fdba7fa194'
2008
 
        yaml_string = dedent("""\
2009
 
        foo:
2010
 
          name: foo
2011
 
          model-uuid: {uuid}
2012
 
          controller-uuid: eb67e1eb-6c54-45f5-8b6a-b6243be97202
2013
 
          owner: admin@local
2014
 
          cloud: lxd
2015
 
          region: localhost
2016
 
          type: lxd
2017
 
          life: alive
2018
 
          status:
2019
 
            current: available
2020
 
            since: 1 minute ago
2021
 
          users:
2022
 
            admin@local:
2023
 
              display-name: admin
2024
 
              access: admin
2025
 
              last-connection: just now
2026
 
            """.format(uuid=model_uuid))
2027
 
        client = EnvJujuClient(JujuData('foo'), None, None)
2028
 
        with patch.object(client, 'get_juju_output') as m_get_juju_output:
2029
 
            m_get_juju_output.return_value = yaml_string
2030
 
            self.assertEqual(
2031
 
                client.get_model_uuid(),
2032
 
                model_uuid
2033
 
            )
2034
 
            m_get_juju_output.assert_called_once_with(
2035
 
                'show-model', '--format', 'yaml', 'foo:foo', include_e=False)
2036
 
 
2037
 
    def test_get_controller_model_uuid_returns_uuid(self):
2038
 
        controller_uuid = 'eb67e1eb-6c54-45f5-8b6a-b6243be97202'
2039
 
        controller_model_uuid = '1c908e10-4f07-459a-8419-bb61553a4660'
2040
 
        yaml_string = dedent("""\
2041
 
        controller:
2042
 
          name: controller
2043
 
          model-uuid: {model}
2044
 
          controller-uuid: {controller}
2045
 
          controller-name: localtempveebers
2046
 
          owner: admin@local
2047
 
          cloud: lxd
2048
 
          region: localhost
2049
 
          type: lxd
2050
 
          life: alive
2051
 
          status:
2052
 
            current: available
2053
 
            since: 59 seconds ago
2054
 
          users:
2055
 
            admin@local:
2056
 
              display-name: admin
2057
 
              access: admin
2058
 
              last-connection: just now""".format(model=controller_model_uuid,
2059
 
                                                  controller=controller_uuid))
2060
 
        client = EnvJujuClient(JujuData('foo'), None, None)
2061
 
        with patch.object(client, 'get_juju_output') as m_get_juju_output:
2062
 
            m_get_juju_output.return_value = yaml_string
2063
 
            self.assertEqual(
2064
 
                client.get_controller_model_uuid(),
2065
 
                controller_model_uuid
2066
 
            )
2067
 
            m_get_juju_output.assert_called_once_with(
2068
 
                'show-model', 'controller',
2069
 
                '--format', 'yaml', include_e=False)
2070
 
 
2071
 
    def test_get_controller_uuid_returns_uuid(self):
2072
 
        controller_uuid = 'eb67e1eb-6c54-45f5-8b6a-b6243be97202'
2073
 
        yaml_string = dedent("""\
2074
 
        foo:
2075
 
          details:
2076
 
            uuid: {uuid}
2077
 
            api-endpoints: ['10.194.140.213:17070']
2078
 
            cloud: lxd
2079
 
            region: localhost
2080
 
          models:
2081
 
            controller:
2082
 
              uuid: {uuid}
2083
 
            default:
2084
 
              uuid: 772cdd39-b454-4bd5-8704-dc9aa9ff1750
2085
 
          current-model: default
2086
 
          account:
2087
 
            user: admin@local
2088
 
          bootstrap-config:
2089
 
            config:
2090
 
            cloud: lxd
2091
 
            cloud-type: lxd
2092
 
            region: localhost""".format(uuid=controller_uuid))
2093
 
        client = EnvJujuClient(JujuData('foo'), None, None)
2094
 
        with patch.object(client, 'get_juju_output') as m_get_juju_output:
2095
 
            m_get_juju_output.return_value = yaml_string
2096
 
            self.assertEqual(
2097
 
                client.get_controller_uuid(),
2098
 
                controller_uuid
2099
 
            )
2100
 
            m_get_juju_output.assert_called_once_with(
2101
 
                'show-controller', '--format', 'yaml', include_e=False)
2102
 
 
2103
 
    def test_get_controller_client(self):
 
2091
            admin_name = client.get_admin_model_name()
 
2092
        self.assertEqual('admin', admin_name)
 
2093
 
 
2094
    def test_get_admin_client(self):
2104
2095
        client = EnvJujuClient(
2105
2096
            JujuData('foo', {'bar': 'baz'}, 'myhome'), None, None)
2106
 
        controller_client = client.get_controller_client()
2107
 
        controller_env = controller_client.env
2108
 
        self.assertEqual('controller', controller_env.environment)
2109
 
        self.assertEqual(
2110
 
            {'bar': 'baz', 'name': 'controller'}, controller_env.config)
 
2097
        admin_client = client.get_admin_client()
 
2098
        admin_env = admin_client.env
 
2099
        self.assertEqual('admin', admin_env.environment)
 
2100
        self.assertEqual({'bar': 'baz', 'name': 'admin'}, admin_env.config)
2111
2101
 
2112
2102
    def test_list_controllers(self):
2113
2103
        client = EnvJujuClient(JujuData('foo'), None, None)
2150
2140
              api-endpoints: ['[::1]:17070', '[fe80::216:3eff:0:9dc7]:17070']
2151
2141
        """
2152
2142
        client = EnvJujuClient(JujuData('foo', {}), None, None)
2153
 
        controller_client = client.get_controller_client()
 
2143
        admin_client = client.get_admin_client()
2154
2144
        client.env.controller.name = 'bar'
2155
 
        with patch.object(controller_client, 'get_juju_output',
 
2145
        with patch.object(admin_client, 'get_juju_output',
2156
2146
                          return_value=data) as gjo:
2157
 
            endpoint = controller_client.get_controller_endpoint()
 
2147
            endpoint = admin_client.get_controller_endpoint()
2158
2148
        gjo.assert_called_once_with('show-controller', 'bar',
2159
2149
                                    include_e=False)
2160
2150
        self.assertEqual('::1', endpoint)
2161
2151
 
2162
2152
    def test_get_controller_members(self):
2163
2153
        status = Status.from_text("""\
2164
 
            model: controller
 
2154
            model: admin
2165
2155
            machines:
2166
2156
              "0":
2167
2157
                dns-name: 10.0.0.0
2211
2201
 
2212
2202
    def test_get_controller_members_one(self):
2213
2203
        status = Status.from_text("""\
2214
 
            model: controller
 
2204
            model: admin
2215
2205
            machines:
2216
2206
              "0":
2217
2207
                dns-name: 10.0.0.0
2259
2249
                          return_value=value) as gjo_mock:
2260
2250
            client.wait_for_ha()
2261
2251
        gjo_mock.assert_called_once_with(
2262
 
            'show-status', '--format', 'yaml', controller=True)
 
2252
            'show-status', '--format', 'yaml', admin=True)
2263
2253
 
2264
2254
    def test_wait_for_ha_no_has_vote(self):
2265
2255
        value = yaml.safe_dump({
2301
2291
                        'Timed out waiting for voting to be enabled.'):
2302
2292
                    client.wait_for_ha()
2303
2293
 
2304
 
    def test_wait_for_ha_timeout_with_status_error(self):
2305
 
        value = yaml.safe_dump({
2306
 
            'machines': {
2307
 
                '0': {'agent-state-info': 'running'},
2308
 
                '1': {'agent-state-info': 'error: foo'},
2309
 
            },
2310
 
            'services': {},
2311
 
        })
2312
 
        client = EnvJujuClient(JujuData('local'), None, None)
2313
 
        with patch('jujupy.until_timeout', autospec=True, return_value=[2, 1]):
2314
 
            with patch.object(client, 'get_juju_output', return_value=value):
2315
 
                with self.assertRaisesRegexp(
2316
 
                        ErroredUnit, '1 is in state error: foo'):
2317
 
                    client.wait_for_ha()
2318
 
 
2319
 
    def test_wait_for_ha_suppresses_deadline(self):
2320
 
        with self.only_status_checks(self.make_ha_status()) as client:
2321
 
            client.wait_for_ha()
2322
 
 
2323
 
    def test_wait_for_ha_checks_deadline(self):
2324
 
        with self.status_does_not_check(self.make_ha_status()) as client:
2325
 
            with self.assertRaises(SoftDeadlineExceeded):
2326
 
                client.wait_for_ha()
2327
 
 
2328
2294
    def test_wait_for_deploy_started(self):
2329
2295
        value = yaml.safe_dump({
2330
2296
            'machines': {
2331
2297
                '0': {'agent-state': 'started'},
2332
2298
            },
2333
 
            'applications': {
 
2299
            'services': {
2334
2300
                'jenkins': {
2335
2301
                    'units': {
2336
2302
                        'jenkins/1': {'baz': 'qux'}
2347
2313
            'machines': {
2348
2314
                '0': {'agent-state': 'started'},
2349
2315
            },
2350
 
            'applications': {},
 
2316
            'services': {},
2351
2317
        })
2352
2318
        client = EnvJujuClient(JujuData('local'), None, None)
2353
2319
        with patch('jujupy.until_timeout', lambda x: range(0)):
2357
2323
                        'Timed out waiting for services to start.'):
2358
2324
                    client.wait_for_deploy_started()
2359
2325
 
2360
 
    def make_deployed_status(self):
2361
 
        return {
2362
 
            'machines': {
2363
 
                '0': {'agent-state': 'started'},
2364
 
            },
2365
 
            'applications': {
2366
 
                'jenkins': {
2367
 
                    'units': {
2368
 
                        'jenkins/1': {'baz': 'qux'}
2369
 
                    }
2370
 
                }
2371
 
            }
2372
 
        }
2373
 
 
2374
 
    def test_wait_for_deploy_started_suppresses_deadline(self):
2375
 
        with self.only_status_checks(self.make_deployed_status()) as client:
2376
 
            client.wait_for_deploy_started()
2377
 
 
2378
 
    def test_wait_for_deploy_started_checks_deadline(self):
2379
 
        with self.status_does_not_check(self.make_deployed_status()) as client:
2380
 
            with self.assertRaises(SoftDeadlineExceeded):
2381
 
                client.wait_for_deploy_started()
2382
 
 
2383
2326
    def test_wait_for_version(self):
2384
2327
        value = self.make_status_yaml('agent-version', '1.17.2', '1.17.2')
2385
2328
        client = EnvJujuClient(JujuData('local'), None, None)
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)
2480
2422
 
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)
2492
2434
 
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'))
2504
2446
 
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'))
2515
 
 
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')
2527
2458
 
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)
2537
2468
 
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',
2546
2477
                                 'bar', 'baz'))
2547
2478
 
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'))
2553
2484
 
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')
2556
2487
 
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)
2561
2492
 
2562
2493
        with temp_dir() as empty_path:
2563
2494
            broken_envvars = dict(PATH=empty_path)
2578
2509
 
2579
2510
    def test_juju_no_check(self):
2580
2511
        env = JujuData('qux')
2581
 
        client = EnvJujuClient(env, None, 'juju')
 
2512
        client = EnvJujuClient(env, None, None)
2582
2513
        environ = dict(os.environ)
2583
2514
        environ['JUJU_HOME'] = client.env.juju_home
2584
2515
        with patch('subprocess.call') as mock:
2585
2516
            client.juju('foo', ('bar', 'baz'), check=False)
2586
 
        mock.assert_called_with(('juju', '--show-log', 'foo', '-m', 'qux:qux',
 
2517
        mock.assert_called_with(('juju', '--show-log', 'foo', '-m', 'qux',
2587
2518
                                 'bar', 'baz'))
2588
2519
 
2589
2520
    def test_juju_no_check_env(self):
2601
2532
        with patch('subprocess.check_call') as cc_mock:
2602
2533
            client.juju('foo', ('bar', 'baz'), timeout=58)
2603
2534
        self.assertEqual(cc_mock.call_args[0][0], (
2604
 
            sys.executable, get_timeout_path(), '58.00', '--', 'baz',
2605
 
            '--show-log', 'foo', '-m', 'qux:qux', 'bar', 'baz'))
 
2535
            sys.executable, get_timeout_path(), '58.00', '--', 'juju',
 
2536
            '--show-log', 'foo', '-m', 'qux', 'bar', 'baz'))
2606
2537
 
2607
2538
    def test_juju_juju_home(self):
2608
2539
        env = JujuData('qux')
2622
2553
 
2623
2554
    def test_juju_extra_env(self):
2624
2555
        env = JujuData('qux')
2625
 
        client = EnvJujuClient(env, None, 'juju')
 
2556
        client = EnvJujuClient(env, None, None)
2626
2557
        extra_env = {'JUJU': '/juju', 'JUJU_HOME': client.env.juju_home}
2627
2558
 
2628
2559
        def check_env(*args, **kwargs):
2631
2562
        with patch('subprocess.check_call', side_effect=check_env) as mock:
2632
2563
            client.juju('quickstart', ('bar', 'baz'), extra_env=extra_env)
2633
2564
        mock.assert_called_with(
2634
 
            ('juju', '--show-log', 'quickstart', '-m', 'qux:qux',
2635
 
             'bar', 'baz'))
 
2565
            ('juju', '--show-log', 'quickstart', '-m', 'qux', 'bar', 'baz'))
2636
2566
 
2637
2567
    def test_juju_backup_with_tgz(self):
2638
2568
        env = JujuData('qux')
2644
2574
                ) as popen_mock:
2645
2575
            backup_file = client.backup()
2646
2576
        self.assertEqual(backup_file, os.path.abspath('juju-backup-24.tgz'))
2647
 
        assert_juju_call(self, popen_mock, client, ('baz', '--show-log',
2648
 
                         'create-backup', '-m', 'qux:qux'))
 
2577
        assert_juju_call(self, popen_mock, client, ('juju', '--show-log',
 
2578
                         'create-backup', '-m', 'qux'))
2649
2579
 
2650
2580
    def test_juju_backup_with_tar_gz(self):
2651
2581
        env = JujuData('qux')
2689
2619
    def test_restore_backup(self):
2690
2620
        env = JujuData('qux')
2691
2621
        client = EnvJujuClient(env, None, '/foobar/baz')
2692
 
        with patch.object(client, 'juju') as gjo_mock:
2693
 
            client.restore_backup('quxx')
2694
 
        gjo_mock.assert_called_once_with(
2695
 
            'restore-backup',
2696
 
            ('-b', '--constraints', 'mem=2G', '--file', 'quxx'))
 
2622
        with patch.object(client, 'get_juju_output') as gjo_mock:
 
2623
            result = client.restore_backup('quxx')
 
2624
        gjo_mock.assert_called_once_with('restore-backup', '-b',
 
2625
                                         '--constraints', 'mem=2G',
 
2626
                                         '--file', 'quxx')
 
2627
        self.assertIs(gjo_mock.return_value, result)
2697
2628
 
2698
2629
    def test_restore_backup_async(self):
2699
2630
        env = JujuData('qux')
2716
2647
        client = EnvJujuClient(env, None, '/foobar/baz')
2717
2648
        with patch('subprocess.Popen') as popen_class_mock:
2718
2649
            with client.juju_async('foo', ('bar', 'baz')) as proc:
2719
 
                assert_juju_call(
2720
 
                    self,
2721
 
                    popen_class_mock,
2722
 
                    client,
2723
 
                    ('baz', '--show-log', 'foo', '-m', 'qux:qux',
2724
 
                     'bar', 'baz'))
 
2650
                assert_juju_call(self, popen_class_mock, client, (
 
2651
                    'juju', '--show-log', 'foo', '-m', 'qux', 'bar', 'baz'))
2725
2652
                self.assertIs(proc, popen_class_mock.return_value)
2726
2653
                self.assertEqual(proc.wait.call_count, 0)
2727
2654
                proc.wait.return_value = 0
2737
2664
                    proc_mock.wait.return_value = 23
2738
2665
        self.assertEqual(err_cxt.exception.returncode, 23)
2739
2666
        self.assertEqual(err_cxt.exception.cmd, (
2740
 
            'baz', '--show-log', 'foo', '-m', 'qux:qux', 'bar', 'baz'))
 
2667
            'juju', '--show-log', 'foo', '-m', 'qux', 'bar', 'baz'))
2741
2668
 
2742
2669
    def test_juju_async_environ(self):
2743
2670
        env = JujuData('qux')
2792
2719
        with patch.object(EnvJujuClient, 'juju') as mock:
2793
2720
            client.deployer('bundle:~juju-qa/some-bundle')
2794
2721
        mock.assert_called_with(
2795
 
            'deployer', ('-e', 'foo:foo', '--debug', '--deploy-delay',
 
2722
            'deployer', ('-e', 'local.foo:foo', '--debug', '--deploy-delay',
2796
2723
                         '10', '--timeout', '3600', '--config',
2797
2724
                         'bundle:~juju-qa/some-bundle'),
2798
2725
            True, include_e=False
2804
2731
        with patch.object(EnvJujuClient, 'juju') as mock:
2805
2732
            client.deployer('bundle:~juju-qa/some-bundle', 'name')
2806
2733
        mock.assert_called_with(
2807
 
            'deployer', ('-e', 'foo:foo', '--debug', '--deploy-delay',
 
2734
            'deployer', ('-e', 'local.foo:foo', '--debug', '--deploy-delay',
2808
2735
                         '10', '--timeout', '3600', '--config',
2809
2736
                         'bundle:~juju-qa/some-bundle', 'name'),
2810
2737
            True, include_e=False
2817
2744
            client.quickstart('bundle:~juju-qa/some-bundle')
2818
2745
        mock.assert_called_with(
2819
2746
            'quickstart',
2820
 
            ('--constraints', 'mem=2G', '--no-browser',
 
2747
            ('--constraints', 'mem=2G arch=amd64', '--no-browser',
2821
2748
             'bundle:~juju-qa/some-bundle'), False, extra_env={'JUJU': '/juju'}
2822
2749
        )
2823
2750
 
2910
2837
            out = client.action_do_fetch("foo/0", "myaction", "param=5")
2911
2838
            self.assertEqual(out, ret)
2912
2839
 
2913
 
    def test_run(self):
2914
 
        client = fake_juju_client(cls=EnvJujuClient)
2915
 
        run_list = [
2916
 
            {"MachineId": "1",
2917
 
             "Stdout": "Linux\n",
2918
 
             "ReturnCode": 255,
2919
 
             "Stderr": "Permission denied (publickey,password)"}]
2920
 
        run_output = json.dumps(run_list)
2921
 
        with patch.object(client._backend, 'get_juju_output',
2922
 
                          return_value=run_output) as gjo_mock:
2923
 
            result = client.run(('wname',), applications=['foo', 'bar'])
2924
 
        self.assertEqual(run_list, result)
2925
 
        gjo_mock.assert_called_once_with(
2926
 
            'run', ('--format', 'json', '--application', 'foo,bar', 'wname'),
2927
 
            frozenset(
2928
 
                ['address-allocation', 'migration']), 'foo',
2929
 
            'name:name', user_name=None)
2930
 
 
2931
2840
    def test_list_space(self):
2932
2841
        client = EnvJujuClient(JujuData(None, {'type': 'local'}),
2933
2842
                               '1.23-series-arch', None)
2964
2873
        client = EnvJujuClient(JujuData('bar', {}), None, '/foo')
2965
2874
        with patch.object(client, 'juju') as juju_mock:
2966
2875
            client.set_config('foo', {'bar': 'baz'})
2967
 
        juju_mock.assert_called_once_with('config', ('foo', 'bar=baz'))
 
2876
        juju_mock.assert_called_once_with('set-config', ('foo', 'bar=baz'))
2968
2877
 
2969
2878
    def test_get_config(self):
2970
2879
        def output(*args, **kwargs):
2986
2895
                          side_effect=output) as gjo_mock:
2987
2896
            results = client.get_config('foo')
2988
2897
        self.assertEqual(expected, results)
2989
 
        gjo_mock.assert_called_once_with('config', 'foo')
 
2898
        gjo_mock.assert_called_once_with('get-config', 'foo')
2990
2899
 
2991
2900
    def test_get_service_config(self):
2992
2901
        def output(*args, **kwargs):
3042
2951
        client = EnvJujuClient(None, '2.0.0', None)
3043
2952
        self.assertFalse(client.is_juju1x())
3044
2953
 
3045
 
    def test__get_register_command_returns_register_token(self):
3046
 
        output = dedent("""\
3047
 
        User "x" added
3048
 
        User "x" granted read access to model "y"
3049
 
        Please send this command to x:
3050
 
            juju register AaBbCc""")
3051
 
        output_cmd = 'AaBbCc'
 
2954
    def test__get_register_command(self):
 
2955
        output = ''.join(['User "x" added\nUser "x" granted read access ',
 
2956
                          'to model "y"\nPlease send this command to x:\n',
 
2957
                          '    juju register AaBbCc'])
 
2958
        output_cmd = 'juju register AaBbCc'
3052
2959
        fake_client = fake_juju_client()
3053
 
 
3054
2960
        register_cmd = fake_client._get_register_command(output)
3055
2961
        self.assertEqual(register_cmd, output_cmd)
3056
2962
 
3066
2972
            fake_client.revoke(username)
3067
2973
            fake_client.juju.assert_called_with('revoke',
3068
2974
                                                ('-c', default_controller,
3069
 
                                                 username, default_permissions,
3070
 
                                                 default_model),
 
2975
                                                 username, default_model,
 
2976
                                                 '--acl', default_permissions),
3071
2977
                                                include_e=False)
3072
2978
 
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,
3077
 
                                                 model),
 
2982
                                                 username, model,
 
2983
                                                 '--acl', default_permissions),
3078
2984
                                                include_e=False)
3079
2985
 
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),
 
2989
                                                 username, model,
 
2990
                                                 '--acl', 'write'),
3084
2991
                                                include_e=False)
3085
2992
 
3086
 
    def test_add_user_perms(self):
3087
 
        fake_client = fake_juju_client()
3088
 
        username = 'fakeuser'
3089
 
 
3090
 
        # Ensure add_user returns expected value.
3091
 
        self.assertEqual(
3092
 
            fake_client.add_user_perms(username),
3093
 
            get_user_register_token(username))
3094
 
 
3095
 
    @staticmethod
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)
3107
 
                if model is None:
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(
3113
 
                        'grant',
3114
 
                        ('fakeuser', permissions,
3115
 
                         '-c', fake_client.env.controller.name),
3116
 
                        include_e=False)
3117
 
                else:
3118
 
                    mock_juju.assert_called_once_with(
3119
 
                        'grant',
3120
 
                        ('fakeuser', permissions,
3121
 
                         model,
3122
 
                         '-c', fake_client.env.controller.name),
3123
 
                        include_e=False)
3124
 
 
3125
 
    def test_assert_add_user_permissions(self):
3126
 
        model = 'foo'
3127
 
        permissions = 'write'
3128
 
 
3129
 
        # Check using default model and permissions
3130
 
        self.assert_add_user_perms(None, None)
3131
 
 
3132
 
        # Check explicit model & default permissions
3133
 
        self.assert_add_user_perms(model, None)
3134
 
 
3135
 
        # Check explicit model & permissions
3136
 
        self.assert_add_user_perms(model, permissions)
3137
 
 
3138
 
        # Check default model & explicit permissions
3139
 
        self.assert_add_user_perms(None, permissions)
3140
 
 
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)
3149
 
 
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)
3158
 
 
3159
 
    def test_logout(self):
3160
 
        env = JujuData('foo')
3161
 
        client = EnvJujuClient(env, None, None)
3162
 
        with patch.object(client, 'juju') as mock:
3163
 
            client.logout()
3164
 
        mock.assert_called_with(
3165
 
            'logout', ('-c', 'foo'), include_e=False)
3166
 
 
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(
3173
 
            'fakehome',
3174
 
            controller_name
3175
 
        )
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')
3180
 
 
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)
3188
 
 
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)
3196
 
 
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')
3205
 
 
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')
3214
 
 
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)
3223
 
 
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)
3232
 
 
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)
3241
 
 
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',
3254
 
                                     '--format', 'yaml')
3255
 
 
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'))
3261
 
 
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')
3267
 
 
3268
 
    def test_sync_tools(self):
3269
 
        client = EnvJujuClient(JujuData('foo'), None, None)
3270
 
        with patch.object(client, 'juju', autospec=True) as mock:
3271
 
            client.sync_tools()
3272
 
        mock.assert_called_once_with('sync-tools', ())
3273
 
 
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'),
3279
 
                                     include_e=False)
3280
 
 
3281
 
 
3282
 
class TestEnvJujuClient2B8(ClientTest):
3283
 
 
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',))
3290
 
 
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
3301
 
        )
3302
 
 
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
3313
 
        )
3314
 
 
3315
 
 
3316
 
class TestEnvJujuClient2B9(ClientTest):
3317
 
 
3318
 
    def test_get_model_uuid_returns_uuid(self):
3319
 
        model_uuid = '9ed1bde9-45c6-4d41-851d-33fdba7fa194'
3320
 
        yaml_string = dedent("""\
3321
 
        foo:
3322
 
          name: foo
3323
 
          model-uuid: {uuid}
3324
 
          controller-uuid: eb67e1eb-6c54-45f5-8b6a-b6243be97202
3325
 
          owner: admin@local
3326
 
          cloud: lxd
3327
 
          region: localhost
3328
 
          type: lxd
3329
 
          life: alive
3330
 
          status:
3331
 
            current: available
3332
 
            since: 1 minute ago
3333
 
          users:
3334
 
            admin@local:
3335
 
              display-name: admin
3336
 
              access: admin
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
3342
 
            self.assertEqual(
3343
 
                client.get_model_uuid(),
3344
 
                model_uuid
3345
 
            )
3346
 
            m_get_juju_output.assert_called_once_with(
3347
 
                'show-model', '--format', 'yaml')
3348
 
 
3349
 
    def test_add_user_perms(self):
3350
 
        fake_client = fake_juju_client(cls=EnvJujuClient2B9)
3351
 
        username = 'fakeuser'
3352
 
        model = 'foo'
3353
 
        permissions = 'write'
3354
 
        output = get_user_register_command_info(username)
3355
 
 
3356
 
        def _get_expected_args(model=None, permissions=None):
3357
 
            return [
3358
 
                username,
3359
 
                '--models', model or fake_client.env.environment,
3360
 
                '--acl', permissions or 'read',
3361
 
                '-c', fake_client.env.controller.name]
3362
 
 
3363
 
        # Ensure add_user_perms returns expected value.
3364
 
        self.assertEqual(
3365
 
            fake_client.add_user_perms(username),
3366
 
            get_user_register_token(username))
3367
 
 
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)
3375
 
 
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)
3381
 
 
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)
3387
 
 
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)
3393
 
 
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'))
3399
 
 
3400
 
    def test_get_config(self):
3401
 
        def output(*args, **kwargs):
3402
 
            return yaml.safe_dump({
3403
 
                'charm': 'foo',
3404
 
                'service': 'foo',
3405
 
                'settings': {
3406
 
                    'dir': {
3407
 
                        'default': 'true',
3408
 
                        'description': 'bla bla',
3409
 
                        'type': 'string',
3410
 
                        'value': '/tmp/charm-dir',
3411
 
                    }
3412
 
                }
3413
 
            })
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')
3421
 
 
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()
3428
 
        assert_juju_call(
3429
 
            self, po_mock, client, (
3430
 
                'juju', '--show-log',
3431
 
                'get-model-config',
3432
 
                '-m', 'foo:foo',
3433
 
                '--format', 'yaml'))
3434
 
        self.assertEqual({'bar': 'baz'}, result)
3435
 
 
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')
3442
 
        self.assertEqual(
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)
3447
 
 
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'))
3459
 
 
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'))
3470
 
 
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',
3483
 
                                     '--format', 'yaml')
3484
 
 
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'))
3490
 
 
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')
3496
 
 
3497
 
 
3498
 
class TestEnvJujuClient2B7(ClientTest):
3499
 
 
3500
 
    def test_get_controller_model_name(self):
3501
 
        models = {
3502
 
            'models': [
3503
 
                {'name': 'admin', 'model-uuid': 'aaaa'},
3504
 
                {'name': 'bar', 'model-uuid': 'bbbb'}],
3505
 
            'current-model': 'bar'
3506
 
        }
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)
3513
 
 
3514
 
    def test_get_controller_model_name_without_controller(self):
3515
 
        models = {
3516
 
            'models': [
3517
 
                {'name': 'bar', 'model-uuid': 'aaaa'},
3518
 
                {'name': 'baz', 'model-uuid': 'bbbb'}],
3519
 
            'current-model': 'bar'
3520
 
        }
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)
3525
 
 
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)
3531
 
 
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'
 
2996
        model = 'foo'
 
2997
        permissions = 'write'
 
2998
 
 
2999
        output = fake_client.add_user(username)
 
3000
        self.assertTrue(output.startswith('juju register'))
 
3001
 
 
3002
        output = fake_client.add_user(username, model)
 
3003
        self.assertTrue(output.startswith('juju register'))
 
3004
 
 
3005
        output = fake_client.add_user(username, model, permissions)
 
3006
        self.assertTrue(output.startswith('juju register'))
3540
3007
 
3541
3008
 
3542
3009
class TestEnvJujuClient2B3(ClientTest):
3571
3038
            '--upload-tools', '--constraints', 'mem=2G', 'foo', 'bar/baz',
3572
3039
            '--config', 'config', '--bootstrap-series', 'angsty'))
3573
3040
 
3574
 
    def test_get_bootstrap_args_reject_new_args(self):
3575
 
        env = JujuData('foo', {'type': 'bar', 'region': 'baz'})
3576
 
        client = EnvJujuClient2B2(env, '2.0-zeta1', None)
3577
 
        base_args = {'upload_tools': True,
3578
 
                     'config_filename': 'config',
3579
 
                     'bootstrap_series': 'angsty'}
3580
 
        with self.assertRaises(ValueError):
3581
 
            client.get_bootstrap_args(auto_upgrade=True, **base_args)
3582
 
        with self.assertRaises(ValueError):
3583
 
            client.get_bootstrap_args(metadata_source='/foo', **base_args)
3584
 
        with self.assertRaises(ValueError):
3585
 
            client.get_bootstrap_args(to='cur', **base_args)
3586
 
        with self.assertRaises(ValueError):
3587
 
            client.get_bootstrap_args(agent_version='1.0.0', **base_args)
3588
 
 
3589
3041
    def test_bootstrap_upload_tools(self):
3590
3042
        env = JujuData('foo', {'type': 'foo', 'region': 'baz'})
3591
3043
        client = EnvJujuClient2B2(env, '2.0-zeta1', None)
3607
3059
                    client.bootstrap()
3608
3060
            mock.assert_called_with(
3609
3061
                'bootstrap', (
3610
 
                    '--constraints', 'mem=2G', 'maas', 'foo/asdf',
 
3062
                    '--constraints', 'mem=2G arch=amd64', 'maas', 'foo/asdf',
3611
3063
                    '--config', config_file.name, '--agent-version', '2.0'),
3612
3064
                include_e=False)
3613
3065
 
3678
3130
                config = yaml.safe_load(config_file)
3679
3131
        self.assertEqual({'test-mode': True}, config)
3680
3132
 
3681
 
    def test_get_controller_model_name(self):
 
3133
    def test_get_admin_model_name(self):
3682
3134
        models = {
3683
3135
            'models': [
3684
3136
                {'name': 'admin', 'model-uuid': 'aaaa'},
3688
3140
        client = EnvJujuClient2B2(JujuData('foo'), None, None)
3689
3141
        with patch.object(client, 'get_models',
3690
3142
                          return_value=models) as gm_mock:
3691
 
            controller_name = client.get_controller_model_name()
 
3143
            admin_name = client.get_admin_model_name()
3692
3144
        gm_mock.assert_called_once_with()
3693
 
        self.assertEqual('admin', controller_name)
 
3145
        self.assertEqual('admin', admin_name)
3694
3146
 
3695
 
    def test_get_controller_model_name_without_controller(self):
 
3147
    def test_get_admin_model_name_without_admin(self):
3696
3148
        models = {
3697
3149
            'models': [
3698
3150
                {'name': 'bar', 'model-uuid': 'aaaa'},
3701
3153
        }
3702
3154
        client = EnvJujuClient2B2(JujuData('foo'), None, None)
3703
3155
        with patch.object(client, 'get_models', return_value=models):
3704
 
            controller_name = client.get_controller_model_name()
3705
 
        self.assertEqual('foo', controller_name)
 
3156
            admin_name = client.get_admin_model_name()
 
3157
        self.assertEqual('foo', admin_name)
3706
3158
 
3707
 
    def test_get_controller_model_name_no_models(self):
 
3159
    def test_get_admin_model_name_no_models(self):
3708
3160
        client = EnvJujuClient2B2(JujuData('foo'), None, None)
3709
3161
        with patch.object(client, 'get_models', return_value={}):
3710
 
            controller_name = client.get_controller_model_name()
3711
 
        self.assertEqual('foo', controller_name)
 
3162
            admin_name = client.get_admin_model_name()
 
3163
        self.assertEqual('foo', admin_name)
3712
3164
 
3713
3165
 
3714
3166
class TestEnvJujuClient2A2(TestCase):
3716
3168
    def test_raise_on_juju_data(self):
3717
3169
        env = JujuData('foo', {'type': 'bar'}, 'baz')
3718
3170
        with self.assertRaisesRegexp(
3719
 
                IncompatibleConfigClass,
3720
 
                'JujuData cannot be used with EnvJujuClient2A2'):
 
3171
                ValueError, 'JujuData cannot be used with EnvJujuClient2A2'):
3721
3172
            EnvJujuClient2A2(env, '1.25', 'full_path')
3722
3173
 
3723
3174
    def test__shell_environ_juju_home(self):
3798
3249
        with self.assertRaises(UpgradeMongoNotSupported):
3799
3250
            client.upgrade_mongo()
3800
3251
 
 
3252
    @patch.object(EnvJujuClient1X, 'get_full_path', return_value='fake-path')
 
3253
    def test_by_version(self, gfp_mock):
 
3254
        def juju_cmd_iterator():
 
3255
            yield '1.17'
 
3256
            yield '1.16'
 
3257
            yield '1.16.1'
 
3258
            yield '1.15'
 
3259
            yield '1.22.1'
 
3260
            yield '1.24-alpha1'
 
3261
            yield '1.24.7'
 
3262
            yield '1.25.1'
 
3263
            yield '1.26.1'
 
3264
            yield '1.27.1'
 
3265
            yield '2.0-alpha1'
 
3266
            yield '2.0-alpha2'
 
3267
            yield '2.0-alpha3'
 
3268
            yield '2.0-beta1'
 
3269
            yield '2.0-beta2'
 
3270
            yield '2.0-beta3'
 
3271
            yield '2.0-beta4'
 
3272
            yield '2.0-beta5'
 
3273
            yield '2.0-beta6'
 
3274
            yield '2.0-beta7'
 
3275
            yield '2.0-delta1'
 
3276
 
 
3277
        context = patch.object(
 
3278
            EnvJujuClient1X, 'get_version',
 
3279
            side_effect=juju_cmd_iterator().send)
 
3280
        with context:
 
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)
 
3343
 
 
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'))
 
3350
 
 
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)
 
3356
 
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)
3808
3364
        env = SimpleEnvironment('foo')
3809
3365
        client = EnvJujuClient1X(env, None, 'my/juju/bin')
3810
3366
        full = client._full_args('bar', False, ('baz', 'qux'))
3811
 
        self.assertEqual(('bin', '--show-log', 'bar', '-e', 'foo', 'baz',
 
3367
        self.assertEqual(('juju', '--show-log', 'bar', '-e', 'foo', 'baz',
3812
3368
                          'qux'), full)
3813
3369
        full = client._full_args('bar', True, ('baz', 'qux'))
3814
3370
        self.assertEqual((
3815
 
            'bin', '--show-log', 'bar', '-e', 'foo',
 
3371
            'juju', '--show-log', 'bar', '-e', 'foo',
3816
3372
            'baz', 'qux'), full)
3817
3373
        client.env = None
3818
3374
        full = client._full_args('bar', False, ('baz', 'qux'))
3819
 
        self.assertEqual(('bin', '--show-log', 'bar', 'baz', 'qux'), full)
 
3375
        self.assertEqual(('juju', '--show-log', 'bar', 'baz', 'qux'), full)
3820
3376
 
3821
3377
    def test_full_args_debug(self):
3822
3378
        env = SimpleEnvironment('foo')
3823
3379
        client = EnvJujuClient1X(env, None, 'my/juju/bin', debug=True)
3824
3380
        full = client._full_args('bar', False, ('baz', 'qux'))
3825
3381
        self.assertEqual((
3826
 
            'bin', '--debug', 'bar', '-e', 'foo', 'baz', 'qux'), full)
 
3382
            'juju', '--debug', 'bar', '-e', 'foo', 'baz', 'qux'), full)
3827
3383
 
3828
 
    def test_full_args_controller(self):
 
3384
    def test_full_args_admin(self):
3829
3385
        env = SimpleEnvironment('foo')
3830
3386
        client = EnvJujuClient1X(env, None, 'my/juju/bin')
3831
 
        full = client._full_args('bar', False, ('baz', 'qux'), controller=True)
 
3387
        full = client._full_args('bar', False, ('baz', 'qux'), admin=True)
3832
3388
        self.assertEqual((
3833
 
            'bin', '--show-log', 'bar', '-e', 'foo', 'baz', 'qux'), full)
 
3389
            'juju', '--show-log', 'bar', '-e', 'foo', 'baz', 'qux'), full)
3834
3390
 
3835
3391
    def test_full_args_action(self):
3836
3392
        env = SimpleEnvironment('foo')
3837
3393
        client = EnvJujuClient1X(env, None, 'my/juju/bin')
3838
3394
        full = client._full_args('action bar', False, ('baz', 'qux'))
3839
3395
        self.assertEqual((
3840
 
            'bin', '--show-log', 'action', 'bar', '-e', 'foo', 'baz', 'qux'),
 
3396
            'juju', '--show-log', 'action', 'bar', '-e', 'foo', 'baz', 'qux'),
3841
3397
            full)
3842
3398
 
3843
3399
    def test_bootstrap_maas(self):
3847
3403
            with patch.object(client.env, 'maas', lambda: True):
3848
3404
                client.bootstrap()
3849
3405
            mock.assert_called_with(
3850
 
                'bootstrap', ('--constraints', 'mem=2G'), False)
 
3406
                'bootstrap', ('--constraints', 'mem=2G arch=amd64'), False)
3851
3407
 
3852
3408
    def test_bootstrap_joyent(self):
3853
3409
        env = SimpleEnvironment('joyent')
3963
3519
                'bar', '--config', config_file.name), include_e=False)
3964
3520
 
3965
3521
    def test_destroy_environment_non_sudo(self):
3966
 
        env = SimpleEnvironment('foo', {'type': 'gce'})
 
3522
        env = SimpleEnvironment('foo')
3967
3523
        client = EnvJujuClient1X(env, None, None)
3968
3524
        with patch.object(client.env, 'needs_sudo', lambda: False):
3969
3525
            with patch.object(client, 'juju') as mock:
3970
3526
                client.destroy_environment()
3971
3527
            mock.assert_called_with(
3972
3528
                'destroy-environment', ('foo', '--force', '-y'),
3973
 
                False, check=False, include_e=False, timeout=600)
 
3529
                False, check=False, include_e=False, timeout=600.0)
3974
3530
 
3975
3531
    def test_destroy_environment_sudo(self):
3976
 
        env = SimpleEnvironment('foo', {'type': 'gce'})
 
3532
        env = SimpleEnvironment('foo')
3977
3533
        client = EnvJujuClient1X(env, None, None)
3978
3534
        with patch.object(client.env, 'needs_sudo', lambda: True):
3979
3535
            with patch.object(client, 'juju') as mock:
3980
3536
                client.destroy_environment()
3981
3537
            mock.assert_called_with(
3982
3538
                'destroy-environment', ('foo', '--force', '-y'),
3983
 
                True, check=False, include_e=False, timeout=600)
 
3539
                True, check=False, include_e=False, timeout=600.0)
3984
3540
 
3985
3541
    def test_destroy_environment_no_force(self):
3986
 
        env = SimpleEnvironment('foo', {'type': 'gce'})
3987
 
        client = EnvJujuClient1X(env, None, None)
3988
 
        with patch.object(client, 'juju') as mock:
3989
 
            client.destroy_environment(force=False)
3990
 
            mock.assert_called_with(
3991
 
                'destroy-environment', ('foo', '-y'),
3992
 
                False, check=False, include_e=False, timeout=600)
3993
 
 
3994
 
    def test_destroy_environment_azure(self):
3995
 
        env = SimpleEnvironment('foo', {'type': 'azure'})
3996
 
        client = EnvJujuClient1X(env, None, None)
3997
 
        with patch.object(client, 'juju') as mock:
3998
 
            client.destroy_environment(force=False)
3999
 
            mock.assert_called_with(
4000
 
                'destroy-environment', ('foo', '-y'),
4001
 
                False, check=False, include_e=False, timeout=1800)
 
3542
        env = SimpleEnvironment('foo')
 
3543
        client = EnvJujuClient1X(env, None, None)
 
3544
        with patch.object(client, 'juju') as mock:
 
3545
            client.destroy_environment(force=False)
 
3546
            mock.assert_called_with(
 
3547
                'destroy-environment', ('foo', '-y'),
 
3548
                False, check=False, include_e=False, timeout=600.0)
4002
3549
 
4003
3550
    def test_destroy_environment_delete_jenv(self):
4004
 
        env = SimpleEnvironment('foo', {'type': 'gce'})
 
3551
        env = SimpleEnvironment('foo')
4005
3552
        client = EnvJujuClient1X(env, None, None)
4006
3553
        with patch.object(client, 'juju'):
4007
3554
            with temp_env({}) as juju_home:
4014
3561
                self.assertFalse(os.path.exists(jenv_path))
4015
3562
 
4016
3563
    def test_destroy_model(self):
4017
 
        env = SimpleEnvironment('foo', {'type': 'gce'})
 
3564
        env = SimpleEnvironment('foo')
4018
3565
        client = EnvJujuClient1X(env, None, None)
4019
3566
        with patch.object(client, 'juju') as mock:
4020
3567
            client.destroy_model()
4021
3568
        mock.assert_called_with(
4022
3569
            'destroy-environment', ('foo', '-y'),
4023
 
            False, check=False, include_e=False, timeout=600)
 
3570
            False, check=False, include_e=False, timeout=600.0)
4024
3571
 
4025
3572
    def test_kill_controller_system(self):
4026
3573
        self.do_kill_controller('system', 'system kill')
4032
3579
        self.do_kill_controller('kill-controller', 'kill-controller')
4033
3580
 
4034
3581
    def do_kill_controller(self, jes_command, kill_command):
4035
 
        client = EnvJujuClient1X(
4036
 
            SimpleEnvironment('foo', {'type': 'gce'}), None, None)
 
3582
        client = EnvJujuClient1X(SimpleEnvironment('foo'), None, None)
4037
3583
        with patch.object(client, 'get_jes_command',
4038
3584
                          return_value=jes_command):
4039
3585
            with patch.object(client, 'juju') as juju_mock:
4044
3590
 
4045
3591
    def test_get_juju_output(self):
4046
3592
        env = SimpleEnvironment('foo')
4047
 
        client = EnvJujuClient1X(env, None, 'juju')
 
3593
        client = EnvJujuClient1X(env, None, None)
4048
3594
        fake_popen = FakePopen('asdf', None, 0)
4049
3595
        with patch('subprocess.Popen', return_value=fake_popen) as mock:
4050
3596
            result = client.get_juju_output('bar')
4055
3601
    def test_get_juju_output_accepts_varargs(self):
4056
3602
        env = SimpleEnvironment('foo')
4057
3603
        fake_popen = FakePopen('asdf', None, 0)
4058
 
        client = EnvJujuClient1X(env, None, 'juju')
 
3604
        client = EnvJujuClient1X(env, None, None)
4059
3605
        with patch('subprocess.Popen', return_value=fake_popen) as mock:
4060
3606
            result = client.get_juju_output('bar', 'baz', '--qux')
4061
3607
        self.assertEqual('asdf', result)
4065
3611
    def test_get_juju_output_stderr(self):
4066
3612
        env = SimpleEnvironment('foo')
4067
3613
        fake_popen = FakePopen('Hello', 'Error!', 1)
4068
 
        client = EnvJujuClient1X(env, None, 'juju')
 
3614
        client = EnvJujuClient1X(env, None, None)
4069
3615
        with self.assertRaises(subprocess.CalledProcessError) as exc:
4070
3616
            with patch('subprocess.Popen', return_value=fake_popen):
4071
3617
                client.get_juju_output('bar')
4075
3621
    def test_get_juju_output_full_cmd(self):
4076
3622
        env = SimpleEnvironment('foo')
4077
3623
        fake_popen = FakePopen(None, 'Hello!', 1)
4078
 
        client = EnvJujuClient1X(env, None, 'juju')
 
3624
        client = EnvJujuClient1X(env, None, None)
4079
3625
        with self.assertRaises(subprocess.CalledProcessError) as exc:
4080
3626
            with patch('subprocess.Popen', return_value=fake_popen):
4081
3627
                client.get_juju_output('bar', '--baz', 'qux')
4086
3632
    def test_get_juju_output_accepts_timeout(self):
4087
3633
        env = SimpleEnvironment('foo')
4088
3634
        fake_popen = FakePopen('asdf', None, 0)
4089
 
        client = EnvJujuClient1X(env, None, 'juju')
 
3635
        client = EnvJujuClient1X(env, None, None)
4090
3636
        with patch('subprocess.Popen', return_value=fake_popen) as po_mock:
4091
3637
            client.get_juju_output('bar', timeout=5)
4092
3638
        self.assertEqual(
4132
3678
                          return_value=output_text) as gjo_mock:
4133
3679
            result = client.get_status()
4134
3680
        gjo_mock.assert_called_once_with(
4135
 
            'status', '--format', 'yaml', controller=False)
4136
 
        self.assertEqual(ServiceStatus, type(result))
 
3681
            'status', '--format', 'yaml', admin=False)
 
3682
        self.assertEqual(Status, type(result))
4137
3683
        self.assertEqual(['a', 'b', 'c'], result.status)
4138
3684
 
4139
3685
    def test_get_status_retries_on_error(self):
4174
3720
                    client.get_status(500)
4175
3721
        mock_ut.assert_called_with(500)
4176
3722
 
4177
 
    def test_get_status_controller(self):
 
3723
    def test_get_status_admin(self):
4178
3724
        output_text = """\
4179
3725
            - a
4180
3726
            - b
4184
3730
        client = EnvJujuClient1X(env, None, None)
4185
3731
        with patch.object(client, 'get_juju_output',
4186
3732
                          return_value=output_text) as gjo_mock:
4187
 
            client.get_status(controller=True)
 
3733
            client.get_status(admin=True)
4188
3734
        gjo_mock.assert_called_once_with(
4189
 
            'status', '--format', 'yaml', controller=True)
 
3735
            'status', '--format', 'yaml', admin=True)
4190
3736
 
4191
3737
    @staticmethod
4192
3738
    def make_status_yaml(key, machine_value, unit_value):
4285
3831
                         [call(60), call(30, start=70), call(60), call(60)])
4286
3832
 
4287
3833
    def test_add_ssh_machines(self):
4288
 
        client = EnvJujuClient1X(SimpleEnvironment('foo'), None, 'juju')
 
3834
        client = EnvJujuClient1X(SimpleEnvironment('foo'), None, '')
4289
3835
        with patch('subprocess.check_call', autospec=True) as cc_mock:
4290
3836
            client.add_ssh_machines(['m-foo', 'm-bar', 'm-baz'])
4291
3837
        assert_juju_call(self, cc_mock, client, (
4297
3843
        self.assertEqual(cc_mock.call_count, 3)
4298
3844
 
4299
3845
    def test_add_ssh_machines_retry(self):
4300
 
        client = EnvJujuClient1X(SimpleEnvironment('foo'), None, 'juju')
 
3846
        client = EnvJujuClient1X(SimpleEnvironment('foo'), None, '')
4301
3847
        with patch('subprocess.check_call', autospec=True,
4302
3848
                   side_effect=[subprocess.CalledProcessError(None, None),
4303
3849
                                None, None, None]) as cc_mock:
4314
3860
        self.assertEqual(cc_mock.call_count, 4)
4315
3861
 
4316
3862
    def test_add_ssh_machines_fail_on_second_machine(self):
4317
 
        client = EnvJujuClient1X(SimpleEnvironment('foo'), None, 'juju')
 
3863
        client = EnvJujuClient1X(SimpleEnvironment('foo'), None, '')
4318
3864
        with patch('subprocess.check_call', autospec=True, side_effect=[
4319
3865
                None, subprocess.CalledProcessError(None, None), None, None
4320
3866
                ]) as cc_mock:
4327
3873
        self.assertEqual(cc_mock.call_count, 2)
4328
3874
 
4329
3875
    def test_add_ssh_machines_fail_on_second_attempt(self):
4330
 
        client = EnvJujuClient1X(SimpleEnvironment('foo'), None, 'juju')
 
3876
        client = EnvJujuClient1X(SimpleEnvironment('foo'), None, '')
4331
3877
        with patch('subprocess.check_call', autospec=True, side_effect=[
4332
3878
                subprocess.CalledProcessError(None, None),
4333
3879
                subprocess.CalledProcessError(None, None)]) as cc_mock:
4493
4039
                        'jenkins', 'sub1', start=now - timedelta(1200))
4494
4040
 
4495
4041
    def test_wait_for_workload(self):
4496
 
        initial_status = ServiceStatus.from_text("""\
 
4042
        initial_status = Status.from_text("""\
4497
4043
            services:
4498
4044
              jenkins:
4499
4045
                units:
4732
4278
    def test_get_model_config(self):
4733
4279
        env = SimpleEnvironment('foo', None)
4734
4280
        fake_popen = FakePopen(yaml.safe_dump({'bar': 'baz'}), None, 0)
4735
 
        client = EnvJujuClient1X(env, None, 'juju')
 
4281
        client = EnvJujuClient1X(env, None, None)
4736
4282
        with patch('subprocess.Popen', return_value=fake_popen) as po_mock:
4737
4283
            result = client.get_model_config()
4738
4284
        assert_juju_call(
4743
4289
    def test_get_env_option(self):
4744
4290
        env = SimpleEnvironment('foo', None)
4745
4291
        fake_popen = FakePopen('https://example.org/juju/tools', None, 0)
4746
 
        client = EnvJujuClient1X(env, None, 'juju')
 
4292
        client = EnvJujuClient1X(env, None, None)
4747
4293
        with patch('subprocess.Popen', return_value=fake_popen) as mock:
4748
4294
            result = client.get_env_option('tools-metadata-url')
4749
4295
        self.assertEqual(
4754
4300
 
4755
4301
    def test_set_env_option(self):
4756
4302
        env = SimpleEnvironment('foo')
4757
 
        client = EnvJujuClient1X(env, None, 'juju')
 
4303
        client = EnvJujuClient1X(env, None, None)
4758
4304
        with patch('subprocess.check_call') as mock:
4759
4305
            client.set_env_option(
4760
4306
                'tools-metadata-url', 'https://example.org/juju/tools')
4764
4310
            ('juju', '--show-log', 'set-env', '-e', 'foo',
4765
4311
             'tools-metadata-url=https://example.org/juju/tools'))
4766
4312
 
4767
 
    def test_set_testing_agent_metadata_url(self):
 
4313
    def test_set_testing_tools_metadata_url(self):
4768
4314
        env = SimpleEnvironment(None, {'type': 'foo'})
4769
4315
        client = EnvJujuClient1X(env, None, None)
4770
4316
        with patch.object(client, 'get_env_option') as mock_get:
4771
4317
            mock_get.return_value = 'https://example.org/juju/tools'
4772
4318
            with patch.object(client, 'set_env_option') as mock_set:
4773
 
                client.set_testing_agent_metadata_url()
 
4319
                client.set_testing_tools_metadata_url()
4774
4320
        mock_get.assert_called_with('tools-metadata-url')
4775
4321
        mock_set.assert_called_with(
4776
4322
            'tools-metadata-url',
4777
4323
            'https://example.org/juju/testing/tools')
4778
4324
 
4779
 
    def test_set_testing_agent_metadata_url_noop(self):
 
4325
    def test_set_testing_tools_metadata_url_noop(self):
4780
4326
        env = SimpleEnvironment(None, {'type': 'foo'})
4781
4327
        client = EnvJujuClient1X(env, None, None)
4782
4328
        with patch.object(client, 'get_env_option') as mock_get:
4783
4329
            mock_get.return_value = 'https://example.org/juju/testing/tools'
4784
4330
            with patch.object(client, 'set_env_option') as mock_set:
4785
 
                client.set_testing_agent_metadata_url()
 
4331
                client.set_testing_tools_metadata_url()
4786
4332
        mock_get.assert_called_with('tools-metadata-url')
4787
4333
        self.assertEqual(0, mock_set.call_count)
4788
4334
 
4789
4335
    def test_juju(self):
4790
4336
        env = SimpleEnvironment('qux')
4791
 
        client = EnvJujuClient1X(env, None, 'juju')
 
4337
        client = EnvJujuClient1X(env, None, None)
4792
4338
        with patch('subprocess.check_call') as mock:
4793
4339
            client.juju('foo', ('bar', 'baz'))
4794
4340
        environ = dict(os.environ)
4807
4353
 
4808
4354
    def test_juju_no_check(self):
4809
4355
        env = SimpleEnvironment('qux')
4810
 
        client = EnvJujuClient1X(env, None, 'juju')
 
4356
        client = EnvJujuClient1X(env, None, None)
4811
4357
        environ = dict(os.environ)
4812
4358
        environ['JUJU_HOME'] = client.env.juju_home
4813
4359
        with patch('subprocess.call') as mock:
4830
4376
        with patch('subprocess.check_call') as cc_mock:
4831
4377
            client.juju('foo', ('bar', 'baz'), timeout=58)
4832
4378
        self.assertEqual(cc_mock.call_args[0][0], (
4833
 
            sys.executable, get_timeout_path(), '58.00', '--', 'baz',
 
4379
            sys.executable, get_timeout_path(), '58.00', '--', 'juju',
4834
4380
            '--show-log', 'foo', '-e', 'qux', 'bar', 'baz'))
4835
4381
 
4836
4382
    def test_juju_juju_home(self):
4851
4397
 
4852
4398
    def test_juju_extra_env(self):
4853
4399
        env = SimpleEnvironment('qux')
4854
 
        client = EnvJujuClient1X(env, None, 'juju')
 
4400
        client = EnvJujuClient1X(env, None, None)
4855
4401
        extra_env = {'JUJU': '/juju', 'JUJU_HOME': client.env.juju_home}
4856
4402
 
4857
4403
        def check_env(*args, **kwargs):
4945
4491
        with patch('subprocess.Popen') as popen_class_mock:
4946
4492
            with client.juju_async('foo', ('bar', 'baz')) as proc:
4947
4493
                assert_juju_call(self, popen_class_mock, client, (
4948
 
                    'baz', '--show-log', 'foo', '-e', 'qux', 'bar', 'baz'))
 
4494
                    'juju', '--show-log', 'foo', '-e', 'qux', 'bar', 'baz'))
4949
4495
                self.assertIs(proc, popen_class_mock.return_value)
4950
4496
                self.assertEqual(proc.wait.call_count, 0)
4951
4497
                proc.wait.return_value = 0
4961
4507
                    proc_mock.wait.return_value = 23
4962
4508
        self.assertEqual(err_cxt.exception.returncode, 23)
4963
4509
        self.assertEqual(err_cxt.exception.cmd, (
4964
 
            'baz', '--show-log', 'foo', '-e', 'qux', 'bar', 'baz'))
 
4510
            'juju', '--show-log', 'foo', '-e', 'qux', 'bar', 'baz'))
4965
4511
 
4966
4512
    def test_juju_async_environ(self):
4967
4513
        env = SimpleEnvironment('qux')
4987
4533
                   return_value=fake_popen) as po_mock:
4988
4534
            self.assertFalse(client.is_jes_enabled())
4989
4535
        assert_juju_call(self, po_mock, client, (
4990
 
            'baz', '--show-log', 'help', 'commands'))
 
4536
            'juju', '--show-log', 'help', 'commands'))
4991
4537
        # Juju 1.25 uses the system command.
4992
4538
        client = EnvJujuClient1X(env, None, '/foobar/baz')
4993
4539
        fake_popen = FakePopen(SYSTEM, None, 0)
5012
4558
            with self.assertRaises(JESNotSupported):
5013
4559
                client.get_jes_command()
5014
4560
        assert_juju_call(self, po_mock, client, (
5015
 
            'baz', '--show-log', 'help', 'commands'))
 
4561
            'juju', '--show-log', 'help', 'commands'))
5016
4562
        # Juju 2.x uses the 'controller kill' command.
5017
4563
        client = EnvJujuClient1X(env, None, '/foobar/baz')
5018
4564
        fake_popen = FakePopen(CONTROLLER, None, 0)
5107
4653
            client.quickstart('bundle:~juju-qa/some-bundle')
5108
4654
        mock.assert_called_with(
5109
4655
            'quickstart',
5110
 
            ('--constraints', 'mem=2G', '--no-browser',
 
4656
            ('--constraints', 'mem=2G arch=amd64', '--no-browser',
5111
4657
             'bundle:~juju-qa/some-bundle'), False, extra_env={'JUJU': '/juju'}
5112
4658
        )
5113
4659
 
5152
4698
            'INFO The model is environment foo\n',
5153
4699
            self.log_stream.getvalue())
5154
4700
 
5155
 
    def test__get_models(self):
5156
 
        data = """\
5157
 
            - name: foo
5158
 
              model-uuid: aaaa
5159
 
            - name: bar
5160
 
              model-uuid: bbbb
5161
 
        """
5162
 
        env = SimpleEnvironment('foo', {'type': 'local'})
5163
 
        client = fake_juju_client(cls=EnvJujuClient1X, env=env)
5164
 
        with patch.object(client, 'get_juju_output', return_value=data):
5165
 
            models = client._get_models()
5166
 
            self.assertEqual(
5167
 
                [{'name': 'foo', 'model-uuid': 'aaaa'},
5168
 
                 {'name': 'bar', 'model-uuid': 'bbbb'}],
5169
 
                models)
5170
 
 
5171
 
    def test__get_models_exception(self):
5172
 
        env = SimpleEnvironment('foo', {'type': 'local'})
5173
 
        client = fake_juju_client(cls=EnvJujuClient1X, env=env)
5174
 
        with patch.object(client, 'get_juju_output',
5175
 
                          side_effect=subprocess.CalledProcessError('a', 'b')):
5176
 
            self.assertEqual([], client._get_models())
5177
 
 
5178
4701
    def test_get_models(self):
5179
4702
        env = SimpleEnvironment('foo', {'type': 'local'})
5180
4703
        client = EnvJujuClient1X(env, '1.23-series-arch', None)
5196
4719
        self.assertIs(client, model_clients[0])
5197
4720
        self.assertEqual('bar', model_clients[1].env.environment)
5198
4721
 
5199
 
    def test_get_controller_model_name_no_models(self):
 
4722
    def test_get_admin_model_name_no_models(self):
5200
4723
        env = SimpleEnvironment('foo', {'type': 'local'})
5201
4724
        client = EnvJujuClient1X(env, '1.23-series-arch', None)
5202
4725
        with patch.object(client, 'get_models', return_value={}):
5203
 
            controller_name = client.get_controller_model_name()
5204
 
        self.assertEqual('foo', controller_name)
 
4726
            admin_name = client.get_admin_model_name()
 
4727
        self.assertEqual('foo', admin_name)
5205
4728
 
5206
 
    def test_get_controller_client(self):
 
4729
    def test_get_admin_client(self):
5207
4730
        client = EnvJujuClient1X(SimpleEnvironment('foo'), {'bar': 'baz'},
5208
4731
                                 'myhome')
5209
 
        controller_client = client.get_controller_client()
5210
 
        self.assertIs(client, controller_client)
 
4732
        admin_client = client.get_admin_client()
 
4733
        self.assertIs(client, admin_client)
5211
4734
 
5212
4735
    def test_list_controllers(self):
5213
4736
        env = SimpleEnvironment('foo', {'type': 'local'})
5291
4814
            out = client.action_do_fetch("foo/0", "myaction", "param=5")
5292
4815
            self.assertEqual(out, ret)
5293
4816
 
5294
 
    def test_run(self):
5295
 
        env = SimpleEnvironment('name', {}, 'foo')
5296
 
        client = fake_juju_client(cls=EnvJujuClient1X, env=env)
5297
 
        run_list = [
5298
 
            {"MachineId": "1",
5299
 
             "Stdout": "Linux\n",
5300
 
             "ReturnCode": 255,
5301
 
             "Stderr": "Permission denied (publickey,password)"}]
5302
 
        run_output = json.dumps(run_list)
5303
 
        with patch.object(client._backend, 'get_juju_output',
5304
 
                          return_value=run_output) as gjo_mock:
5305
 
            result = client.run(('wname',), applications=['foo', 'bar'])
5306
 
        self.assertEqual(run_list, result)
5307
 
        gjo_mock.assert_called_once_with(
5308
 
            'run', ('--format', 'json', '--service', 'foo,bar', 'wname'),
5309
 
            frozenset(
5310
 
                ['address-allocation', 'migration']),
5311
 
            'foo', 'name', user_name=None)
5312
 
 
5313
4817
    def test_list_space(self):
5314
4818
        client = EnvJujuClient1X(SimpleEnvironment(None, {'type': 'local'}),
5315
4819
                                 '1.23-series-arch', None)
5398
4902
                    Exception, 'Timed out waiting for juju get'):
5399
4903
                client.get_service_config('foo')
5400
4904
 
5401
 
    def test_ssh_keys(self):
5402
 
        client = EnvJujuClient1X(SimpleEnvironment('foo', {}), None, None)
5403
 
        given_output = 'ssh keys output'
5404
 
        with patch.object(client, 'get_juju_output', autospec=True,
5405
 
                          return_value=given_output) as mock:
5406
 
            output = client.ssh_keys()
5407
 
        self.assertEqual(output, given_output)
5408
 
        mock.assert_called_once_with('authorized-keys list')
5409
 
 
5410
 
    def test_ssh_keys_full(self):
5411
 
        client = EnvJujuClient1X(SimpleEnvironment('foo', {}), None, None)
5412
 
        given_output = 'ssh keys full output'
5413
 
        with patch.object(client, 'get_juju_output', autospec=True,
5414
 
                          return_value=given_output) as mock:
5415
 
            output = client.ssh_keys(full=True)
5416
 
        self.assertEqual(output, given_output)
5417
 
        mock.assert_called_once_with('authorized-keys list', '--full')
5418
 
 
5419
 
    def test_add_ssh_key(self):
5420
 
        client = EnvJujuClient1X(SimpleEnvironment('foo', {}), None, None)
5421
 
        with patch.object(client, 'get_juju_output', autospec=True,
5422
 
                          return_value='') as mock:
5423
 
            output = client.add_ssh_key('ak', 'bk')
5424
 
        self.assertEqual(output, '')
5425
 
        mock.assert_called_once_with(
5426
 
            'authorized-keys add', 'ak', 'bk', merge_stderr=True)
5427
 
 
5428
 
    def test_remove_ssh_key(self):
5429
 
        client = EnvJujuClient1X(SimpleEnvironment('foo', {}), None, None)
5430
 
        with patch.object(client, 'get_juju_output', autospec=True,
5431
 
                          return_value='') as mock:
5432
 
            output = client.remove_ssh_key('ak', 'bk')
5433
 
        self.assertEqual(output, '')
5434
 
        mock.assert_called_once_with(
5435
 
            'authorized-keys delete', 'ak', 'bk', merge_stderr=True)
5436
 
 
5437
 
    def test_import_ssh_key(self):
5438
 
        client = EnvJujuClient1X(SimpleEnvironment('foo', {}), None, None)
5439
 
        with patch.object(client, 'get_juju_output', autospec=True,
5440
 
                          return_value='') as mock:
5441
 
            output = client.import_ssh_key('gh:au', 'lp:bu')
5442
 
        self.assertEqual(output, '')
5443
 
        mock.assert_called_once_with(
5444
 
            'authorized-keys import', 'gh:au', 'lp:bu', merge_stderr=True)
5445
 
 
5446
4905
 
5447
4906
class TestUniquifyLocal(TestCase):
5448
4907
 
5735
5194
            'machines': {
5736
5195
                '1': {'foo': 'bar', 'containers': {'1/lxc/0': {'baz': 'qux'}}}
5737
5196
            },
5738
 
            'applications': {}}, '')
 
5197
            'services': {}}, '')
5739
5198
        self.assertEqual(list(status.iter_machines()),
5740
5199
                         [('1', status.status['machines']['1'])])
5741
5200
 
5744
5203
            'machines': {
5745
5204
                '1': {'foo': 'bar', 'containers': {'1/lxc/0': {'baz': 'qux'}}}
5746
5205
            },
5747
 
            'applications': {}}, '')
 
5206
            'services': {}}, '')
5748
5207
        self.assertEqual(list(status.iter_machines(containers=True)), [
5749
5208
            ('1', status.status['machines']['1']),
5750
5209
            ('1/lxc/0', {'baz': 'qux'}),
5751
5210
        ])
5752
5211
 
5753
5212
    def test_agent_items_empty(self):
5754
 
        status = Status({'machines': {}, 'applications': {}}, '')
 
5213
        status = Status({'machines': {}, 'services': {}}, '')
5755
5214
        self.assertItemsEqual([], status.agent_items())
5756
5215
 
5757
5216
    def test_agent_items(self):
5759
5218
            'machines': {
5760
5219
                '1': {'foo': 'bar'}
5761
5220
            },
5762
 
            'applications': {
 
5221
            'services': {
5763
5222
                'jenkins': {
5764
5223
                    'units': {
5765
5224
                        'jenkins/1': {
5784
5243
                    '2': {'qux': 'baz'},
5785
5244
                }}
5786
5245
            },
5787
 
            'applications': {}
 
5246
            'services': {}
5788
5247
        }, '')
5789
5248
        expected = [
5790
5249
            ('1', {'foo': 'bar', 'containers': {'2': {'qux': 'baz'}}}),
5807
5266
                '1': {'agent-state': 'good'},
5808
5267
                '2': {},
5809
5268
            },
5810
 
            'applications': {
 
5269
            'services': {
5811
5270
                'jenkins': {
5812
5271
                    'units': {
5813
5272
                        'jenkins/1': {'agent-state': 'bad'},
5842
5301
                '1': {'agent-state': 'good'},
5843
5302
                '2': {},
5844
5303
            },
5845
 
            'applications': {
 
5304
            'services': {
5846
5305
                'jenkins': {
5847
5306
                    'units': {
5848
5307
                        'jenkins/1': {'agent-state': 'bad'},
5859
5318
            'machines': {
5860
5319
                '1': {},
5861
5320
            },
5862
 
            'applications': {
 
5321
            'services': {
5863
5322
                'jenkins': {
5864
5323
                    'units': {
5865
5324
                        'jenkins/1': {'agent-state': 'bad'},
5884
5343
            'machines': {
5885
5344
                '1': {},
5886
5345
            },
5887
 
            'applications': {
 
5346
            'services': {
5888
5347
                'ubuntu': {},
5889
5348
                'jenkins': {
5890
5349
                    'units': {
5928
5387
            'machines': {
5929
5388
                '1': {},
5930
5389
            },
5931
 
            'applications': {
 
5390
            'services': {
5932
5391
                'jenkins': {
5933
5392
                    'units': {
5934
5393
                        'jenkins/1': {'agent-state': 'bad'},
5950
5409
                '1': {'agent-state': 'good'},
5951
5410
                '2': {},
5952
5411
            },
5953
 
            'applications': {
 
5412
            'services': {
5954
5413
                'jenkins': {
5955
5414
                    'units': {
5956
5415
                        'jenkins/1': {'agent-state': 'bad'},
5972
5431
                '1': {'agent-state': 'good'},
5973
5432
                '2': {},
5974
5433
            },
5975
 
            'applications': {
 
5434
            'services': {
5976
5435
                'jenkins': {
5977
5436
                    'units': {
5978
5437
                        'jenkins/1': {'agent-status': {'current': 'bad'}},
5995
5454
                '1': {'juju-status': {'current': 'good'}},
5996
5455
                '2': {},
5997
5456
            },
5998
 
            'applications': {
 
5457
            'services': {
5999
5458
                'jenkins': {
6000
5459
                    'units': {
6001
5460
                        'jenkins/1': {'juju-status': {'current': 'bad'}},
6018
5477
                '1': {'agent-state': 'good'},
6019
5478
                '2': {},
6020
5479
            },
6021
 
            'applications': {
 
5480
            'services': {
6022
5481
                'jenkins': {
6023
5482
                    'units': {
6024
5483
                        'jenkins/1': {'agent-state': 'bad'},
6036
5495
                '1': {'agent-state': 'started'},
6037
5496
                '2': {'agent-state': 'started'},
6038
5497
            },
6039
 
            'applications': {
 
5498
            'services': {
6040
5499
                'jenkins': {
6041
5500
                    'units': {
6042
5501
                        'jenkins/1': {
6060
5519
                '1': {'agent-state': 'started'},
6061
5520
                '2': {'agent-state': 'started'},
6062
5521
            },
6063
 
            'applications': {
 
5522
            'services': {
6064
5523
                'jenkins': {
6065
5524
                    'units': {
6066
5525
                        'jenkins/1': {
6083
5542
            'machines': {
6084
5543
                '1': {'agent-state': 'any-error'},
6085
5544
            },
6086
 
            'applications': {}
 
5545
            'services': {}
6087
5546
        }, '')
6088
5547
        with self.assertRaisesRegexp(ErroredUnit,
6089
5548
                                     '1 is in state any-error'):
6090
5549
            status.check_agents_started('env1')
6091
5550
 
6092
 
    def do_check_agents_started_agent_state_info_failure(self, failure):
 
5551
    def do_check_agents_started_failure(self, failure):
6093
5552
        status = Status({
6094
5553
            'machines': {'0': {
6095
5554
                'agent-state-info': failure}},
6096
 
            'applications': {},
 
5555
            'services': {},
6097
5556
        }, '')
6098
5557
        with self.assertRaises(ErroredUnit) as e_cxt:
6099
5558
            status.check_agents_started()
6103
5562
        self.assertEqual(e.unit_name, '0')
6104
5563
        self.assertEqual(e.state, failure)
6105
5564
 
6106
 
    def do_check_agents_started_juju_status_failure(self, failure):
6107
 
        status = Status({
6108
 
            'machines': {
6109
 
                '0': {
6110
 
                    'juju-status': {
6111
 
                        'current': 'error',
6112
 
                        'message': failure}
6113
 
                    },
6114
 
                }
6115
 
            }, '')
6116
 
        with self.assertRaises(ErroredUnit) as e_cxt:
6117
 
            status.check_agents_started()
6118
 
        e = e_cxt.exception
6119
 
        # if message is blank, the failure should reflect the state instead
6120
 
        if not failure:
6121
 
            failure = 'error'
6122
 
        self.assertEqual(
6123
 
            str(e), '0 is in state {}'.format(failure))
6124
 
        self.assertEqual(e.unit_name, '0')
6125
 
        self.assertEqual(e.state, failure)
6126
 
 
6127
 
    def do_check_agents_started_info_and_status_failure(self, failure):
6128
 
        status = Status({
6129
 
            'machines': {
6130
 
                '0': {
6131
 
                    'agent-state-info': failure,
6132
 
                    'juju-status': {
6133
 
                        'current': 'error',
6134
 
                        'message': failure}
6135
 
                    },
6136
 
                }
6137
 
            }, '')
6138
 
        with self.assertRaises(ErroredUnit) as e_cxt:
6139
 
            status.check_agents_started()
6140
 
        e = e_cxt.exception
6141
 
        self.assertEqual(
6142
 
            str(e), '0 is in state {}'.format(failure))
6143
 
        self.assertEqual(e.unit_name, '0')
6144
 
        self.assertEqual(e.state, failure)
6145
 
 
6146
 
    def test_check_agents_started_read_juju_status_error(self):
6147
 
        failures = ['no "centos7" images in us-east-1 with arches [amd64]',
6148
 
                    'sending new instance request: GCE operation ' +
6149
 
                    '"operation-143" failed', '']
6150
 
        for failure in failures:
6151
 
            self.do_check_agents_started_juju_status_failure(failure)
6152
 
 
6153
 
    def test_check_agents_started_read_agent_state_info_error(self):
6154
 
        failures = ['cannot set up groups foobar', 'cannot run instance',
6155
 
                    'cannot run instances', 'error executing "lxc-start"']
6156
 
        for failure in failures:
6157
 
            self.do_check_agents_started_agent_state_info_failure(failure)
 
5565
    def test_check_agents_cannot_set_up_groups(self):
 
5566
        self.do_check_agents_started_failure('cannot set up groups foobar')
 
5567
 
 
5568
    def test_check_agents_error(self):
 
5569
        self.do_check_agents_started_failure('error executing "lxc-start"')
 
5570
 
 
5571
    def test_check_agents_cannot_run_instances(self):
 
5572
        self.do_check_agents_started_failure('cannot run instances')
 
5573
 
 
5574
    def test_check_agents_cannot_run_instance(self):
 
5575
        self.do_check_agents_started_failure('cannot run instance')
6158
5576
 
6159
5577
    def test_check_agents_started_agent_info_error(self):
6160
5578
        # Sometimes the error is indicated in a special 'agent-state-info'
6163
5581
            'machines': {
6164
5582
                '1': {'agent-state-info': 'any-error'},
6165
5583
            },
6166
 
            'applications': {}
 
5584
            'services': {}
6167
5585
        }, '')
6168
5586
        with self.assertRaisesRegexp(ErroredUnit,
6169
5587
                                     '1 is in state any-error'):
6175
5593
                '1': {'agent-version': '1.6.2'},
6176
5594
                '2': {'agent-version': '1.6.1'},
6177
5595
            },
6178
 
            'applications': {
 
5596
            'services': {
6179
5597
                'jenkins': {
6180
5598
                    'units': {
6181
5599
                        'jenkins/0': {
6197
5615
                '1': {'juju-status': {'version': '1.6.2'}},
6198
5616
                '2': {'juju-status': {'version': '1.6.1'}},
6199
5617
            },
6200
 
            'applications': {
 
5618
            'services': {
6201
5619
                'jenkins': {
6202
5620
                    'units': {
6203
5621
                        'jenkins/0': {
6248
5666
        self.assertEqual(status.status_text, text)
6249
5667
        self.assertEqual(status.status, {
6250
5668
            'machines': {'0': {'agent-state': 'pending'}},
6251
 
            'applications': {'jenkins': {'units': {'jenkins/0': {
 
5669
            'services': {'jenkins': {'units': {'jenkins/0': {
6252
5670
                'agent-state': 'horsefeathers'}}}}
6253
5671
        })
6254
5672
 
6265
5683
            'machines': {
6266
5684
                '1': {'agent-state': 'started'},
6267
5685
            },
6268
 
            'applications': {
 
5686
            'services': {
6269
5687
                'jenkins': {
6270
5688
                    'units': {
6271
5689
                        'jenkins/0': unit_with_subordinates,
6290
5708
        self.assertIsInstance(gen, types.GeneratorType)
6291
5709
        self.assertEqual(expected, list(gen))
6292
5710
 
6293
 
    def test_get_applications_gets_applications(self):
6294
 
        status = Status({
6295
 
            'services': {'service': {}},
6296
 
            'applications': {'application': {}},
6297
 
            }, '')
6298
 
        self.assertEqual({'application': {}}, status.get_applications())
6299
 
 
6300
 
 
6301
 
class TestServiceStatus(FakeHomeTestCase):
6302
 
 
6303
 
    def test_get_applications_gets_services(self):
6304
 
        status = ServiceStatus({
6305
 
            'services': {'service': {}},
6306
 
            'applications': {'application': {}},
6307
 
            }, '')
6308
 
        self.assertEqual({'service': {}}, status.get_applications())
6309
 
 
6310
5711
 
6311
5712
def fast_timeout(count):
6312
5713
    if False:
6441
5842
                                   {'baz': 'qux'}) as jes_home:
6442
5843
                self.assertFalse(os.path.exists(foo_path))
6443
5844
 
6444
 
    def test_get_cloud_credentials_returns_config(self):
6445
 
        env = SimpleEnvironment(
6446
 
            'foo', {'type': 'ec2', 'region': 'foo'}, 'home')
6447
 
        env.credentials = {'credentials': {
6448
 
            'aws': {'credentials': {'aws': True}},
6449
 
            'azure': {'credentials': {'azure': True}},
6450
 
            }}
6451
 
        self.assertEqual(env.config, env.get_cloud_credentials())
6452
 
 
6453
5845
    def test_dump_yaml(self):
6454
5846
        env = SimpleEnvironment('baz', {'type': 'qux'}, 'home')
6455
5847
        with temp_dir() as path:
6570
5962
            'type': 'manual', 'region': 'bar',
6571
5963
            'bootstrap-host': 'baz'}, 'home').get_region())
6572
5964
 
6573
 
    def test_get_cloud_credentials(self):
6574
 
        juju_data = JujuData('foo', {'type': 'ec2', 'region': 'foo'}, 'home')
6575
 
        juju_data.credentials = {'credentials': {
6576
 
            'aws': {'credentials': {'aws': True}},
6577
 
            'azure': {'credentials': {'azure': True}},
6578
 
            }}
6579
 
        self.assertEqual({'aws': True}, juju_data.get_cloud_credentials())
6580
 
 
6581
5965
    def test_dump_yaml(self):
6582
5966
        cloud_dict = {'clouds': {'foo': {}}}
6583
5967
        credential_dict = {'credential': {'bar': {}}}
6740
6124
        self.assertEqual(sio.getvalue(), changes[-1] + "\n")
6741
6125
 
6742
6126
 
 
6127
class TestMakeClient(TestCase):
 
6128
 
 
6129
    @contextmanager
 
6130
    def make_client_cxt(self):
 
6131
        td = temp_dir()
 
6132
        te = temp_env({'environments': {'foo': {
 
6133
            'orig-name': 'foo', 'name': 'foo'}}})
 
6134
        with td as juju_path, te, patch('subprocess.Popen',
 
6135
                                        side_effect=ValueError):
 
6136
            with patch('subprocess.check_output') as co_mock:
 
6137
                co_mock.return_value = '1.18'
 
6138
                juju_path = os.path.join(juju_path, 'juju')
 
6139
                yield juju_path
 
6140
 
 
6141
    def test_make_client(self):
 
6142
        with self.make_client_cxt() as juju_path:
 
6143
            client = make_client(juju_path, False, 'foo', 'bar')
 
6144
        self.assertEqual(client.full_path, juju_path)
 
6145
        self.assertEqual(client.debug, False)
 
6146
        self.assertEqual(client.env.config['orig-name'], 'foo')
 
6147
        self.assertEqual(client.env.config['name'], 'bar')
 
6148
        self.assertEqual(client.env.environment, 'bar')
 
6149
 
 
6150
    def test_make_client_debug(self):
 
6151
        with self.make_client_cxt() as juju_path:
 
6152
            client = make_client(juju_path, True, 'foo', 'bar')
 
6153
        self.assertEqual(client.debug, True)
 
6154
 
 
6155
    def test_make_client_no_temp_env_name(self):
 
6156
        with self.make_client_cxt() as juju_path:
 
6157
            client = make_client(juju_path, False, 'foo', None)
 
6158
        self.assertEqual(client.full_path, juju_path)
 
6159
        self.assertEqual(client.env.config['orig-name'], 'foo')
 
6160
        self.assertEqual(client.env.config['name'], 'foo')
 
6161
        self.assertEqual(client.env.environment, 'foo')
 
6162
 
 
6163
 
6743
6164
class AssessParseStateServerFromErrorTestCase(TestCase):
6744
6165
 
6745
6166
    def test_parse_new_state_server_from_error(self):