~nskaggs/juju-ci-tools/ci-jimm

1711.6.4 by Aaron Bentley
Initial assess_add_cloud test.
1
#!/usr/bin/env python
2
3
from argparse import ArgumentParser
1727.2.16 by Aaron Bentley
Test iter_clouds.
4
from collections import namedtuple
1727.2.13 by Aaron Bentley
More fixes.
5
from copy import deepcopy
1711.6.5 by Aaron Bentley
Use external clouds.yal file for assessment.
6
import logging
1812.2.1 by Aaron Bentley
Assume no endpoint validation for 2.1.
7
import re
1711.6.5 by Aaron Bentley
Use external clouds.yal file for assessment.
8
import sys
1711.6.4 by Aaron Bentley
Initial assess_add_cloud test.
9
1711.6.5 by Aaron Bentley
Use external clouds.yal file for assessment.
10
import yaml
1711.6.4 by Aaron Bentley
Initial assess_add_cloud test.
11
12
from jujupy import (
1727.2.9 by Aaron Bentley
Expect AuthNotAccepted where auth is bogus.
13
    AuthNotAccepted,
1850.1.16 by Aaron Bentley
Switch assess_add_cloud to ModelClient name.
14
    ModelClient,
1711.6.4 by Aaron Bentley
Initial assess_add_cloud test.
15
    get_client_class,
1803.1.1 by Aaron Bentley
Fix add-cloud-interactive.
16
    InvalidEndpoint,
1711.6.4 by Aaron Bentley
Initial assess_add_cloud test.
17
    JujuData,
1727.2.18 by Aaron Bentley
Check invalid name handling.
18
    NameNotAccepted,
1727.2.17 by Aaron Bentley
Add bogus type testing.
19
    TypeNotAccepted,
1711.6.4 by Aaron Bentley
Initial assess_add_cloud test.
20
    )
21
from utility import (
22
    add_arg_juju_bin,
1711.6.5 by Aaron Bentley
Use external clouds.yal file for assessment.
23
    JujuAssertionError,
1711.6.4 by Aaron Bentley
Initial assess_add_cloud test.
24
    temp_dir,
25
    )
26
27
1794.2.1 by Aaron Bentley
Add expected-failure support.
28
class CloudMismatch(JujuAssertionError):
1794.2.2 by Aaron Bentley
Clean-up and docs.
29
    """The clouds did not match in some way."""
1794.2.1 by Aaron Bentley
Add expected-failure support.
30
31
    def __init__(self):
32
        super(CloudMismatch, self).__init__('Cloud mismatch')
33
34
35
class NameMismatch(JujuAssertionError):
1794.2.2 by Aaron Bentley
Clean-up and docs.
36
    """The cloud names did not match."""
1794.2.1 by Aaron Bentley
Add expected-failure support.
37
38
    def __init__(self):
39
        super(NameMismatch, self).__init__('Name mismatch')
40
41
42
class NotRaised(Exception):
1794.2.2 by Aaron Bentley
Clean-up and docs.
43
    """An expected exception was not raised."""
1794.2.1 by Aaron Bentley
Add expected-failure support.
44
45
    def __init__(self):
46
        msg = 'Expected exception not raised: {}'.format(
47
            cloud_spec.exception)
48
        super(NotRaised, self).__init__(msg)
49
50
1794.2.2 by Aaron Bentley
Clean-up and docs.
51
CloudSpec = namedtuple('CloudSpec', [
52
    'label', 'name', 'config', 'exception', 'xfail_bug'])
1794.2.1 by Aaron Bentley
Add expected-failure support.
53
54
55
def cloud_spec(label, name, config, exception=None, xfail_bug=None):
1794.2.2 by Aaron Bentley
Clean-up and docs.
56
    """Generate a CloudSpec, with defaults.
57
58
    :param label: The label to display in test results.
59
    :param name: The name to use for the cloud.
60
    :param config: The cloud-config.
61
    :param exception: The exception that is expected to be raised (if any).
62
    :param xfail_bug: If this CloudSpec represents an expected failure, the
63
        bug number.
64
    """
1794.2.1 by Aaron Bentley
Add expected-failure support.
65
    return CloudSpec(label, name, config, exception, xfail_bug)
66
67
68
def xfail(spec, bug, xfail_exception):
1794.2.3 by Aaron Bentley
Implement expected failures and get tests passing.
69
    """Return a variant of a CloudSpec that is expected to fail.
70
71
    Wrapping the original spec improves maintainability, because the xfail can
72
    be removed to restore the original value.
73
    """
1794.2.1 by Aaron Bentley
Add expected-failure support.
74
    return CloudSpec(spec.label, spec.name, spec.config, xfail_exception, bug)
1727.2.16 by Aaron Bentley
Test iter_clouds.
75
76
1727.2.3 by Aaron Bentley
Test long cloud names.
77
def assess_cloud(client, cloud_name, example_cloud):
1794.2.2 by Aaron Bentley
Clean-up and docs.
78
    """Assess interactively adding a cloud.
79
80
    Will raise an exception
81
    - If no clouds are present after interactive add-cloud.
82
    - If the resulting cloud name doesn't match the supplied cloud-name.
83
    - If the cloud data doesn't match the supplied cloud data.
84
    """
1711.6.10 by Aaron Bentley
Extract and use JujuData.read_clouds.
85
    clouds = client.env.read_clouds()
1711.6.9 by Aaron Bentley
Extract cloud-loading to a separate function.
86
    if len(clouds['clouds']) > 0:
1711.6.4 by Aaron Bentley
Initial assess_add_cloud test.
87
        raise AssertionError('Clouds already present!')
1727.2.8 by Aaron Bentley
Merged aci-updates into interative-add-cloud-error.
88
    client.add_cloud_interactive(cloud_name, example_cloud)
1711.6.10 by Aaron Bentley
Extract and use JujuData.read_clouds.
89
    clouds = client.env.read_clouds()
1711.6.9 by Aaron Bentley
Extract cloud-loading to a separate function.
90
    if len(clouds['clouds']) == 0:
1711.6.5 by Aaron Bentley
Use external clouds.yal file for assessment.
91
        raise JujuAssertionError('Clouds missing!')
1727.2.3 by Aaron Bentley
Test long cloud names.
92
    if clouds['clouds'].keys() != [cloud_name]:
1794.2.1 by Aaron Bentley
Add expected-failure support.
93
        raise NameMismatch()
1727.2.3 by Aaron Bentley
Test long cloud names.
94
    if clouds['clouds'][cloud_name] != example_cloud:
1711.6.5 by Aaron Bentley
Use external clouds.yal file for assessment.
95
        sys.stderr.write('\nExpected:\n')
96
        yaml.dump(example_cloud, sys.stderr)
97
        sys.stderr.write('\nActual:\n')
1727.2.3 by Aaron Bentley
Test long cloud names.
98
        yaml.dump(clouds['clouds'][cloud_name], sys.stderr)
1794.2.1 by Aaron Bentley
Add expected-failure support.
99
        raise CloudMismatch()
1711.6.4 by Aaron Bentley
Initial assess_add_cloud test.
100
101
1812.2.1 by Aaron Bentley
Assume no endpoint validation for 2.1.
102
def iter_clouds(clouds, endpoint_validation):
1794.2.2 by Aaron Bentley
Clean-up and docs.
103
    """Iterate through CloudSpecs."""
1794.2.1 by Aaron Bentley
Add expected-failure support.
104
    yield cloud_spec('bogus-type', 'bogus-type', {'type': 'bogus'},
1794.2.2 by Aaron Bentley
Clean-up and docs.
105
                     exception=TypeNotAccepted)
1727.2.1 by Aaron Bentley
Test with 4096-char endpoints.
106
    for cloud_name, cloud in clouds.items():
1808.2.1 by Aaron Bentley
Update with expected endpoint failures for manual.
107
        spec = cloud_spec(cloud_name, cloud_name, cloud)
1812.2.1 by Aaron Bentley
Assume no endpoint validation for 2.1.
108
        if cloud['type'] == 'manual' and endpoint_validation:
1808.2.1 by Aaron Bentley
Update with expected endpoint failures for manual.
109
            spec = xfail(spec, 1649721, InvalidEndpoint)
110
        yield spec
1727.2.1 by Aaron Bentley
Test with 4096-char endpoints.
111
112
    for cloud_name, cloud in clouds.items():
1808.2.1 by Aaron Bentley
Update with expected endpoint failures for manual.
113
        spec = xfail(cloud_spec('long-name-{}'.format(cloud_name), 'A' * 4096,
114
                                cloud, NameNotAccepted), 1641970, NameMismatch)
1812.2.1 by Aaron Bentley
Assume no endpoint validation for 2.1.
115
        if cloud['type'] == 'manual' and endpoint_validation:
1808.2.1 by Aaron Bentley
Update with expected endpoint failures for manual.
116
            spec = xfail(spec, 1649721, InvalidEndpoint)
117
        yield spec
118
        spec = xfail(
1794.2.1 by Aaron Bentley
Add expected-failure support.
119
            cloud_spec('invalid-name-{}'.format(cloud_name), 'invalid/name',
1794.2.7 by Aaron Bentley
Fix bug number.
120
                       cloud, NameNotAccepted), 1641981, None)
1812.2.1 by Aaron Bentley
Assume no endpoint validation for 2.1.
121
        if cloud['type'] == 'manual' and endpoint_validation:
1808.2.1 by Aaron Bentley
Update with expected endpoint failures for manual.
122
            spec = xfail(spec, 1649721, InvalidEndpoint)
123
        yield spec
1727.2.9 by Aaron Bentley
Expect AuthNotAccepted where auth is bogus.
124
125
        if cloud['type'] not in ('maas', 'manual', 'vsphere'):
126
            variant = deepcopy(cloud)
127
            variant_name = 'bogus-auth-{}'.format(cloud_name)
128
            variant['auth-types'] = ['asdf']
1794.2.1 by Aaron Bentley
Add expected-failure support.
129
            yield cloud_spec(variant_name, cloud_name, variant,
1794.2.2 by Aaron Bentley
Clean-up and docs.
130
                             AuthNotAccepted)
1727.2.3 by Aaron Bentley
Test long cloud names.
131
1727.2.4 by Aaron Bentley
Skip only endpoint tests due to lack of endpoint.
132
        if 'endpoint' in cloud:
133
            variant = deepcopy(cloud)
134
            variant['endpoint'] = 'A' * 4096
135
            if variant['type'] == 'vsphere':
136
                for region in variant['regions'].values():
137
                    region['endpoint'] = variant['endpoint']
138
            variant_name = 'long-endpoint-{}'.format(cloud_name)
1803.1.1 by Aaron Bentley
Fix add-cloud-interactive.
139
            spec = cloud_spec(variant_name, cloud_name, variant,
140
                              InvalidEndpoint)
1873.1.2 by Aaron Bentley
add-cloud expects vsphere validation after 2.1.
141
            if not endpoint_validation:
1803.1.1 by Aaron Bentley
Fix add-cloud-interactive.
142
                spec = xfail(spec, 1641970, CloudMismatch)
143
            yield spec
1727.2.4 by Aaron Bentley
Skip only endpoint tests due to lack of endpoint.
144
1727.2.16 by Aaron Bentley
Test iter_clouds.
145
        for region_name in cloud.get('regions', {}).keys():
146
            if cloud['type'] == 'vsphere':
147
                continue
148
            variant = deepcopy(cloud)
149
            region = variant['regions'][region_name]
150
            region['endpoint'] = 'A' * 4096
151
            variant_name = 'long-endpoint-{}-{}'.format(cloud_name,
152
                                                        region_name)
1812.2.1 by Aaron Bentley
Assume no endpoint validation for 2.1.
153
            spec = cloud_spec(variant_name, cloud_name, variant,
154
                              InvalidEndpoint)
155
            if not endpoint_validation:
156
                spec = xfail(spec, 1641970, CloudMismatch)
157
            yield spec
1794.2.1 by Aaron Bentley
Add expected-failure support.
158
1727.2.1 by Aaron Bentley
Test with 4096-char endpoints.
159
1794.2.5 by Aaron Bentley
Make assess_all_clouds more testable, test expected failures.
160
def assess_all_clouds(client, cloud_specs):
1794.2.6 by Aaron Bentley
Cleanup.
161
    """Test all the supplied cloud_specs and return the results.
162
163
    Returns a tuple of succeeded, expected_failed, and failed.
164
    succeeded and failed are sets of cloud labels.  expected_failed is a dict
165
    linking a given bug to its associated failures.
166
    """
1711.6.7 by Aaron Bentley
Extract assessment to assess_all_clouds.
167
    succeeded = set()
1794.2.5 by Aaron Bentley
Make assess_all_clouds more testable, test expected failures.
168
    xfailed = {}
1711.6.7 by Aaron Bentley
Extract assessment to assess_all_clouds.
169
    failed = set()
1711.6.9 by Aaron Bentley
Extract cloud-loading to a separate function.
170
    client.env.load_yaml()
1794.2.5 by Aaron Bentley
Make assess_all_clouds more testable, test expected failures.
171
    for cloud_spec in cloud_specs:
1794.2.1 by Aaron Bentley
Add expected-failure support.
172
        sys.stdout.write('Testing {}.\n'.format(cloud_spec.label))
1711.6.7 by Aaron Bentley
Extract assessment to assess_all_clouds.
173
        try:
1794.2.1 by Aaron Bentley
Add expected-failure support.
174
            if cloud_spec.exception is None:
175
                assess_cloud(client, cloud_spec.name, cloud_spec.config)
1727.2.9 by Aaron Bentley
Expect AuthNotAccepted where auth is bogus.
176
            else:
177
                try:
1794.2.1 by Aaron Bentley
Add expected-failure support.
178
                    assess_cloud(client, cloud_spec.name, cloud_spec.config)
179
                except cloud_spec.exception:
1727.2.9 by Aaron Bentley
Expect AuthNotAccepted where auth is bogus.
180
                    pass
181
                else:
1794.2.1 by Aaron Bentley
Add expected-failure support.
182
                    raise NotRaised(cloud_spec.exception)
1711.6.7 by Aaron Bentley
Extract assessment to assess_all_clouds.
183
        except Exception as e:
184
            logging.exception(e)
1794.2.1 by Aaron Bentley
Add expected-failure support.
185
            failed.add(cloud_spec.label)
1711.6.7 by Aaron Bentley
Extract assessment to assess_all_clouds.
186
        else:
1794.2.1 by Aaron Bentley
Add expected-failure support.
187
            if cloud_spec.xfail_bug is not None:
1794.2.5 by Aaron Bentley
Make assess_all_clouds more testable, test expected failures.
188
                xfailed.setdefault(
1794.2.1 by Aaron Bentley
Add expected-failure support.
189
                    cloud_spec.xfail_bug, set()).add(cloud_spec.label)
190
            else:
191
                succeeded.add(cloud_spec.label)
1711.6.7 by Aaron Bentley
Extract assessment to assess_all_clouds.
192
        finally:
193
            client.env.clouds = {'clouds': {}}
194
            client.env.dump_yaml(client.env.juju_home, {})
1794.2.5 by Aaron Bentley
Make assess_all_clouds more testable, test expected failures.
195
    return succeeded, xfailed, failed
1711.6.7 by Aaron Bentley
Extract assessment to assess_all_clouds.
196
197
1711.6.8 by Aaron Bentley
Fix empty list handling.
198
def write_status(status, tests):
199
    if len(tests) == 0:
200
        test_str = 'none'
201
    else:
202
        test_str = ', '.join(sorted(tests))
203
    sys.stdout.write('{}: {}\n'.format(status, test_str))
204
205
1711.6.4 by Aaron Bentley
Initial assess_add_cloud test.
206
def parse_args():
207
    parser = ArgumentParser()
1711.6.5 by Aaron Bentley
Use external clouds.yal file for assessment.
208
    parser.add_argument('example_clouds',
209
                        help='A clouds.yaml file to use for testing.')
1711.6.4 by Aaron Bentley
Initial assess_add_cloud test.
210
    add_arg_juju_bin(parser)
211
    return parser.parse_args()
212
213
214
def main():
1711.6.5 by Aaron Bentley
Use external clouds.yal file for assessment.
215
    args = parse_args()
216
    juju_bin = args.juju_bin
1850.1.16 by Aaron Bentley
Switch assess_add_cloud to ModelClient name.
217
    version = ModelClient.get_version(juju_bin)
1711.6.4 by Aaron Bentley
Initial assess_add_cloud test.
218
    client_class = get_client_class(version)
219
    if client_class.config_class is not JujuData:
220
        logging.warn('This test does not support old jujus.')
1711.6.5 by Aaron Bentley
Use external clouds.yal file for assessment.
221
    with open(args.example_clouds) as f:
222
        clouds = yaml.safe_load(f)['clouds']
1812.2.1 by Aaron Bentley
Assume no endpoint validation for 2.1.
223
    endpoint_validation = bool(not re.match('2\.1[^\d]', version))
224
    cloud_specs = iter_clouds(clouds, endpoint_validation)
1711.6.4 by Aaron Bentley
Initial assess_add_cloud test.
225
    with temp_dir() as juju_home:
226
        env = JujuData('foo', config=None, juju_home=juju_home)
227
        client = client_class(env, version, juju_bin)
1794.2.5 by Aaron Bentley
Make assess_all_clouds more testable, test expected failures.
228
        succeeded, xfailed, failed = assess_all_clouds(client, cloud_specs)
1711.6.8 by Aaron Bentley
Fix empty list handling.
229
    write_status('Succeeded', succeeded)
1794.2.5 by Aaron Bentley
Make assess_all_clouds more testable, test expected failures.
230
    for bug, failures in sorted(xfailed.items()):
1794.2.1 by Aaron Bentley
Add expected-failure support.
231
        write_status('Expected fail (bug #{})'.format(bug), failures)
1711.6.8 by Aaron Bentley
Fix empty list handling.
232
    write_status('Failed', failed)
1739 by Aaron Bentley
Use exit status 1 on error.
233
    if len(failed) > 0:
234
        return 1
235
    return 0
1711.6.4 by Aaron Bentley
Initial assess_add_cloud test.
236
237
238
if __name__ == '__main__':
1739 by Aaron Bentley
Use exit status 1 on error.
239
    sys.exit(main())