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

650.1.3 by Aaron Bentley
Begin implementing compatibility-test.
1
#!/usr/bin/env python
915.2.7 by seman.said at canonical
Updated comp-test per review suggestion.
2
650.1.3 by Aaron Bentley
Begin implementing compatibility-test.
3
from argparse import ArgumentParser
650.1.7 by Aaron Bentley
Start testing mixed environs.
4
from contextlib import contextmanager
1053.1.1 by Martin Packman
Refactor log collection to ensure local logs are only collected once
5
import logging
650.1.12 by Aaron Bentley
Rename to assess-foreign, update to use EnvJujuClient.
6
from textwrap import dedent
678 by Aaron Bentley
Support retrying certain 1.18 operations with system juju.
7
from subprocess import CalledProcessError
1074.3.1 by seman.said at canonical
Added script to run client-server test on Windows.
8
import sys
650.1.3 by Aaron Bentley
Begin implementing compatibility-test.
9
1515.2.1 by Martin Packman
Move fake_juju_client and related code into a new top level fakejuju file
10
from fakejuju import (
11
    fake_juju_client,
12
)
1485.1.1 by Martin
Switch all imports of local_charm_path to using jujucharm over utility
13
from jujucharm import (
14
    local_charm_path,
15
)
650.1.3 by Aaron Bentley
Begin implementing compatibility-test.
16
from jujupy import (
1465.5.2 by Aaron Bentley
Implement client_from_config, delete by_version.
17
    client_from_config,
1341.3.3 by Aaron Bentley
Get working.
18
    EnvJujuClient1X,
1504.1.2 by Aaron Bentley
Handle JujuData incompatibility.
19
    IncompatibleConfigClass,
663.1.9 by Aaron Bentley
Assess heterogenous-control supports --upload-tools.
20
    SimpleEnvironment,
650.1.8 by Aaron Bentley
Implement most compatibility testing.
21
    until_timeout,
650.1.3 by Aaron Bentley
Begin implementing compatibility-test.
22
    )
23
from deploy_stack import (
1162.2.27 by Aaron Bentley
Switch assess_heterogeneous_control to BootstrapManager.
24
    BootstrapManager,
650.1.7 by Aaron Bentley
Start testing mixed environs.
25
    check_token,
26
    get_random_string,
650.1.3 by Aaron Bentley
Begin implementing compatibility-test.
27
    )
915.2.3 by seman.said at canonical
Updated code per review suggestion
28
from jujuci import add_credential_args
1345.1.3 by Seman
Deploy charm by path.
29
from utility import (
30
    configure_logging,
31
)
650.1.3 by Aaron Bentley
Begin implementing compatibility-test.
32
33
650.1.12 by Aaron Bentley
Rename to assess-foreign, update to use EnvJujuClient.
34
def prepare_dummy_env(client):
650.1.17 by Aaron Bentley
Clean up.
35
    """Use a client to prepare a dummy environment."""
1345.1.3 by Seman
Deploy charm by path.
36
    charm_source = local_charm_path(
37
        charm='dummy-source', juju_ver=client.version)
38
    client.deploy(charm_source)
39
    charm_sink = local_charm_path(charm='dummy-sink', juju_ver=client.version)
40
    client.deploy(charm_sink)
650.1.8 by Aaron Bentley
Implement most compatibility testing.
41
    token = get_random_string()
1239.1.1 by Aaron Bentley
Implement and use EnvJujuClient.set_config.
42
    client.set_config('dummy-source', {'token': token})
650.1.12 by Aaron Bentley
Rename to assess-foreign, update to use EnvJujuClient.
43
    client.juju('add-relation', ('dummy-source', 'dummy-sink'))
44
    client.juju('expose', ('dummy-sink',))
650.1.8 by Aaron Bentley
Implement most compatibility testing.
45
    return token
650.1.3 by Aaron Bentley
Begin implementing compatibility-test.
46
47
1162.2.27 by Aaron Bentley
Switch assess_heterogeneous_control to BootstrapManager.
48
def get_clients(initial, other, base_env, debug, agent_url):
774.2.3 by Aaron Bentley
Support agent-url
49
    """Return the clients to use for testing."""
1438.1.2 by Aaron Bentley
Support FAKE for assess_heterogeneous_control.
50
    if initial == 'FAKE':
1465.5.2 by Aaron Bentley
Implement client_from_config, delete by_version.
51
        environment = SimpleEnvironment.from_config(base_env)
1438.1.2 by Aaron Bentley
Support FAKE for assess_heterogeneous_control.
52
        client = fake_juju_client(env=environment)
53
        return client, client, client
1465.5.2 by Aaron Bentley
Implement client_from_config, delete by_version.
54
    else:
55
        initial_client = client_from_config(base_env, initial, debug=debug)
56
        environment = initial_client.env
57
    if agent_url is None:
1762.2.7 by Aaron Bentley
Remove config property.
58
        environment.discard_option('tools-metadata-url')
1465.5.2 by Aaron Bentley
Implement client_from_config, delete by_version.
59
    other_client = initial_client.clone_path_cls(other)
677 by Aaron Bentley
Use system juju for teardown and log dumping.
60
    # System juju is assumed to be released and the best choice for tearing
61
    # down environments reliably.  (For example, 1.18.x cannot tear down
62
    # environments with alpha agent-versions.)
1504.1.2 by Aaron Bentley
Handle JujuData incompatibility.
63
    try:
64
        released_client = initial_client.clone_path_cls(None)
65
    except IncompatibleConfigClass:
66
        # If initial_client's config class is incompatible with the system
67
        # juju, use initial client for teardown.
68
        released_client = initial_client
1341.3.3 by Aaron Bentley
Get working.
69
    # If released_client is a different major version, it cannot tear down
70
    # initial client, so use initial client for teardown.
1363.5.22 by Aaron Bentley
Fix lint.
71
    if (
72
            isinstance(released_client, EnvJujuClient1X) !=
73
            isinstance(initial_client, EnvJujuClient1X)
74
            ):
75
        released_client = initial_client
1341.3.3 by Aaron Bentley
Get working.
76
    else:
77
        # If system juju is used, ensure it has identical env to
78
        # initial_client.
79
        released_client.env = initial_client.env
774.2.3 by Aaron Bentley
Support agent-url
80
    return initial_client, other_client, released_client
81
82
83
def assess_heterogeneous(initial, other, base_env, environment_name, log_dir,
1063.1.2 by Curtis Hovey
pass agent_stream and series to update_env.
84
                         upload_tools, debug, agent_url, agent_stream, series):
774.2.3 by Aaron Bentley
Support agent-url
85
    """Top level function that prepares the clients and environment.
86
915.2.2 by seman.said at canonical
Added unit test and updated code to upload compatibility test results to S3
87
    initial and other are paths to the binary used initially, and a binary
774.2.3 by Aaron Bentley
Support agent-url
88
    used later.  base_env is the name of the environment to base the
89
    environment on and environment_name is the new name for the environment.
90
    """
1305.1.1 by Aaron Bentley
assess_heterogeneous_control does not tear down with inappropriate client.
91
    initial_client, other_client, teardown_client = get_clients(
1162.2.27 by Aaron Bentley
Switch assess_heterogeneous_control to BootstrapManager.
92
        initial, other, base_env, debug, agent_url)
93
    jes_enabled = initial_client.is_jes_enabled()
94
    bs_manager = BootstrapManager(
1305.1.1 by Aaron Bentley
assess_heterogeneous_control does not tear down with inappropriate client.
95
        environment_name, initial_client, teardown_client,
1162.2.27 by Aaron Bentley
Switch assess_heterogeneous_control to BootstrapManager.
96
        bootstrap_host=None, machines=[], series=series, agent_url=agent_url,
97
        agent_stream=agent_stream, region=None, log_dir=log_dir,
98
        keep_env=False, permanent=jes_enabled, jes_enabled=jes_enabled)
99
    test_control_heterogeneous(bs_manager, other_client, upload_tools)
100
101
102
@contextmanager
103
def run_context(bs_manager, other, upload_tools):
104
    try:
105
        bs_manager.keep_env = True
106
        with bs_manager.booted_context(upload_tools):
1341.3.3 by Aaron Bentley
Get working.
107
            if other.env.juju_home != bs_manager.client.env.juju_home:
108
                raise AssertionError('Juju home out of sync')
1162.2.27 by Aaron Bentley
Switch assess_heterogeneous_control to BootstrapManager.
109
            yield
110
        # Test clean shutdown of an environment.
1221.5.14 by Aaron Bentley
Fix destroy_environment for jes-enabled jujus.
111
        callback_with_fallback(other, bs_manager.tear_down_client,
112
                               nice_tear_down)
1162.2.27 by Aaron Bentley
Switch assess_heterogeneous_control to BootstrapManager.
113
    except:
114
        bs_manager.tear_down()
115
        raise
116
117
118
def test_control_heterogeneous(bs_manager, other, upload_tools):
650.1.17 by Aaron Bentley
Clean up.
119
    """Test if one binary can control an environment set up by the other."""
1162.2.27 by Aaron Bentley
Switch assess_heterogeneous_control to BootstrapManager.
120
    initial = bs_manager.client
121
    released = bs_manager.tear_down_client
122
    with run_context(bs_manager, other, upload_tools):
663.1.9 by Aaron Bentley
Assess heterogenous-control supports --upload-tools.
123
        token = prepare_dummy_env(initial)
650.1.19 by Aaron Bentley
Switch native/foreign terminology to initial/other/hetrogeneous.
124
        initial.wait_for_started()
1341.3.3 by Aaron Bentley
Get working.
125
        if sys.platform != "win32":
126
            # Currently, juju ssh is not working on Windows.
127
            check_token(initial, token)
128
            check_series(other)
129
            other.juju('run', ('--all', 'uname -a'))
130
        other.get_config('dummy-source')
131
        other.get_model_config()
132
        other.juju('remove-relation', ('dummy-source', 'dummy-sink'))
133
        status = other.get_status()
134
        other.juju('unexpose', ('dummy-sink',))
135
        status = other.get_status()
1420.2.1 by Aaron Bentley
Update tests to use applications, not services.
136
        if status.get_applications()['dummy-sink']['exposed']:
1341.3.3 by Aaron Bentley
Get working.
137
            raise AssertionError('dummy-sink is still exposed')
138
        status = other.get_status()
139
        charm_path = local_charm_path(
140
            charm='dummy-sink', juju_ver=other.version)
141
        juju_with_fallback(other, released, 'deploy',
142
                           (charm_path, 'sink2'))
143
        other.wait_for_started()
144
        other.juju('add-relation', ('dummy-source', 'sink2'))
145
        status = other.get_status()
146
        other.juju('expose', ('sink2',))
147
        status = other.get_status()
1420.2.1 by Aaron Bentley
Update tests to use applications, not services.
148
        if 'sink2' not in status.get_applications():
1341.3.3 by Aaron Bentley
Get working.
149
            raise AssertionError('Sink2 missing')
150
        other.remove_service('sink2')
151
        for ignored in until_timeout(30):
152
            status = other.get_status()
1420.2.1 by Aaron Bentley
Update tests to use applications, not services.
153
            if 'sink2' not in status.get_applications():
1341.3.3 by Aaron Bentley
Get working.
154
                break
155
        else:
156
            raise AssertionError('Sink2 not destroyed')
157
        other.juju('add-relation', ('dummy-source', 'dummy-sink'))
158
        status = other.get_status()
1420.2.1 by Aaron Bentley
Update tests to use applications, not services.
159
        relations = status.get_applications()['dummy-sink']['relations']
1341.3.3 by Aaron Bentley
Get working.
160
        if not relations['source'] == ['dummy-source']:
161
            raise AssertionError('source is not dummy-source.')
162
        other.juju('expose', ('dummy-sink',))
163
        status = other.get_status()
1420.2.1 by Aaron Bentley
Update tests to use applications, not services.
164
        if not status.get_applications()['dummy-sink']['exposed']:
1341.3.3 by Aaron Bentley
Get working.
165
            raise AssertionError('dummy-sink is not exposed')
166
        other.juju('add-unit', ('dummy-sink',))
167
        if not has_agent(other, 'dummy-sink/1'):
168
            raise AssertionError('dummy-sink/1 was not added.')
169
        other.juju('remove-unit', ('dummy-sink/1',))
170
        status = other.get_status()
171
        if has_agent(other, 'dummy-sink/1'):
172
            raise AssertionError('dummy-sink/1 was not removed.')
173
        container_type = other.preferred_container()
1341.3.1 by Aaron Bentley
Initial work on assess-heterogeneous-control.
174
        other.juju('add-machine', (container_type,))
175
        status = other.get_status()
176
        container_machine, = set(k for k, v in status.agent_items() if
177
                                 k.endswith('/{}/0'.format(container_type)))
178
        container_holder = container_machine.split('/')[0]
179
        other.juju('remove-machine', (container_machine,))
180
        wait_until_removed(other, container_machine)
181
        other.juju('remove-machine', (container_holder,))
182
        wait_until_removed(other, container_holder)
678 by Aaron Bentley
Support retrying certain 1.18 operations with system juju.
183
1173.1.3 by Aaron Bentley
Prevent nosetests from running test_control_heterogeneous directly.
184
# suppress nosetests
185
test_control_heterogeneous.__test__ = False
186
678 by Aaron Bentley
Support retrying certain 1.18 operations with system juju.
187
188
def juju_with_fallback(other, released, command, args, include_e=True):
189
    """Fallback to released juju when 1.18 fails.
190
191
    Get as much test coverage of 1.18 as we can, by falling back to a released
192
    juju for commands that we expect to fail (due to unsupported agent version
193
    format).
194
    """
1221.5.14 by Aaron Bentley
Fix destroy_environment for jes-enabled jujus.
195
    def call_juju(client):
196
        client.juju(command, args, include_e=include_e)
197
    return callback_with_fallback(other, released, call_juju)
198
199
200
def callback_with_fallback(other, released, callback):
679 by Aaron Bentley
Rewrite juju_with_fallback so there is only one EnvJujuClient invocation.
201
    for client in [other, released]:
202
        try:
1221.5.14 by Aaron Bentley
Fix destroy_environment for jes-enabled jujus.
203
            callback(client)
679 by Aaron Bentley
Rewrite juju_with_fallback so there is only one EnvJujuClient invocation.
204
        except CalledProcessError:
205
            if not client.version.startswith('1.18.'):
206
                raise
207
        else:
208
            break
650.1.12 by Aaron Bentley
Rename to assess-foreign, update to use EnvJujuClient.
209
210
1221.5.14 by Aaron Bentley
Fix destroy_environment for jes-enabled jujus.
211
def nice_tear_down(client):
212
    if client.is_jes_enabled():
213
        client.kill_controller()
214
    else:
215
        if client.destroy_environment(force=False) != 0:
216
            raise CalledProcessError(1, 'juju destroy-environment')
217
218
650.1.20 by Aaron Bentley
Test outcomes better.
219
def has_agent(client, agent_id):
220
    return bool(agent_id in dict(client.get_status().agent_items()))
221
222
650.1.12 by Aaron Bentley
Rename to assess-foreign, update to use EnvJujuClient.
223
def wait_until_removed(client, agent_id):
650.1.17 by Aaron Bentley
Clean up.
224
    """Wait for an agent to be removed from the environment."""
650.1.20 by Aaron Bentley
Test outcomes better.
225
    for ignored in until_timeout(240):
226
        if not has_agent(client, agent_id):
227
            return
650.1.8 by Aaron Bentley
Implement most compatibility testing.
228
    else:
650.1.20 by Aaron Bentley
Test outcomes better.
229
        raise AssertionError('Machine not destroyed: {}.'.format(agent_id))
650.1.8 by Aaron Bentley
Implement most compatibility testing.
230
231
1357.2.2 by Seman
Added assess_multi_series_charms.
232
def check_series(client,  machine='0', series=None):
650.1.17 by Aaron Bentley
Clean up.
233
    """Use 'juju ssh' to check that the deployed series meets expectations."""
1357.2.2 by Seman
Added assess_multi_series_charms.
234
    result = client.get_juju_output('ssh', machine, 'lsb_release', '-c')
650.1.8 by Aaron Bentley
Implement most compatibility testing.
235
    label, codename = result.rstrip().split('\t')
236
    if label != 'Codename:':
237
        raise AssertionError()
1357.2.2 by Seman
Added assess_multi_series_charms.
238
    if series:
239
        expected_codename = series
240
    else:
1699.1.2 by Aaron Bentley
Convert direct config access to get_option.
241
        expected_codename = client.env.get_option('default-series')
650.1.8 by Aaron Bentley
Implement most compatibility testing.
242
    if codename != expected_codename:
243
        raise AssertionError(
244
            'Series is {}, not {}'.format(codename, expected_codename))
650.1.7 by Aaron Bentley
Start testing mixed environs.
245
246
774.2.3 by Aaron Bentley
Support agent-url
247
def parse_args(argv=None):
650.1.12 by Aaron Bentley
Rename to assess-foreign, update to use EnvJujuClient.
248
    parser = ArgumentParser(description=dedent("""\
249
        Determine whether one juju version can control an environment created
250
        by another version.
251
    """))
650.1.19 by Aaron Bentley
Switch native/foreign terminology to initial/other/hetrogeneous.
252
    parser.add_argument('initial', help='The initial juju binary.')
253
    parser.add_argument('other', help='A different juju binary.')
650.1.3 by Aaron Bentley
Begin implementing compatibility-test.
254
    parser.add_argument('base_environment', help='The environment to base on.')
255
    parser.add_argument('environment_name', help='The new environment name.')
650.1.10 by Aaron Bentley
Stop dumping logs in cwd.
256
    parser.add_argument('log_dir', help='The directory to dump logs to.')
663.1.7 by Aaron Bentley
Add --upload-tools to assess-heterogeneous-control.
257
    parser.add_argument(
258
        '--upload-tools', action='store_true', default=False,
259
        help='Upload local version of tools before bootstrapping.')
260
    parser.add_argument('--debug', help='Run juju with --debug',
261
                        action='store_true', default=False)
774.2.3 by Aaron Bentley
Support agent-url
262
    parser.add_argument('--agent-url', default=None)
1063.1.4 by Curtis Hovey
Remove redundant args
263
    parser.add_argument('--agent-stream', action='store',
1063.1.1 by Curtis Hovey
Added --agent-steam and --series.
264
                        help='URL for retrieving agent binaries.')
1063.1.4 by Curtis Hovey
Remove redundant args
265
    parser.add_argument('--series', action='store',
1063.1.1 by Curtis Hovey
Added --agent-steam and --series.
266
                        help='Name of the Ubuntu series to use.')
915.2.3 by seman.said at canonical
Updated code per review suggestion
267
    add_credential_args(parser)
774.2.3 by Aaron Bentley
Support agent-url
268
    return parser.parse_args(argv)
269
270
271
def main():
272
    args = parse_args()
1053.1.1 by Martin Packman
Refactor log collection to ensure local logs are only collected once
273
    configure_logging(logging.INFO)
650.1.19 by Aaron Bentley
Switch native/foreign terminology to initial/other/hetrogeneous.
274
    assess_heterogeneous(args.initial, args.other, args.base_environment,
663.1.7 by Aaron Bentley
Add --upload-tools to assess-heterogeneous-control.
275
                         args.environment_name, args.log_dir,
1063.1.2 by Curtis Hovey
pass agent_stream and series to update_env.
276
                         args.upload_tools, args.debug, args.agent_url,
277
                         args.agent_stream, args.series)
650.1.3 by Aaron Bentley
Begin implementing compatibility-test.
278
279
280
if __name__ == '__main__':
281
    main()