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

« back to all changes in this revision

Viewing changes to tests/test_jujupy.py

  • Committer: Aaron Bentley
  • Date: 2016-05-25 16:12:07 UTC
  • mto: This revision was merged to the branch mainline in revision 1447.
  • Revision ID: aaron.bentley@canonical.com-20160525161207-h5z5uev3jkyewvxt
Update tests to use applications, not services.

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
 
10
from hashlib import sha512
8
11
from itertools import count
9
12
import logging
10
13
import os
44
47
    EnvJujuClient2A1,
45
48
    EnvJujuClient2A2,
46
49
    EnvJujuClient2B2,
 
50
    EnvJujuClient2B3,
 
51
    EnvJujuClient2B7,
47
52
    ErroredUnit,
48
53
    GroupReporter,
49
54
    get_cache_path,
53
58
    jes_home_path,
54
59
    JESByDefault,
55
60
    JESNotSupported,
 
61
    Juju2Backend,
56
62
    JujuData,
57
63
    JUJU_DEV_FEATURE_FLAGS,
58
64
    KILL_CONTROLLER,
61
67
    make_safe_config,
62
68
    parse_new_state_server_from_error,
63
69
    SimpleEnvironment,
 
70
    ServiceStatus,
64
71
    Status,
65
72
    SYSTEM,
66
73
    tear_down,
83
90
__metaclass__ = type
84
91
 
85
92
 
 
93
def c_output(func):
 
94
    def wrapper(*args, **kwargs):
 
95
        result = func(*args, **kwargs)
 
96
        if 'service' in result:
 
97
            raise AssertionError('Result contained service')
 
98
        return result
 
99
    return wrapper
 
100
 
 
101
 
86
102
class AdminOperation(Exception):
87
103
 
88
104
    def __init__(self, operation):
106
122
        self.state = 'not-bootstrapped'
107
123
        self.models = {}
108
124
 
109
 
    def create_model(self, name):
 
125
    def add_model(self, name):
110
126
        state = FakeEnvironmentState()
111
127
        state.name = name
112
128
        self.models[name] = state
114
130
        state.controller.state = 'created'
115
131
        return state
116
132
 
 
133
    def require_admin(self, operation, name):
 
134
        if name != self.admin_model.name:
 
135
            raise AdminOperation(operation)
 
136
 
 
137
    def bootstrap(self, model_name, config, separate_admin):
 
138
        default_model = self.add_model(model_name)
 
139
        default_model.name = model_name
 
140
        if separate_admin:
 
141
            admin_model = default_model.controller.add_model('admin')
 
142
        else:
 
143
            admin_model = default_model
 
144
        self.admin_model = admin_model
 
145
        admin_model.state_servers.append(admin_model.add_machine())
 
146
        self.state = 'bootstrapped'
 
147
        default_model.model_config = copy.deepcopy(config)
 
148
        self.models[default_model.name] = default_model
 
149
        return default_model
 
150
 
117
151
 
118
152
class FakeEnvironmentState:
119
153
    """A Fake environment state that can be used by multiple FakeClients."""
122
156
        self._clear()
123
157
        if controller is not None:
124
158
            self.controller = controller
 
159
        else:
 
160
            self.controller = FakeControllerState()
125
161
 
126
162
    def _clear(self):
127
 
        self.controller = FakeControllerState()
128
163
        self.name = None
129
164
        self.machine_id_iter = count()
130
165
        self.state_servers = []
170
205
        self.remove_machine(machine_id)
171
206
        self.state_servers.remove(machine_id)
172
207
 
173
 
    def bootstrap(self, env, commandline_config, separate_admin):
174
 
        self.name = env.environment
175
 
        if separate_admin:
176
 
            admin_model = self.controller.create_model('admin')
177
 
        else:
178
 
            admin_model = self
179
 
        self.controller.admin_model = admin_model
180
 
        admin_model.state_servers.append(admin_model.add_machine())
181
 
        self.controller.state = 'bootstrapped'
182
 
        self.model_config = copy.deepcopy(env.config)
183
 
        self.model_config.update(commandline_config)
184
 
        self.controller.models[self.name] = self
185
 
 
186
208
    def destroy_environment(self):
187
209
        self._clear()
188
210
        self.controller.state = 'destroyed'
197
219
        self._clear()
198
220
        self.controller.state = 'model-destroyed'
199
221
 
 
222
    def restore_backup(self):
 
223
        self.controller.require_admin('restore', self.name)
 
224
        if len(self.state_servers) > 0:
 
225
            exc = subprocess.CalledProcessError('Operation not permitted', 1,
 
226
                                                2)
 
227
            exc.stderr = 'Operation not permitted'
 
228
            raise exc
 
229
 
 
230
    def enable_ha(self):
 
231
        self.controller.require_admin('enable-ha', self.name)
 
232
        for n in range(2):
 
233
            self.state_servers.append(self.add_machine())
 
234
 
200
235
    def deploy(self, charm_name, service_name):
201
236
        self.add_unit(service_name)
202
237
 
224
259
    def get_status_dict(self):
225
260
        machines = {}
226
261
        for machine_id in self.machines:
227
 
            machine_dict = {}
 
262
            machine_dict = {'juju-status': {'current': 'idle'}}
228
263
            hostname = self.machine_host_names.get(machine_id)
 
264
            machine_dict['instance-id'] = machine_id
229
265
            if hostname is not None:
230
266
                machine_dict['dns-name'] = hostname
231
267
            machines[machine_id] = machine_dict
 
268
            if machine_id in self.state_servers:
 
269
                machine_dict['controller-member-status'] = 'has-vote'
232
270
        for host, containers in self.containers.items():
233
271
            machines[host]['containers'] = dict((c, {}) for c in containers)
234
272
        services = {}
235
273
        for service, units in self.services.items():
236
274
            unit_map = {}
237
275
            for unit_id, machine_id in units:
238
 
                unit_map[unit_id] = {'machine': machine_id}
 
276
                unit_map[unit_id] = {
 
277
                    'machine': machine_id,
 
278
                    'juju-status': {'current': 'idle'}}
239
279
            services[service] = {
240
280
                'units': unit_map,
241
281
                'relations': self.relations.get(service, {}),
242
282
                'exposed': service in self.exposed,
243
283
                }
244
 
        return {'machines': machines, 'services': services}
245
 
 
246
 
 
247
 
class FakeJujuClient:
248
 
    """A fake juju client for tests.
 
284
        return {'machines': machines, 'applications': services}
 
285
 
 
286
 
 
287
class FakeBackend:
 
288
    """A fake juju backend for tests.
249
289
 
250
290
    This is a partial implementation, but should be suitable for many uses,
251
291
    and can be extended.
252
292
 
253
 
    The state is provided by _backing_state, so that multiple clients can
254
 
    manipulate the same state.
 
293
    The state is provided by controller_state, so that multiple clients and
 
294
    backends can manipulate the same state.
255
295
    """
256
 
    def __init__(self, env=None, full_path=None, debug=False,
257
 
                 jes_enabled=False, version='2.0.0'):
258
 
        self._backing_state = FakeEnvironmentState()
259
 
        if env is None:
260
 
            env = SimpleEnvironment('name', {
261
 
                'type': 'foo',
262
 
                'default-series': 'angsty',
263
 
                }, juju_home='foo')
264
 
        self.env = env
 
296
 
 
297
    def __init__(self, controller_state, feature_flags=None, version=None,
 
298
                 full_path=None, debug=False):
 
299
        assert isinstance(controller_state, FakeControllerState)
 
300
        self.controller_state = controller_state
 
301
        if feature_flags is None:
 
302
            feature_flags = set()
 
303
        self.feature_flags = feature_flags
 
304
        self.version = version
265
305
        self.full_path = full_path
266
306
        self.debug = debug
267
 
        self.bootstrap_replaces = {}
268
 
        self._jes_enabled = jes_enabled
269
 
        self._separate_admin = jes_enabled
270
 
        self.version = version
 
307
        self.juju_timings = {}
271
308
 
272
 
    def clone(self, env, full_path=None, debug=None):
 
309
    def clone(self, full_path=None,  version=None, debug=None,
 
310
              feature_flags=None):
 
311
        if version is None:
 
312
            version = self.version
273
313
        if full_path is None:
274
314
            full_path = self.full_path
275
315
        if debug is None:
276
316
            debug = self.debug
277
 
        client = self.__class__(env, full_path, debug,
278
 
                                jes_enabled=self._jes_enabled)
279
 
        client._backing_state = self._backing_state
280
 
        return client
281
 
 
282
 
    def by_version(self, env, path, debug):
283
 
        return FakeJujuClient(env, path, debug)
284
 
 
285
 
    def get_matching_agent_version(self):
286
 
        return '1.2-alpha3'
287
 
 
288
 
    def _acquire_state_client(self, state):
289
 
        if state.name == self.env.environment:
290
 
            return self
291
 
        new_env = self.env.clone(model_name=state.name)
292
 
        new_client = self.clone(new_env)
293
 
        new_client._backing_state = state
294
 
        return new_client
295
 
 
296
 
    def get_admin_client(self):
297
 
        admin_model = self._backing_state.controller.admin_model
298
 
        return self._acquire_state_client(admin_model)
299
 
 
300
 
    def iter_model_clients(self):
301
 
        if not self._jes_enabled:
302
 
            raise JESNotSupported()
303
 
        for state in self._backing_state.controller.models.values():
304
 
            yield self._acquire_state_client(state)
305
 
 
306
 
    def is_jes_enabled(self):
307
 
        return self._jes_enabled
308
 
 
309
 
    def get_cache_path(self):
310
 
        return get_cache_path(self.env.juju_home, models=True)
311
 
 
312
 
    def get_juju_output(self, command, *args, **kwargs):
 
317
        if feature_flags is None:
 
318
            feature_flags = set(self.feature_flags)
 
319
        controller_state = self.controller_state
 
320
        return self.__class__(controller_state, feature_flags, version,
 
321
                              full_path, debug)
 
322
 
 
323
    def set_feature(self, feature, enabled):
 
324
        if enabled:
 
325
            self.feature_flags.add(feature)
 
326
        else:
 
327
            self.feature_flags.discard(feature)
 
328
 
 
329
    def is_feature_enabled(self, feature):
 
330
        if feature == 'jes':
 
331
            return True
 
332
        return bool(feature in self.feature_flags)
 
333
 
 
334
    def deploy(self, model_state, charm_name, service_name=None, series=None):
 
335
        if service_name is None:
 
336
            service_name = charm_name.split(':')[-1].split('/')[-1]
 
337
        model_state.deploy(charm_name, service_name)
 
338
 
 
339
    def bootstrap(self, args):
 
340
        parser = ArgumentParser()
 
341
        parser.add_argument('controller_name')
 
342
        parser.add_argument('cloud_name_region')
 
343
        parser.add_argument('--constraints')
 
344
        parser.add_argument('--config')
 
345
        parser.add_argument('--default-model')
 
346
        parser.add_argument('--agent-version')
 
347
        parser.add_argument('--bootstrap-series')
 
348
        parser.add_argument('--upload-tools', action='store_true')
 
349
        parsed = parser.parse_args(args)
 
350
        with open(parsed.config) as config_file:
 
351
            config = yaml.safe_load(config_file)
 
352
        cloud_region = parsed.cloud_name_region.split('/', 1)
 
353
        cloud = cloud_region[0]
 
354
        # Although they are specified with specific arguments instead of as
 
355
        # config, these values are listed by get-model-config:
 
356
        # name, region, type (from cloud).
 
357
        config['type'] = cloud
 
358
        if len(cloud_region) > 1:
 
359
            config['region'] = cloud_region[1]
 
360
        config['name'] = parsed.default_model
 
361
        if parsed.bootstrap_series is not None:
 
362
            config['default-series'] = parsed.bootstrap_series
 
363
        self.controller_state.bootstrap(parsed.default_model, config,
 
364
                                        self.is_feature_enabled('jes'))
 
365
 
 
366
    def quickstart(self, model_name, config, bundle):
 
367
        default_model = self.controller_state.bootstrap(
 
368
            model_name, config, self.is_feature_enabled('jes'))
 
369
        default_model.deploy_bundle(bundle)
 
370
 
 
371
    def destroy_environment(self, model_name):
 
372
        try:
 
373
            state = self.controller_state.models[model_name]
 
374
        except KeyError:
 
375
            return 0
 
376
        state.destroy_environment()
 
377
        return 0
 
378
 
 
379
    def add_machines(self, model_state, args):
 
380
        if len(args) == 0:
 
381
            return model_state.add_machine()
 
382
        ssh_machines = [a[4:] for a in args if a.startswith('ssh:')]
 
383
        if len(ssh_machines) == len(args):
 
384
            return model_state.add_ssh_machines(ssh_machines)
 
385
        (container_type,) = args
 
386
        model_state.add_container(container_type)
 
387
 
 
388
    def get_admin_model_name(self):
 
389
        return self.controller_state.admin_model.name
 
390
 
 
391
    def make_controller_dict(self, controller_name):
 
392
        admin_model = self.controller_state.admin_model
 
393
        server_id = list(admin_model.state_servers)[0]
 
394
        server_hostname = admin_model.machine_host_names[server_id]
 
395
        api_endpoint = '{}:23'.format(server_hostname)
 
396
        return {controller_name: {'details': {'api-endpoints': [
 
397
            api_endpoint]}}}
 
398
 
 
399
    def list_models(self):
 
400
        model_names = [state.name for state in
 
401
                       self.controller_state.models.values()]
 
402
        return {'models': [{'name': n} for n in model_names]}
 
403
 
 
404
    def juju(self, command, args, used_feature_flags,
 
405
             juju_home, model=None, check=True, timeout=None, extra_env=None):
 
406
        if 'service' in command:
 
407
            raise Exception('No service')
 
408
        if model is not None:
 
409
            model_state = self.controller_state.models[model]
 
410
            if command == 'enable-ha':
 
411
                model_state.enable_ha()
 
412
            if (command, args[:1]) == ('set-config', ('dummy-source',)):
 
413
                name, value = args[1].split('=')
 
414
                if name == 'token':
 
415
                    model_state.token = value
 
416
            if command == 'deploy':
 
417
                parser = ArgumentParser()
 
418
                parser.add_argument('charm_name')
 
419
                parser.add_argument('service_name', nargs='?')
 
420
                parser.add_argument('--to')
 
421
                parser.add_argument('--series')
 
422
                parsed = parser.parse_args(args)
 
423
                self.deploy(model_state, parsed.charm_name,
 
424
                            parsed.service_name, parsed.series)
 
425
            if command == 'remove-application':
 
426
                model_state.destroy_service(*args)
 
427
            if command == 'add-relation':
 
428
                if args[0] == 'dummy-source':
 
429
                    model_state.relations[args[1]] = {'source': [args[0]]}
 
430
            if command == 'expose':
 
431
                (service,) = args
 
432
                model_state.exposed.add(service)
 
433
            if command == 'unexpose':
 
434
                (service,) = args
 
435
                model_state.exposed.remove(service)
 
436
            if command == 'add-unit':
 
437
                (service,) = args
 
438
                model_state.add_unit(service)
 
439
            if command == 'remove-unit':
 
440
                (unit_id,) = args
 
441
                model_state.remove_unit(unit_id)
 
442
            if command == 'add-machine':
 
443
                return self.add_machines(model_state, args)
 
444
            if command == 'remove-machine':
 
445
                parser = ArgumentParser()
 
446
                parser.add_argument('machine_id')
 
447
                parser.add_argument('--force', action='store_true')
 
448
                parsed = parser.parse_args(args)
 
449
                machine_id = parsed.machine_id
 
450
                if '/' in machine_id:
 
451
                    model_state.remove_container(machine_id)
 
452
                else:
 
453
                    model_state.remove_machine(machine_id)
 
454
            if command == 'quickstart':
 
455
                parser = ArgumentParser()
 
456
                parser.add_argument('--constraints')
 
457
                parser.add_argument('--no-browser', action='store_true')
 
458
                parser.add_argument('bundle')
 
459
                parsed = parser.parse_args(args)
 
460
                # Released quickstart doesn't seem to provide the config via
 
461
                # the commandline.
 
462
                self.quickstart(model, {}, parsed.bundle)
 
463
        else:
 
464
            if command == 'bootstrap':
 
465
                self.bootstrap(args)
 
466
            if command == 'kill-controller':
 
467
                if self.controller_state.state == 'not-bootstrapped':
 
468
                    return
 
469
                model = args[0]
 
470
                model_state = self.controller_state.models[model]
 
471
                model_state.kill_controller()
 
472
            if command == 'destroy-model':
 
473
                if not self.is_feature_enabled('jes'):
 
474
                    raise JESNotSupported()
 
475
                model = args[0]
 
476
                model_state = self.controller_state.models[model]
 
477
                model_state.destroy_model()
 
478
            if command == 'add-model':
 
479
                if not self.is_feature_enabled('jes'):
 
480
                    raise JESNotSupported()
 
481
                parser = ArgumentParser()
 
482
                parser.add_argument('-c', '--controller')
 
483
                parser.add_argument('--config')
 
484
                parser.add_argument('model_name')
 
485
                parsed = parser.parse_args(args)
 
486
                self.controller_state.add_model(parsed.model_name)
 
487
 
 
488
    @contextmanager
 
489
    def juju_async(self, command, args, used_feature_flags,
 
490
                   juju_home, model=None, timeout=None):
 
491
        yield
 
492
        self.juju(command, args, used_feature_flags,
 
493
                  juju_home, model, timeout=timeout)
 
494
 
 
495
    @c_output
 
496
    def get_juju_output(self, command, args, used_feature_flags,
 
497
                        juju_home, model=None, timeout=None):
 
498
        if 'service' in command:
 
499
            raise Exception('No service')
 
500
        if model is not None:
 
501
            model_state = self.controller_state.models[model]
313
502
        if (command, args) == ('ssh', ('dummy-sink/0', GET_TOKEN_SCRIPT)):
314
 
            return self._backing_state.token
 
503
            return model_state.token
315
504
        if (command, args) == ('ssh', ('0', 'lsb_release', '-c')):
316
 
            return 'Codename:\t{}\n'.format(self.env.config['default-series'])
317
 
 
318
 
    def juju(self, cmd, args, include_e=True):
319
 
        # TODO: Use argparse or change all call sites to use functions.
320
 
        if (cmd, args[:1]) == ('set', ('dummy-source',)):
321
 
            name, value = args[1].split('=')
322
 
            if name == 'token':
323
 
                self._backing_state.token = value
324
 
        if cmd == 'deploy':
325
 
            self.deploy(*args)
326
 
        if cmd == 'destroy-service':
327
 
            self._backing_state.destroy_service(*args)
328
 
        if cmd == 'add-relation':
329
 
            if args[0] == 'dummy-source':
330
 
                self._backing_state.relations[args[1]] = {'source': [args[0]]}
331
 
        if cmd == 'expose':
332
 
            (service,) = args
333
 
            self._backing_state.exposed.add(service)
334
 
        if cmd == 'unexpose':
335
 
            (service,) = args
336
 
            self._backing_state.exposed.remove(service)
337
 
        if cmd == 'add-unit':
338
 
            (service,) = args
339
 
            self._backing_state.add_unit(service)
340
 
        if cmd == 'remove-unit':
341
 
            (unit_id,) = args
342
 
            self._backing_state.remove_unit(unit_id)
343
 
        if cmd == 'add-machine':
344
 
            (container_type,) = args
345
 
            self._backing_state.add_container(container_type)
346
 
        if cmd == 'remove-machine':
347
 
            (machine_id,) = args
348
 
            if '/' in machine_id:
349
 
                self._backing_state.remove_container(machine_id)
350
 
            else:
351
 
                self._backing_state.remove_machine(machine_id)
352
 
 
353
 
    def bootstrap(self, upload_tools=False, bootstrap_series=None):
354
 
        commandline_config = {}
355
 
        if bootstrap_series is not None:
356
 
            commandline_config['default-series'] = bootstrap_series
357
 
        self._backing_state.bootstrap(self.env, commandline_config,
358
 
                                      self._separate_admin)
359
 
 
360
 
    @contextmanager
361
 
    def bootstrap_async(self, upload_tools=False):
362
 
        yield
363
 
 
364
 
    def quickstart(self, bundle):
365
 
        self._backing_state.bootstrap(self.env, {}, self._separate_admin)
366
 
        self._backing_state.deploy_bundle(bundle)
367
 
 
368
 
    def create_environment(self, controller_client, config_file):
369
 
        if not self._jes_enabled:
370
 
            raise JESNotSupported()
371
 
        model_state = controller_client._backing_state.controller.create_model(
372
 
            self.env.environment)
373
 
        self._backing_state = model_state
374
 
 
375
 
    def destroy_model(self):
376
 
        if not self._jes_enabled:
377
 
            raise JESNotSupported()
378
 
        self._backing_state.destroy_model()
379
 
 
380
 
    def destroy_environment(self, force=True, delete_jenv=False):
381
 
        self._backing_state.destroy_environment()
382
 
        return 0
383
 
 
384
 
    def kill_controller(self):
385
 
        self._backing_state.kill_controller()
386
 
 
387
 
    def add_ssh_machines(self, machines):
388
 
        self._backing_state.add_ssh_machines(machines)
389
 
 
390
 
    def deploy(self, charm_name, service_name=None, series=None):
391
 
        if service_name is None:
392
 
            service_name = charm_name.split(':')[-1].split('/')[-1]
393
 
        self._backing_state.deploy(charm_name, service_name)
394
 
 
395
 
    def remove_service(self, service):
396
 
        self._backing_state.destroy_service(service)
397
 
 
398
 
    def wait_for_started(self, timeout=1200, start=None):
399
 
        return self.get_status()
400
 
 
401
 
    def wait_for_deploy_started(self):
402
 
        pass
403
 
 
404
 
    def show_status(self):
405
 
        pass
406
 
 
407
 
    def get_status(self, admin=False):
408
 
        status_dict = self._backing_state.get_status_dict()
409
 
        return Status(status_dict, yaml.safe_dump(status_dict))
410
 
 
411
 
    def status_until(self, timeout):
412
 
        yield self.get_status()
413
 
 
414
 
    def set_config(self, service, options):
415
 
        option_strings = ['{}={}'.format(*item) for item in options.items()]
416
 
        self.juju('set', (service,) + tuple(option_strings))
417
 
 
418
 
    def get_config(self, service):
419
 
        pass
420
 
 
421
 
    def get_model_config(self):
422
 
        return copy.deepcopy(self._backing_state.model_config)
423
 
 
424
 
    def deployer(self, bundle, name=None):
425
 
        pass
426
 
 
427
 
    def wait_for_workloads(self, timeout=600):
428
 
        pass
429
 
 
430
 
    def get_juju_timings(self):
431
 
        pass
432
 
 
433
 
    def _require_admin(self, operation):
434
 
        if self.get_admin_client() != self:
435
 
            raise AdminOperation(operation)
436
 
 
437
 
    def backup(self):
438
 
        self._require_admin('backup')
439
 
 
440
 
    def restore_backup(self, backup_file):
441
 
        self._require_admin('restore')
442
 
        if len(self._backing_state.state_servers) > 0:
443
 
            exc = subprocess.CalledProcessError('Operation not permitted', 1,
444
 
                                                2)
445
 
            exc.stderr = 'Operation not permitted'
446
 
            raise exc
447
 
 
448
 
    def enable_ha(self):
449
 
        self._require_admin('enable-ha')
450
 
 
451
 
    def wait_for_ha(self):
452
 
        self._require_admin('wait-for-ha')
453
 
 
454
 
    def get_controller_leader(self):
455
 
        return self.get_controller_members()[0]
456
 
 
457
 
    def get_controller_members(self):
458
 
        return [Machine(s, {'instance-id': s})
459
 
                for s in self._backing_state.state_servers]
 
505
            return 'Codename:\t{}\n'.format(
 
506
                model_state.model_config['default-series'])
 
507
        if command == 'get-model-config':
 
508
            return yaml.safe_dump(model_state.model_config)
 
509
        if command == 'restore-backup':
 
510
            model_state.restore_backup()
 
511
        if command == 'show-controller':
 
512
            return yaml.safe_dump(self.make_controller_dict(args[0]))
 
513
        if command == 'list-models':
 
514
            return yaml.safe_dump(self.list_models())
 
515
        if command == ('add-user'):
 
516
            permissions = 'read'
 
517
            if set(["--acl", "write"]).issubset(args):
 
518
                permissions = 'write'
 
519
            username = args[0]
 
520
            model = args[2]
 
521
            code = b64encode(sha512(username).digest())
 
522
            info_string = \
 
523
                'User "{}" added\nUser "{}"granted {} access to model "{}\n"' \
 
524
                .format(username, username, permissions, model)
 
525
            register_string = \
 
526
                'Please send this command to {}\n    juju register {}' \
 
527
                .format(username, code)
 
528
            return info_string + register_string
 
529
        if command == 'show-status':
 
530
            status_dict = model_state.get_status_dict()
 
531
            return yaml.safe_dump(status_dict)
 
532
        if command == 'create-backup':
 
533
            self.controller_state.require_admin('backup', model)
 
534
            return 'juju-backup-0.tar.gz'
 
535
        return ''
 
536
 
 
537
    def pause(self, seconds):
 
538
        pass
 
539
 
 
540
 
 
541
class FakeBackend2B7(FakeBackend):
 
542
 
 
543
    def juju(self, command, args, used_feature_flags,
 
544
             juju_home, model=None, check=True, timeout=None, extra_env=None):
 
545
        if model is not None:
 
546
            model_state = self.controller_state.models[model]
 
547
        if command == 'destroy-service':
 
548
            model_state.destroy_service(*args)
 
549
        if command == 'remove-service':
 
550
            model_state.destroy_service(*args)
 
551
        return super(FakeBackend2B7).juju(command, args, used_feature_flags,
 
552
                                          juju_home, model, check, timeout,
 
553
                                          extra_env)
 
554
 
 
555
 
 
556
class FakeBackendOptionalJES(FakeBackend):
 
557
 
 
558
    def is_feature_enabled(self, feature):
 
559
        return bool(feature in self.feature_flags)
 
560
 
 
561
 
 
562
def fake_juju_client(env=None, full_path=None, debug=False, version='2.0.0',
 
563
                     _backend=None, cls=EnvJujuClient):
 
564
    if env is None:
 
565
        env = JujuData('name', {
 
566
            'type': 'foo',
 
567
            'default-series': 'angsty',
 
568
            'region': 'bar',
 
569
            }, juju_home='foo')
 
570
    juju_home = env.juju_home
 
571
    if juju_home is None:
 
572
        juju_home = 'foo'
 
573
    if _backend is None:
 
574
        backend_state = FakeControllerState()
 
575
        _backend = FakeBackend(
 
576
            backend_state, version=version, full_path=full_path,
 
577
            debug=debug)
 
578
        _backend.set_feature('jes', True)
 
579
    client = cls(
 
580
        env, version, full_path, juju_home, debug, _backend=_backend)
 
581
    client.bootstrap_replaces = {}
 
582
    return client
 
583
 
 
584
 
 
585
def fake_juju_client_optional_jes(env=None, full_path=None, debug=False,
 
586
                                  jes_enabled=True, version='2.0.0',
 
587
                                  _backend=None):
 
588
    if _backend is None:
 
589
        backend_state = FakeControllerState()
 
590
        _backend = FakeBackendOptionalJES(
 
591
            backend_state, version=version, full_path=full_path,
 
592
            debug=debug)
 
593
        _backend.set_feature('jes', jes_enabled)
 
594
    client = fake_juju_client(env, full_path, debug, version, _backend,
 
595
                              cls=FakeJujuClientOptionalJES)
 
596
    client.used_feature_flags = frozenset(['address-allocation', 'jes'])
 
597
    return client
 
598
 
 
599
 
 
600
class FakeJujuClientOptionalJES(EnvJujuClient):
 
601
 
 
602
    def get_admin_model_name(self):
 
603
        return self._backend.controller_state.admin_model.name
460
604
 
461
605
 
462
606
class TestErroredUnit(TestCase):
506
650
                self.assertEqual({'foo': 'bar'}, yaml.safe_load(f))
507
651
 
508
652
 
 
653
class TestJuju2Backend(TestCase):
 
654
 
 
655
    def test_juju2_backend(self):
 
656
        backend = Juju2Backend('/bin/path', '2.0', set(), False)
 
657
        self.assertEqual('/bin/path', backend.full_path)
 
658
        self.assertEqual('2.0', backend.version)
 
659
 
 
660
 
 
661
class TestEnvJujuClient2B7(ClientTest):
 
662
 
 
663
    def test_remove_service(self):
 
664
        env = EnvJujuClient2B7(
 
665
            JujuData('foo', {'type': 'local'}), '1.234-76', None)
 
666
        with patch.object(env, 'juju') as mock_juju:
 
667
            env.remove_service('mondogb')
 
668
        mock_juju.assert_called_with('remove-service', ('mondogb',))
 
669
 
 
670
 
509
671
class TestEnvJujuClient26(ClientTest, CloudSigmaTest):
510
672
 
511
673
    client_class = EnvJujuClient26
594
756
        self.assertEqual(client1.version, client2.version)
595
757
        self.assertEqual(client1.full_path, client2.full_path)
596
758
        self.assertIs(client1.debug, client2.debug)
 
759
        self.assertEqual(client1._backend, client2._backend)
597
760
 
598
761
    def test_clone_changed(self):
599
762
        client1 = self.client_class(
825
988
        vsn.assert_called_once_with(('foo/bar/baz', '--version'))
826
989
 
827
990
    def test_get_matching_agent_version(self):
828
 
        client = EnvJujuClient(JujuData(None, {'type': 'local'}),
829
 
                               '1.23-series-arch', None)
 
991
        client = EnvJujuClient(
 
992
            JujuData(None, {'type': 'local'}, juju_home='foo'),
 
993
            '1.23-series-arch', None)
830
994
        self.assertEqual('1.23.1', client.get_matching_agent_version())
831
995
        self.assertEqual('1.23', client.get_matching_agent_version(
832
996
                         no_build=True))
833
 
        client.version = '1.20-beta1-series-arch'
 
997
        client = client.clone(version='1.20-beta1-series-arch')
834
998
        self.assertEqual('1.20-beta1.1', client.get_matching_agent_version())
835
999
 
836
1000
    def test_upgrade_juju_nonlocal(self):
876
1040
            yield '2.0-beta1'
877
1041
            yield '2.0-beta2'
878
1042
            yield '2.0-beta3'
 
1043
            yield '2.0-beta4'
 
1044
            yield '2.0-beta5'
 
1045
            yield '2.0-beta6'
 
1046
            yield '2.0-beta7'
879
1047
            yield '2.0-delta1'
880
1048
 
881
1049
        context = patch.object(
925
1093
            self.assertIs(type(client), EnvJujuClient2B2)
926
1094
            self.assertEqual(client.version, '2.0-beta2')
927
1095
            client = EnvJujuClient.by_version(None)
928
 
            self.assertIs(type(client), EnvJujuClient)
 
1096
            self.assertIs(type(client), EnvJujuClient2B3)
929
1097
            self.assertEqual(client.version, '2.0-beta3')
930
1098
            client = EnvJujuClient.by_version(None)
 
1099
            self.assertIs(type(client), EnvJujuClient2B3)
 
1100
            self.assertEqual(client.version, '2.0-beta4')
 
1101
            client = EnvJujuClient.by_version(None)
 
1102
            self.assertIs(type(client), EnvJujuClient2B3)
 
1103
            self.assertEqual(client.version, '2.0-beta5')
 
1104
            client = EnvJujuClient.by_version(None)
 
1105
            self.assertIs(type(client), EnvJujuClient2B3)
 
1106
            self.assertEqual(client.version, '2.0-beta6')
 
1107
            client = EnvJujuClient.by_version(None)
 
1108
            self.assertIs(type(client), EnvJujuClient)
 
1109
            self.assertEqual(client.version, '2.0-beta7')
 
1110
            client = EnvJujuClient.by_version(None)
931
1111
            self.assertIs(type(client), EnvJujuClient)
932
1112
            self.assertEqual(client.version, '2.0-delta1')
933
1113
            with self.assertRaises(StopIteration):
957
1137
        self.assertEqual(client1.full_path, client2.full_path)
958
1138
        self.assertIs(client1.debug, client2.debug)
959
1139
        self.assertEqual(client1.feature_flags, client2.feature_flags)
 
1140
        self.assertEqual(client1._backend, client2._backend)
960
1141
 
961
1142
    def test_clone_changed(self):
962
1143
        client1 = EnvJujuClient(JujuData('foo'), '1.27', 'full/path',
993
1174
 
994
1175
    def test_full_args_debug(self):
995
1176
        env = JujuData('foo')
996
 
        client = EnvJujuClient(env, None, 'my/juju/bin')
997
 
        client.debug = True
 
1177
        client = EnvJujuClient(env, None, 'my/juju/bin', debug=True)
998
1178
        full = client._full_args('bar', False, ('baz', 'qux'))
999
1179
        self.assertEqual((
1000
1180
            'juju', '--debug', 'bar', '-m', 'foo', 'baz', 'qux'), full)
1044
1224
            'maas-server': 'foo',
1045
1225
            'manta-key-id': 'foo',
1046
1226
            'manta-user': 'foo',
 
1227
            'management-subscription-id': 'foo',
 
1228
            'management-certificate': 'foo',
1047
1229
            'name': 'foo',
1048
1230
            'password': 'foo',
1049
1231
            'prefer-ipv6': 'foo',
1198
1380
            '--config', 'config', '--default-model', 'foo',
1199
1381
            '--bootstrap-series', 'angsty'))
1200
1382
 
1201
 
    def test_create_environment_hypenated_controller(self):
1202
 
        self.do_create_environment(
1203
 
            'kill-controller', 'create-model', ('-c', 'foo'))
 
1383
    def test_add_model_hypenated_controller(self):
 
1384
        self.do_add_model(
 
1385
            'kill-controller', 'add-model', ('-c', 'foo'))
1204
1386
 
1205
 
    def do_create_environment(self, jes_command, create_cmd,
1206
 
                              controller_option):
 
1387
    def do_add_model(self, jes_command, create_cmd, controller_option):
1207
1388
        controller_client = EnvJujuClient(JujuData('foo'), None, None)
1208
 
        client = EnvJujuClient(JujuData('bar'), None, None)
 
1389
        model_data = JujuData('bar', {'type': 'foo'})
 
1390
        client = EnvJujuClient(model_data, None, None)
1209
1391
        with patch.object(client, 'get_jes_command',
1210
1392
                          return_value=jes_command):
1211
 
            with patch.object(client, 'juju') as juju_mock:
1212
1393
                with patch.object(controller_client, 'juju') as ccj_mock:
1213
 
                    client.create_environment(controller_client, 'temp')
1214
 
        if juju_mock.call_count == 0:
1215
 
            ccj_mock.assert_called_once_with(
1216
 
                create_cmd, controller_option + ('bar', '--config', 'temp'),
1217
 
                include_e=False)
1218
 
        else:
1219
 
            juju_mock.assert_called_once_with(
1220
 
                create_cmd, controller_option + ('bar', '--config', 'temp'),
1221
 
                include_e=False)
 
1394
                    with observable_temp_file() as config_file:
 
1395
                        controller_client.add_model(model_data)
 
1396
        ccj_mock.assert_called_once_with(
 
1397
            create_cmd, controller_option + (
 
1398
                'bar', '--config', config_file.name), include_e=False)
1222
1399
 
1223
1400
    def test_destroy_environment(self):
1224
1401
        env = JujuData('foo')
1388
1565
            machines:
1389
1566
              "0":
1390
1567
                {0}: {1}
1391
 
            services:
 
1568
            applications:
1392
1569
              jenkins:
1393
1570
                units:
1394
1571
                  jenkins/0:
1448
1625
        mock_juju.assert_called_with(
1449
1626
            'deploy', ('local:blah', '--series', 'xenial'))
1450
1627
 
 
1628
    def test_deploy_resource(self):
 
1629
        env = EnvJujuClient(JujuData('foo', {'type': 'local'}), None, None)
 
1630
        with patch.object(env, 'juju') as mock_juju:
 
1631
            env.deploy('local:blah', resource='foo=/path/dir')
 
1632
        mock_juju.assert_called_with(
 
1633
            'deploy', ('local:blah', '--resource', 'foo=/path/dir'))
 
1634
 
 
1635
    def test_attach(self):
 
1636
        env = EnvJujuClient(JujuData('foo', {'type': 'local'}), None, None)
 
1637
        with patch.object(env, 'juju') as mock_juju:
 
1638
            env.attach('foo', resource='foo=/path/dir')
 
1639
        mock_juju.assert_called_with('attach', ('foo', 'foo=/path/dir'))
 
1640
 
 
1641
    def test_list_resources(self):
 
1642
        data = 'resourceid: resource/foo'
 
1643
        client = EnvJujuClient(JujuData('local'), None, None)
 
1644
        with patch.object(
 
1645
                client, 'get_juju_output', return_value=data) as mock_gjo:
 
1646
            status = client.list_resources('foo')
 
1647
        self.assertEqual(status, yaml.safe_load(data))
 
1648
        mock_gjo.assert_called_with(
 
1649
            'list-resources', '--format', 'yaml', 'foo', '--details')
 
1650
 
1451
1651
    def test_deploy_bundle_2x(self):
1452
1652
        client = EnvJujuClient(JujuData('an_env', None),
1453
1653
                               '1.23-series-arch', None)
1456
1656
        mock_juju.assert_called_with(
1457
1657
            'deploy', ('bundle:~juju-qa/some-bundle'), timeout=3600)
1458
1658
 
 
1659
    def test_deploy_bundle_template(self):
 
1660
        client = EnvJujuClient(JujuData('an_env', None),
 
1661
                               '1.23-series-arch', None)
 
1662
        with patch.object(client, 'juju') as mock_juju:
 
1663
            client.deploy_bundle('bundle:~juju-qa/some-{container}-bundle')
 
1664
        mock_juju.assert_called_with(
 
1665
            'deploy', ('bundle:~juju-qa/some-lxd-bundle'), timeout=3600)
 
1666
 
 
1667
    def test_upgrade_charm(self):
 
1668
        env = EnvJujuClient(
 
1669
            JujuData('foo', {'type': 'local'}), '2.34-74', None)
 
1670
        with patch.object(env, 'juju') as mock_juju:
 
1671
            env.upgrade_charm('foo-service',
 
1672
                              '/bar/repository/angsty/mongodb')
 
1673
        mock_juju.assert_called_once_with(
 
1674
            'upgrade-charm', ('foo-service', '--path',
 
1675
                              '/bar/repository/angsty/mongodb',))
 
1676
 
1459
1677
    def test_remove_service(self):
1460
1678
        env = EnvJujuClient(
1461
1679
            JujuData('foo', {'type': 'local'}), '1.234-76', None)
1462
1680
        with patch.object(env, 'juju') as mock_juju:
1463
1681
            env.remove_service('mondogb')
1464
 
        mock_juju.assert_called_with('remove-service', ('mondogb',))
 
1682
        mock_juju.assert_called_with('remove-application', ('mondogb',))
1465
1683
 
1466
1684
    def test_status_until_always_runs_once(self):
1467
1685
        client = EnvJujuClient(
1695
1913
            machines:
1696
1914
              "0":
1697
1915
                agent-state: started
1698
 
            services:
 
1916
            applications:
1699
1917
              jenkins:
1700
1918
                units:
1701
1919
                  jenkins/0:
1718
1936
            machines:
1719
1937
              "0":
1720
1938
                agent-state: started
1721
 
            services:
 
1939
            applications:
1722
1940
              jenkins:
1723
1941
                units:
1724
1942
                  jenkins/0:
1736
1954
 
1737
1955
    def test_wait_for_workload(self):
1738
1956
        initial_status = Status.from_text("""\
1739
 
            services:
 
1957
            applications:
1740
1958
              jenkins:
1741
1959
                units:
1742
1960
                  jenkins/0:
1748
1966
                        current: unknown
1749
1967
        """)
1750
1968
        final_status = Status(copy.deepcopy(initial_status.status), None)
1751
 
        final_status.status['services']['jenkins']['units']['jenkins/0'][
 
1969
        final_status.status['applications']['jenkins']['units']['jenkins/0'][
1752
1970
            'workload-status']['current'] = 'active'
1753
1971
        client = EnvJujuClient(JujuData('local'), None, None)
1754
1972
        writes = []
2088
2306
            'machines': {
2089
2307
                '0': {'agent-state': 'started'},
2090
2308
            },
2091
 
            'services': {
 
2309
            'applications': {
2092
2310
                'jenkins': {
2093
2311
                    'units': {
2094
2312
                        'jenkins/1': {'baz': 'qux'}
2105
2323
            'machines': {
2106
2324
                '0': {'agent-state': 'started'},
2107
2325
            },
2108
 
            'services': {},
 
2326
            'applications': {},
2109
2327
        })
2110
2328
        client = EnvJujuClient(JujuData('local'), None, None)
2111
2329
        with patch('jujupy.until_timeout', lambda x: range(0)):
2268
2486
        mock.assert_called_with(('juju', '--show-log', 'foo', '-m', 'qux',
2269
2487
                                 'bar', 'baz'))
2270
2488
 
 
2489
    def test_expect_returns_pexpect_spawn_object(self):
 
2490
        env = JujuData('qux')
 
2491
        client = EnvJujuClient(env, None, None)
 
2492
        with patch('pexpect.spawn') as mock:
 
2493
            process = client.expect('foo', ('bar', 'baz'))
 
2494
 
 
2495
        self.assertIs(process, mock.return_value)
 
2496
        mock.assert_called_once_with('juju --show-log foo -m qux bar baz')
 
2497
 
 
2498
    def test_expect_uses_provided_envvar_path(self):
 
2499
        from pexpect import ExceptionPexpect
 
2500
        env = JujuData('qux')
 
2501
        client = EnvJujuClient(env, None, None)
 
2502
 
 
2503
        with temp_dir() as empty_path:
 
2504
            broken_envvars = dict(PATH=empty_path)
 
2505
            self.assertRaises(
 
2506
                ExceptionPexpect,
 
2507
                client.expect,
 
2508
                'ls', (), extra_env=broken_envvars,
 
2509
                )
 
2510
 
2271
2511
    def test_juju_env(self):
2272
2512
        env = JujuData('qux')
2273
2513
        client = EnvJujuClient(env, None, '/foobar/baz')
2338
2578
        env = JujuData('qux')
2339
2579
        client = EnvJujuClient(env, None, '/foobar/baz')
2340
2580
 
2341
 
        def check_env(*args, **kwargs):
2342
 
            return 'foojuju-backup-24.tgzz'
2343
 
        with patch('subprocess.check_output',
2344
 
                   side_effect=check_env) as co_mock:
 
2581
        with patch(
 
2582
                'subprocess.Popen',
 
2583
                return_value=FakePopen('foojuju-backup-24.tgzz', '', 0),
 
2584
                ) as popen_mock:
2345
2585
            backup_file = client.backup()
2346
2586
        self.assertEqual(backup_file, os.path.abspath('juju-backup-24.tgz'))
2347
 
        assert_juju_call(self, co_mock, client, ('juju', '--show-log',
 
2587
        assert_juju_call(self, popen_mock, client, ('juju', '--show-log',
2348
2588
                         'create-backup', '-m', 'qux'))
2349
2589
 
2350
2590
    def test_juju_backup_with_tar_gz(self):
2351
2591
        env = JujuData('qux')
2352
2592
        client = EnvJujuClient(env, None, '/foobar/baz')
2353
 
        with patch('subprocess.check_output',
2354
 
                   return_value='foojuju-backup-123-456.tar.gzbar'):
 
2593
        with patch('subprocess.Popen',
 
2594
                   return_value=FakePopen(
 
2595
                       'foojuju-backup-123-456.tar.gzbar', '', 0)):
2355
2596
            backup_file = client.backup()
2356
2597
        self.assertEqual(
2357
2598
            backup_file, os.path.abspath('juju-backup-123-456.tar.gz'))
2359
2600
    def test_juju_backup_no_file(self):
2360
2601
        env = JujuData('qux')
2361
2602
        client = EnvJujuClient(env, None, '/foobar/baz')
2362
 
        with patch('subprocess.check_output', return_value=''):
 
2603
        with patch('subprocess.Popen', return_value=FakePopen('', '', 0)):
2363
2604
            with self.assertRaisesRegexp(
2364
2605
                    Exception, 'The backup file was not found in output'):
2365
2606
                client.backup()
2367
2608
    def test_juju_backup_wrong_file(self):
2368
2609
        env = JujuData('qux')
2369
2610
        client = EnvJujuClient(env, None, '/foobar/baz')
2370
 
        with patch('subprocess.check_output',
2371
 
                   return_value='mumu-backup-24.tgz'):
 
2611
        with patch('subprocess.Popen',
 
2612
                   return_value=FakePopen('mumu-backup-24.tgz', '', 0)):
2372
2613
            with self.assertRaisesRegexp(
2373
2614
                    Exception, 'The backup file was not found in output'):
2374
2615
                client.backup()
2380
2621
 
2381
2622
        def side_effect(*args, **kwargs):
2382
2623
            self.assertEqual(environ, os.environ)
2383
 
            return 'foojuju-backup-123-456.tar.gzbar'
2384
 
        with patch('subprocess.check_output', side_effect=side_effect):
 
2624
            return FakePopen('foojuju-backup-123-456.tar.gzbar', '', 0)
 
2625
        with patch('subprocess.Popen', side_effect=side_effect):
2385
2626
            client.backup()
2386
2627
            self.assertNotEqual(environ, os.environ)
2387
2628
 
2476
2717
    def test_get_juju_timings(self):
2477
2718
        env = JujuData('foo')
2478
2719
        client = EnvJujuClient(env, None, 'my/juju/bin')
2479
 
        client.juju_timings = {("juju", "op1"): [1], ("juju", "op2"): [2]}
 
2720
        client._backend.juju_timings = {("juju", "op1"): [1],
 
2721
                                        ("juju", "op2"): [2]}
2480
2722
        flattened_timings = client.get_juju_timings()
2481
2723
        expected = {"juju op1": [1], "juju op2": [2]}
2482
2724
        self.assertEqual(flattened_timings, expected)
2483
2725
 
2484
2726
    def test_deployer(self):
2485
 
        client = EnvJujuClient(JujuData(None, {'type': 'local'}),
 
2727
        client = EnvJujuClient(JujuData('foo', {'type': 'local'}),
2486
2728
                               '1.23-series-arch', None)
2487
2729
        with patch.object(EnvJujuClient, 'juju') as mock:
2488
2730
            client.deployer('bundle:~juju-qa/some-bundle')
2489
2731
        mock.assert_called_with(
2490
 
            'deployer', ('--debug', '--deploy-delay', '10', '--timeout',
2491
 
                         '3600', '--config', 'bundle:~juju-qa/some-bundle'),
2492
 
            True
 
2732
            'deployer', ('-e', 'local.foo:foo', '--debug', '--deploy-delay',
 
2733
                         '10', '--timeout', '3600', '--config',
 
2734
                         'bundle:~juju-qa/some-bundle'),
 
2735
            True, include_e=False
2493
2736
        )
2494
2737
 
2495
2738
    def test_deployer_with_bundle_name(self):
2496
 
        client = EnvJujuClient(JujuData(None, {'type': 'local'}),
2497
 
                               '1.23-series-arch', None)
 
2739
        client = EnvJujuClient(JujuData('foo', {'type': 'local'}),
 
2740
                               '2.0.0-series-arch', None)
2498
2741
        with patch.object(EnvJujuClient, 'juju') as mock:
2499
2742
            client.deployer('bundle:~juju-qa/some-bundle', 'name')
2500
2743
        mock.assert_called_with(
2501
 
            'deployer', ('--debug', '--deploy-delay', '10', '--timeout',
2502
 
                         '3600', '--config', 'bundle:~juju-qa/some-bundle',
2503
 
                         'name'),
2504
 
            True
 
2744
            'deployer', ('-e', 'local.foo:foo', '--debug', '--deploy-delay',
 
2745
                         '10', '--timeout', '3600', '--config',
 
2746
                         'bundle:~juju-qa/some-bundle', 'name'),
 
2747
            True, include_e=False
2505
2748
        )
2506
2749
 
2507
2750
    def test_quickstart_maas(self):
2537
2780
             'bundle:~juju-qa/some-bundle'), False, extra_env={'JUJU': '/juju'}
2538
2781
        )
2539
2782
 
 
2783
    def test_quickstart_template(self):
 
2784
        client = EnvJujuClient(JujuData(None, {'type': 'local'}),
 
2785
                               '1.23-series-arch', '/juju')
 
2786
        with patch.object(EnvJujuClient, 'juju') as mock:
 
2787
            client.quickstart('bundle:~juju-qa/some-{container}-bundle')
 
2788
        mock.assert_called_with(
 
2789
            'quickstart', (
 
2790
                '--constraints', 'mem=2G', '--no-browser',
 
2791
                'bundle:~juju-qa/some-lxd-bundle'),
 
2792
            True, extra_env={'JUJU': '/juju'})
 
2793
 
2540
2794
    def test_action_do(self):
2541
2795
        client = EnvJujuClient(JujuData(None, {'type': 'local'}),
2542
2796
                               '1.23-series-arch', None)
2699
2953
            client.enable_feature('nomongo')
2700
2954
        self.assertEqual(str(ctx.exception), "Unknown feature flag: 'nomongo'")
2701
2955
 
 
2956
    def test_is_juju1x(self):
 
2957
        client = EnvJujuClient(None, '1.25.5', None)
 
2958
        self.assertTrue(client.is_juju1x())
 
2959
 
 
2960
    def test_is_juju1x_false(self):
 
2961
        client = EnvJujuClient(None, '2.0.0', None)
 
2962
        self.assertFalse(client.is_juju1x())
 
2963
 
 
2964
    def test__get_register_command(self):
 
2965
        output = ''.join(['User "x" added\nUser "x" granted read access ',
 
2966
                          'to model "y"\nPlease send this command to x:\n',
 
2967
                          '    juju register AaBbCc'])
 
2968
        output_cmd = 'juju register AaBbCc'
 
2969
        fake_client = fake_juju_client()
 
2970
        register_cmd = fake_client._get_register_command(output)
 
2971
        self.assertEqual(register_cmd, output_cmd)
 
2972
 
 
2973
    def test_revoke(self):
 
2974
        fake_client = fake_juju_client()
 
2975
        username = 'fakeuser'
 
2976
        model = 'foo'
 
2977
        default_permissions = 'read'
 
2978
        default_model = fake_client.model_name
 
2979
        default_controller = fake_client.env.controller.name
 
2980
 
 
2981
        with patch.object(fake_client, 'juju', return_value=True):
 
2982
            fake_client.revoke(username)
 
2983
            fake_client.juju.assert_called_with('revoke',
 
2984
                                                ('-c', default_controller,
 
2985
                                                 username, default_model,
 
2986
                                                 '--acl', default_permissions),
 
2987
                                                include_e=False)
 
2988
 
 
2989
            fake_client.revoke(username, model)
 
2990
            fake_client.juju.assert_called_with('revoke',
 
2991
                                                ('-c', default_controller,
 
2992
                                                 username, model,
 
2993
                                                 '--acl', default_permissions),
 
2994
                                                include_e=False)
 
2995
 
 
2996
            fake_client.revoke(username, model, permissions='write')
 
2997
            fake_client.juju.assert_called_with('revoke',
 
2998
                                                ('-c', default_controller,
 
2999
                                                 username, model,
 
3000
                                                 '--acl', 'write'),
 
3001
                                                include_e=False)
 
3002
 
 
3003
    def test_add_user(self):
 
3004
        fake_client = fake_juju_client()
 
3005
        username = 'fakeuser'
 
3006
        model = 'foo'
 
3007
        permissions = 'write'
 
3008
 
 
3009
        output = fake_client.add_user(username)
 
3010
        self.assertTrue(output.startswith('juju register'))
 
3011
 
 
3012
        output = fake_client.add_user(username, model)
 
3013
        self.assertTrue(output.startswith('juju register'))
 
3014
 
 
3015
        output = fake_client.add_user(username, model, permissions)
 
3016
        self.assertTrue(output.startswith('juju register'))
 
3017
 
 
3018
 
 
3019
class TestEnvJujuClient2B3(ClientTest):
 
3020
 
 
3021
    def test_add_model_hypenated_controller(self):
 
3022
        self.do_add_model(
 
3023
            'kill-controller', 'create-model', ('-c', 'foo'))
 
3024
 
 
3025
    def do_add_model(self, jes_command, create_cmd, controller_option):
 
3026
        controller_client = EnvJujuClient2B3(JujuData('foo'), None, None)
 
3027
        model_data = JujuData('bar', {'type': 'foo'})
 
3028
        client = EnvJujuClient2B3(model_data, None, None)
 
3029
        with patch.object(client, 'get_jes_command',
 
3030
                          return_value=jes_command):
 
3031
                with patch.object(controller_client, 'juju') as ccj_mock:
 
3032
                    with observable_temp_file() as config_file:
 
3033
                        controller_client.add_model(model_data)
 
3034
        ccj_mock.assert_called_once_with(
 
3035
            create_cmd, controller_option + (
 
3036
                'bar', '--config', config_file.name), include_e=False)
 
3037
 
2702
3038
 
2703
3039
class TestEnvJujuClient2B2(ClientTest):
2704
3040
 
2890
3226
        self.assertEqual('1.23.1', client.get_matching_agent_version())
2891
3227
        self.assertEqual('1.23', client.get_matching_agent_version(
2892
3228
                         no_build=True))
2893
 
        client.version = '1.20-beta1-series-arch'
 
3229
        client = client.clone(version='1.20-beta1-series-arch')
2894
3230
        self.assertEqual('1.20-beta1.1', client.get_matching_agent_version())
2895
3231
 
2896
3232
    def test_upgrade_juju_nonlocal(self):
2942
3278
            yield '2.0-beta1'
2943
3279
            yield '2.0-beta2'
2944
3280
            yield '2.0-beta3'
 
3281
            yield '2.0-beta4'
 
3282
            yield '2.0-beta5'
 
3283
            yield '2.0-beta6'
 
3284
            yield '2.0-beta7'
2945
3285
            yield '2.0-delta1'
2946
3286
 
2947
3287
        context = patch.object(
2991
3331
            self.assertIs(type(client), EnvJujuClient2B2)
2992
3332
            self.assertEqual(client.version, '2.0-beta2')
2993
3333
            client = EnvJujuClient1X.by_version(None)
2994
 
            self.assertIs(type(client), EnvJujuClient)
 
3334
            self.assertIs(type(client), EnvJujuClient2B3)
2995
3335
            self.assertEqual(client.version, '2.0-beta3')
2996
3336
            client = EnvJujuClient1X.by_version(None)
 
3337
            self.assertIs(type(client), EnvJujuClient2B3)
 
3338
            self.assertEqual(client.version, '2.0-beta4')
 
3339
            client = EnvJujuClient1X.by_version(None)
 
3340
            self.assertIs(type(client), EnvJujuClient2B3)
 
3341
            self.assertEqual(client.version, '2.0-beta5')
 
3342
            client = EnvJujuClient1X.by_version(None)
 
3343
            self.assertIs(type(client), EnvJujuClient2B3)
 
3344
            self.assertEqual(client.version, '2.0-beta6')
 
3345
            client = EnvJujuClient1X.by_version(None)
 
3346
            self.assertIs(type(client), EnvJujuClient)
 
3347
            self.assertEqual(client.version, '2.0-beta7')
 
3348
            client = EnvJujuClient1X.by_version(None)
2997
3349
            self.assertIs(type(client), EnvJujuClient)
2998
3350
            self.assertEqual(client.version, '2.0-delta1')
2999
3351
            with self.assertRaises(StopIteration):
3034
3386
 
3035
3387
    def test_full_args_debug(self):
3036
3388
        env = SimpleEnvironment('foo')
3037
 
        client = EnvJujuClient1X(env, None, 'my/juju/bin')
3038
 
        client.debug = True
 
3389
        client = EnvJujuClient1X(env, None, 'my/juju/bin', debug=True)
3039
3390
        full = client._full_args('bar', False, ('baz', 'qux'))
3040
3391
        self.assertEqual((
3041
3392
            'juju', '--debug', 'bar', '-e', 'foo', 'baz', 'qux'), full)
3165
3516
 
3166
3517
    def do_create_environment(self, jes_command, create_cmd,
3167
3518
                              controller_option):
3168
 
        controller_client = EnvJujuClient1X(SimpleEnvironment('foo'), None,
 
3519
        controller_client = EnvJujuClient1X(SimpleEnvironment('foo'), '1.26.1',
3169
3520
                                            None)
3170
 
        client = EnvJujuClient1X(SimpleEnvironment('bar'), None, None)
3171
 
        with patch.object(client, 'get_jes_command',
 
3521
        model_env = SimpleEnvironment('bar', {'type': 'foo'})
 
3522
        with patch.object(controller_client, 'get_jes_command',
3172
3523
                          return_value=jes_command):
3173
 
            with patch.object(client, 'juju') as juju_mock:
3174
 
                client.create_environment(controller_client, 'temp')
 
3524
            with patch.object(controller_client, 'juju') as juju_mock:
 
3525
                with observable_temp_file() as config_file:
 
3526
                    controller_client.add_model(model_env)
3175
3527
        juju_mock.assert_called_once_with(
3176
 
            create_cmd, controller_option + ('bar', '--config', 'temp'),
3177
 
            include_e=False)
 
3528
            create_cmd, controller_option + (
 
3529
                'bar', '--config', config_file.name), include_e=False)
3178
3530
 
3179
3531
    def test_destroy_environment_non_sudo(self):
3180
3532
        env = SimpleEnvironment('foo')
3337
3689
            result = client.get_status()
3338
3690
        gjo_mock.assert_called_once_with(
3339
3691
            'status', '--format', 'yaml', admin=False)
3340
 
        self.assertEqual(Status, type(result))
 
3692
        self.assertEqual(ServiceStatus, type(result))
3341
3693
        self.assertEqual(['a', 'b', 'c'], result.status)
3342
3694
 
3343
3695
    def test_get_status_retries_on_error(self):
3443
3795
        mock_juju.assert_called_with(
3444
3796
            'deploy', ('local:mondogb', 'my-mondogb',))
3445
3797
 
 
3798
    def test_upgrade_charm(self):
 
3799
        client = EnvJujuClient1X(
 
3800
            SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None)
 
3801
        with patch.object(client, 'juju') as mock_juju:
 
3802
            client.upgrade_charm('foo-service',
 
3803
                                 '/bar/repository/angsty/mongodb')
 
3804
        mock_juju.assert_called_once_with(
 
3805
            'upgrade-charm', ('foo-service', '--repository',
 
3806
                              '/bar/repository',))
 
3807
 
3446
3808
    def test_remove_service(self):
3447
3809
        env = EnvJujuClient1X(
3448
3810
            SimpleEnvironment('foo', {'type': 'local'}), '1.234-76', None)
3687
4049
                        'jenkins', 'sub1', start=now - timedelta(1200))
3688
4050
 
3689
4051
    def test_wait_for_workload(self):
3690
 
        initial_status = Status.from_text("""\
 
4052
        initial_status = ServiceStatus.from_text("""\
3691
4053
            services:
3692
4054
              jenkins:
3693
4055
                units:
4230
4592
    def test_get_juju_timings(self):
4231
4593
        env = SimpleEnvironment('foo')
4232
4594
        client = EnvJujuClient1X(env, None, 'my/juju/bin')
4233
 
        client.juju_timings = {("juju", "op1"): [1], ("juju", "op2"): [2]}
 
4595
        client._backend.juju_timings = {("juju", "op1"): [1],
 
4596
                                        ("juju", "op2"): [2]}
4234
4597
        flattened_timings = client.get_juju_timings()
4235
4598
        expected = {"juju op1": [1], "juju op2": [2]}
4236
4599
        self.assertEqual(flattened_timings, expected)
4246
4609
            False
4247
4610
        )
4248
4611
 
 
4612
    def test_deploy_bundle_template(self):
 
4613
        client = EnvJujuClient1X(SimpleEnvironment('an_env', None),
 
4614
                                 '1.23-series-arch', None)
 
4615
        with patch.object(client, 'juju') as mock_juju:
 
4616
            client.deploy_bundle('bundle:~juju-qa/some-{container}-bundle')
 
4617
        mock_juju.assert_called_with(
 
4618
            'deployer', (
 
4619
                '--debug', '--deploy-delay', '10', '--timeout', '3600',
 
4620
                '--config', 'bundle:~juju-qa/some-lxc-bundle',
 
4621
                ),
 
4622
            False)
 
4623
 
4249
4624
    def test_deployer(self):
4250
4625
        client = EnvJujuClient1X(SimpleEnvironment(None, {'type': 'local'}),
4251
4626
                                 '1.23-series-arch', None)
4257
4632
            True
4258
4633
        )
4259
4634
 
 
4635
    def test_deployer_template(self):
 
4636
        client = EnvJujuClient1X(SimpleEnvironment(None, {'type': 'local'}),
 
4637
                                 '1.23-series-arch', None)
 
4638
        with patch.object(EnvJujuClient1X, 'juju') as mock:
 
4639
            client.deployer('bundle:~juju-qa/some-{container}-bundle')
 
4640
        mock.assert_called_with(
 
4641
            'deployer', (
 
4642
                '--debug', '--deploy-delay', '10', '--timeout', '3600',
 
4643
                '--config', 'bundle:~juju-qa/some-lxc-bundle',
 
4644
                ), True
 
4645
        )
 
4646
 
4260
4647
    def test_deployer_with_bundle_name(self):
4261
4648
        client = EnvJujuClient1X(SimpleEnvironment(None, {'type': 'local'}),
4262
4649
                                 '1.23-series-arch', None)
4302
4689
             'bundle:~juju-qa/some-bundle'), False, extra_env={'JUJU': '/juju'}
4303
4690
        )
4304
4691
 
 
4692
    def test_quickstart_template(self):
 
4693
        client = EnvJujuClient1X(SimpleEnvironment(None, {'type': 'local'}),
 
4694
                                 '1.23-series-arch', '/juju')
 
4695
        with patch.object(EnvJujuClient1X, 'juju') as mock:
 
4696
            client.quickstart('bundle:~juju-qa/some-{container}-bundle')
 
4697
        mock.assert_called_with(
 
4698
            'quickstart', (
 
4699
                '--constraints', 'mem=2G', '--no-browser',
 
4700
                'bundle:~juju-qa/some-lxc-bundle'),
 
4701
            True, extra_env={'JUJU': '/juju'})
 
4702
 
4305
4703
    def test_list_models(self):
4306
4704
        env = SimpleEnvironment('foo', {'type': 'local'})
4307
4705
        client = EnvJujuClient1X(env, '1.23-series-arch', None)
4601
4999
class TestMakeSafeConfig(TestCase):
4602
5000
 
4603
5001
    def test_default(self):
4604
 
        client = FakeJujuClient(SimpleEnvironment('foo', {'type': 'bar'}))
 
5002
        client = fake_juju_client(JujuData('foo', {'type': 'bar'},
 
5003
                                           juju_home='foo'),
 
5004
                                  version='1.2-alpha3-asdf-asdf')
4605
5005
        config = make_safe_config(client)
4606
5006
        self.assertEqual({
4607
5007
            'name': 'foo',
4612
5012
 
4613
5013
    def test_local(self):
4614
5014
        with temp_dir() as juju_home:
4615
 
            env = SimpleEnvironment('foo', {'type': 'local'},
4616
 
                                    juju_home=juju_home)
4617
 
            client = FakeJujuClient(env)
 
5015
            env = JujuData('foo', {'type': 'local'}, juju_home=juju_home)
 
5016
            client = fake_juju_client(env)
4618
5017
            with patch('jujupy.check_free_disk_space'):
4619
5018
                config = make_safe_config(client)
4620
5019
        self.assertEqual(get_local_root(client.env.juju_home, client.env),
4621
5020
                         config['root-dir'])
4622
5021
 
4623
5022
    def test_bootstrap_replaces_agent_version(self):
4624
 
        client = FakeJujuClient(SimpleEnvironment('foo', {'type': 'bar'}))
 
5023
        client = fake_juju_client(JujuData('foo', {'type': 'bar'},
 
5024
                                  juju_home='foo'))
4625
5025
        client.bootstrap_replaces = {'agent-version'}
4626
5026
        self.assertNotIn('agent-version', make_safe_config(client))
4627
5027
        client.env.config['agent-version'] = '1.23'
4804
5204
            'machines': {
4805
5205
                '1': {'foo': 'bar', 'containers': {'1/lxc/0': {'baz': 'qux'}}}
4806
5206
            },
4807
 
            'services': {}}, '')
 
5207
            'applications': {}}, '')
4808
5208
        self.assertEqual(list(status.iter_machines()),
4809
5209
                         [('1', status.status['machines']['1'])])
4810
5210
 
4813
5213
            'machines': {
4814
5214
                '1': {'foo': 'bar', 'containers': {'1/lxc/0': {'baz': 'qux'}}}
4815
5215
            },
4816
 
            'services': {}}, '')
 
5216
            'applications': {}}, '')
4817
5217
        self.assertEqual(list(status.iter_machines(containers=True)), [
4818
5218
            ('1', status.status['machines']['1']),
4819
5219
            ('1/lxc/0', {'baz': 'qux'}),
4820
5220
        ])
4821
5221
 
4822
5222
    def test_agent_items_empty(self):
4823
 
        status = Status({'machines': {}, 'services': {}}, '')
 
5223
        status = Status({'machines': {}, 'applications': {}}, '')
4824
5224
        self.assertItemsEqual([], status.agent_items())
4825
5225
 
4826
5226
    def test_agent_items(self):
4828
5228
            'machines': {
4829
5229
                '1': {'foo': 'bar'}
4830
5230
            },
4831
 
            'services': {
 
5231
            'applications': {
4832
5232
                'jenkins': {
4833
5233
                    'units': {
4834
5234
                        'jenkins/1': {
4853
5253
                    '2': {'qux': 'baz'},
4854
5254
                }}
4855
5255
            },
4856
 
            'services': {}
 
5256
            'applications': {}
4857
5257
        }, '')
4858
5258
        expected = [
4859
5259
            ('1', {'foo': 'bar', 'containers': {'2': {'qux': 'baz'}}}),
4876
5276
                '1': {'agent-state': 'good'},
4877
5277
                '2': {},
4878
5278
            },
4879
 
            'services': {
 
5279
            'applications': {
4880
5280
                'jenkins': {
4881
5281
                    'units': {
4882
5282
                        'jenkins/1': {'agent-state': 'bad'},
4911
5311
                '1': {'agent-state': 'good'},
4912
5312
                '2': {},
4913
5313
            },
4914
 
            'services': {
 
5314
            'applications': {
4915
5315
                'jenkins': {
4916
5316
                    'units': {
4917
5317
                        'jenkins/1': {'agent-state': 'bad'},
4928
5328
            'machines': {
4929
5329
                '1': {},
4930
5330
            },
4931
 
            'services': {
 
5331
            'applications': {
4932
5332
                'jenkins': {
4933
5333
                    'units': {
4934
5334
                        'jenkins/1': {'agent-state': 'bad'},
4953
5353
            'machines': {
4954
5354
                '1': {},
4955
5355
            },
4956
 
            'services': {
 
5356
            'applications': {
4957
5357
                'ubuntu': {},
4958
5358
                'jenkins': {
4959
5359
                    'units': {
4997
5397
            'machines': {
4998
5398
                '1': {},
4999
5399
            },
5000
 
            'services': {
 
5400
            'applications': {
5001
5401
                'jenkins': {
5002
5402
                    'units': {
5003
5403
                        'jenkins/1': {'agent-state': 'bad'},
5019
5419
                '1': {'agent-state': 'good'},
5020
5420
                '2': {},
5021
5421
            },
5022
 
            'services': {
 
5422
            'applications': {
5023
5423
                'jenkins': {
5024
5424
                    'units': {
5025
5425
                        'jenkins/1': {'agent-state': 'bad'},
5041
5441
                '1': {'agent-state': 'good'},
5042
5442
                '2': {},
5043
5443
            },
5044
 
            'services': {
 
5444
            'applications': {
5045
5445
                'jenkins': {
5046
5446
                    'units': {
5047
5447
                        'jenkins/1': {'agent-status': {'current': 'bad'}},
5064
5464
                '1': {'juju-status': {'current': 'good'}},
5065
5465
                '2': {},
5066
5466
            },
5067
 
            'services': {
 
5467
            'applications': {
5068
5468
                'jenkins': {
5069
5469
                    'units': {
5070
5470
                        'jenkins/1': {'juju-status': {'current': 'bad'}},
5087
5487
                '1': {'agent-state': 'good'},
5088
5488
                '2': {},
5089
5489
            },
5090
 
            'services': {
 
5490
            'applications': {
5091
5491
                'jenkins': {
5092
5492
                    'units': {
5093
5493
                        'jenkins/1': {'agent-state': 'bad'},
5105
5505
                '1': {'agent-state': 'started'},
5106
5506
                '2': {'agent-state': 'started'},
5107
5507
            },
5108
 
            'services': {
 
5508
            'applications': {
5109
5509
                'jenkins': {
5110
5510
                    'units': {
5111
5511
                        'jenkins/1': {
5129
5529
                '1': {'agent-state': 'started'},
5130
5530
                '2': {'agent-state': 'started'},
5131
5531
            },
5132
 
            'services': {
 
5532
            'applications': {
5133
5533
                'jenkins': {
5134
5534
                    'units': {
5135
5535
                        'jenkins/1': {
5152
5552
            'machines': {
5153
5553
                '1': {'agent-state': 'any-error'},
5154
5554
            },
5155
 
            'services': {}
 
5555
            'applications': {}
5156
5556
        }, '')
5157
5557
        with self.assertRaisesRegexp(ErroredUnit,
5158
5558
                                     '1 is in state any-error'):
5162
5562
        status = Status({
5163
5563
            'machines': {'0': {
5164
5564
                'agent-state-info': failure}},
5165
 
            'services': {},
 
5565
            'applications': {},
5166
5566
        }, '')
5167
5567
        with self.assertRaises(ErroredUnit) as e_cxt:
5168
5568
            status.check_agents_started()
5191
5591
            'machines': {
5192
5592
                '1': {'agent-state-info': 'any-error'},
5193
5593
            },
5194
 
            'services': {}
 
5594
            'applications': {}
5195
5595
        }, '')
5196
5596
        with self.assertRaisesRegexp(ErroredUnit,
5197
5597
                                     '1 is in state any-error'):
5203
5603
                '1': {'agent-version': '1.6.2'},
5204
5604
                '2': {'agent-version': '1.6.1'},
5205
5605
            },
5206
 
            'services': {
 
5606
            'applications': {
5207
5607
                'jenkins': {
5208
5608
                    'units': {
5209
5609
                        'jenkins/0': {
5225
5625
                '1': {'juju-status': {'version': '1.6.2'}},
5226
5626
                '2': {'juju-status': {'version': '1.6.1'}},
5227
5627
            },
5228
 
            'services': {
 
5628
            'applications': {
5229
5629
                'jenkins': {
5230
5630
                    'units': {
5231
5631
                        'jenkins/0': {
5276
5676
        self.assertEqual(status.status_text, text)
5277
5677
        self.assertEqual(status.status, {
5278
5678
            'machines': {'0': {'agent-state': 'pending'}},
 
5679
            'applications': {'jenkins': {'units': {'jenkins/0': {
 
5680
                'agent-state': 'horsefeathers'}}}}
 
5681
        })
 
5682
 
 
5683
    def test_iter_units(self):
 
5684
        started_unit = {'agent-state': 'started'}
 
5685
        unit_with_subordinates = {
 
5686
            'agent-state': 'started',
 
5687
            'subordinates': {
 
5688
                'ntp/0': started_unit,
 
5689
                'nrpe/0': started_unit,
 
5690
            },
 
5691
        }
 
5692
        status = Status({
 
5693
            'machines': {
 
5694
                '1': {'agent-state': 'started'},
 
5695
            },
 
5696
            'applications': {
 
5697
                'jenkins': {
 
5698
                    'units': {
 
5699
                        'jenkins/0': unit_with_subordinates,
 
5700
                    }
 
5701
                },
 
5702
                'application': {
 
5703
                    'units': {
 
5704
                        'application/0': started_unit,
 
5705
                        'application/1': started_unit,
 
5706
                    }
 
5707
                },
 
5708
            }
 
5709
        }, '')
 
5710
        expected = [
 
5711
            ('application/0', started_unit),
 
5712
            ('application/1', started_unit),
 
5713
            ('jenkins/0', unit_with_subordinates),
 
5714
            ('nrpe/0', started_unit),
 
5715
            ('ntp/0', started_unit),
 
5716
        ]
 
5717
        gen = status.iter_units()
 
5718
        self.assertIsInstance(gen, types.GeneratorType)
 
5719
        self.assertEqual(expected, list(gen))
 
5720
 
 
5721
 
 
5722
class TestServiceStatus(FakeHomeTestCase):
 
5723
 
 
5724
    def test_iter_machines_no_containers(self):
 
5725
        status = ServiceStatus({
 
5726
            'machines': {
 
5727
                '1': {'foo': 'bar', 'containers': {'1/lxc/0': {'baz': 'qux'}}}
 
5728
            },
 
5729
            'services': {}}, '')
 
5730
        self.assertEqual(list(status.iter_machines()),
 
5731
                         [('1', status.status['machines']['1'])])
 
5732
 
 
5733
    def test_iter_machines_containers(self):
 
5734
        status = ServiceStatus({
 
5735
            'machines': {
 
5736
                '1': {'foo': 'bar', 'containers': {'1/lxc/0': {'baz': 'qux'}}}
 
5737
            },
 
5738
            'services': {}}, '')
 
5739
        self.assertEqual(list(status.iter_machines(containers=True)), [
 
5740
            ('1', status.status['machines']['1']),
 
5741
            ('1/lxc/0', {'baz': 'qux'}),
 
5742
        ])
 
5743
 
 
5744
    def test_agent_items_empty(self):
 
5745
        status = ServiceStatus({'machines': {}, 'services': {}}, '')
 
5746
        self.assertItemsEqual([], status.agent_items())
 
5747
 
 
5748
    def test_agent_items(self):
 
5749
        status = ServiceStatus({
 
5750
            'machines': {
 
5751
                '1': {'foo': 'bar'}
 
5752
            },
 
5753
            'services': {
 
5754
                'jenkins': {
 
5755
                    'units': {
 
5756
                        'jenkins/1': {
 
5757
                            'subordinates': {
 
5758
                                'sub': {'baz': 'qux'}
 
5759
                            }
 
5760
                        }
 
5761
                    }
 
5762
                }
 
5763
            }
 
5764
        }, '')
 
5765
        expected = [
 
5766
            ('1', {'foo': 'bar'}),
 
5767
            ('jenkins/1', {'subordinates': {'sub': {'baz': 'qux'}}}),
 
5768
            ('sub', {'baz': 'qux'})]
 
5769
        self.assertItemsEqual(expected, status.agent_items())
 
5770
 
 
5771
    def test_agent_items_containers(self):
 
5772
        status = ServiceStatus({
 
5773
            'machines': {
 
5774
                '1': {'foo': 'bar', 'containers': {
 
5775
                    '2': {'qux': 'baz'},
 
5776
                }}
 
5777
            },
 
5778
            'services': {}
 
5779
        }, '')
 
5780
        expected = [
 
5781
            ('1', {'foo': 'bar', 'containers': {'2': {'qux': 'baz'}}}),
 
5782
            ('2', {'qux': 'baz'})
 
5783
        ]
 
5784
        self.assertItemsEqual(expected, status.agent_items())
 
5785
 
 
5786
    def test_get_service_count_zero(self):
 
5787
        status = ServiceStatus({
 
5788
            'machines': {
 
5789
                '1': {'agent-state': 'good'},
 
5790
                '2': {},
 
5791
            },
 
5792
        }, '')
 
5793
        self.assertEqual(0, status.get_service_count())
 
5794
 
 
5795
    def test_get_service_count(self):
 
5796
        status = ServiceStatus({
 
5797
            'machines': {
 
5798
                '1': {'agent-state': 'good'},
 
5799
                '2': {},
 
5800
            },
 
5801
            'services': {
 
5802
                'jenkins': {
 
5803
                    'units': {
 
5804
                        'jenkins/1': {'agent-state': 'bad'},
 
5805
                    }
 
5806
                },
 
5807
                'dummy-sink': {
 
5808
                    'units': {
 
5809
                        'dummy-sink/0': {'agent-state': 'started'},
 
5810
                    }
 
5811
                },
 
5812
                'juju-reports': {
 
5813
                    'units': {
 
5814
                        'juju-reports/0': {'agent-state': 'pending'},
 
5815
                    }
 
5816
                }
 
5817
            }
 
5818
        }, '')
 
5819
        self.assertEqual(3, status.get_service_count())
 
5820
 
 
5821
    def test_get_service_unit_count_zero(self):
 
5822
        status = ServiceStatus({
 
5823
            'machines': {
 
5824
                '1': {'agent-state': 'good'},
 
5825
                '2': {},
 
5826
            },
 
5827
        }, '')
 
5828
        self.assertEqual(0, status.get_service_unit_count('jenkins'))
 
5829
 
 
5830
    def test_get_service_unit_count(self):
 
5831
        status = ServiceStatus({
 
5832
            'machines': {
 
5833
                '1': {'agent-state': 'good'},
 
5834
                '2': {},
 
5835
            },
 
5836
            'services': {
 
5837
                'jenkins': {
 
5838
                    'units': {
 
5839
                        'jenkins/1': {'agent-state': 'bad'},
 
5840
                        'jenkins/2': {'agent-state': 'bad'},
 
5841
                        'jenkins/3': {'agent-state': 'bad'},
 
5842
                    }
 
5843
                }
 
5844
            }
 
5845
        }, '')
 
5846
        self.assertEqual(3, status.get_service_unit_count('jenkins'))
 
5847
 
 
5848
    def test_get_unit(self):
 
5849
        status = ServiceStatus({
 
5850
            'machines': {
 
5851
                '1': {},
 
5852
            },
 
5853
            'services': {
 
5854
                'jenkins': {
 
5855
                    'units': {
 
5856
                        'jenkins/1': {'agent-state': 'bad'},
 
5857
                    }
 
5858
                },
 
5859
                'dummy-sink': {
 
5860
                    'units': {
 
5861
                        'jenkins/2': {'agent-state': 'started'},
 
5862
                    }
 
5863
                },
 
5864
            }
 
5865
        }, '')
 
5866
        self.assertEqual(
 
5867
            status.get_unit('jenkins/1'), {'agent-state': 'bad'})
 
5868
        self.assertEqual(
 
5869
            status.get_unit('jenkins/2'), {'agent-state': 'started'})
 
5870
        with self.assertRaisesRegexp(KeyError, 'jenkins/3'):
 
5871
            status.get_unit('jenkins/3')
 
5872
 
 
5873
    def test_service_subordinate_units(self):
 
5874
        status = ServiceStatus({
 
5875
            'machines': {
 
5876
                '1': {},
 
5877
            },
 
5878
            'services': {
 
5879
                'ubuntu': {},
 
5880
                'jenkins': {
 
5881
                    'units': {
 
5882
                        'jenkins/1': {
 
5883
                            'subordinates': {
 
5884
                                'chaos-monkey/0': {'agent-state': 'started'},
 
5885
                            }
 
5886
                        }
 
5887
                    }
 
5888
                },
 
5889
                'dummy-sink': {
 
5890
                    'units': {
 
5891
                        'jenkins/2': {
 
5892
                            'subordinates': {
 
5893
                                'chaos-monkey/1': {'agent-state': 'started'}
 
5894
                            }
 
5895
                        },
 
5896
                        'jenkins/3': {
 
5897
                            'subordinates': {
 
5898
                                'chaos-monkey/2': {'agent-state': 'started'}
 
5899
                            }
 
5900
                        }
 
5901
                    }
 
5902
                }
 
5903
            }
 
5904
        }, '')
 
5905
        self.assertItemsEqual(
 
5906
            status.service_subordinate_units('ubuntu'),
 
5907
            [])
 
5908
        self.assertItemsEqual(
 
5909
            status.service_subordinate_units('jenkins'),
 
5910
            [('chaos-monkey/0', {'agent-state': 'started'},)])
 
5911
        self.assertItemsEqual(
 
5912
            status.service_subordinate_units('dummy-sink'), [
 
5913
                ('chaos-monkey/1', {'agent-state': 'started'}),
 
5914
                ('chaos-monkey/2', {'agent-state': 'started'})]
 
5915
            )
 
5916
 
 
5917
    def test_get_open_ports(self):
 
5918
        status = ServiceStatus({
 
5919
            'machines': {
 
5920
                '1': {},
 
5921
            },
 
5922
            'services': {
 
5923
                'jenkins': {
 
5924
                    'units': {
 
5925
                        'jenkins/1': {'agent-state': 'bad'},
 
5926
                    }
 
5927
                },
 
5928
                'dummy-sink': {
 
5929
                    'units': {
 
5930
                        'jenkins/2': {'open-ports': ['42/tcp']},
 
5931
                    }
 
5932
                },
 
5933
            }
 
5934
        }, '')
 
5935
        self.assertEqual(status.get_open_ports('jenkins/1'), [])
 
5936
        self.assertEqual(status.get_open_ports('jenkins/2'), ['42/tcp'])
 
5937
 
 
5938
    def test_agent_states_with_agent_state(self):
 
5939
        status = ServiceStatus({
 
5940
            'machines': {
 
5941
                '1': {'agent-state': 'good'},
 
5942
                '2': {},
 
5943
            },
 
5944
            'services': {
 
5945
                'jenkins': {
 
5946
                    'units': {
 
5947
                        'jenkins/1': {'agent-state': 'bad'},
 
5948
                        'jenkins/2': {'agent-state': 'good'},
 
5949
                    }
 
5950
                }
 
5951
            }
 
5952
        }, '')
 
5953
        expected = {
 
5954
            'good': ['1', 'jenkins/2'],
 
5955
            'bad': ['jenkins/1'],
 
5956
            'no-agent': ['2'],
 
5957
        }
 
5958
        self.assertEqual(expected, status.agent_states())
 
5959
 
 
5960
    def test_agent_states_with_agent_status(self):
 
5961
        status = ServiceStatus({
 
5962
            'machines': {
 
5963
                '1': {'agent-state': 'good'},
 
5964
                '2': {},
 
5965
            },
 
5966
            'services': {
 
5967
                'jenkins': {
 
5968
                    'units': {
 
5969
                        'jenkins/1': {'agent-status': {'current': 'bad'}},
 
5970
                        'jenkins/2': {'agent-status': {'current': 'good'}},
 
5971
                        'jenkins/3': {},
 
5972
                    }
 
5973
                }
 
5974
            }
 
5975
        }, '')
 
5976
        expected = {
 
5977
            'good': ['1', 'jenkins/2'],
 
5978
            'bad': ['jenkins/1'],
 
5979
            'no-agent': ['2', 'jenkins/3'],
 
5980
        }
 
5981
        self.assertEqual(expected, status.agent_states())
 
5982
 
 
5983
    def test_agent_states_with_juju_status(self):
 
5984
        status = ServiceStatus({
 
5985
            'machines': {
 
5986
                '1': {'juju-status': {'current': 'good'}},
 
5987
                '2': {},
 
5988
            },
 
5989
            'services': {
 
5990
                'jenkins': {
 
5991
                    'units': {
 
5992
                        'jenkins/1': {'juju-status': {'current': 'bad'}},
 
5993
                        'jenkins/2': {'juju-status': {'current': 'good'}},
 
5994
                        'jenkins/3': {},
 
5995
                    }
 
5996
                }
 
5997
            }
 
5998
        }, '')
 
5999
        expected = {
 
6000
            'good': ['1', 'jenkins/2'],
 
6001
            'bad': ['jenkins/1'],
 
6002
            'no-agent': ['2', 'jenkins/3'],
 
6003
        }
 
6004
        self.assertEqual(expected, status.agent_states())
 
6005
 
 
6006
    def test_check_agents_started_not_started(self):
 
6007
        status = ServiceStatus({
 
6008
            'machines': {
 
6009
                '1': {'agent-state': 'good'},
 
6010
                '2': {},
 
6011
            },
 
6012
            'services': {
 
6013
                'jenkins': {
 
6014
                    'units': {
 
6015
                        'jenkins/1': {'agent-state': 'bad'},
 
6016
                        'jenkins/2': {'agent-state': 'good'},
 
6017
                    }
 
6018
                }
 
6019
            }
 
6020
        }, '')
 
6021
        self.assertEqual(status.agent_states(),
 
6022
                         status.check_agents_started('env1'))
 
6023
 
 
6024
    def test_check_agents_started_all_started_with_agent_state(self):
 
6025
        status = ServiceStatus({
 
6026
            'machines': {
 
6027
                '1': {'agent-state': 'started'},
 
6028
                '2': {'agent-state': 'started'},
 
6029
            },
 
6030
            'services': {
 
6031
                'jenkins': {
 
6032
                    'units': {
 
6033
                        'jenkins/1': {
 
6034
                            'agent-state': 'started',
 
6035
                            'subordinates': {
 
6036
                                'sub1': {
 
6037
                                    'agent-state': 'started'
 
6038
                                }
 
6039
                            }
 
6040
                        },
 
6041
                        'jenkins/2': {'agent-state': 'started'},
 
6042
                    }
 
6043
                }
 
6044
            }
 
6045
        }, '')
 
6046
        self.assertIs(None, status.check_agents_started('env1'))
 
6047
 
 
6048
    def test_check_agents_started_all_started_with_agent_status(self):
 
6049
        status = ServiceStatus({
 
6050
            'machines': {
 
6051
                '1': {'agent-state': 'started'},
 
6052
                '2': {'agent-state': 'started'},
 
6053
            },
 
6054
            'services': {
 
6055
                'jenkins': {
 
6056
                    'units': {
 
6057
                        'jenkins/1': {
 
6058
                            'agent-status': {'current': 'idle'},
 
6059
                            'subordinates': {
 
6060
                                'sub1': {
 
6061
                                    'agent-status': {'current': 'idle'}
 
6062
                                }
 
6063
                            }
 
6064
                        },
 
6065
                        'jenkins/2': {'agent-status': {'current': 'idle'}},
 
6066
                    }
 
6067
                }
 
6068
            }
 
6069
        }, '')
 
6070
        self.assertIs(None, status.check_agents_started('env1'))
 
6071
 
 
6072
    def test_check_agents_started_agent_error(self):
 
6073
        status = ServiceStatus({
 
6074
            'machines': {
 
6075
                '1': {'agent-state': 'any-error'},
 
6076
            },
 
6077
            'services': {}
 
6078
        }, '')
 
6079
        with self.assertRaisesRegexp(ErroredUnit,
 
6080
                                     '1 is in state any-error'):
 
6081
            status.check_agents_started('env1')
 
6082
 
 
6083
    def do_check_agents_started_failure(self, failure):
 
6084
        status = ServiceStatus({
 
6085
            'machines': {'0': {
 
6086
                'agent-state-info': failure}},
 
6087
            'services': {},
 
6088
        }, '')
 
6089
        with self.assertRaises(ErroredUnit) as e_cxt:
 
6090
            status.check_agents_started()
 
6091
        e = e_cxt.exception
 
6092
        self.assertEqual(
 
6093
            str(e), '0 is in state {}'.format(failure))
 
6094
        self.assertEqual(e.unit_name, '0')
 
6095
        self.assertEqual(e.state, failure)
 
6096
 
 
6097
    def test_check_agents_cannot_set_up_groups(self):
 
6098
        self.do_check_agents_started_failure('cannot set up groups foobar')
 
6099
 
 
6100
    def test_check_agents_error(self):
 
6101
        self.do_check_agents_started_failure('error executing "lxc-start"')
 
6102
 
 
6103
    def test_check_agents_cannot_run_instances(self):
 
6104
        self.do_check_agents_started_failure('cannot run instances')
 
6105
 
 
6106
    def test_check_agents_cannot_run_instance(self):
 
6107
        self.do_check_agents_started_failure('cannot run instance')
 
6108
 
 
6109
    def test_check_agents_started_agent_info_error(self):
 
6110
        # Sometimes the error is indicated in a special 'agent-state-info'
 
6111
        # field.
 
6112
        status = ServiceStatus({
 
6113
            'machines': {
 
6114
                '1': {'agent-state-info': 'any-error'},
 
6115
            },
 
6116
            'services': {}
 
6117
        }, '')
 
6118
        with self.assertRaisesRegexp(ErroredUnit,
 
6119
                                     '1 is in state any-error'):
 
6120
            status.check_agents_started('env1')
 
6121
 
 
6122
    def test_get_agent_versions_1x(self):
 
6123
        status = ServiceStatus({
 
6124
            'machines': {
 
6125
                '1': {'agent-version': '1.6.2'},
 
6126
                '2': {'agent-version': '1.6.1'},
 
6127
            },
 
6128
            'services': {
 
6129
                'jenkins': {
 
6130
                    'units': {
 
6131
                        'jenkins/0': {
 
6132
                            'agent-version': '1.6.1'},
 
6133
                        'jenkins/1': {},
 
6134
                    },
 
6135
                }
 
6136
            }
 
6137
        }, '')
 
6138
        self.assertEqual({
 
6139
            '1.6.2': {'1'},
 
6140
            '1.6.1': {'jenkins/0', '2'},
 
6141
            'unknown': {'jenkins/1'},
 
6142
        }, status.get_agent_versions())
 
6143
 
 
6144
    def test_get_agent_versions_2x(self):
 
6145
        status = ServiceStatus({
 
6146
            'machines': {
 
6147
                '1': {'juju-status': {'version': '1.6.2'}},
 
6148
                '2': {'juju-status': {'version': '1.6.1'}},
 
6149
            },
 
6150
            'services': {
 
6151
                'jenkins': {
 
6152
                    'units': {
 
6153
                        'jenkins/0': {
 
6154
                            'juju-status': {'version': '1.6.1'}},
 
6155
                        'jenkins/1': {},
 
6156
                    },
 
6157
                }
 
6158
            }
 
6159
        }, '')
 
6160
        self.assertEqual({
 
6161
            '1.6.2': {'1'},
 
6162
            '1.6.1': {'jenkins/0', '2'},
 
6163
            'unknown': {'jenkins/1'},
 
6164
        }, status.get_agent_versions())
 
6165
 
 
6166
    def test_iter_new_machines(self):
 
6167
        old_status = ServiceStatus({
 
6168
            'machines': {
 
6169
                'bar': 'bar_info',
 
6170
            }
 
6171
        }, '')
 
6172
        new_status = ServiceStatus({
 
6173
            'machines': {
 
6174
                'foo': 'foo_info',
 
6175
                'bar': 'bar_info',
 
6176
            }
 
6177
        }, '')
 
6178
        self.assertItemsEqual(new_status.iter_new_machines(old_status),
 
6179
                              [('foo', 'foo_info')])
 
6180
 
 
6181
    def test_get_instance_id(self):
 
6182
        status = ServiceStatus({
 
6183
            'machines': {
 
6184
                '0': {'instance-id': 'foo-bar'},
 
6185
                '1': {},
 
6186
            }
 
6187
        }, '')
 
6188
        self.assertEqual(status.get_instance_id('0'), 'foo-bar')
 
6189
        with self.assertRaises(KeyError):
 
6190
            status.get_instance_id('1')
 
6191
        with self.assertRaises(KeyError):
 
6192
            status.get_instance_id('2')
 
6193
 
 
6194
    def test_from_text(self):
 
6195
        text = TestEnvJujuClient1X.make_status_yaml(
 
6196
            'agent-state', 'pending', 'horsefeathers')
 
6197
        status = ServiceStatus.from_text(text)
 
6198
        self.assertEqual(status.status_text, text)
 
6199
        self.assertEqual(status.status, {
 
6200
            'machines': {'0': {'agent-state': 'pending'}},
5279
6201
            'services': {'jenkins': {'units': {'jenkins/0': {
5280
6202
                'agent-state': 'horsefeathers'}}}}
5281
6203
        })
5289
6211
                'nrpe/0': started_unit,
5290
6212
            },
5291
6213
        }
5292
 
        status = Status({
 
6214
        status = ServiceStatus({
5293
6215
            'machines': {
5294
6216
                '1': {'agent-state': 'started'},
5295
6217
            },
5546
6468
                            'home').get_region())
5547
6469
 
5548
6470
    def test_get_region_old_azure(self):
5549
 
        with self.assertRaisesRegexp(
5550
 
                ValueError, 'Non-ARM Azure not supported.'):
5551
 
            JujuData('foo', {'type': 'azure', 'location': 'bar'},
5552
 
                     'home').get_region()
 
6471
        self.assertEqual('northeu', JujuData('foo', {
 
6472
            'type': 'azure', 'location': 'North EU'}, 'home').get_region())
5553
6473
 
5554
6474
    def test_get_region_azure_arm(self):
5555
6475
        self.assertEqual('bar', JujuData('foo', {