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

161 by Curtis Hovey
Windows and py3 compatability.
1
from __future__ import print_function
2
1306.1.1 by Curtis Hovey
Save spike to get leader and followers.
3
from collections import (
4
    defaultdict,
5
    namedtuple,
6
)
807.1.1 by Aaron Bentley
Avoid creating temp juju homes inside temp juju homes.
7
from contextlib import (
8
    contextmanager,
9
    nested,
953.3.9 by Nate Finch
more code review changes
10
)
1315.2.3 by Aaron Bentley
Use admin client to get bootstrap host.
11
from copy import deepcopy
2 by Aaron Bentley
Added initial deploy_stack.
12
from cStringIO import StringIO
768.1.1 by Aaron Bentley
Add timeout to EnvJujuClient.destroy_environment.
13
from datetime import timedelta
663.1.2 by Aaron Bentley
Port bootstrap_from_env to EnvJujuClient, add unit tests.
14
import errno
751.1.1 by Aaron Bentley
Give equal time to old and new clients in DeployManyAttempt
15
from itertools import chain
715.2.1 by John George
add debug logging option to show command and output from juju calls made from get_juju_output
16
import logging
161 by Curtis Hovey
Windows and py3 compatability.
17
import os
717.2.1 by Aaron Bentley
Extract EnvJujuClient.backup from assess_recovery.
18
import re
1260.2.13 by Aaron Bentley
Fix lint.
19
from shutil import rmtree
2 by Aaron Bentley
Added initial deploy_stack.
20
import subprocess
21
import sys
1260.2.1 by Aaron Bentley
Spike to support cloud-credentials.
22
from tempfile import NamedTemporaryFile
1048.2.1 by John George
Capture duration of juju commands from the client perspective.
23
import time
2 by Aaron Bentley
Added initial deploy_stack.
24
663.1.2 by Aaron Bentley
Port bootstrap_from_env to EnvJujuClient, add unit tests.
25
import yaml
26
27
from jujuconfig import (
28
    get_environments_path,
29
    get_jenv_path,
777.2.1 by Aaron Bentley
Always delete jenv in industrial test.
30
    get_juju_home,
663.1.2 by Aaron Bentley
Port bootstrap_from_env to EnvJujuClient, add unit tests.
31
    get_selected_environment,
953.3.9 by Nate Finch
more code review changes
32
)
663.1.2 by Aaron Bentley
Port bootstrap_from_env to EnvJujuClient, add unit tests.
33
from utility import (
34
    check_free_disk_space,
777.2.1 by Aaron Bentley
Always delete jenv in industrial test.
35
    ensure_deleted,
1091.4.1 by James Tunnicliffe
Merged upstream
36
    ensure_dir,
1167.2.1 by Martin Packman
Support IPv6 in wait_for_port and move handling of literal addresses closer to final use
37
    is_ipv6_address,
763.1.2 by Curtis Hovey
Added puase to sleep after ha, but remove it for tests.
38
    pause,
663.1.2 by Aaron Bentley
Port bootstrap_from_env to EnvJujuClient, add unit tests.
39
    scoped_environ,
1310.1.2 by Curtis Hovey
Address ipv6 split address and port.
40
    split_address_port,
663.1.2 by Aaron Bentley
Port bootstrap_from_env to EnvJujuClient, add unit tests.
41
    temp_dir,
42
    until_timeout,
953.3.9 by Nate Finch
more code review changes
43
)
112.1.4 by Aaron Bentley
Read config, use it to determine whether provider is local.
44
2 by Aaron Bentley
Added initial deploy_stack.
45
1092.2.2 by Aaron Bentley
Fix lint.
46
__metaclass__ = type
47
1246.1.2 by Curtis Hovey
Added coalesce_agent_status to handle machine agent-state and unit agent-status.
48
AGENTS_READY = set(['started', 'idle'])
163 by Curtis Hovey
Extracted the windows command incase it needs to be reused.
49
WIN_JUJU_CMD = os.path.join('\\', 'Progra~2', 'Juju', 'juju.exe')
50
928.2.2 by Aaron Bentley
Auto-enable support for cloudsigma in 1.24.
51
JUJU_DEV_FEATURE_FLAGS = 'JUJU_DEV_FEATURE_FLAGS'
1242.2.1 by Aaron Bentley
EnvJujuClient assumes JES enabled, since it assumes 2.0.
52
CONTROLLER = 'controller'
53
KILL_CONTROLLER = 'kill-controller'
54
SYSTEM = 'system'
928.2.2 by Aaron Bentley
Auto-enable support for cloudsigma in 1.24.
55
1259.2.1 by Martin Packman
Add flag to run_deployer to use juju 2.0 bundle deployment
56
_DEFAULT_BUNDLE_TIMEOUT = 3600
57
1242.2.1 by Aaron Bentley
EnvJujuClient assumes JES enabled, since it assumes 2.0.
58
_jes_cmds = {KILL_CONTROLLER: {
1199 by Curtis Hovey
Revert lp:~sinzui/juju-ci-tools/kill-controller-command r1198 because it exposes that controllers are
59
    'create': 'create-environment',
1242.2.1 by Aaron Bentley
EnvJujuClient assumes JES enabled, since it assumes 2.0.
60
    'kill': KILL_CONTROLLER,
1306.1.2 by Curtis Hovey
Fix missing brace.
61
}}
1242.2.1 by Aaron Bentley
EnvJujuClient assumes JES enabled, since it assumes 2.0.
62
for super_cmd in [SYSTEM, CONTROLLER]:
1199 by Curtis Hovey
Revert lp:~sinzui/juju-ci-tools/kill-controller-command r1198 because it exposes that controllers are
63
    _jes_cmds[super_cmd] = {
64
        'create': '{} create-environment'.format(super_cmd),
65
        'kill': '{} kill'.format(super_cmd),
1306.1.1 by Curtis Hovey
Save spike to get leader and followers.
66
    }
1162.1.1 by Aaron Bentley
Abstract jes support, make enable_jes optional in assess_jes.
67
1091.4.1 by James Tunnicliffe
Merged upstream
68
log = logging.getLogger("jujupy")
69
163 by Curtis Hovey
Extracted the windows command incase it needs to be reused.
70
1069.1.1 by Aaron Bentley
Replace timeout utility in _full_args.
71
def get_timeout_path():
72
    import timeout
73
    return os.path.abspath(timeout.__file__)
74
75
76
def get_timeout_prefix(duration, timeout_path=None):
77
    """Return extra arguments to run a command with a timeout."""
78
    if timeout_path is None:
79
        timeout_path = get_timeout_path()
80
    return (sys.executable, timeout_path, '%.2f' % duration, '--')
81
82
953.3.8 by Nate Finch
more review changes
83
def parse_new_state_server_from_error(error):
976.2.1 by Aaron Bentley
Fix parse_new_state_server_from_error
84
    err_str = str(error)
85
    output = getattr(error, 'output', None)
86
    if output is not None:
87
        err_str += output
88
    matches = re.findall(r'Attempting to connect to (.*):22', err_str)
953.3.8 by Nate Finch
more review changes
89
    if matches:
90
        return matches[-1]
91
    return None
92
93
2 by Aaron Bentley
Added initial deploy_stack.
94
class ErroredUnit(Exception):
95
301.1.3 by Aaron Bentley
Remove environment name from log messages and errors.
96
    def __init__(self, unit_name, state):
97
        msg = '%s is in state %s' % (unit_name, state)
19.1.14 by Aaron Bentley
More error handling fixes.
98
        Exception.__init__(self, msg)
771.2.1 by Aaron Bentley
Increase set of agent-state-infos that indicate failure.
99
        self.unit_name = unit_name
100
        self.state = state
2 by Aaron Bentley
Added initial deploy_stack.
101
102
1242.3.1 by Aaron Bentley
Update get_bootstrap_args.
103
class BootstrapMismatch(Exception):
104
105
    def __init__(self, arg_name, arg_val, env_name, env_val):
106
        super(BootstrapMismatch, self).__init__(
107
            '--{} {} does not match {}: {}'.format(
108
                arg_name, arg_val, env_name, env_val))
109
110
1258.2.5 by Curtis Hovey
Added upgrade_mongo to EnvJujuClient.
111
class UpgradeMongoNotSupported(Exception):
112
113
    def __init__(self):
114
        super(UpgradeMongoNotSupported, self).__init__(
115
            'This client does not support upgrade-mongo')
116
117
1044.1.9 by Aaron Bentley
Update and add tests.
118
class JESNotSupported(Exception):
119
120
    def __init__(self):
121
        super(JESNotSupported, self).__init__(
1183.1.9 by Curtis Hovey
Revert change that makes diff hard to read.
122
            'This client does not support JES')
1044.1.9 by Aaron Bentley
Update and add tests.
123
124
125
class JESByDefault(Exception):
126
127
    def __init__(self):
1044.1.13 by Aaron Bentley
Add more enable_jes tests.
128
        super(JESByDefault, self).__init__(
1183.1.9 by Curtis Hovey
Revert change that makes diff hard to read.
129
            'This client does not need to enable JES')
1183.1.2 by Curtis Hovey
Save point.
130
131
1315.2.1 by Aaron Bentley
Use machine id instead of machine number.
132
Machine = namedtuple('Machine', ['machine_id', 'info'])
1306.1.7 by Curtis Hovey
Added get_controller_leader.
133
134
19.1.18 by Aaron Bentley
Provide destroy-environment script to simplify code.
135
def yaml_loads(yaml_str):
136
    return yaml.safe_load(StringIO(yaml_str))
137
138
1246.1.2 by Curtis Hovey
Added coalesce_agent_status to handle machine agent-state and unit agent-status.
139
def coalesce_agent_status(agent_item):
140
    """Return the machine agent-state or the unit agent-status."""
141
    state = agent_item.get('agent-state')
142
    if state is None and agent_item.get('agent-status') is not None:
143
        state = agent_item.get('agent-status').get('current')
1293 by Curtis Hovey
Support juju-status.
144
    if state is None and agent_item.get('juju-status') is not None:
145
        state = agent_item.get('juju-status').get('current')
1246.1.2 by Curtis Hovey
Added coalesce_agent_status to handle machine agent-state and unit agent-status.
146
    if state is None:
147
        state = 'no-agent'
148
    return state
149
150
953.3.7 by Nate Finch
update for code review comments
151
def make_client(juju_path, debug, env_name, temp_env_name):
152
    env = SimpleEnvironment.from_config(env_name)
153
    if temp_env_name is not None:
1315.2.23 by Aaron Bentley
Introduce set_model_name, update tests, check controller on bootstrap.
154
        env.set_model_name(temp_env_name)
1169.3.1 by John George
Enforce full path to the juju binary it given, from add_basic_testing_arguments.
155
    return EnvJujuClient.by_version(env, juju_path, debug)
953.3.7 by Nate Finch
update for code review comments
156
157
185.1.1 by Aaron Bentley
Fix cloud test handling of 'Unable to connect to environment.'
158
class CannotConnectEnv(subprocess.CalledProcessError):
159
160
    def __init__(self, e):
161
        super(CannotConnectEnv, self).__init__(e.returncode, e.cmd, e.output)
162
163
1196.1.1 by Martin Packman
Refactoring jujupy including new wait_for_workloads and iter_units methods
164
class StatusNotMet(Exception):
165
166
    _fmt = 'Expected status not reached in {env}.'
167
168
    def __init__(self, environment_name, status):
169
        self.env = environment_name
747.3.3 by Aaron Bentley
Implement retrying add-machine.
170
        self.status = status
171
1196.1.1 by Martin Packman
Refactoring jujupy including new wait_for_workloads and iter_units methods
172
    def __str__(self):
173
        return self._fmt.format(env=self.env)
174
175
176
class AgentsNotStarted(StatusNotMet):
177
178
    _fmt = 'Timed out waiting for agents to start in {env}.'
179
180
181
class VersionsNotUpdated(StatusNotMet):
182
183
    _fmt = 'Some versions did not update.'
184
185
186
class WorkloadsNotReady(StatusNotMet):
187
188
    _fmt = 'Workloads not ready in {env}.'
189
747.3.3 by Aaron Bentley
Implement retrying add-machine.
190
1260.2.1 by Aaron Bentley
Spike to support cloud-credentials.
191
@contextmanager
192
def temp_yaml_file(yaml_dict):
1272.1.1 by Aaron Bentley
temp_yaml_file provides the name of a closed file, for Windows.
193
    temp_file = NamedTemporaryFile(suffix='.yaml', delete=False)
194
    try:
195
        with temp_file:
196
            yaml.safe_dump(yaml_dict, temp_file)
197
        yield temp_file.name
198
    finally:
199
        os.unlink(temp_file.name)
1260.2.1 by Aaron Bentley
Spike to support cloud-credentials.
200
201
657.1.1 by Aaron Bentley
Extract EnvJujuClient from JujuClientDevel.
202
class EnvJujuClient:
203
1251.2.4 by Aaron Bentley
Rename bootstrap_supports => bootstrap_replaces
204
    # The environments.yaml options that are replaced by bootstrap options.
205
    #
1242.3.8 by Aaron Bentley
Override default-series even when --bootstrap-series is supplied.
206
    # As described in bug #1538735, default-series and --bootstrap-series must
1251.2.5 by Aaron Bentley
Tweak verbiage.
207
    # match.  'default-series' should be here, but is omitted so that
208
    # default-series is always forced to match --bootstrap-series.
1251.2.4 by Aaron Bentley
Rename bootstrap_supports => bootstrap_replaces
209
    bootstrap_replaces = frozenset(['agent-version'])
1242.3.7 by Aaron Bentley
BootstrapManager support for bootstrap option.
210
1263.1.1 by Martin Packman
Refactor how juju feature flags are handled with new enable_feature method
211
    # What feature flags have existed that CI used.
212
    known_feature_flags = frozenset([
213
        'actions', 'jes', 'address-allocation', 'cloudsigma'])
214
215
    # What feature flags are used by this version of the juju client.
216
    used_feature_flags = frozenset(['address-allocation'])
217
1221.5.15 by Aaron Bentley
Use 'show-status' instead of 'status'.
218
    _show_status = 'show-status'
219
657.1.7 by Aaron Bentley
Clean-up.
220
    @classmethod
650.1.9 by Aaron Bentley
Merged trunk into compatibility-test.
221
    def get_version(cls, juju_path=None):
650.1.1 by Aaron Bentley
Add juju_path to from_config
222
        if juju_path is None:
223
            juju_path = 'juju'
224
        return subprocess.check_output((juju_path, '--version')).strip()
657.1.7 by Aaron Bentley
Clean-up.
225
1263.1.1 by Martin Packman
Refactor how juju feature flags are handled with new enable_feature method
226
    def enable_feature(self, flag):
227
        """Enable juju feature by setting the given flag.
228
229
        New versions of juju with the feature enabled by default will silently
230
        allow this call, but will not export the environment variable.
231
        """
232
        if flag not in self.known_feature_flags:
233
            raise ValueError('Unknown feature flag: %r' % (flag,))
234
        self.feature_flags.add(flag)
235
1242.2.1 by Aaron Bentley
EnvJujuClient assumes JES enabled, since it assumes 2.0.
236
    def get_jes_command(self):
237
        """For Juju 2.0, this is always kill-controller."""
238
        return KILL_CONTROLLER
239
1145.2.11 by Curtis Hovey
Let the client set permanent based on client.is_jes_enabled.
240
    def is_jes_enabled(self):
1145.2.2 by Curtis Hovey
Extracted get_jes_command from is_jes_enabled.
241
        """Does the state-server support multiple environments."""
1145.2.6 by Curtis Hovey
Raise JESNotSupported from get_jes_command when jes is not supported.
242
        try:
1145.2.7 by Curtis Hovey
Remove get_jes_command cache. Restored test that simulated the change in help output caused by the jes feature flag.
243
            self.get_jes_command()
1145.2.6 by Curtis Hovey
Raise JESNotSupported from get_jes_command when jes is not supported.
244
            return True
245
        except JESNotSupported:
246
            return False
1145.2.2 by Curtis Hovey
Extracted get_jes_command from is_jes_enabled.
247
1162.2.21 by Aaron Bentley
Implement and use tear_down(try_jes=True)
248
    def enable_jes(self):
249
        """Enable JES if JES is optional.
250
251
        Specifically implemented by the clients that optionally support JES.
252
        This version raises either JESByDefault or JESNotSupported.
253
254
        :raises: JESByDefault when JES is always enabled; Juju has the
255
            'destroy-controller' command.
256
        :raises: JESNotSupported when JES is not supported; Juju does not have
257
            the 'system kill' command when the JES feature flag is set.
258
        """
259
        if self.is_jes_enabled():
260
            raise JESByDefault()
261
        else:
262
            raise JESNotSupported()
263
657.1.7 by Aaron Bentley
Clean-up.
264
    @classmethod
265
    def get_full_path(cls):
161 by Curtis Hovey
Windows and py3 compatability.
266
        if sys.platform == 'win32':
163 by Curtis Hovey
Extracted the windows command incase it needs to be reused.
267
            return WIN_JUJU_CMD
107.1.2 by Aaron Bentley
Use full path when running under sudo.
268
        return subprocess.check_output(('which', 'juju')).rstrip('\n')
19.1.18 by Aaron Bentley
Provide destroy-environment script to simplify code.
269
657.1.2 by Aaron Bentley
Add EnvJujuClient tests from JujuClientDevel.
270
    @classmethod
663.1.7 by Aaron Bentley
Add --upload-tools to assess-heterogeneous-control.
271
    def by_version(cls, env, juju_path=None, debug=False):
650.1.1 by Aaron Bentley
Add juju_path to from_config
272
        version = cls.get_version(juju_path)
273
        if juju_path is None:
274
            full_path = cls.get_full_path()
275
        else:
276
            full_path = os.path.abspath(juju_path)
657.1.2 by Aaron Bentley
Add EnvJujuClient tests from JujuClientDevel.
277
        if version.startswith('1.16'):
278
            raise Exception('Unsupported juju: %s' % version)
953.3.9 by Nate Finch
more code review changes
279
        elif re.match('^1\.22[.-]', version):
1044.1.6 by Aaron Bentley
Use per-client JUJU_HOME.
280
            client_class = EnvJujuClient22
928.2.2 by Aaron Bentley
Auto-enable support for cloudsigma in 1.24.
281
        elif re.match('^1\.24[.-]', version):
1044.1.6 by Aaron Bentley
Use per-client JUJU_HOME.
282
            client_class = EnvJujuClient24
957.2.1 by Aaron Bentley
Treat cloudsigma as provisional in 2.5
283
        elif re.match('^1\.25[.-]', version):
1044.1.6 by Aaron Bentley
Use per-client JUJU_HOME.
284
            client_class = EnvJujuClient25
1074.2.1 by Aaron Bentley
Add EnvJujuClient support for Juju 2.6.
285
        elif re.match('^1\.26[.-]', version):
286
            client_class = EnvJujuClient26
1221.1.16 by Aaron Bentley
by_version uses 1X for 1.X-series clients.
287
        elif re.match('^1\.', version):
288
            client_class = EnvJujuClient1X
1254.1.1 by Aaron Bentley
Use EnvJujuClient for 2.0-alpha2.
289
        elif re.match('^2\.0-alpha1', version):
1221.1.17 by Aaron Bentley
Provide for a separate 2.0-alpha1 client.
290
            client_class = EnvJujuClient2A1
1283.2.1 by Aaron Bentley
Expect cloud-credentials behaviour from beta1.
291
        elif re.match('^2\.0-alpha2', version):
1258.1.3 by Aaron Bentley
Support transitional jujus.
292
            client_class = EnvJujuClient2A2
1331 by Aaron Bentley
New behaviour for beta3, not beta2.
293
        elif re.match('^2\.0-(alpha3|beta[12])', version):
1330 by Aaron Bentley
Use EnvJujuClient for beta2.
294
            client_class = EnvJujuClient2B2
295
        else:
1283.2.1 by Aaron Bentley
Expect cloud-credentials behaviour from beta1.
296
            client_class = EnvJujuClient
1201.2.13 by Aaron Bentley
Fix juju_home handling in by_version.
297
        return client_class(env, version, full_path, debug=debug)
657.1.2 by Aaron Bentley
Add EnvJujuClient tests from JujuClientDevel.
298
1221.1.9 by Aaron Bentley
Further extend clone().
299
    def clone(self, env=None, version=None, full_path=None, debug=None,
300
              cls=None):
301
        """Create a clone of this EnvJujuClient.
302
303
        By default, the class, environment, version, full_path, and debug
304
        settings will match the original, but each can be overridden.
305
        """
1221.1.8 by Aaron Bentley
Implement EnvJujuClient.clone.
306
        if env is None:
307
            env = self.env
308
        if version is None:
309
            version = self.version
310
        if full_path is None:
311
            full_path = self.full_path
312
        if debug is None:
313
            debug = self.debug
1221.1.9 by Aaron Bentley
Further extend clone().
314
        if cls is None:
315
            cls = self.__class__
1263.1.1 by Martin Packman
Refactor how juju feature flags are handled with new enable_feature method
316
        other = cls(env, version, full_path, debug=debug)
317
        other.feature_flags.update(
318
            self.feature_flags.intersection(other.used_feature_flags))
319
        return other
1221.1.8 by Aaron Bentley
Implement EnvJujuClient.clone.
320
1240.2.1 by Aaron Bentley
Support models/cache.yaml
321
    def get_cache_path(self):
322
        return get_cache_path(self.env.juju_home, models=True)
323
1306.1.24 by Curtis Hovey
Added admin=False to _full_call_args and get_status().
324
    def _full_args(self, command, sudo, args,
325
                   timeout=None, include_e=True, admin=False):
196 by Curtis Hovey
Reverted most of the hacks for the juju 1.17.1 tests. Kept some test fixes.
326
        # sudo is not needed for devel releases.
1306.1.24 by Curtis Hovey
Added admin=False to _full_call_args and get_status().
327
        if admin:
1306.1.25 by Curtis Hovey
Alaway ask for admin when waiting for ha.
328
            e_arg = ('-m', self.get_admin_model_name())
1306.1.24 by Curtis Hovey
Added admin=False to _full_call_args and get_status().
329
        elif self.env is None or not include_e:
657.1.1 by Aaron Bentley
Extract EnvJujuClient from JujuClientDevel.
330
            e_arg = ()
331
        else:
1221.1.19 by Aaron Bentley
Tweaked enough to bootstrap.
332
            e_arg = ('-m', self.env.environment)
1069.1.1 by Aaron Bentley
Replace timeout utility in _full_args.
333
        if timeout is None:
372.1.2 by Aaron Bentley
Support supplying timeout to calls.
334
            prefix = ()
335
        else:
1069.1.1 by Aaron Bentley
Replace timeout utility in _full_args.
336
            prefix = get_timeout_prefix(timeout, self._timeout_path)
471.1.3 by Aaron Bentley
Support debug flag.
337
        logging = '--debug' if self.debug else '--show-log'
953.3.13 by Nate Finch
more code review changes
338
1091.3.1 by James Tunnicliffe
Supporting functions for testing networking between containers in a Juju environment.
339
        # If args is a string, make it a tuple. This makes writing commands
340
        # with one argument a bit nicer.
1091.3.2 by James Tunnicliffe
clean_environment bails early if there is no environment to clean.
341
        if isinstance(args, basestring):
1091.3.1 by James Tunnicliffe
Supporting functions for testing networking between containers in a Juju environment.
342
            args = (args,)
953.3.13 by Nate Finch
more code review changes
343
        # we split the command here so that the caller can control where the -e
344
        # <env> flag goes.  Everything in the command string is put before the
345
        # -e flag.
953.3.11 by Nate Finch
set longer timeout for fill log actions, and fix -e testing problem
346
        command = command.split()
966.1.6 by John George
Non-action specific handling of juju sub-commands.
347
        return prefix + ('juju', logging,) + tuple(command) + e_arg + args
19.1.17 by Aaron Bentley
Isolate client to support incompatible command line changes.
348
1260.2.6 by Aaron Bentley
Introduce JujuData, remove workarounds for cloud-credentials.
349
    @staticmethod
350
    def _get_env(env):
1260.2.11 by Aaron Bentley
Get jujupy tests passing, remove region for manual.
351
        if not isinstance(env, JujuData) and isinstance(env,
352
                                                        SimpleEnvironment):
1260.2.23 by Aaron Bentley
Test SimpleEnvironment/JujuData on init.
353
            # FIXME: JujuData should be used from the start.
1260.2.6 by Aaron Bentley
Introduce JujuData, remove workarounds for cloud-credentials.
354
            env = JujuData.from_env(env)
355
        return env
356
1044.1.6 by Aaron Bentley
Use per-client JUJU_HOME.
357
    def __init__(self, env, version, full_path, juju_home=None, debug=False):
1260.2.6 by Aaron Bentley
Introduce JujuData, remove workarounds for cloud-credentials.
358
        self.env = self._get_env(env)
657.1.7 by Aaron Bentley
Clean-up.
359
        self.version = version
360
        self.full_path = full_path
361
        self.debug = debug
1263.1.1 by Martin Packman
Refactor how juju feature flags are handled with new enable_feature method
362
        self.feature_flags = set()
1193.2.1 by Aaron Bentley
Make EnvJujuClient.juju_home a reference to EnvJUjuClient.env.juju_home.
363
        if env is not None:
364
            if juju_home is None:
365
                if env.juju_home is None:
366
                    env.juju_home = get_juju_home()
367
            else:
368
                env.juju_home = juju_home
1048.2.1 by John George
Capture duration of juju commands from the client perspective.
369
        self.juju_timings = {}
1069.1.1 by Aaron Bentley
Replace timeout utility in _full_args.
370
        self._timeout_path = get_timeout_path()
657.1.7 by Aaron Bentley
Clean-up.
371
1044.1.6 by Aaron Bentley
Use per-client JUJU_HOME.
372
    def _shell_environ(self):
653 by Aaron Bentley
Add assess-bootstrap.
373
        """Generate a suitable shell environment.
374
375
        Juju's directory must be in the PATH to support plugins.
376
        """
652 by Aaron Bentley
Insert path-to-juju in PATH, fix test_assess_recovery
377
        env = dict(os.environ)
378
        if self.full_path is not None:
1080.1.12 by Aaron Bentley
Use os.pathsep in PATH.
379
            env['PATH'] = '{}{}{}'.format(os.path.dirname(self.full_path),
380
                                          os.pathsep, env['PATH'])
1263.1.1 by Martin Packman
Refactor how juju feature flags are handled with new enable_feature method
381
        flags = self.feature_flags.intersection(self.used_feature_flags)
382
        if flags:
383
            env[JUJU_DEV_FEATURE_FLAGS] = ','.join(sorted(flags))
1258.1.1 by Aaron Bentley
Use JUJU_DATA where client may be 2.0alpha2+
384
        env['JUJU_DATA'] = self.env.juju_home
652 by Aaron Bentley
Insert path-to-juju in PATH, fix test_assess_recovery
385
        return env
386
1044.1.1 by Aaron Bentley
Use the deploy_job script everywhere, fix windows weirdness.
387
    def add_ssh_machines(self, machines):
1256.1.1 by Aaron Bentley
Retry initial failed add-machine if needed.
388
        for count, machine in enumerate(machines):
389
            try:
390
                self.juju('add-machine', ('ssh:' + machine,))
391
            except subprocess.CalledProcessError:
392
                if count != 0:
393
                    raise
394
                logging.warning('add-machine failed.  Will retry.')
395
                pause(30)
396
                self.juju('add-machine', ('ssh:' + machine,))
1044.1.1 by Aaron Bentley
Use the deploy_job script everywhere, fix windows weirdness.
397
1260.2.5 by Aaron Bentley
Split up cloud and region determination.
398
    @staticmethod
399
    def get_cloud_region(cloud, region):
1260.2.4 by Aaron Bentley
Implement openstack + maas support.
400
        if region is None:
401
            return cloud
402
        return '{}/{}'.format(cloud, region)
403
404
    def get_bootstrap_args(self, upload_tools, config_filename,
405
                           bootstrap_series=None):
406
        """Return the bootstrap arguments for the substrate."""
407
        if self.env.maas:
408
            constraints = 'mem=2G arch=amd64'
409
        elif self.env.joyent:
410
            # Only accept kvm packages by requiring >1 cpu core, see lp:1446264
411
            constraints = 'mem=2G cpu-cores=1'
412
        else:
413
            constraints = 'mem=2G'
1260.2.24 by Aaron Bentley
Move cloud/location to JujuData, add tests, fix lint.
414
        cloud_region = self.get_cloud_region(self.env.get_cloud(),
415
                                             self.env.get_region())
1260.2.1 by Aaron Bentley
Spike to support cloud-credentials.
416
        args = ['--constraints', constraints, self.env.environment,
1315.2.2 by Aaron Bentley
Use --default-model in bootstrap.
417
                cloud_region, '--config', config_filename,
418
                '--default-model', self.env.environment]
663.1.6 by Aaron Bentley
Base support for upload-tools
419
        if upload_tools:
1260.2.6 by Aaron Bentley
Introduce JujuData, remove workarounds for cloud-credentials.
420
            args.insert(0, '--upload-tools')
1260.2.1 by Aaron Bentley
Spike to support cloud-credentials.
421
        else:
422
            args.extend(['--agent-version', self.get_matching_agent_version()])
423
1242.3.1 by Aaron Bentley
Update get_bootstrap_args.
424
        if bootstrap_series is not None:
1260.2.1 by Aaron Bentley
Spike to support cloud-credentials.
425
            args.extend(['--bootstrap-series', bootstrap_series])
426
        return tuple(args)
807.1.2 by Aaron Bentley
Use async bootstraps for industrial testing.
427
1260.2.11 by Aaron Bentley
Get jujupy tests passing, remove region for manual.
428
    @contextmanager
429
    def _bootstrap_config(self):
1260.2.1 by Aaron Bentley
Spike to support cloud-credentials.
430
        config_dict = make_safe_config(self)
431
        # Strip unneeded variables.
1260.2.8 by Aaron Bentley
Fix lint.
432
        config_dict = dict((k, v) for k, v in config_dict.items() if k not in {
1260.2.26 by Aaron Bentley
Test _bootstrap_config.
433
            'access-key',
1325.1.1 by Aaron Bentley
Exclude admin-secret from configs.
434
            'admin-secret',
1260.2.26 by Aaron Bentley
Test _bootstrap_config.
435
            'application-id',
436
            'application-password',
437
            'auth-url',
1280.1.1 by Aaron Bentley
Supply bootstrap host as manual region.
438
            'bootstrap-host',
1260.2.26 by Aaron Bentley
Test _bootstrap_config.
439
            'client-email',
440
            'client-id',
441
            'control-bucket',
442
            'location',
443
            'maas-oauth',
444
            'maas-server',
445
            'manta-key-id',
446
            'manta-user',
447
            'name',
448
            'password',
449
            'private-key',
450
            'region',
451
            'sdc-key-id',
452
            'sdc-url',
453
            'sdc-user',
454
            'secret-key',
455
            'storage-account-name',
456
            'subscription-id',
457
            'tenant-id',
458
            'tenant-name',
459
            'type',
460
            'username',
1306.1.1 by Curtis Hovey
Save spike to get leader and followers.
461
        })
1260.2.1 by Aaron Bentley
Spike to support cloud-credentials.
462
        with temp_yaml_file(config_dict) as config_filename:
1260.2.11 by Aaron Bentley
Get jujupy tests passing, remove region for manual.
463
            yield config_filename
464
1315.2.23 by Aaron Bentley
Introduce set_model_name, update tests, check controller on bootstrap.
465
    def _check_bootstrap(self):
466
        if self.env.environment != self.env.controller.name:
467
            raise AssertionError(
468
                'Controller and environment names should not vary (yet)')
469
1260.2.11 by Aaron Bentley
Get jujupy tests passing, remove region for manual.
470
    def bootstrap(self, upload_tools=False, bootstrap_series=None):
471
        """Bootstrap a controller."""
1315.2.23 by Aaron Bentley
Introduce set_model_name, update tests, check controller on bootstrap.
472
        self._check_bootstrap()
1260.2.11 by Aaron Bentley
Get jujupy tests passing, remove region for manual.
473
        with self._bootstrap_config() as config_filename:
1260.2.1 by Aaron Bentley
Spike to support cloud-credentials.
474
            args = self.get_bootstrap_args(
475
                upload_tools, config_filename, bootstrap_series)
476
            self.juju('bootstrap', args, include_e=False)
477
807.1.2 by Aaron Bentley
Use async bootstraps for industrial testing.
478
    @contextmanager
1260.2.11 by Aaron Bentley
Get jujupy tests passing, remove region for manual.
479
    def bootstrap_async(self, upload_tools=False, bootstrap_series=None):
1315.2.23 by Aaron Bentley
Introduce set_model_name, update tests, check controller on bootstrap.
480
        self._check_bootstrap()
1260.2.11 by Aaron Bentley
Get jujupy tests passing, remove region for manual.
481
        with self._bootstrap_config() as config_filename:
482
            args = self.get_bootstrap_args(
483
                upload_tools, config_filename, bootstrap_series)
1260.2.20 by Aaron Bentley
Get all tests passing.
484
            with self.juju_async('bootstrap', args, include_e=False):
1260.2.11 by Aaron Bentley
Get jujupy tests passing, remove region for manual.
485
                yield
486
                log.info('Waiting for bootstrap of {}.'.format(
487
                    self.env.environment))
807.1.2 by Aaron Bentley
Use async bootstraps for industrial testing.
488
1162.1.1 by Aaron Bentley
Abstract jes support, make enable_jes optional in assess_jes.
489
    def create_environment(self, controller_client, config_file):
1315.2.22 by Aaron Bentley
Fake merge of trunk.
490
        controller_client.controller_juju('create-model', (
491
            self.env.environment, '--config', config_file))
1162.2.2 by Aaron Bentley
Switch to direct BootstrapManager.
492
1221.1.15 by Aaron Bentley
Implement and use destroy_model for destroying models.
493
    def destroy_model(self):
1210.1.1 by Aaron Bentley
Destroy environment without --force if possible.
494
        exit_status = self.juju(
1221.1.1 by Aaron Bentley
Support -model renames in jujupy and test_jujupy.
495
            'destroy-model', (self.env.environment, '-y',),
1221.1.15 by Aaron Bentley
Implement and use destroy_model for destroying models.
496
            include_e=False, timeout=timedelta(minutes=10).total_seconds())
1210.1.1 by Aaron Bentley
Destroy environment without --force if possible.
497
        return exit_status
657.1.1 by Aaron Bentley
Extract EnvJujuClient from JujuClientDevel.
498
1162.1.1 by Aaron Bentley
Abstract jes support, make enable_jes optional in assess_jes.
499
    def kill_controller(self):
500
        """Kill a controller and its environments."""
501
        seen_cmd = self.get_jes_command()
502
        self.juju(
1315.2.22 by Aaron Bentley
Fake merge of trunk.
503
            _jes_cmds[seen_cmd]['kill'], (self.env.controller.name, '-y'),
1162.1.1 by Aaron Bentley
Abstract jes support, make enable_jes optional in assess_jes.
504
            include_e=False, check=False, timeout=600)
505
657.1.1 by Aaron Bentley
Extract EnvJujuClient from JujuClientDevel.
506
    def get_juju_output(self, command, *args, **kwargs):
953.3.13 by Nate Finch
more code review changes
507
        """Call a juju command and return the output.
508
509
        Sub process will be called as 'juju <command> <args> <kwargs>'. Note
510
        that <command> may be a space delimited list of arguments. The -e
953.3.15 by Aaron Bentley
Fix lint.
511
        <environment> flag will be placed after <command> and before args.
953.3.13 by Nate Finch
more code review changes
512
        """
657.1.1 by Aaron Bentley
Extract EnvJujuClient from JujuClientDevel.
513
        args = self._full_args(command, False, args,
953.3.1 by Nate Finch
add log rotation test and support for actions in jujupy
514
                               timeout=kwargs.get('timeout'),
1306.1.24 by Curtis Hovey
Added admin=False to _full_call_args and get_status().
515
                               include_e=kwargs.get('include_e', True),
516
                               admin=kwargs.get('admin', False))
652 by Aaron Bentley
Insert path-to-juju in PATH, fix test_assess_recovery
517
        env = self._shell_environ()
1091.4.1 by James Tunnicliffe
Merged upstream
518
        log.debug(args)
519
        # Mutate os.environ instead of supplying env parameter so
520
        # Windows can search env['PATH']
521
        with scoped_environ(env):
522
            proc = subprocess.Popen(
523
                args, stdout=subprocess.PIPE, stdin=subprocess.PIPE,
524
                stderr=subprocess.PIPE)
525
            sub_output, sub_error = proc.communicate()
526
            log.debug(sub_output)
527
            if proc.returncode != 0:
528
                log.debug(sub_error)
529
                e = subprocess.CalledProcessError(
1326.1.1 by Aaron Bentley
Include all arguments in exception.
530
                    proc.returncode, args, sub_output)
1091.4.1 by James Tunnicliffe
Merged upstream
531
                e.stderr = sub_error
532
                if (
533
                    'Unable to connect to environment' in sub_error or
534
                        'MissingOrIncorrectVersionHeader' in sub_error or
535
                        '307: Temporary Redirect' in sub_error):
218 by Curtis Hovey
Convert MissingOrIncorrectVersionHeader to CannotConnectEnv.
536
                    raise CannotConnectEnv(e)
1091.4.1 by James Tunnicliffe
Merged upstream
537
                raise e
538
        return sub_output
19.1.18 by Aaron Bentley
Provide destroy-environment script to simplify code.
539
1221.5.15 by Aaron Bentley
Use 'show-status' instead of 'status'.
540
    def show_status(self):
541
        """Print the status to output."""
1246.1.4 by Curtis Hovey
Safe print yaml status to ensure continuity.
542
        self.juju(self._show_status, ('--format', 'yaml'))
1221.5.15 by Aaron Bentley
Use 'show-status' instead of 'status'.
543
1306.1.24 by Curtis Hovey
Added admin=False to _full_call_args and get_status().
544
    def get_status(self, timeout=60, raw=False, admin=False, *args):
19.1.17 by Aaron Bentley
Isolate client to support incompatible command line changes.
545
        """Get the current status as a dict."""
1196.1.1 by Martin Packman
Refactoring jujupy including new wait_for_workloads and iter_units methods
546
        # GZ 2015-12-16: Pass remaining timeout into get_juju_output call.
419.1.13 by Aaron Bentley
Allow 10 minutes for status to start working again.
547
        for ignored in until_timeout(timeout):
331.1.1 by Aaron Bentley
juju status retries on error for 30 seconds.
548
            try:
979.3.5 by Horacio Durán
Addressed curtis observations.
549
                if raw:
1221.5.15 by Aaron Bentley
Use 'show-status' instead of 'status'.
550
                    return self.get_juju_output(self._show_status, *args)
551
                return Status.from_text(
1246.1.1 by Curtis Hovey
Call status with --format yaml.
552
                    self.get_juju_output(
1306.1.24 by Curtis Hovey
Added admin=False to _full_call_args and get_status().
553
                        self._show_status, '--format', 'yaml', admin=admin))
1091.5.4 by James Tunnicliffe
SSH wrapper now trying even harder to retry on connection errors.
554
            except subprocess.CalledProcessError:
331.1.1 by Aaron Bentley
juju status retries on error for 30 seconds.
555
                pass
556
        raise Exception(
1091.5.4 by James Tunnicliffe
SSH wrapper now trying even harder to retry on connection errors.
557
            'Timed out waiting for juju status to succeed')
19.1.17 by Aaron Bentley
Isolate client to support incompatible command line changes.
558
1251.1.1 by Aaron Bentley
Implement and use set_model_constraints.
559
    @staticmethod
560
    def _dict_as_option_strings(options):
561
        return tuple('{}={}'.format(*item) for item in options.items())
562
1239.1.1 by Aaron Bentley
Implement and use EnvJujuClient.set_config.
563
    def set_config(self, service, options):
1251.1.1 by Aaron Bentley
Implement and use set_model_constraints.
564
        option_strings = self._dict_as_option_strings(options)
565
        self.juju('set-config', (service,) + option_strings)
1239.1.1 by Aaron Bentley
Implement and use EnvJujuClient.set_config.
566
1221.5.11 by Aaron Bentley
Extract EnvJujuClient.get_config.
567
    def get_config(self, service):
1221.5.12 by Aaron Bentley
Support get-config.
568
        return yaml_loads(self.get_juju_output('get-config', service))
1221.5.11 by Aaron Bentley
Extract EnvJujuClient.get_config.
569
979.2.1 by John George
Add support for checking chaos is complete, from run_chaos_monkey.py
570
    def get_service_config(self, service, timeout=60):
571
        for ignored in until_timeout(timeout):
572
            try:
1221.5.11 by Aaron Bentley
Extract EnvJujuClient.get_config.
573
                return self.get_config(service)
979.2.1 by John George
Add support for checking chaos is complete, from run_chaos_monkey.py
574
            except subprocess.CalledProcessError:
575
                pass
576
        raise Exception(
577
            'Timed out waiting for juju get %s' % (service))
578
1251.1.1 by Aaron Bentley
Implement and use set_model_constraints.
579
    def set_model_constraints(self, constraints):
580
        constraint_strings = self._dict_as_option_strings(constraints)
581
        return self.juju('set-model-constraints', constraint_strings)
582
1243.1.1 by Aaron Bentley
Support rename of get-env/set-env => get-model-config/set-model-config.
583
    def get_model_config(self):
584
        """Return the value of the environment's configured option."""
585
        return yaml.safe_load(self.get_juju_output('get-model-config'))
586
657.1.7 by Aaron Bentley
Clean-up.
587
    def get_env_option(self, option):
588
        """Return the value of the environment's configured option."""
1243.1.1 by Aaron Bentley
Support rename of get-env/set-env => get-model-config/set-model-config.
589
        return self.get_juju_output('get-model-config', option)
657.1.7 by Aaron Bentley
Clean-up.
590
591
    def set_env_option(self, option, value):
592
        """Set the value of the option in the environment."""
593
        option_value = "%s=%s" % (option, value)
1243.1.1 by Aaron Bentley
Support rename of get-env/set-env => get-model-config/set-model-config.
594
        return self.juju('set-model-config', (option_value,))
657.1.7 by Aaron Bentley
Clean-up.
595
880.1.17 by Aaron Bentley
Fake merge of trunk into no-environment.
596
    def set_testing_tools_metadata_url(self):
597
        url = self.get_env_option('tools-metadata-url')
598
        if 'testing' not in url:
599
            testing_url = url.replace('/tools', '/testing/tools')
600
            self.set_env_option('tools-metadata-url', testing_url)
601
768.1.1 by Aaron Bentley
Add timeout to EnvJujuClient.destroy_environment.
602
    def juju(self, command, args, sudo=False, check=True, include_e=True,
1044.1.6 by Aaron Bentley
Use per-client JUJU_HOME.
603
             timeout=None, extra_env=None):
657.1.7 by Aaron Bentley
Clean-up.
604
        """Run a command under juju for the current environment."""
768.1.1 by Aaron Bentley
Add timeout to EnvJujuClient.destroy_environment.
605
        args = self._full_args(command, sudo, args, include_e=include_e,
606
                               timeout=timeout)
1091.4.1 by James Tunnicliffe
Merged upstream
607
        log.info(' '.join(args))
1044.1.6 by Aaron Bentley
Use per-client JUJU_HOME.
608
        env = self._shell_environ()
966.2.1 by Curtis Hovey
Added extra_env, but tests fail.
609
        if extra_env is not None:
610
            env.update(extra_env)
657.1.7 by Aaron Bentley
Clean-up.
611
        if check:
1044.1.9 by Aaron Bentley
Update and add tests.
612
            call_func = subprocess.check_call
613
        else:
614
            call_func = subprocess.call
1048.2.1 by John George
Capture duration of juju commands from the client perspective.
615
        start_time = time.time()
1080.1.14 by Aaron Bentley
Add docs.
616
        # Mutate os.environ instead of supplying env parameter so Windows can
617
        # search env['PATH']
1080.1.10 by Aaron Bentley
Supply the shell environment via os.environ.
618
        with scoped_environ(env):
1080.1.11 by Aaron Bentley
Stop supplying env to subprocess calls.
619
            rval = call_func(args)
1048.2.1 by John George
Capture duration of juju commands from the client perspective.
620
        self.juju_timings.setdefault(args, []).append(
621
            (time.time() - start_time))
622
        return rval
623
1315.2.22 by Aaron Bentley
Fake merge of trunk.
624
    def controller_juju(self, command, args):
625
        args = ('-c', self.env.controller.name) + args
626
        return self.juju(command, args, include_e=False)
627
1048.2.1 by John George
Capture duration of juju commands from the client perspective.
628
    def get_juju_timings(self):
629
        stringified_timings = {}
630
        for command, timings in self.juju_timings.items():
631
            stringified_timings[' '.join(command)] = timings
632
        return stringified_timings
657.1.7 by Aaron Bentley
Clean-up.
633
807.1.2 by Aaron Bentley
Use async bootstraps for industrial testing.
634
    @contextmanager
1044.1.6 by Aaron Bentley
Use per-client JUJU_HOME.
635
    def juju_async(self, command, args, include_e=True, timeout=None):
807.1.2 by Aaron Bentley
Use async bootstraps for industrial testing.
636
        full_args = self._full_args(command, False, args, include_e=include_e,
637
                                    timeout=timeout)
1091.4.1 by James Tunnicliffe
Merged upstream
638
        log.info(' '.join(args))
1044.1.6 by Aaron Bentley
Use per-client JUJU_HOME.
639
        env = self._shell_environ()
1080.1.14 by Aaron Bentley
Add docs.
640
        # Mutate os.environ instead of supplying env parameter so Windows can
641
        # search env['PATH']
1080.1.10 by Aaron Bentley
Supply the shell environment via os.environ.
642
        with scoped_environ(env):
1080.1.11 by Aaron Bentley
Stop supplying env to subprocess calls.
643
            proc = subprocess.Popen(full_args)
807.1.3 by Aaron Bentley
Get juju_async under test.
644
        yield proc
807.1.2 by Aaron Bentley
Use async bootstraps for industrial testing.
645
        retcode = proc.wait()
646
        if retcode != 0:
647
            raise subprocess.CalledProcessError(retcode, full_args)
648
1145.3.3 by John George
Allow a service name to be passed when calling juju deploy.
649
    def deploy(self, charm, repository=None, to=None, service=None):
874.2.4 by Aaron Bentley
Initial upgrade-charm test.
650
        args = [charm]
651
        if repository is not None:
652
            args.extend(['--repository', repository])
1014.2.1 by John George
background_chaos WIP
653
        if to is not None:
654
            args.extend(['--to', to])
1145.3.3 by John George
Allow a service name to be passed when calling juju deploy.
655
        if service is not None:
656
            args.extend([service])
874.2.4 by Aaron Bentley
Initial upgrade-charm test.
657
        return self.juju('deploy', tuple(args))
650.1.12 by Aaron Bentley
Rename to assess-foreign, update to use EnvJujuClient.
658
1221.4.1 by Aaron Bentley
Extract destroy-service to a method.
659
    def remove_service(self, service):
1221.4.2 by Aaron Bentley
Introduce EnvJujuClient.remove_service.
660
        self.juju('remove-service', (service,))
1221.4.1 by Aaron Bentley
Extract destroy-service to a method.
661
1259.2.1 by Martin Packman
Add flag to run_deployer to use juju 2.0 bundle deployment
662
    def deploy_bundle(self, bundle, timeout=_DEFAULT_BUNDLE_TIMEOUT):
663
        """Deploy bundle using native juju 2.0 deploy command."""
664
        self.juju('deploy', bundle, timeout=timeout)
665
1204.1.2 by John George
Pass deployer timeout values as function parameters.
666
    def deployer(self, bundle, name=None, deploy_delay=10, timeout=3600):
884 by John George
Add juju-deployer test support.
667
        """deployer, using sudo if necessary."""
668
        args = (
669
            '--debug',
1204.1.2 by John George
Pass deployer timeout values as function parameters.
670
            '--deploy-delay', str(deploy_delay),
671
            '--timeout', str(timeout),
884 by John George
Add juju-deployer test support.
672
            '--config', bundle,
673
        )
953.2.1 by John George
Support taking a bundle name in addition to the bundle config file path.
674
        if name:
675
            args += (name,)
884 by John George
Add juju-deployer test support.
676
        self.juju('deployer', args, self.env.needs_sudo())
677
1260.2.30 by Aaron Bentley
Updates from review
678
    def _get_substrate_constraints(self):
679
        if self.env.maas:
680
            return 'mem=2G arch=amd64'
681
        elif self.env.joyent:
682
            # Only accept kvm packages by requiring >1 cpu core, see lp:1446264
683
            return 'mem=2G cpu-cores=1'
684
        else:
685
            return 'mem=2G'
686
796.2.2 by John George
Call quickstart from EnvJujuClient, with constraints
687
    def quickstart(self, bundle, upload_tools=False):
688
        """quickstart, using sudo if necessary."""
689
        if self.env.maas:
690
            constraints = 'mem=2G arch=amd64'
691
        else:
692
            constraints = 'mem=2G'
693
        args = ('--constraints', constraints)
694
        if upload_tools:
695
            args = ('--upload-tools',) + args
696
        args = args + ('--no-browser', bundle,)
966.2.2 by Curtis Hovey
pass extra_env={'JUJU': None} with the quickstart command.
697
        self.juju('quickstart', args, self.env.needs_sudo(),
698
                  extra_env={'JUJU': self.full_path})
796.2.2 by John George
Call quickstart from EnvJujuClient, with constraints
699
874.2.4 by Aaron Bentley
Initial upgrade-charm test.
700
    def status_until(self, timeout, start=None):
874.2.8 by Aaron Bentley
Add docs, simplify get_unit.
701
        """Call and yield status until the timeout is reached.
702
703
        Status will always be yielded once before checking the timeout.
704
705
        This is intended for implementing things like wait_for_started.
706
707
        :param timeout: The number of seconds to wait before timing out.
708
        :param start: If supplied, the time to count from when determining
709
            timeout.
710
        """
874.2.4 by Aaron Bentley
Initial upgrade-charm test.
711
        yield self.get_status()
712
        for remaining in until_timeout(timeout, start=start):
713
            yield self.get_status()
714
1196.1.6 by Martin Packman
Changes for review by abentley
715
    def _wait_for_status(self, reporter, translate, exc_type=StatusNotMet,
1196.1.4 by Martin Packman
Finish increased sanity on deployer tests
716
                         timeout=1200, start=None):
1196.1.1 by Martin Packman
Refactoring jujupy including new wait_for_workloads and iter_units methods
717
        """Wait till status reaches an expected state with pretty reporting.
718
719
        Always tries to get status at least once. Each status call has an
720
        internal timeout of 60 seconds. This is independent of the timeout for
721
        the whole wait, note this means this function may be overrun.
722
723
        :param reporter: A GroupReporter instance for output.
724
        :param translate: A callable that takes status to make states dict.
1196.1.6 by Martin Packman
Changes for review by abentley
725
        :param exc_type: Optional StatusNotMet subclass to raise on timeout.
1196.1.1 by Martin Packman
Refactoring jujupy including new wait_for_workloads and iter_units methods
726
        :param timeout: Optional number of seconds to wait before timing out.
727
        :param start: Optional time to count from when determining timeout.
728
        """
729
        status = None
730
        try:
731
            for _ in chain([None], until_timeout(timeout, start=start)):
732
                try:
733
                    status = self.get_status()
734
                except CannotConnectEnv:
735
                    log.info('Suppressing "Unable to connect to environment"')
736
                    continue
737
                states = translate(status)
738
                if states is None:
739
                    break
740
                reporter.update(states)
741
            else:
742
                if status is not None:
743
                    log.error(status.status_text)
1196.1.6 by Martin Packman
Changes for review by abentley
744
                raise exc_type(self.env.environment, status)
1196.1.1 by Martin Packman
Refactoring jujupy including new wait_for_workloads and iter_units methods
745
        finally:
746
            reporter.finish()
747
        return status
748
751.1.1 by Aaron Bentley
Give equal time to old and new clients in DeployManyAttempt
749
    def wait_for_started(self, timeout=1200, start=None):
657.1.5 by Aaron Bentley
Move wait_for_started to EnvJujuClient.
750
        """Wait until all unit/machine agents are 'started'."""
751.3.1 by Martin Packman
Use dots rather than repeated lines when waiting for status changes
751
        reporter = GroupReporter(sys.stdout, 'started')
1196.1.1 by Martin Packman
Refactoring jujupy including new wait_for_workloads and iter_units methods
752
        return self._wait_for_status(
753
            reporter, Status.check_agents_started, AgentsNotStarted,
754
            timeout=timeout, start=start)
657.1.5 by Aaron Bentley
Move wait_for_started to EnvJujuClient.
755
966.1.10 by John George
Check that subordinate unit count matches service unit count in wait_for_subordinate_units.
756
    def wait_for_subordinate_units(self, service, unit_prefix, timeout=1200,
757
                                   start=None):
966.1.12 by John George
Verify subordinate units are started in wait_for_subordinate_units and add the reporter.
758
        """Wait until all service units have a started subordinate with
966.1.10 by John George
Check that subordinate unit count matches service unit count in wait_for_subordinate_units.
759
        unit_prefix."""
1196.1.1 by Martin Packman
Refactoring jujupy including new wait_for_workloads and iter_units methods
760
        def status_to_subordinate_states(status):
761
            service_unit_count = status.get_service_unit_count(service)
762
            subordinate_unit_count = 0
763
            unit_states = defaultdict(list)
764
            for name, unit in status.service_subordinate_units(service):
765
                if name.startswith(unit_prefix + '/'):
766
                    subordinate_unit_count += 1
1246.1.2 by Curtis Hovey
Added coalesce_agent_status to handle machine agent-state and unit agent-status.
767
                    unit_states[coalesce_agent_status(unit)].append(name)
1196.1.1 by Martin Packman
Refactoring jujupy including new wait_for_workloads and iter_units methods
768
            if (subordinate_unit_count == service_unit_count and
1246.1.2 by Curtis Hovey
Added coalesce_agent_status to handle machine agent-state and unit agent-status.
769
                    set(unit_states.keys()).issubset(AGENTS_READY)):
1196.1.1 by Martin Packman
Refactoring jujupy including new wait_for_workloads and iter_units methods
770
                return None
771
            return unit_states
966.1.12 by John George
Verify subordinate units are started in wait_for_subordinate_units and add the reporter.
772
        reporter = GroupReporter(sys.stdout, 'started')
1196.1.1 by Martin Packman
Refactoring jujupy including new wait_for_workloads and iter_units methods
773
        self._wait_for_status(
774
            reporter, status_to_subordinate_states, AgentsNotStarted,
775
            timeout=timeout, start=start)
966.1.9 by John George
Add wait_for_subordinate_unit to jujupy.py.
776
1196.1.1 by Martin Packman
Refactoring jujupy including new wait_for_workloads and iter_units methods
777
    def wait_for_version(self, version, timeout=300, start=None):
778
        def status_to_version(status):
779
            versions = status.get_agent_versions()
780
            if versions.keys() == [version]:
781
                return None
782
            return versions
751.3.1 by Martin Packman
Use dots rather than repeated lines when waiting for status changes
783
        reporter = GroupReporter(sys.stdout, version)
1196.1.1 by Martin Packman
Refactoring jujupy including new wait_for_workloads and iter_units methods
784
        self._wait_for_status(reporter, status_to_version, VersionsNotUpdated,
1196.1.4 by Martin Packman
Finish increased sanity on deployer tests
785
                              timeout=timeout, start=start)
657.1.6 by Aaron Bentley
Move wait_for_version to EnvJujuClient.
786
1306.1.21 by Curtis Hovey
added list-controllers and list-models support for 2.x.
787
    def list_models(self):
788
        """List the models registered with the current controller."""
1315.2.22 by Aaron Bentley
Fake merge of trunk.
789
        self.controller_juju('list-models', ())
1306.1.21 by Curtis Hovey
added list-controllers and list-models support for 2.x.
790
791
    def get_models(self):
792
        """return a models dict with a 'models': [] key-value pair."""
793
        output = self.get_juju_output(
794
            'list-models', '-c', self.env.environment, '--format', 'yaml',
795
            include_e=False)
796
        models = yaml_loads(output)
797
        return models
798
1315.2.8 by Aaron Bentley
Dump logs for all models, including on JES.
799
    def _get_models(self):
800
        """return a list of model dicts."""
801
        return self.get_models()['models']
802
803
    def iter_model_clients(self):
1318.2.1 by Aaron Bentley
Unify cloning code, add docs.
804
        """Iterate through all the models that share this model's controller.
805
806
        Works only if JES is enabled.
807
        """
1315.2.8 by Aaron Bentley
Dump logs for all models, including on JES.
808
        models = self._get_models()
809
        if not models:
810
            yield self
811
        for model in models:
1318.2.1 by Aaron Bentley
Unify cloning code, add docs.
812
            yield self._acquire_model_client(model['name'])
1315.2.8 by Aaron Bentley
Dump logs for all models, including on JES.
813
1306.1.21 by Curtis Hovey
added list-controllers and list-models support for 2.x.
814
    def get_admin_model_name(self):
815
        """Return the name of the 'admin' model.
816
817
        Return the name of the environment when an 'admin' model does
818
        not exist.
819
        """
1315.2.3 by Aaron Bentley
Use admin client to get bootstrap host.
820
        return 'admin'
821
1318.2.1 by Aaron Bentley
Unify cloning code, add docs.
822
    def _acquire_model_client(self, name):
823
        """Get a client for a model with the supplied name.
824
825
        If the name matches self, self is used.  Otherwise, a clone is used.
826
        """
827
        if name == self.env.environment:
828
            return self
829
        else:
830
            env = self.env.clone(model_name=name)
831
            return self.clone(env=env)
832
1315.2.3 by Aaron Bentley
Use admin client to get bootstrap host.
833
    def get_admin_client(self):
1315.2.8 by Aaron Bentley
Dump logs for all models, including on JES.
834
        """Return a client for the admin model.  May return self.
835
836
        This may be inaccurate for models created using create_environment
837
        rather than bootstrap.
838
        """
1318.2.1 by Aaron Bentley
Unify cloning code, add docs.
839
        return self._acquire_model_client(self.get_admin_model_name())
1306.1.21 by Curtis Hovey
added list-controllers and list-models support for 2.x.
840
841
    def list_controllers(self):
842
        """List the controllers."""
843
        self.juju('list-controllers', (), include_e=False)
844
1306.1.4 by Curtis Hovey
Added get_controller_endpoint for show-controller.
845
    def get_controller_endpoint(self):
846
        """Return the address of the controller leader."""
1324.1.1 by Aaron Bentley
Use controller name for show-controller.
847
        controller = self.env.controller.name
1306.1.19 by Curtis Hovey
Fix calls to show-controller with include_e=False.
848
        output = self.get_juju_output(
1324.1.1 by Aaron Bentley
Use controller name for show-controller.
849
            'show-controller', controller, include_e=False)
1306.1.19 by Curtis Hovey
Fix calls to show-controller with include_e=False.
850
        info = yaml_loads(output)
1324.1.1 by Aaron Bentley
Use controller name for show-controller.
851
        endpoint = info[controller]['details']['api-endpoints'][0]
1310.1.2 by Curtis Hovey
Address ipv6 split address and port.
852
        address, port = split_address_port(endpoint)
1306.1.4 by Curtis Hovey
Added get_controller_endpoint for show-controller.
853
        return address
854
1306.1.6 by Curtis Hovey
Added get_controller_members.
855
    def get_controller_members(self):
1306.1.8 by Curtis Hovey
Update docstrings.
856
        """Return a list of Machines that are members of the controller.
1306.1.6 by Curtis Hovey
Added get_controller_members.
857
1306.1.8 by Curtis Hovey
Update docstrings.
858
        The first machine in the list is the leader. the remaining machines
1306.1.6 by Curtis Hovey
Added get_controller_members.
859
        are followers in a HA relationship.
860
        """
1310.1.3 by Curtis Hovey
Address sorting comment from review.
861
        members = []
1306.1.6 by Curtis Hovey
Added get_controller_members.
862
        status = self.get_status()
1315.2.1 by Aaron Bentley
Use machine id instead of machine number.
863
        for machine_id, machine in status.iter_machines():
1306.1.6 by Curtis Hovey
Added get_controller_members.
864
            if self.get_controller_member_status(machine):
1315.2.1 by Aaron Bentley
Use machine id instead of machine number.
865
                members.append(Machine(machine_id, machine))
1310.1.3 by Curtis Hovey
Address sorting comment from review.
866
        if len(members) <= 1:
867
            return members
1306.1.6 by Curtis Hovey
Added get_controller_members.
868
        # Search for the leader and make it the first in the list.
1310.1.3 by Curtis Hovey
Address sorting comment from review.
869
        # If the endpoint address is not the same as the leader's dns_name,
870
        # the members are return in the order they were discovered.
1306.1.6 by Curtis Hovey
Added get_controller_members.
871
        endpoint = self.get_controller_endpoint()
1310.1.3 by Curtis Hovey
Address sorting comment from review.
872
        log.debug('Controller endpoint is at {}'.format(endpoint))
873
        members.sort(key=lambda m: m.info.get('dns-name') != endpoint)
874
        return members
1306.1.6 by Curtis Hovey
Added get_controller_members.
875
1306.1.7 by Curtis Hovey
Added get_controller_leader.
876
    def get_controller_leader(self):
1306.1.8 by Curtis Hovey
Update docstrings.
877
        """Return the controller leader Machine."""
1306.1.7 by Curtis Hovey
Added get_controller_leader.
878
        controller_members = self.get_controller_members()
879
        return controller_members[0]
880
1259.1.2 by Aaron Bentley
Implement get_controller_member_status.
881
    @staticmethod
882
    def get_controller_member_status(info_dict):
1306.1.8 by Curtis Hovey
Update docstrings.
883
        """Return the controller-member-status of the machine if it exists."""
1259.1.2 by Aaron Bentley
Implement get_controller_member_status.
884
        return info_dict.get('controller-member-status')
885
703.1.1 by Aaron Bentley
Add ensure-availability stage to industrial_test.
886
    def wait_for_ha(self, timeout=1200):
887
        desired_state = 'has-vote'
751.3.1 by Martin Packman
Use dots rather than repeated lines when waiting for status changes
888
        reporter = GroupReporter(sys.stdout, desired_state)
943.1.1 by Martin Packman
Use try/finally to always call finish on GroupReporter instances
889
        try:
890
            for remaining in until_timeout(timeout):
1306.1.25 by Curtis Hovey
Alaway ask for admin when waiting for ha.
891
                status = self.get_status(admin=True)
943.1.1 by Martin Packman
Use try/finally to always call finish on GroupReporter instances
892
                states = {}
893
                for machine, info in status.iter_machines():
1259.1.2 by Aaron Bentley
Implement get_controller_member_status.
894
                    status = self.get_controller_member_status(info)
943.1.1 by Martin Packman
Use try/finally to always call finish on GroupReporter instances
895
                    if status is None:
896
                        continue
897
                    states.setdefault(status, []).append(machine)
898
                if states.keys() == [desired_state]:
899
                    if len(states.get(desired_state, [])) >= 3:
900
                        # XXX sinzui 2014-12-04: bug 1399277 happens because
901
                        # juju claims HA is ready when the monogo replica sets
902
                        # are not. Juju is not fully usable. The replica set
903
                        # lag might be 5 minutes.
904
                        pause(300)
905
                        return
906
                reporter.update(states)
907
            else:
908
                raise Exception('Timed out waiting for voting to be enabled.')
909
        finally:
889.2.1 by Martin Packman
Make sure to finish GroupReporter instances even in error cases
910
            reporter.finish()
703.1.1 by Aaron Bentley
Add ensure-availability stage to industrial_test.
911
796.2.1 by John George
Add quickstart_deploy.py
912
    def wait_for_deploy_started(self, service_count=1, timeout=1200):
913
        """Wait until service_count services are 'started'.
914
915
        :param service_count: The number of services for which to wait.
916
        :param timeout: The number of seconds to wait.
917
        """
918
        for remaining in until_timeout(timeout):
919
            status = self.get_status()
920
            if status.get_service_count() >= service_count:
921
                return
922
        else:
923
            raise Exception('Timed out waiting for services to start.')
924
1215 by John George
Workload timeout increate to 10 minutes.
925
    def wait_for_workloads(self, timeout=600, start=None):
1196.1.1 by Martin Packman
Refactoring jujupy including new wait_for_workloads and iter_units methods
926
        """Wait until all unit workloads are in a ready state."""
927
        def status_to_workloads(status):
928
            unit_states = defaultdict(list)
929
            for name, unit in status.iter_units():
930
                workload = unit.get('workload-status')
931
                if workload is not None:
1196.1.6 by Martin Packman
Changes for review by abentley
932
                    state = workload['current']
1204.3.1 by Martin Packman
Fix bug introduced with unknown workloads and add test coverage
933
                else:
934
                    state = 'unknown'
935
                unit_states[state].append(name)
936
            if set(('active', 'unknown')).issuperset(unit_states):
1196.1.1 by Martin Packman
Refactoring jujupy including new wait_for_workloads and iter_units methods
937
                return None
1204.3.1 by Martin Packman
Fix bug introduced with unknown workloads and add test coverage
938
            unit_states.pop('unknown', None)
1196.1.1 by Martin Packman
Refactoring jujupy including new wait_for_workloads and iter_units methods
939
            return unit_states
1196.1.6 by Martin Packman
Changes for review by abentley
940
        reporter = GroupReporter(sys.stdout, 'active')
1196.1.1 by Martin Packman
Refactoring jujupy including new wait_for_workloads and iter_units methods
941
        self._wait_for_status(reporter, status_to_workloads, WorkloadsNotReady,
1196.1.4 by Martin Packman
Finish increased sanity on deployer tests
942
                              timeout=timeout, start=start)
1196.1.1 by Martin Packman
Refactoring jujupy including new wait_for_workloads and iter_units methods
943
1091.3.1 by James Tunnicliffe
Supporting functions for testing networking between containers in a Juju environment.
944
    def wait_for(self, thing, search_type, timeout=300):
1091.3.2 by James Tunnicliffe
clean_environment bails early if there is no environment to clean.
945
        """ Wait for a something (thing) matching none/all/some machines.
946
947
        Examples:
948
          wait_for('containers', 'all')
949
          This will wait for a container to appear on all machines.
950
951
          wait_for('machines-not-0', 'none')
952
          This will wait for all machines other than 0 to be removed.
953
954
        :param thing: string, either 'containers' or 'not-machine-0'
955
        :param search_type: string containing none, some or all
956
        :param timeout: number of seconds to wait for condition to be true.
957
        :return:
958
        """
959
        try:
960
            for status in self.status_until(timeout):
961
                hit = False
962
                miss = False
963
964
                for machine, details in status.status['machines'].iteritems():
965
                    if thing == 'containers':
966
                        if 'containers' in details:
967
                            hit = True
968
                        else:
969
                            miss = True
970
971
                    elif thing == 'machines-not-0':
972
                        if machine != '0':
973
                            hit = True
974
                        else:
975
                            miss = True
976
977
                    else:
978
                        raise ValueError("Unrecognised thing to wait for: %s",
979
                                         thing)
980
981
                if search_type == 'none':
982
                    if not hit:
983
                        return
984
                elif search_type == 'some':
985
                    if hit:
986
                        return
987
                elif search_type == 'all':
988
                    if not miss:
989
                        return
990
        except Exception:
991
            raise Exception("Timed out waiting for %s" % thing)
1091.3.1 by James Tunnicliffe
Supporting functions for testing networking between containers in a Juju environment.
992
657.1.4 by Aaron Bentley
Move upgrade functionality to EnvJujuClient.
993
    def get_matching_agent_version(self, no_build=False):
994
        # strip the series and srch from the built version.
995
        version_parts = self.version.split('-')
996
        if len(version_parts) == 4:
997
            version_number = '-'.join(version_parts[0:2])
998
        else:
999
            version_number = version_parts[0]
1000
        if not no_build and self.env.local:
1001
            version_number += '.1'
1002
        return version_number
1003
650.1.9 by Aaron Bentley
Merged trunk into compatibility-test.
1004
    def upgrade_juju(self, force_version=True):
1005
        args = ()
1006
        if force_version:
1007
            version = self.get_matching_agent_version(no_build=True)
1008
            args += ('--version', version)
657.1.4 by Aaron Bentley
Move upgrade functionality to EnvJujuClient.
1009
        if self.env.local:
1010
            args += ('--upload-tools',)
659 by Aaron Bentley
Fix upgrade's juju invocation.
1011
        self.juju('upgrade-juju', args)
657.1.4 by Aaron Bentley
Move upgrade functionality to EnvJujuClient.
1012
1258.2.5 by Curtis Hovey
Added upgrade_mongo to EnvJujuClient.
1013
    def upgrade_mongo(self):
1014
        self.juju('upgrade-mongo', ())
1015
717.2.1 by Aaron Bentley
Extract EnvJujuClient.backup from assess_recovery.
1016
    def backup(self):
1017
        environ = self._shell_environ()
808.2.6 by Aaron Bentley
Fix Azure terminate instances to work as Juju expects.
1018
        try:
1080.1.14 by Aaron Bentley
Add docs.
1019
            # Mutate os.environ instead of supplying env parameter so Windows
1020
            # can search env['PATH']
1080.1.10 by Aaron Bentley
Supply the shell environment via os.environ.
1021
            with scoped_environ(environ):
1221.4.4 by Aaron Bentley
Support create-backup, restore-backup, enable-ha.
1022
                args = self._full_args(
1023
                    'create-backup', False, (), include_e=True)
1326.1.2 by Aaron Bentley
Emit backup command args.
1024
                log.info(' '.join(args))
1221.4.4 by Aaron Bentley
Support create-backup, restore-backup, enable-ha.
1025
                output = subprocess.check_output(args)
808.2.6 by Aaron Bentley
Fix Azure terminate instances to work as Juju expects.
1026
        except subprocess.CalledProcessError as e:
1091.4.1 by James Tunnicliffe
Merged upstream
1027
            log.info(e.output)
808.2.6 by Aaron Bentley
Fix Azure terminate instances to work as Juju expects.
1028
            raise
1091.4.1 by James Tunnicliffe
Merged upstream
1029
        log.info(output)
757.1.1 by Curtis Hovey
Support both .tgz and .tar.gz backup file names.
1030
        backup_file_pattern = re.compile('(juju-backup-[0-9-]+\.(t|tar.)gz)')
717.2.1 by Aaron Bentley
Extract EnvJujuClient.backup from assess_recovery.
1031
        match = backup_file_pattern.search(output)
1032
        if match is None:
1033
            raise Exception("The backup file was not found in output: %s" %
1034
                            output)
1035
        backup_file_name = match.group(1)
1036
        backup_file_path = os.path.abspath(backup_file_name)
1091.4.1 by James Tunnicliffe
Merged upstream
1037
        log.info("State-Server backup at %s", backup_file_path)
717.2.1 by Aaron Bentley
Extract EnvJujuClient.backup from assess_recovery.
1038
        return backup_file_path
1039
1221.4.4 by Aaron Bentley
Support create-backup, restore-backup, enable-ha.
1040
    def restore_backup(self, backup_file):
1281.1.1 by Aaron Bentley
Rollback r1280 per Ian's email.
1041
        return self.get_juju_output('restore-backup', '-b', '--constraints',
1042
                                    'mem=2G', '--file', backup_file)
1221.4.4 by Aaron Bentley
Support create-backup, restore-backup, enable-ha.
1043
1044
    def restore_backup_async(self, backup_file):
1281.1.1 by Aaron Bentley
Rollback r1280 per Ian's email.
1045
        return self.juju_async('restore-backup', ('-b', '--constraints',
1046
                               'mem=2G', '--file', backup_file))
1221.4.4 by Aaron Bentley
Support create-backup, restore-backup, enable-ha.
1047
1048
    def enable_ha(self):
1049
        self.juju('enable-ha', ('-n', '3'))
1050
953.3.11 by Nate Finch
set longer timeout for fill log actions, and fix -e testing problem
1051
    def action_fetch(self, id, action=None, timeout="1m"):
953.3.1 by Nate Finch
add log rotation test and support for actions in jujupy
1052
        """Fetches the results of the action with the given id.
1053
953.3.5 by Nate Finch
tests for new action stuff
1054
        Will wait for up to 1 minute for the action results.
953.3.7 by Nate Finch
update for code review comments
1055
        The action name here is just used for an more informational error in
953.3.5 by Nate Finch
tests for new action stuff
1056
        cases where it's available.
953.3.1 by Nate Finch
add log rotation test and support for actions in jujupy
1057
        Returns the yaml output of the fetched action.
1058
        """
1221.1.25 by Aaron Bentley
Use flat names for action commands.
1059
        out = self.get_juju_output("show-action-output", id, "--wait", timeout)
953.3.1 by Nate Finch
add log rotation test and support for actions in jujupy
1060
        status = yaml_loads(out)["status"]
1061
        if status != "completed":
953.3.8 by Nate Finch
more review changes
1062
            name = ""
953.3.7 by Nate Finch
update for code review comments
1063
            if action is not None:
953.3.8 by Nate Finch
more review changes
1064
                name = " " + action
953.3.9 by Nate Finch
more code review changes
1065
            raise Exception(
1066
                "timed out waiting for action%s to complete during fetch" %
1067
                name)
953.3.2 by Nate Finch
log_rot fixes
1068
        return out
953.3.1 by Nate Finch
add log rotation test and support for actions in jujupy
1069
1070
    def action_do(self, unit, action, *args):
1071
        """Performs the given action on the given unit.
1072
1073
        Action params should be given as args in the form foo=bar.
1074
        Returns the id of the queued action.
1075
        """
953.3.13 by Nate Finch
more code review changes
1076
        args = (unit, action) + args
1077
1221.1.25 by Aaron Bentley
Use flat names for action commands.
1078
        output = self.get_juju_output("run-action", *args)
953.3.9 by Nate Finch
more code review changes
1079
        action_id_pattern = re.compile(
1080
            'Action queued with id: ([a-f0-9\-]{36})')
953.3.1 by Nate Finch
add log rotation test and support for actions in jujupy
1081
        match = action_id_pattern.search(output)
1082
        if match is None:
1083
            raise Exception("Action id not found in output: %s" %
1084
                            output)
1085
        return match.group(1)
1086
953.3.11 by Nate Finch
set longer timeout for fill log actions, and fix -e testing problem
1087
    def action_do_fetch(self, unit, action, timeout="1m", *args):
983.1.1 by Martin Packman
Add new Remote class for accessing juju machines
1088
        """Performs given action on given unit and waits for the results.
953.3.1 by Nate Finch
add log rotation test and support for actions in jujupy
1089
1090
        Action params should be given as args in the form foo=bar.
1091
        Returns the yaml output of the action.
1092
        """
1093
        id = self.action_do(unit, action, *args)
953.3.11 by Nate Finch
set longer timeout for fill log actions, and fix -e testing problem
1094
        return self.action_fetch(id, action, timeout)
953.3.1 by Nate Finch
add log rotation test and support for actions in jujupy
1095
1221.5.1 by Aaron Bentley
Extract EnvJujuClient.list_space.
1096
    def list_space(self):
1221.5.2 by Aaron Bentley
Support list-space for EnvJujuClient.
1097
        return yaml.safe_load(self.get_juju_output('list-space'))
1221.5.1 by Aaron Bentley
Extract EnvJujuClient.list_space.
1098
1221.5.3 by Aaron Bentley
Extract EnvJujuClient.add_space.
1099
    def add_space(self, space):
1221.5.4 by Aaron Bentley
Support space create => add-space.
1100
        self.juju('add-space', (space),)
1221.5.3 by Aaron Bentley
Extract EnvJujuClient.add_space.
1101
1221.5.5 by Aaron Bentley
Extract add_subnet.
1102
    def add_subnet(self, subnet, space):
1221.5.6 by Aaron Bentley
Rename subnet add to add-subnet.
1103
        self.juju('add-subnet', (subnet, space))
1221.5.5 by Aaron Bentley
Extract add_subnet.
1104
657.1.1 by Aaron Bentley
Extract EnvJujuClient from JujuClientDevel.
1105
1315.2.2 by Aaron Bentley
Use --default-model in bootstrap.
1106
class EnvJujuClient2B2(EnvJujuClient):
1107
1108
    def get_bootstrap_args(self, upload_tools, config_filename,
1109
                           bootstrap_series=None):
1110
        """Return the bootstrap arguments for the substrate."""
1111
        if self.env.maas:
1112
            constraints = 'mem=2G arch=amd64'
1113
        elif self.env.joyent:
1114
            # Only accept kvm packages by requiring >1 cpu core, see lp:1446264
1115
            constraints = 'mem=2G cpu-cores=1'
1116
        else:
1117
            constraints = 'mem=2G'
1118
        cloud_region = self.get_cloud_region(self.env.get_cloud(),
1119
                                             self.env.get_region())
1120
        args = ['--constraints', constraints, self.env.environment,
1121
                cloud_region, '--config', config_filename]
1122
        if upload_tools:
1123
            args.insert(0, '--upload-tools')
1124
        else:
1125
            args.extend(['--agent-version', self.get_matching_agent_version()])
1126
1127
        if bootstrap_series is not None:
1128
            args.extend(['--bootstrap-series', bootstrap_series])
1129
        return tuple(args)
1130
1315.2.3 by Aaron Bentley
Use admin client to get bootstrap host.
1131
    def get_admin_client(self):
1315.2.5 by Aaron Bentley
Update docs.
1132
        """Return a client for the admin model.  May return self."""
1315.2.3 by Aaron Bentley
Use admin client to get bootstrap host.
1133
        return self
1134
1135
    def get_admin_model_name(self):
1136
        """Return the name of the 'admin' model.
1137
1138
        Return the name of the environment when an 'admin' model does
1139
        not exist.
1140
        """
1141
        models = self.get_models()
1142
        # The dict can be empty because 1.x does not support the models.
1143
        # This is an ambiguous case for the jes feature flag which supports
1144
        # multiple models, but none is named 'admin' by default. Since the
1145
        # jes case also uses '-e' for models, the env is the admin model.
1146
        for model in models.get('models', []):
1147
            if 'admin' in model['name']:
1148
                return 'admin'
1149
        return self.env.environment
1150
1151
1152
class EnvJujuClient2A2(EnvJujuClient2B2):
1258.1.3 by Aaron Bentley
Support transitional jujus.
1153
    """Drives Juju 2.0-alpha2 clients."""
1154
1260.2.23 by Aaron Bentley
Test SimpleEnvironment/JujuData on init.
1155
    @classmethod
1156
    def _get_env(cls, env):
1157
        if isinstance(env, JujuData):
1158
            raise ValueError(
1159
                'JujuData cannot be used with {}'.format(cls.__name__))
1260.2.6 by Aaron Bentley
Introduce JujuData, remove workarounds for cloud-credentials.
1160
        return env
1161
1258.1.3 by Aaron Bentley
Support transitional jujus.
1162
    def _shell_environ(self):
1163
        """Generate a suitable shell environment.
1164
1263.1.1 by Martin Packman
Refactor how juju feature flags are handled with new enable_feature method
1165
        For 2.0-alpha2 set both JUJU_HOME and JUJU_DATA.
1258.1.3 by Aaron Bentley
Support transitional jujus.
1166
        """
1263.1.1 by Martin Packman
Refactor how juju feature flags are handled with new enable_feature method
1167
        env = super(EnvJujuClient2A2, self)._shell_environ()
1258.1.3 by Aaron Bentley
Support transitional jujus.
1168
        env['JUJU_HOME'] = self.env.juju_home
1169
        return env
1170
1260.2.2 by Aaron Bentley
Implement support for the CPCs.
1171
    def bootstrap(self, upload_tools=False, bootstrap_series=None):
1172
        """Bootstrap a controller."""
1315.2.23 by Aaron Bentley
Introduce set_model_name, update tests, check controller on bootstrap.
1173
        self._check_bootstrap()
1260.2.2 by Aaron Bentley
Implement support for the CPCs.
1174
        args = self.get_bootstrap_args(upload_tools, bootstrap_series)
1175
        self.juju('bootstrap', args, self.env.needs_sudo())
1176
1260.2.11 by Aaron Bentley
Get jujupy tests passing, remove region for manual.
1177
    @contextmanager
1178
    def bootstrap_async(self, upload_tools=False):
1315.2.23 by Aaron Bentley
Introduce set_model_name, update tests, check controller on bootstrap.
1179
        self._check_bootstrap()
1260.2.11 by Aaron Bentley
Get jujupy tests passing, remove region for manual.
1180
        args = self.get_bootstrap_args(upload_tools)
1181
        with self.juju_async('bootstrap', args):
1182
            yield
1183
            log.info('Waiting for bootstrap of {}.'.format(
1184
                self.env.environment))
1185
1260.2.1 by Aaron Bentley
Spike to support cloud-credentials.
1186
    def get_bootstrap_args(self, upload_tools, bootstrap_series=None):
1187
        """Return the bootstrap arguments for the substrate."""
1260.2.30 by Aaron Bentley
Updates from review
1188
        constraints = self._get_substrate_constraints()
1260.2.1 by Aaron Bentley
Spike to support cloud-credentials.
1189
        args = ('--constraints', constraints,
1190
                '--agent-version', self.get_matching_agent_version())
1191
        if upload_tools:
1192
            args = ('--upload-tools',) + args
1193
        if bootstrap_series is not None:
1194
            args = args + ('--bootstrap-series', bootstrap_series)
1195
        return args
1196
1258.1.3 by Aaron Bentley
Support transitional jujus.
1197
1260.2.2 by Aaron Bentley
Implement support for the CPCs.
1198
class EnvJujuClient2A1(EnvJujuClient2A2):
1221.1.17 by Aaron Bentley
Provide for a separate 2.0-alpha1 client.
1199
    """Drives Juju 2.0-alpha1 clients."""
1200
1221.5.15 by Aaron Bentley
Use 'show-status' instead of 'status'.
1201
    _show_status = 'status'
1202
1240.2.1 by Aaron Bentley
Support models/cache.yaml
1203
    def get_cache_path(self):
1204
        return get_cache_path(self.env.juju_home, models=False)
1205
1306.1.24 by Curtis Hovey
Added admin=False to _full_call_args and get_status().
1206
    def _full_args(self, command, sudo, args,
1207
                   timeout=None, include_e=True, admin=False):
1221.1.19 by Aaron Bentley
Tweaked enough to bootstrap.
1208
        # sudo is not needed for devel releases.
1306.1.24 by Curtis Hovey
Added admin=False to _full_call_args and get_status().
1209
        # admin is ignored. only environment exists.
1221.1.19 by Aaron Bentley
Tweaked enough to bootstrap.
1210
        if self.env is None or not include_e:
1211
            e_arg = ()
1212
        else:
1213
            e_arg = ('-e', self.env.environment)
1214
        if timeout is None:
1215
            prefix = ()
1216
        else:
1217
            prefix = get_timeout_prefix(timeout, self._timeout_path)
1218
        logging = '--debug' if self.debug else '--show-log'
1219
1220
        # If args is a string, make it a tuple. This makes writing commands
1221
        # with one argument a bit nicer.
1222
        if isinstance(args, basestring):
1223
            args = (args,)
1224
        # we split the command here so that the caller can control where the -e
1225
        # <env> flag goes.  Everything in the command string is put before the
1226
        # -e flag.
1227
        command = command.split()
1228
        return prefix + ('juju', logging,) + tuple(command) + e_arg + args
1229
1258.1.1 by Aaron Bentley
Use JUJU_DATA where client may be 2.0alpha2+
1230
    def _shell_environ(self):
1231
        """Generate a suitable shell environment.
1232
1263.1.1 by Martin Packman
Refactor how juju feature flags are handled with new enable_feature method
1233
        For 2.0-alpha1 and earlier set only JUJU_HOME and not JUJU_DATA.
1258.1.1 by Aaron Bentley
Use JUJU_DATA where client may be 2.0alpha2+
1234
        """
1263.1.1 by Martin Packman
Refactor how juju feature flags are handled with new enable_feature method
1235
        env = super(EnvJujuClient2A1, self)._shell_environ()
1258.1.1 by Aaron Bentley
Use JUJU_DATA where client may be 2.0alpha2+
1236
        env['JUJU_HOME'] = self.env.juju_home
1263.1.1 by Martin Packman
Refactor how juju feature flags are handled with new enable_feature method
1237
        del env['JUJU_DATA']
1258.1.1 by Aaron Bentley
Use JUJU_DATA where client may be 2.0alpha2+
1238
        return env
1239
1221.4.2 by Aaron Bentley
Introduce EnvJujuClient.remove_service.
1240
    def remove_service(self, service):
1241
        self.juju('destroy-service', (service,))
1242
1221.4.4 by Aaron Bentley
Support create-backup, restore-backup, enable-ha.
1243
    def backup(self):
1244
        environ = self._shell_environ()
1245
        # juju-backup does not support the -e flag.
1246
        environ['JUJU_ENV'] = self.env.environment
1247
        try:
1248
            # Mutate os.environ instead of supplying env parameter so Windows
1249
            # can search env['PATH']
1250
            with scoped_environ(environ):
1326.1.2 by Aaron Bentley
Emit backup command args.
1251
                args = ['juju', 'backup']
1252
                log.info(' '.join(args))
1253
                output = subprocess.check_output(args)
1221.4.4 by Aaron Bentley
Support create-backup, restore-backup, enable-ha.
1254
        except subprocess.CalledProcessError as e:
1255
            log.info(e.output)
1256
            raise
1257
        log.info(output)
1258
        backup_file_pattern = re.compile('(juju-backup-[0-9-]+\.(t|tar.)gz)')
1259
        match = backup_file_pattern.search(output)
1260
        if match is None:
1261
            raise Exception("The backup file was not found in output: %s" %
1262
                            output)
1263
        backup_file_name = match.group(1)
1264
        backup_file_path = os.path.abspath(backup_file_name)
1265
        log.info("State-Server backup at %s", backup_file_path)
1266
        return backup_file_path
1267
1268
    def restore_backup(self, backup_file):
1269
        return self.get_juju_output('restore', '--constraints', 'mem=2G',
1270
                                    backup_file)
1271
1272
    def restore_backup_async(self, backup_file):
1273
        return self.juju_async('restore', ('--constraints', 'mem=2G',
1274
                                           backup_file))
1275
1276
    def enable_ha(self):
1277
        self.juju('ensure-availability', ('-n', '3'))
1278
1306.1.23 by Curtis Hovey
Added 1.x variations to list-controllers and list-models.
1279
    def list_models(self):
1280
        """List the models registered with the current controller."""
1281
        log.info('The model is environment {}'.format(self.env.environment))
1282
1283
    def get_models(self):
1284
        """return a models dict with a 'models': [] key-value pair."""
1285
        return {}
1286
1315.2.8 by Aaron Bentley
Dump logs for all models, including on JES.
1287
    def _get_models(self):
1288
        """return a list of model dicts."""
1289
        # In 2.0-alpha1, 'list-models' produced a yaml list rather than a
1290
        # dict, but the command and parsing are the same.
1291
        return super(EnvJujuClient2A1, self).get_models()
1292
1306.1.23 by Curtis Hovey
Added 1.x variations to list-controllers and list-models.
1293
    def list_controllers(self):
1294
        """List the controllers."""
1295
        log.info(
1296
            'The controller is environment {}'.format(self.env.environment))
1297
1259.1.2 by Aaron Bentley
Implement get_controller_member_status.
1298
    @staticmethod
1299
    def get_controller_member_status(info_dict):
1300
        return info_dict.get('state-server-member-status')
1259.1.1 by Aaron Bentley
Expect controller-member-status in 2.0-alpha2
1301
1221.1.25 by Aaron Bentley
Use flat names for action commands.
1302
    def action_fetch(self, id, action=None, timeout="1m"):
1303
        """Fetches the results of the action with the given id.
1304
1305
        Will wait for up to 1 minute for the action results.
1306
        The action name here is just used for an more informational error in
1307
        cases where it's available.
1308
        Returns the yaml output of the fetched action.
1309
        """
1310
        # the command has to be "action fetch" so that the -e <env> args are
1311
        # placed after "fetch", since that's where action requires them to be.
1312
        out = self.get_juju_output("action fetch", id, "--wait", timeout)
1313
        status = yaml_loads(out)["status"]
1314
        if status != "completed":
1315
            name = ""
1316
            if action is not None:
1317
                name = " " + action
1318
            raise Exception(
1319
                "timed out waiting for action%s to complete during fetch" %
1320
                name)
1321
        return out
1322
1323
    def action_do(self, unit, action, *args):
1324
        """Performs the given action on the given unit.
1325
1326
        Action params should be given as args in the form foo=bar.
1327
        Returns the id of the queued action.
1328
        """
1329
        args = (unit, action) + args
1330
1331
        # the command has to be "action do" so that the -e <env> args are
1332
        # placed after "do", since that's where action requires them to be.
1333
        output = self.get_juju_output("action do", *args)
1334
        action_id_pattern = re.compile(
1335
            'Action queued with id: ([a-f0-9\-]{36})')
1336
        match = action_id_pattern.search(output)
1337
        if match is None:
1338
            raise Exception("Action id not found in output: %s" %
1339
                            output)
1340
        return match.group(1)
1341
1221.5.2 by Aaron Bentley
Support list-space for EnvJujuClient.
1342
    def list_space(self):
1343
        return yaml.safe_load(self.get_juju_output('space list'))
1344
1221.5.4 by Aaron Bentley
Support space create => add-space.
1345
    def add_space(self, space):
1346
        self.juju('space create', (space),)
1347
1221.5.6 by Aaron Bentley
Rename subnet add to add-subnet.
1348
    def add_subnet(self, subnet, space):
1349
        self.juju('subnet add', (subnet, space))
1350
1251.1.1 by Aaron Bentley
Implement and use set_model_constraints.
1351
    def set_model_constraints(self, constraints):
1352
        constraint_strings = self._dict_as_option_strings(constraints)
1353
        return self.juju('set-constraints', constraint_strings)
1354
1239.1.1 by Aaron Bentley
Implement and use EnvJujuClient.set_config.
1355
    def set_config(self, service, options):
1356
        option_strings = ['{}={}'.format(*item) for item in options.items()]
1357
        self.juju('set', (service,) + tuple(option_strings))
1358
1221.5.12 by Aaron Bentley
Support get-config.
1359
    def get_config(self, service):
1360
        return yaml_loads(self.get_juju_output('get', service))
1361
1243.1.1 by Aaron Bentley
Support rename of get-env/set-env => get-model-config/set-model-config.
1362
    def get_model_config(self):
1363
        """Return the value of the environment's configured option."""
1364
        return yaml.safe_load(self.get_juju_output('get-env'))
1365
1366
    def get_env_option(self, option):
1367
        """Return the value of the environment's configured option."""
1368
        return self.get_juju_output('get-env', option)
1369
1370
    def set_env_option(self, option, value):
1371
        """Set the value of the option in the environment."""
1372
        option_value = "%s=%s" % (option, value)
1373
        return self.juju('set-env', (option_value,))
1374
1221.1.17 by Aaron Bentley
Provide for a separate 2.0-alpha1 client.
1375
1221.1.21 by Aaron Bentley
Support -m for 2.0-alpha2.
1376
class EnvJujuClient1X(EnvJujuClient2A1):
1221.1.1 by Aaron Bentley
Support -model renames in jujupy and test_jujupy.
1377
    """Base for all 1.x client drivers."""
1378
1251.2.4 by Aaron Bentley
Rename bootstrap_supports => bootstrap_replaces
1379
    # The environments.yaml options that are replaced by bootstrap options.
1380
    # For Juju 1.x, no bootstrap options are used.
1381
    bootstrap_replaces = frozenset()
1242.3.7 by Aaron Bentley
BootstrapManager support for bootstrap option.
1382
1251.2.2 by Aaron Bentley
Remove support for --to.
1383
    def get_bootstrap_args(self, upload_tools, bootstrap_series=None):
1251.2.7 by Aaron Bentley
Update docs
1384
        """Return the bootstrap arguments for the substrate."""
1260.2.30 by Aaron Bentley
Updates from review
1385
        constraints = self._get_substrate_constraints()
1242.3.1 by Aaron Bentley
Update get_bootstrap_args.
1386
        args = ('--constraints', constraints)
1387
        if upload_tools:
1388
            args = ('--upload-tools',) + args
1389
        if bootstrap_series is not None:
1390
            env_val = self.env.config.get('default-series')
1391
            if bootstrap_series != env_val:
1392
                raise BootstrapMismatch(
1393
                    'bootstrap-series', bootstrap_series, 'default-series',
1394
                    env_val)
1395
        return args
1396
1242.2.1 by Aaron Bentley
EnvJujuClient assumes JES enabled, since it assumes 2.0.
1397
    def get_jes_command(self):
1398
        """Return the JES command to destroy a controller.
1399
1400
        Juju 2.x has 'kill-controller'.
1401
        Some intermediate versions had 'controller kill'.
1402
        Juju 1.25 has 'system kill' when the jes feature flag is set.
1403
1404
        :raises: JESNotSupported when the version of Juju does not expose
1405
            a JES command.
1406
        :return: The JES command.
1407
        """
1408
        commands = self.get_juju_output('help', 'commands', include_e=False)
1409
        for line in commands.splitlines():
1410
            for cmd in _jes_cmds.keys():
1411
                if line.startswith(cmd):
1412
                    return cmd
1413
        raise JESNotSupported()
1414
1221.1.1 by Aaron Bentley
Support -model renames in jujupy and test_jujupy.
1415
    def create_environment(self, controller_client, config_file):
1416
        seen_cmd = self.get_jes_command()
1242.2.1 by Aaron Bentley
EnvJujuClient assumes JES enabled, since it assumes 2.0.
1417
        if seen_cmd == SYSTEM:
1221.1.1 by Aaron Bentley
Support -model renames in jujupy and test_jujupy.
1418
            controller_option = ('-s', controller_client.env.environment)
1419
        else:
1420
            controller_option = ('-c', controller_client.env.environment)
1421
        self.juju(_jes_cmds[seen_cmd]['create'], controller_option + (
1422
            self.env.environment, '--config', config_file), include_e=False)
1423
1221.1.15 by Aaron Bentley
Implement and use destroy_model for destroying models.
1424
    def destroy_model(self):
1425
        """With JES enabled, destroy-environment destroys the model."""
1426
        self.destroy_environment(force=False)
1427
1221.1.1 by Aaron Bentley
Support -model renames in jujupy and test_jujupy.
1428
    def destroy_environment(self, force=True, delete_jenv=False):
1429
        if force:
1430
            force_arg = ('--force',)
1431
        else:
1432
            force_arg = ()
1433
        exit_status = self.juju(
1434
            'destroy-environment',
1435
            (self.env.environment,) + force_arg + ('-y',),
1436
            self.env.needs_sudo(), check=False, include_e=False,
1437
            timeout=timedelta(minutes=10).total_seconds())
1438
        if delete_jenv:
1439
            jenv_path = get_jenv_path(self.env.juju_home, self.env.environment)
1440
            ensure_deleted(jenv_path)
1441
        return exit_status
1442
1315.2.8 by Aaron Bentley
Dump logs for all models, including on JES.
1443
    def _get_models(self):
1444
        """return a list of model dicts."""
1445
        return yaml.safe_load(self.get_juju_output(
1446
            'environments', '-s', self.env.environment, '--format', 'yaml',
1447
            include_e=False))
1448
1259.2.1 by Martin Packman
Add flag to run_deployer to use juju 2.0 bundle deployment
1449
    def deploy_bundle(self, bundle, timeout=_DEFAULT_BUNDLE_TIMEOUT):
1450
        """Deploy bundle using deployer for Juju 1.X version."""
1451
        self.deployer(bundle, timeout=timeout)
1452
1306.1.5 by Curtis Hovey
Added get_controller_endpoint for get_endpoints.
1453
    def get_controller_endpoint(self):
1454
        """Return the address of the state-server leader."""
1306.1.19 by Curtis Hovey
Fix calls to show-controller with include_e=False.
1455
        endpoint = self.get_juju_output('api-endpoints')
1310.1.2 by Curtis Hovey
Address ipv6 split address and port.
1456
        address, port = split_address_port(endpoint)
1306.1.5 by Curtis Hovey
Added get_controller_endpoint for get_endpoints.
1457
        return address
1458
1258.2.5 by Curtis Hovey
Added upgrade_mongo to EnvJujuClient.
1459
    def upgrade_mongo(self):
1460
        raise UpgradeMongoNotSupported()
1221.1.1 by Aaron Bentley
Support -model renames in jujupy and test_jujupy.
1461
1462
1463
class EnvJujuClient22(EnvJujuClient1X):
953.3.9 by Nate Finch
more code review changes
1464
1263.1.1 by Martin Packman
Refactor how juju feature flags are handled with new enable_feature method
1465
    used_feature_flags = frozenset(['actions'])
953.3.9 by Nate Finch
more code review changes
1466
1263.1.1 by Martin Packman
Refactor how juju feature flags are handled with new enable_feature method
1467
    def __init__(self, *args, **kwargs):
1468
        super(EnvJujuClient22, self).__init__(*args, **kwargs)
1469
        self.feature_flags.add('actions')
953.3.9 by Nate Finch
more code review changes
1470
1471
1221.1.1 by Aaron Bentley
Support -model renames in jujupy and test_jujupy.
1472
class EnvJujuClient26(EnvJujuClient1X):
1074.2.1 by Aaron Bentley
Add EnvJujuClient support for Juju 2.6.
1473
    """Drives Juju 2.6-series clients."""
928.2.2 by Aaron Bentley
Auto-enable support for cloudsigma in 1.24.
1474
1263.1.1 by Martin Packman
Refactor how juju feature flags are handled with new enable_feature method
1475
    used_feature_flags = frozenset(['address-allocation', 'cloudsigma', 'jes'])
1476
1044.1.9 by Aaron Bentley
Update and add tests.
1477
    def __init__(self, *args, **kwargs):
1074.2.1 by Aaron Bentley
Add EnvJujuClient support for Juju 2.6.
1478
        super(EnvJujuClient26, self).__init__(*args, **kwargs)
1263.1.1 by Martin Packman
Refactor how juju feature flags are handled with new enable_feature method
1479
        if self.env is None or self.env.config is None:
1480
            return
1481
        if self.env.config.get('type') == 'cloudsigma':
1482
            self.feature_flags.add('cloudsigma')
1221.1.8 by Aaron Bentley
Implement EnvJujuClient.clone.
1483
1044.1.9 by Aaron Bentley
Update and add tests.
1484
    def enable_jes(self):
1145.2.3 by Curtis Hovey
Fix get_jes_command to exit the loop and set _jes_command correctly.
1485
        """Enable JES if JES is optional.
1486
1487
        :raises: JESByDefault when JES is always enabled; Juju has the
1160.1.4 by Curtis Hovey
Fix comments.
1488
            'destroy-controller' command.
1145.2.3 by Curtis Hovey
Fix get_jes_command to exit the loop and set _jes_command correctly.
1489
        :raises: JESNotSupported when JES is not supported; Juju does not have
1160.1.4 by Curtis Hovey
Fix comments.
1490
            the 'system kill' command when the JES feature flag is set.
1145.2.3 by Curtis Hovey
Fix get_jes_command to exit the loop and set _jes_command correctly.
1491
        """
1263.1.1 by Martin Packman
Refactor how juju feature flags are handled with new enable_feature method
1492
1493
        if 'jes' in self.feature_flags:
1044.1.9 by Aaron Bentley
Update and add tests.
1494
            return
1145.2.15 by Curtis Hovey
Do not check for controller in enable_jes.
1495
        if self.is_jes_enabled():
1044.1.9 by Aaron Bentley
Update and add tests.
1496
            raise JESByDefault()
1263.1.1 by Martin Packman
Refactor how juju feature flags are handled with new enable_feature method
1497
        self.feature_flags.add('jes')
1050.2.1 by Aaron Bentley
Make is_jes_enabled public.
1498
        if not self.is_jes_enabled():
1263.1.1 by Martin Packman
Refactor how juju feature flags are handled with new enable_feature method
1499
            self.feature_flags.remove('jes')
1044.1.9 by Aaron Bentley
Update and add tests.
1500
            raise JESNotSupported()
1501
1162.2.20 by Aaron Bentley
Implement disable_jes.
1502
    def disable_jes(self):
1263.1.1 by Martin Packman
Refactor how juju feature flags are handled with new enable_feature method
1503
        if 'jes' in self.feature_flags:
1504
            self.feature_flags.remove('jes')
1162.2.20 by Aaron Bentley
Implement disable_jes.
1505
1091.3.1 by James Tunnicliffe
Supporting functions for testing networking between containers in a Juju environment.
1506
    def enable_container_address_allocation(self):
1263.1.1 by Martin Packman
Refactor how juju feature flags are handled with new enable_feature method
1507
        self.feature_flags.add('address-allocation')
928.2.2 by Aaron Bentley
Auto-enable support for cloudsigma in 1.24.
1508
1509
1074.2.1 by Aaron Bentley
Add EnvJujuClient support for Juju 2.6.
1510
class EnvJujuClient25(EnvJujuClient26):
1511
    """Drives Juju 2.5-series clients."""
1512
1513
957.2.1 by Aaron Bentley
Treat cloudsigma as provisional in 2.5
1514
class EnvJujuClient24(EnvJujuClient25):
1044.1.17 by Aaron Bentley
Fix docs
1515
    """Similar to EnvJujuClient25, but lacking JES support."""
957.2.1 by Aaron Bentley
Treat cloudsigma as provisional in 2.5
1516
1263.1.1 by Martin Packman
Refactor how juju feature flags are handled with new enable_feature method
1517
    used_feature_flags = frozenset(['cloudsigma'])
1518
1044.1.9 by Aaron Bentley
Update and add tests.
1519
    def enable_jes(self):
1520
        raise JESNotSupported()
1521
1256.1.2 by Aaron Bentley
Expect old add-machine behaviour from 1.24.x and earlier.
1522
    def add_ssh_machines(self, machines):
1523
        for machine in machines:
1524
            self.juju('add-machine', ('ssh:' + machine,))
1525
957.2.1 by Aaron Bentley
Treat cloudsigma as provisional in 2.5
1526
663.1.2 by Aaron Bentley
Port bootstrap_from_env to EnvJujuClient, add unit tests.
1527
def get_local_root(juju_home, env):
1528
    return os.path.join(juju_home, env.environment)
1529
1530
663.1.8 by Aaron Bentley
Extract temp_bootstrap_env from bootstrap_from_env.
1531
def bootstrap_from_env(juju_home, client):
1532
    with temp_bootstrap_env(juju_home, client):
1533
        client.bootstrap()
1534
1535
796.2.4 by John George
Added support for setting the juju path, series and agent_url.
1536
def quickstart_from_env(juju_home, client, bundle):
1537
    with temp_bootstrap_env(juju_home, client):
1538
        client.quickstart(bundle)
1539
1540
1162.2.22 by Aaron Bentley
Extract maybe_jes to clarify logic.
1541
@contextmanager
1542
def maybe_jes(client, jes_enabled, try_jes):
1162.2.23 by Aaron Bentley
Update docs.
1543
    """If JES is desired and not enabled, try to enable it for this context.
1162.2.22 by Aaron Bentley
Extract maybe_jes to clarify logic.
1544
1545
    JES will be in its previous state after exiting this context.
1546
    If jes_enabled is True or try_jes is False, the context is a no-op.
1162.2.23 by Aaron Bentley
Update docs.
1547
    If enable_jes() raises JESNotSupported, JES will not be enabled in the
1548
    context.
1162.2.22 by Aaron Bentley
Extract maybe_jes to clarify logic.
1549
1550
    The with value is True if JES is enabled in the context.
1551
    """
1552
1553
    class JESUnwanted(Exception):
1554
        """Non-error.  Used to avoid enabling JES if not wanted."""
1555
1556
    try:
1557
        if not try_jes or jes_enabled:
1558
            raise JESUnwanted
1559
        client.enable_jes()
1560
    except (JESNotSupported, JESUnwanted):
1561
        yield jes_enabled
1562
        return
1563
    else:
1564
        try:
1565
            yield True
1566
        finally:
1567
            client.disable_jes()
1568
1569
1162.2.21 by Aaron Bentley
Implement and use tear_down(try_jes=True)
1570
def tear_down(client, jes_enabled, try_jes=False):
1571
    """Tear down a JES or non-JES environment.
1572
1199 by Curtis Hovey
Revert lp:~sinzui/juju-ci-tools/kill-controller-command r1198 because it exposes that controllers are
1573
    JES environments are torn down via 'controller kill' or 'system kill',
1162.2.21 by Aaron Bentley
Implement and use tear_down(try_jes=True)
1574
    and non-JES environments are torn down via 'destroy-environment --force.'
1575
    """
1162.2.22 by Aaron Bentley
Extract maybe_jes to clarify logic.
1576
    with maybe_jes(client, jes_enabled, try_jes) as jes_enabled:
1577
        if jes_enabled:
1578
            client.kill_controller()
1162.2.21 by Aaron Bentley
Implement and use tear_down(try_jes=True)
1579
        else:
1210.1.1 by Aaron Bentley
Destroy environment without --force if possible.
1580
            if client.destroy_environment(force=False) != 0:
1581
                client.destroy_environment(force=True)
1162.2.21 by Aaron Bentley
Implement and use tear_down(try_jes=True)
1582
1583
696.1.10 by Aaron Bentley
Move uniquify_local to jujupy.
1584
def uniquify_local(env):
1585
    """Ensure that local environments have unique port settings.
1586
1587
    This allows local environments to be duplicated despite
1588
    https://bugs.launchpad.net/bugs/1382131
1589
    """
1590
    if not env.local:
1591
        return
1592
    port_defaults = {
1593
        'api-port': 17070,
1594
        'state-port': 37017,
1595
        'storage-port': 8040,
1596
        'syslog-port': 6514,
1597
    }
1598
    for key, default in port_defaults.items():
1599
        env.config[key] = env.config.get(key, default) + 1
1600
1601
1044.1.9 by Aaron Bentley
Update and add tests.
1602
def dump_environments_yaml(juju_home, config):
1603
    environments_path = get_environments_path(juju_home)
1604
    with open(environments_path, 'w') as config_file:
1605
        yaml.safe_dump(config, config_file)
1606
1607
663.1.8 by Aaron Bentley
Extract temp_bootstrap_env from bootstrap_from_env.
1608
@contextmanager
807.1.1 by Aaron Bentley
Avoid creating temp juju homes inside temp juju homes.
1609
def _temp_env(new_config, parent=None, set_home=True):
716.1.2 by Aaron Bentley
Extract _temp_env from temp_bootstrap_env.
1610
    """Use the supplied config as juju environment.
1611
1612
    This is not a fully-formed version for bootstrapping.  See
1613
    temp_bootstrap_env.
1614
    """
1615
    with temp_dir(parent) as temp_juju_home:
1044.1.9 by Aaron Bentley
Update and add tests.
1616
        dump_environments_yaml(temp_juju_home, new_config)
807.1.1 by Aaron Bentley
Avoid creating temp juju homes inside temp juju homes.
1617
        if set_home:
1618
            context = scoped_environ()
1619
        else:
1620
            context = nested()
1621
        with context:
1622
            if set_home:
1623
                os.environ['JUJU_HOME'] = temp_juju_home
1258.1.1 by Aaron Bentley
Use JUJU_DATA where client may be 2.0alpha2+
1624
                os.environ['JUJU_DATA'] = temp_juju_home
716.1.2 by Aaron Bentley
Extract _temp_env from temp_bootstrap_env.
1625
            yield temp_juju_home
1626
1627
1044.1.9 by Aaron Bentley
Update and add tests.
1628
def jes_home_path(juju_home, dir_name):
1048.1.3 by Aaron Bentley
Add make_jes_home and jes_home_path to tests.
1629
    return os.path.join(juju_home, 'jes-homes', dir_name)
1044.1.9 by Aaron Bentley
Update and add tests.
1630
1631
1240.2.1 by Aaron Bentley
Support models/cache.yaml
1632
def get_cache_path(juju_home, models=False):
1633
    if models:
1634
        root = os.path.join(juju_home, 'models')
1635
    else:
1636
        root = os.path.join(juju_home, 'environments')
1637
    return os.path.join(root, 'cache.yaml')
1050.2.2 by Aaron Bentley
Retain config.yaml instead of .jenv for JES.
1638
1639
1242.3.16 by Aaron Bentley
Make agent-version omission client-dependent.
1640
def make_safe_config(client):
663.1.2 by Aaron Bentley
Port bootstrap_from_env to EnvJujuClient, add unit tests.
1641
    config = dict(client.env.config)
1242.3.18 by Aaron Bentley
Rename bootstrap_supports to bootstrap_replaces.
1642
    if 'agent-version' in client.bootstrap_replaces:
1242.3.16 by Aaron Bentley
Make agent-version omission client-dependent.
1643
        config.pop('agent-version', None)
1644
    else:
1242.3.10 by Aaron Bentley
Base support for agent-version.
1645
        config['agent-version'] = client.get_matching_agent_version()
709.1.1 by Aaron Bentley
Always set test-mode to True.
1646
    # AFAICT, we *always* want to set test-mode to True.  If we ever find a
1647
    # use-case where we don't, we can make this optional.
1648
    config['test-mode'] = True
990.3.1 by Curtis Hovey
Ensure the env name is in the config.
1649
    # Explicitly set 'name', which Juju implicitly sets to env.environment to
1650
    # ensure MAASAccount knows what the name will be.
1651
    config['name'] = client.env.environment
663.1.2 by Aaron Bentley
Port bootstrap_from_env to EnvJujuClient, add unit tests.
1652
    if config['type'] == 'local':
1193.2.5 by Aaron Bentley
Remove EnvJujuClient.juju_home completely.
1653
        config.setdefault('root-dir', get_local_root(client.env.juju_home,
995.1.14 by Aaron Bentley
Destroy environments.
1654
                          client.env))
663.1.2 by Aaron Bentley
Port bootstrap_from_env to EnvJujuClient, add unit tests.
1655
        # MongoDB requires a lot of free disk space, and the only
1656
        # visible error message is from "juju bootstrap":
1657
        # "cannot initiate replication set" if disk space is low.
1658
        # What "low" exactly means, is unclear, but 8GB should be
1659
        # enough.
1660
        ensure_dir(config['root-dir'])
1661
        check_free_disk_space(config['root-dir'], 8000000, "MongoDB files")
1662
        if client.env.kvm:
1663
            check_free_disk_space(
1664
                "/var/lib/uvtool/libvirt/images", 2000000,
1665
                "KVM disk files")
1666
        else:
1667
            check_free_disk_space(
1668
                "/var/lib/lxc", 2000000, "LXC containers")
995.1.13 by Aaron Bentley
Get assess_jes_deploy running to completion.
1669
    return config
1670
1671
1672
@contextmanager
1242.3.16 by Aaron Bentley
Make agent-version omission client-dependent.
1673
def temp_bootstrap_env(juju_home, client, set_home=True, permanent=False):
995.1.13 by Aaron Bentley
Get assess_jes_deploy running to completion.
1674
    """Create a temporary environment for bootstrapping.
1675
1676
    This involves creating a temporary juju home directory and returning its
1677
    location.
1678
1679
    :param set_home: Set JUJU_HOME to match the temporary home in this
1680
        context.  If False, juju_home should be supplied to bootstrap.
1681
    """
1682
    new_config = {
1251.2.6 by Aaron Bentley
Remove spurious change.
1683
        'environments': {client.env.environment: make_safe_config(client)}}
995.1.13 by Aaron Bentley
Get assess_jes_deploy running to completion.
1684
    # Always bootstrap a matching environment.
663.1.2 by Aaron Bentley
Port bootstrap_from_env to EnvJujuClient, add unit tests.
1685
    jenv_path = get_jenv_path(juju_home, client.env.environment)
1044.1.9 by Aaron Bentley
Update and add tests.
1686
    if permanent:
1260.2.6 by Aaron Bentley
Introduce JujuData, remove workarounds for cloud-credentials.
1687
        context = client.env.make_jes_home(
1688
            juju_home, client.env.environment, new_config)
1044.1.9 by Aaron Bentley
Update and add tests.
1689
    else:
1690
        context = _temp_env(new_config, juju_home, set_home)
1691
    with context as temp_juju_home:
663.1.2 by Aaron Bentley
Port bootstrap_from_env to EnvJujuClient, add unit tests.
1692
        if os.path.lexists(jenv_path):
1693
            raise Exception('%s already exists!' % jenv_path)
1694
        new_jenv_path = get_jenv_path(temp_juju_home, client.env.environment)
1695
        # Create a symlink to allow access while bootstrapping, and to reduce
1696
        # races.  Can't use a hard link because jenv doesn't exist until
1697
        # partway through bootstrap.
1698
        ensure_dir(os.path.join(juju_home, 'environments'))
1044.1.1 by Aaron Bentley
Use the deploy_job script everywhere, fix windows weirdness.
1699
        # Skip creating symlink where not supported (i.e. Windows).
1048.1.4 by Aaron Bentley
Improve condition order.
1700
        if not permanent and getattr(os, 'symlink', None) is not None:
1044.1.1 by Aaron Bentley
Use the deploy_job script everywhere, fix windows weirdness.
1701
            os.symlink(new_jenv_path, jenv_path)
1193.2.1 by Aaron Bentley
Make EnvJujuClient.juju_home a reference to EnvJUjuClient.env.juju_home.
1702
        old_juju_home = client.env.juju_home
1703
        client.env.juju_home = temp_juju_home
716.1.2 by Aaron Bentley
Extract _temp_env from temp_bootstrap_env.
1704
        try:
807.1.1 by Aaron Bentley
Avoid creating temp juju homes inside temp juju homes.
1705
            yield temp_juju_home
716.1.2 by Aaron Bentley
Extract _temp_env from temp_bootstrap_env.
1706
        finally:
1044.1.9 by Aaron Bentley
Update and add tests.
1707
            if not permanent:
1708
                # replace symlink with file before deleting temp home.
1709
                try:
1710
                    os.rename(new_jenv_path, jenv_path)
1711
                except OSError as e:
1712
                    if e.errno != errno.ENOENT:
1713
                        raise
1714
                    # Remove dangling symlink
1048.1.5 by Aaron Bentley
Updates from review.
1715
                    try:
1716
                        os.unlink(jenv_path)
1717
                    except OSError as e:
1718
                        if e.errno != errno.ENOENT:
1719
                            raise
1193.2.1 by Aaron Bentley
Make EnvJujuClient.juju_home a reference to EnvJUjuClient.env.juju_home.
1720
                client.env.juju_home = old_juju_home
663.1.2 by Aaron Bentley
Port bootstrap_from_env to EnvJujuClient, add unit tests.
1721
1722
1153.4.3 by Martin Packman
Changes from review by abentley
1723
def get_machine_dns_name(client, machine, timeout=600):
1724
    """Wait for dns-name on a juju machine."""
1725
    for status in client.status_until(timeout=timeout):
1726
        try:
1727
            return _dns_name_for_machine(status, machine)
1728
        except KeyError:
1153.4.4 by Martin Packman
Log in jujupy context
1729
            log.debug("No dns-name yet for machine %s", machine)
1153.4.3 by Martin Packman
Changes from review by abentley
1730
1731
1732
def _dns_name_for_machine(status, machine):
1733
    host = status.status['machines'][machine]['dns-name']
1167.2.1 by Martin Packman
Support IPv6 in wait_for_port and move handling of literal addresses closer to final use
1734
    if is_ipv6_address(host):
1735
        log.warning("Selected IPv6 address for machine %s: %r", machine, host)
1736
    return host
1153.4.3 by Martin Packman
Changes from review by abentley
1737
1738
1315.2.22 by Aaron Bentley
Fake merge of trunk.
1739
class Controller:
1740
1741
    def __init__(self, name):
1742
        self.name = name
1743
1744
34.1.3 by Aaron Bentley
Add status and environment tests.
1745
class Status:
1746
754.1.1 by Aaron Bentley
Emit status text for agent timeout.
1747
    def __init__(self, status, status_text):
34.1.3 by Aaron Bentley
Add status and environment tests.
1748
        self.status = status
754.1.1 by Aaron Bentley
Emit status text for agent timeout.
1749
        self.status_text = status_text
1750
1751
    @classmethod
1752
    def from_text(cls, text):
1753
        status_yaml = yaml_loads(text)
1754
        return cls(status_yaml, text)
34.1.3 by Aaron Bentley
Add status and environment tests.
1755
1091.3.1 by James Tunnicliffe
Supporting functions for testing networking between containers in a Juju environment.
1756
    def iter_machines(self, containers=False, machines=True):
34.1.3 by Aaron Bentley
Add status and environment tests.
1757
        for machine_name, machine in sorted(self.status['machines'].items()):
1091.3.1 by James Tunnicliffe
Supporting functions for testing networking between containers in a Juju environment.
1758
            if machines:
1759
                yield machine_name, machine
805.1.3 by Aaron Bentley
Allow iterating through container machines.
1760
            if containers:
1761
                for contained, unit in machine.get('containers', {}).items():
1762
                    yield contained, unit
419.1.12 by Aaron Bentley
Add wait for ha to come up.
1763
703.1.3 by Aaron Bentley
Start implementing deploy-many stage.
1764
    def iter_new_machines(self, old_status):
1765
        for machine, data in self.iter_machines():
1766
            if machine in old_status.status['machines']:
1767
                continue
1768
            yield machine, data
1769
1196.1.6 by Martin Packman
Changes for review by abentley
1770
    def iter_units(self):
1196.1.1 by Martin Packman
Refactoring jujupy including new wait_for_workloads and iter_units methods
1771
        for service_name, service in sorted(self.status['services'].items()):
1772
            for unit_name, unit in sorted(service.get('units', {}).items()):
1773
                yield unit_name, unit
1196.1.6 by Martin Packman
Changes for review by abentley
1774
                subordinates = unit.get('subordinates', ())
1775
                for sub_name in sorted(subordinates):
1776
                    yield sub_name, subordinates[sub_name]
1196.1.1 by Martin Packman
Refactoring jujupy including new wait_for_workloads and iter_units methods
1777
419.1.12 by Aaron Bentley
Add wait for ha to come up.
1778
    def agent_items(self):
805.1.3 by Aaron Bentley
Allow iterating through container machines.
1779
        for machine_name, machine in self.iter_machines(containers=True):
650.1.8 by Aaron Bentley
Implement most compatibility testing.
1780
            yield machine_name, machine
1196.1.1 by Martin Packman
Refactoring jujupy including new wait_for_workloads and iter_units methods
1781
        for unit_name, unit in self.iter_units():
1782
            yield unit_name, unit
34.1.3 by Aaron Bentley
Add status and environment tests.
1783
1784
    def agent_states(self):
1785
        """Map agent states to the units and machines in those states."""
1786
        states = defaultdict(list)
1787
        for item_name, item in self.agent_items():
1246.1.2 by Curtis Hovey
Added coalesce_agent_status to handle machine agent-state and unit agent-status.
1788
            states[coalesce_agent_status(item)].append(item_name)
34.1.3 by Aaron Bentley
Add status and environment tests.
1789
        return states
1790
657.1.5 by Aaron Bentley
Move wait_for_started to EnvJujuClient.
1791
    def check_agents_started(self, environment_name=None):
34.1.3 by Aaron Bentley
Add status and environment tests.
1792
        """Check whether all agents are in the 'started' state.
1793
1794
        If not, return agent_states output.  If so, return None.
1795
        If an error is encountered for an agent, raise ErroredUnit
1796
        """
771.2.1 by Aaron Bentley
Increase set of agent-state-infos that indicate failure.
1797
        bad_state_info = re.compile(
1798
            '(.*error|^(cannot set up groups|cannot run instance)).*')
34.1.3 by Aaron Bentley
Add status and environment tests.
1799
        for item_name, item in self.agent_items():
1800
            state_info = item.get('agent-state-info', '')
771.2.1 by Aaron Bentley
Increase set of agent-state-infos that indicate failure.
1801
            if bad_state_info.match(state_info):
301.1.3 by Aaron Bentley
Remove environment name from log messages and errors.
1802
                raise ErroredUnit(item_name, state_info)
34.1.3 by Aaron Bentley
Add status and environment tests.
1803
        states = self.agent_states()
1246.1.2 by Curtis Hovey
Added coalesce_agent_status to handle machine agent-state and unit agent-status.
1804
        if set(states.keys()).issubset(AGENTS_READY):
34.1.3 by Aaron Bentley
Add status and environment tests.
1805
            return None
1806
        for state, entries in states.items():
1807
            if 'error' in state:
966.2.1 by Curtis Hovey
Added extra_env, but tests fail.
1808
                raise ErroredUnit(entries[0], state)
34.1.3 by Aaron Bentley
Add status and environment tests.
1809
        return states
1810
796.2.1 by John George
Add quickstart_deploy.py
1811
    def get_service_count(self):
1812
        return len(self.status.get('services', {}))
1813
966.1.10 by John George
Check that subordinate unit count matches service unit count in wait_for_subordinate_units.
1814
    def get_service_unit_count(self, service):
1815
        return len(
1816
            self.status.get('services', {}).get(service, {}).get('units', {}))
1817
84.1.4 by Aaron Bentley
Move version-dicting to jujupy.
1818
    def get_agent_versions(self):
1819
        versions = defaultdict(set)
1820
        for item_name, item in self.agent_items():
1294 by Curtis Hovey
Get machine version from juju-status.
1821
            if item.get('juju-status', None):
1822
                version = item['juju-status'].get('version', 'unknown')
1823
                versions[version].add(item_name)
1824
            else:
1825
                versions[item.get('agent-version', 'unknown')].add(item_name)
84.1.4 by Aaron Bentley
Move version-dicting to jujupy.
1826
        return versions
1827
717.2.2 by Aaron Bentley
Checkpoint with assess_recovery working.
1828
    def get_instance_id(self, machine_id):
1829
        return self.status['machines'][machine_id]['instance-id']
1830
874.2.4 by Aaron Bentley
Initial upgrade-charm test.
1831
    def get_unit(self, unit_name):
874.2.8 by Aaron Bentley
Add docs, simplify get_unit.
1832
        """Return metadata about a unit."""
874.2.4 by Aaron Bentley
Initial upgrade-charm test.
1833
        for service in sorted(self.status['services'].values()):
874.2.8 by Aaron Bentley
Add docs, simplify get_unit.
1834
            if unit_name in service.get('units', {}):
1835
                return service['units'][unit_name]
874.2.4 by Aaron Bentley
Initial upgrade-charm test.
1836
        raise KeyError(unit_name)
1837
966.1.9 by John George
Add wait_for_subordinate_unit to jujupy.py.
1838
    def service_subordinate_units(self, service_name):
966.1.1 by John George
Add get_subordinate_units() to jujupy.Status.
1839
        """Return subordinate metadata for a service_name."""
1840
        services = self.status.get('services', {})
966.1.12 by John George
Verify subordinate units are started in wait_for_subordinate_units and add the reporter.
1841
        if service_name in services:
966.1.1 by John George
Add get_subordinate_units() to jujupy.Status.
1842
            for unit in sorted(services[service_name].get(
1843
                    'units', {}).values()):
966.1.9 by John George
Add wait_for_subordinate_unit to jujupy.py.
1844
                for sub_name, sub in unit.get('subordinates', {}).items():
1845
                    yield sub_name, sub
966.1.1 by John George
Add get_subordinate_units() to jujupy.Status.
1846
874.2.4 by Aaron Bentley
Initial upgrade-charm test.
1847
    def get_open_ports(self, unit_name):
874.2.8 by Aaron Bentley
Add docs, simplify get_unit.
1848
        """List the open ports for the specified unit.
1849
1850
        If no ports are listed for the unit, the empty list is returned.
1851
        """
874.2.4 by Aaron Bentley
Initial upgrade-charm test.
1852
        return self.get_unit(unit_name).get('open-ports', [])
1853
1854
657.1.3 by Aaron Bentley
Extract SimpleEnvironment from Environment.
1855
class SimpleEnvironment:
2 by Aaron Bentley
Added initial deploy_stack.
1856
1315.2.22 by Aaron Bentley
Fake merge of trunk.
1857
    def __init__(self, environment, config=None, juju_home=None,
1858
                 controller=None):
1859
        if controller is None:
1860
            controller = Controller(environment)
1861
        self.controller = controller
2 by Aaron Bentley
Added initial deploy_stack.
1862
        self.environment = environment
112.1.4 by Aaron Bentley
Read config, use it to determine whether provider is local.
1863
        self.config = config
1193.2.1 by Aaron Bentley
Make EnvJujuClient.juju_home a reference to EnvJUjuClient.env.juju_home.
1864
        self.juju_home = juju_home
112.1.4 by Aaron Bentley
Read config, use it to determine whether provider is local.
1865
        if self.config is not None:
1866
            self.local = bool(self.config.get('type') == 'local')
530.1.1 by Curtis Hovey
Extend the timeout to retrieve the dummy source token
1867
            self.kvm = (
1868
                self.local and bool(self.config.get('container') == 'kvm'))
667.1.1 by John George
Add amd64 contraint, when bootstrapping the maas test environment.
1869
            self.maas = bool(self.config.get('type') == 'maas')
940.2.1 by Martin Packman
Only use kvm packages on joyent provider
1870
            self.joyent = bool(self.config.get('type') == 'joyent')
112.1.4 by Aaron Bentley
Read config, use it to determine whether provider is local.
1871
        else:
1872
            self.local = False
940.2.1 by Martin Packman
Only use kvm packages on joyent provider
1873
            self.kvm = False
667.1.1 by John George
Add amd64 contraint, when bootstrapping the maas test environment.
1874
            self.maas = False
940.2.1 by Martin Packman
Only use kvm packages on joyent provider
1875
            self.joyent = False
112.1.4 by Aaron Bentley
Read config, use it to determine whether provider is local.
1876
1315.2.8 by Aaron Bentley
Dump logs for all models, including on JES.
1877
    def clone(self, model_name=None):
1878
        config = deepcopy(self.config)
1879
        if model_name is None:
1880
            model_name = self.environment
1881
        else:
1882
            config['name'] = model_name
1315.2.22 by Aaron Bentley
Fake merge of trunk.
1883
        result = self.__class__(model_name, config, self.juju_home,
1884
                                self.controller)
1315.2.8 by Aaron Bentley
Dump logs for all models, including on JES.
1885
        result.local = self.local
1886
        result.kvm = self.kvm
1887
        result.maas = self.maas
1888
        result.joyent = self.joyent
1889
        return result
1890
696.1.7 by Aaron Bentley
Get industrial_test working at the commandline.
1891
    def __eq__(self, other):
1892
        if type(self) != type(other):
1893
            return False
1894
        if self.environment != other.environment:
1895
            return False
1896
        if self.config != other.config:
1897
            return False
1898
        if self.local != other.local:
1899
            return False
1900
        if self.maas != other.maas:
1901
            return False
1902
        return True
1903
1904
    def __ne__(self, other):
1905
        return not self == other
1906
1315.2.23 by Aaron Bentley
Introduce set_model_name, update tests, check controller on bootstrap.
1907
    def set_model_name(self, model_name, set_controller=True):
1908
        if set_controller:
1909
            self.controller.name = model_name
1910
        self.environment = model_name
1911
        self.config['name'] = model_name
1912
112.1.4 by Aaron Bentley
Read config, use it to determine whether provider is local.
1913
    @classmethod
1914
    def from_config(cls, name):
1260.2.6 by Aaron Bentley
Introduce JujuData, remove workarounds for cloud-credentials.
1915
        return cls._from_config(name)
1916
1917
    @classmethod
1918
    def _from_config(cls, name):
1082.1.1 by Aaron Bentley
SimpleEnvironment.from_config env_name can be None.
1919
        config, selected = get_selected_environment(name)
1920
        if name is None:
1921
            name = selected
1922
        return cls(name, config)
657.1.3 by Aaron Bentley
Extract SimpleEnvironment from Environment.
1923
1924
    def needs_sudo(self):
1925
        return self.local
1926
1260.2.6 by Aaron Bentley
Introduce JujuData, remove workarounds for cloud-credentials.
1927
    @contextmanager
1928
    def make_jes_home(self, juju_home, dir_name, new_config):
1929
        home_path = jes_home_path(juju_home, dir_name)
1930
        if os.path.exists(home_path):
1931
            rmtree(home_path)
1932
        os.makedirs(home_path)
1260.2.25 by Aaron Bentley
Test load/dump yaml.
1933
        self.dump_yaml(home_path, new_config)
1260.2.6 by Aaron Bentley
Introduce JujuData, remove workarounds for cloud-credentials.
1934
        yield home_path
1935
1260.2.25 by Aaron Bentley
Test load/dump yaml.
1936
    def dump_yaml(self, path, config):
1937
        dump_environments_yaml(path, config)
1260.2.6 by Aaron Bentley
Introduce JujuData, remove workarounds for cloud-credentials.
1938
1939
1940
class JujuData(SimpleEnvironment):
1941
1315.2.22 by Aaron Bentley
Fake merge of trunk.
1942
    def __init__(self, environment, config=None, juju_home=None,
1943
                 controller=None):
1260.2.31 by Aaron Bentley
Restore get_juju_home.
1944
        if juju_home is None:
1945
            juju_home = get_juju_home()
1315.2.22 by Aaron Bentley
Fake merge of trunk.
1946
        super(JujuData, self).__init__(environment, config, juju_home,
1947
                                       controller)
1281.1.1 by Aaron Bentley
Rollback r1280 per Ian's email.
1948
        self.credentials = {}
1949
        self.clouds = {}
1260.2.6 by Aaron Bentley
Introduce JujuData, remove workarounds for cloud-credentials.
1950
1315.2.3 by Aaron Bentley
Use admin client to get bootstrap host.
1951
    def clone(self, model_name=None):
1315.2.8 by Aaron Bentley
Dump logs for all models, including on JES.
1952
        result = super(JujuData, self).clone(model_name)
1315.2.3 by Aaron Bentley
Use admin client to get bootstrap host.
1953
        result.credentials = deepcopy(self.credentials)
1954
        result.clouds = deepcopy(self.clouds)
1955
        return result
1956
1260.2.6 by Aaron Bentley
Introduce JujuData, remove workarounds for cloud-credentials.
1957
    @classmethod
1958
    def from_env(cls, env):
1959
        juju_data = cls(env.environment, env.config, env.juju_home)
1960
        juju_data.load_yaml()
1961
        return juju_data
1962
1963
    def load_yaml(self):
1964
        with open(os.path.join(self.juju_home, 'credentials.yaml')) as f:
1965
            self.credentials = yaml.safe_load(f)
1966
        with open(os.path.join(self.juju_home, 'clouds.yaml')) as f:
1967
            self.clouds = yaml.safe_load(f)
1968
1969
    @classmethod
1970
    def from_config(cls, name):
1971
        juju_data = cls._from_config(name)
1972
        juju_data.load_yaml()
1973
        return juju_data
1974
1260.2.25 by Aaron Bentley
Test load/dump yaml.
1975
    def dump_yaml(self, path, config):
1260.2.30 by Aaron Bentley
Updates from review
1976
        """Dump the configuration files to the specified path.
1977
1978
        config is unused, but is accepted for compatibility with
1979
        SimpleEnvironment and make_jes_home().
1980
        """
1260.2.25 by Aaron Bentley
Test load/dump yaml.
1981
        with open(os.path.join(path, 'credentials.yaml'), 'w') as f:
1260.2.6 by Aaron Bentley
Introduce JujuData, remove workarounds for cloud-credentials.
1982
            yaml.safe_dump(self.credentials, f)
1260.2.25 by Aaron Bentley
Test load/dump yaml.
1983
        with open(os.path.join(path, 'clouds.yaml'), 'w') as f:
1260.2.6 by Aaron Bentley
Introduce JujuData, remove workarounds for cloud-credentials.
1984
            yaml.safe_dump(self.clouds, f)
1985
1260.2.24 by Aaron Bentley
Move cloud/location to JujuData, add tests, fix lint.
1986
    def find_endpoint_cloud(self, cloud_type, endpoint):
1987
        for cloud, cloud_config in self.clouds['clouds'].items():
1988
            if cloud_config['type'] != cloud_type:
1989
                continue
1990
            if cloud_config['endpoint'] == endpoint:
1991
                return cloud
1992
        raise LookupError('No such endpoint: {}'.format(endpoint))
1993
1994
    def get_cloud(self):
1995
        provider = self.config['type']
1996
        # Separate cloud recommended by: Juju Cloud / Credentials / BootStrap /
1997
        # Model CLI specification
1998
        if provider == 'ec2' and self.config['region'] == 'cn-north-1':
1999
            return 'aws-china'
2000
        if provider not in ('maas', 'openstack'):
2001
            return {
2002
                'ec2': 'aws',
2003
                'gce': 'google',
2004
            }.get(provider, provider)
2005
        if provider == 'maas':
2006
            endpoint = self.config['maas-server']
2007
        elif provider == 'openstack':
2008
            endpoint = self.config['auth-url']
2009
        return self.find_endpoint_cloud(provider, endpoint)
2010
2011
    def get_region(self):
2012
        provider = self.config['type']
2013
        if provider == 'azure':
2014
            if 'tenant-id' not in self.config:
2015
                raise ValueError('Non-ARM Azure not supported.')
2016
            return self.config['location']
2017
        elif provider == 'joyent':
2018
            matcher = re.compile('https://(.*).api.joyentcloud.com')
2019
            return matcher.match(self.config['sdc-url']).group(1)
2020
        elif provider == 'lxd':
2021
            return 'localhost'
1280.1.1 by Aaron Bentley
Supply bootstrap host as manual region.
2022
        elif provider == 'manual':
2023
            return self.config['bootstrap-host']
1260.2.24 by Aaron Bentley
Move cloud/location to JujuData, add tests, fix lint.
2024
        elif provider in ('maas', 'manual'):
2025
            return None
2026
        else:
2027
            return self.config['region']
2028
657.1.3 by Aaron Bentley
Extract SimpleEnvironment from Environment.
2029
751.3.1 by Martin Packman
Use dots rather than repeated lines when waiting for status changes
2030
class GroupReporter:
2031
2032
    def __init__(self, stream, expected):
2033
        self.stream = stream
1196.1.6 by Martin Packman
Changes for review by abentley
2034
        self.expected = expected
751.3.1 by Martin Packman
Use dots rather than repeated lines when waiting for status changes
2035
        self.last_group = None
2036
        self.ticks = 0
922.1.1 by Martin Packman
Add wrapping at 80 characters to group reporter
2037
        self.wrap_offset = 0
922.1.2 by Martin Packman
Use width of 79 and add test suggested by abentley in review
2038
        self.wrap_width = 79
751.3.1 by Martin Packman
Use dots rather than repeated lines when waiting for status changes
2039
2040
    def _write(self, string):
2041
        self.stream.write(string)
2042
        self.stream.flush()
2043
769.1.1 by Martin Packman
Add finish method to GroupReporter to end dotties
2044
    def finish(self):
2045
        if self.last_group:
2046
            self._write("\n")
2047
751.3.1 by Martin Packman
Use dots rather than repeated lines when waiting for status changes
2048
    def update(self, group):
2049
        if group == self.last_group:
922.1.1 by Martin Packman
Add wrapping at 80 characters to group reporter
2050
            if (self.wrap_offset + self.ticks) % self.wrap_width == 0:
2051
                self._write("\n")
2052
            self._write("." if self.ticks or not self.wrap_offset else " .")
751.3.1 by Martin Packman
Use dots rather than repeated lines when waiting for status changes
2053
            self.ticks += 1
2054
            return
2055
        value_listing = []
2056
        for value, entries in sorted(group.items()):
1196.1.6 by Martin Packman
Changes for review by abentley
2057
            if value == self.expected:
751.3.1 by Martin Packman
Use dots rather than repeated lines when waiting for status changes
2058
                continue
2059
            value_listing.append('%s: %s' % (value, ', '.join(entries)))
2060
        string = ' | '.join(value_listing)
930.1.1 by Martin Packman
Fix wrapping of dots when printing updated group status
2061
        lead_length = len(string) + 1
751.3.1 by Martin Packman
Use dots rather than repeated lines when waiting for status changes
2062
        if self.last_group:
2063
            string = "\n" + string
2064
        self._write(string)
2065
        self.last_group = group
2066
        self.ticks = 0
922.1.1 by Martin Packman
Add wrapping at 80 characters to group reporter
2067
        self.wrap_offset = lead_length if lead_length < self.wrap_width else 0