~juju-qa/juju-ci-tools/trunk

« back to all changes in this revision

Viewing changes to assess_add_cloud.py

  • Committer: Curtis Hovey
  • Date: 2017-01-25 02:32:29 UTC
  • mfrom: (1855 trunk)
  • mto: This revision was merged to the branch mainline in revision 1865.
  • Revision ID: curtis@canonical.com-20170125023229-g7c6bzt0cqe1j8g3
Merged trunk and resolved conflicts.

Show diffs side-by-side

added added

removed removed

Lines of Context:
4
4
from collections import namedtuple
5
5
from copy import deepcopy
6
6
import logging
 
7
import re
7
8
import sys
8
9
 
9
10
import yaml
10
11
 
11
12
from jujupy import (
12
13
    AuthNotAccepted,
13
 
    EnvJujuClient,
 
14
    ModelClient,
14
15
    get_client_class,
 
16
    InvalidEndpoint,
15
17
    JujuData,
16
18
    NameNotAccepted,
17
19
    TypeNotAccepted,
23
25
    )
24
26
 
25
27
 
26
 
CloudSpec = namedtuple('CloudSpec', ['label', 'name', 'config', 'exception'])
 
28
class CloudMismatch(JujuAssertionError):
 
29
    """The clouds did not match in some way."""
 
30
 
 
31
    def __init__(self):
 
32
        super(CloudMismatch, self).__init__('Cloud mismatch')
 
33
 
 
34
 
 
35
class NameMismatch(JujuAssertionError):
 
36
    """The cloud names did not match."""
 
37
 
 
38
    def __init__(self):
 
39
        super(NameMismatch, self).__init__('Name mismatch')
 
40
 
 
41
 
 
42
class NotRaised(Exception):
 
43
    """An expected exception was not raised."""
 
44
 
 
45
    def __init__(self):
 
46
        msg = 'Expected exception not raised: {}'.format(
 
47
            cloud_spec.exception)
 
48
        super(NotRaised, self).__init__(msg)
 
49
 
 
50
 
 
51
CloudSpec = namedtuple('CloudSpec', [
 
52
    'label', 'name', 'config', 'exception', 'xfail_bug'])
 
53
 
 
54
 
 
55
def cloud_spec(label, name, config, exception=None, xfail_bug=None):
 
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
    """
 
65
    return CloudSpec(label, name, config, exception, xfail_bug)
 
66
 
 
67
 
 
68
def xfail(spec, bug, xfail_exception):
 
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
    """
 
74
    return CloudSpec(spec.label, spec.name, spec.config, xfail_exception, bug)
27
75
 
28
76
 
29
77
def assess_cloud(client, cloud_name, example_cloud):
 
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
    """
30
85
    clouds = client.env.read_clouds()
31
86
    if len(clouds['clouds']) > 0:
32
87
        raise AssertionError('Clouds already present!')
35
90
    if len(clouds['clouds']) == 0:
36
91
        raise JujuAssertionError('Clouds missing!')
37
92
    if clouds['clouds'].keys() != [cloud_name]:
38
 
        raise JujuAssertionError('Name mismatch')
 
93
        raise NameMismatch()
39
94
    if clouds['clouds'][cloud_name] != example_cloud:
40
95
        sys.stderr.write('\nExpected:\n')
41
96
        yaml.dump(example_cloud, sys.stderr)
42
97
        sys.stderr.write('\nActual:\n')
43
98
        yaml.dump(clouds['clouds'][cloud_name], sys.stderr)
44
 
        raise JujuAssertionError('Cloud mismatch')
45
 
 
46
 
 
47
 
def iter_clouds(clouds):
48
 
    yield CloudSpec('bogus-type', 'bogus-type', {'type': 'bogus'},
49
 
                    exception=TypeNotAccepted)
50
 
    for cloud_name, cloud in clouds.items():
51
 
        yield CloudSpec(cloud_name, cloud_name, cloud, exception=None)
52
 
 
53
 
    for cloud_name, cloud in clouds.items():
54
 
        yield CloudSpec('long-name-{}'.format(cloud_name), 'A' * 4096, cloud,
55
 
                        exception=None)
56
 
        yield CloudSpec('invalid-name-{}'.format(cloud_name), 'invalid/name',
57
 
                        cloud, exception=NameNotAccepted)
 
99
        raise CloudMismatch()
 
100
 
 
101
 
 
102
def iter_clouds(clouds, endpoint_validation):
 
103
    """Iterate through CloudSpecs."""
 
104
    yield cloud_spec('bogus-type', 'bogus-type', {'type': 'bogus'},
 
105
                     exception=TypeNotAccepted)
 
106
    for cloud_name, cloud in clouds.items():
 
107
        spec = cloud_spec(cloud_name, cloud_name, cloud)
 
108
        if cloud['type'] == 'manual' and endpoint_validation:
 
109
            spec = xfail(spec, 1649721, InvalidEndpoint)
 
110
        yield spec
 
111
 
 
112
    for cloud_name, cloud in clouds.items():
 
113
        spec = xfail(cloud_spec('long-name-{}'.format(cloud_name), 'A' * 4096,
 
114
                                cloud, NameNotAccepted), 1641970, NameMismatch)
 
115
        if cloud['type'] == 'manual' and endpoint_validation:
 
116
            spec = xfail(spec, 1649721, InvalidEndpoint)
 
117
        yield spec
 
118
        spec = xfail(
 
119
            cloud_spec('invalid-name-{}'.format(cloud_name), 'invalid/name',
 
120
                       cloud, NameNotAccepted), 1641981, None)
 
121
        if cloud['type'] == 'manual' and endpoint_validation:
 
122
            spec = xfail(spec, 1649721, InvalidEndpoint)
 
123
        yield spec
58
124
 
59
125
        if cloud['type'] not in ('maas', 'manual', 'vsphere'):
60
126
            variant = deepcopy(cloud)
61
127
            variant_name = 'bogus-auth-{}'.format(cloud_name)
62
128
            variant['auth-types'] = ['asdf']
63
 
            yield CloudSpec(variant_name, cloud_name, variant,
64
 
                            AuthNotAccepted)
 
129
            yield cloud_spec(variant_name, cloud_name, variant,
 
130
                             AuthNotAccepted)
65
131
 
66
132
        if 'endpoint' in cloud:
67
133
            variant = deepcopy(cloud)
70
136
                for region in variant['regions'].values():
71
137
                    region['endpoint'] = variant['endpoint']
72
138
            variant_name = 'long-endpoint-{}'.format(cloud_name)
73
 
            yield CloudSpec(variant_name, cloud_name, variant, exception=None)
 
139
            spec = cloud_spec(variant_name, cloud_name, variant,
 
140
                              InvalidEndpoint)
 
141
            if variant['type'] == 'vsphere' or not endpoint_validation:
 
142
                spec = xfail(spec, 1641970, CloudMismatch)
 
143
            yield spec
74
144
 
75
145
        for region_name in cloud.get('regions', {}).keys():
76
146
            if cloud['type'] == 'vsphere':
80
150
            region['endpoint'] = 'A' * 4096
81
151
            variant_name = 'long-endpoint-{}-{}'.format(cloud_name,
82
152
                                                        region_name)
83
 
            yield CloudSpec(variant_name, cloud_name, variant,
84
 
                            exception=None)
85
 
 
86
 
 
87
 
def assess_all_clouds(client, clouds):
 
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
 
158
 
 
159
 
 
160
def assess_all_clouds(client, cloud_specs):
 
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
    """
88
167
    succeeded = set()
 
168
    xfailed = {}
89
169
    failed = set()
90
170
    client.env.load_yaml()
91
 
    for cloud_label, cloud_name, cloud, expected in iter_clouds(clouds):
92
 
        sys.stdout.write('Testing {}.\n'.format(cloud_label))
 
171
    for cloud_spec in cloud_specs:
 
172
        sys.stdout.write('Testing {}.\n'.format(cloud_spec.label))
93
173
        try:
94
 
            if expected is None:
95
 
                assess_cloud(client, cloud_name, cloud)
 
174
            if cloud_spec.exception is None:
 
175
                assess_cloud(client, cloud_spec.name, cloud_spec.config)
96
176
            else:
97
177
                try:
98
 
                    assess_cloud(client, cloud_name, cloud)
99
 
                except expected:
 
178
                    assess_cloud(client, cloud_spec.name, cloud_spec.config)
 
179
                except cloud_spec.exception:
100
180
                    pass
101
181
                else:
102
 
                    raise Exception(
103
 
                        'Expected exception not raised: {}'.format(expected))
 
182
                    raise NotRaised(cloud_spec.exception)
104
183
        except Exception as e:
105
184
            logging.exception(e)
106
 
            failed.add(cloud_label)
 
185
            failed.add(cloud_spec.label)
107
186
        else:
108
 
            succeeded.add(cloud_label)
 
187
            if cloud_spec.xfail_bug is not None:
 
188
                xfailed.setdefault(
 
189
                    cloud_spec.xfail_bug, set()).add(cloud_spec.label)
 
190
            else:
 
191
                succeeded.add(cloud_spec.label)
109
192
        finally:
110
193
            client.env.clouds = {'clouds': {}}
111
194
            client.env.dump_yaml(client.env.juju_home, {})
112
 
    return succeeded, failed
 
195
    return succeeded, xfailed, failed
113
196
 
114
197
 
115
198
def write_status(status, tests):
131
214
def main():
132
215
    args = parse_args()
133
216
    juju_bin = args.juju_bin
134
 
    version = EnvJujuClient.get_version(juju_bin)
 
217
    version = ModelClient.get_version(juju_bin)
135
218
    client_class = get_client_class(version)
136
219
    if client_class.config_class is not JujuData:
137
220
        logging.warn('This test does not support old jujus.')
138
221
    with open(args.example_clouds) as f:
139
222
        clouds = yaml.safe_load(f)['clouds']
 
223
    endpoint_validation = bool(not re.match('2\.1[^\d]', version))
 
224
    cloud_specs = iter_clouds(clouds, endpoint_validation)
140
225
    with temp_dir() as juju_home:
141
226
        env = JujuData('foo', config=None, juju_home=juju_home)
142
227
        client = client_class(env, version, juju_bin)
143
 
        succeeded, failed = assess_all_clouds(client, clouds)
 
228
        succeeded, xfailed, failed = assess_all_clouds(client, cloud_specs)
144
229
    write_status('Succeeded', succeeded)
 
230
    for bug, failures in sorted(xfailed.items()):
 
231
        write_status('Expected fail (bug #{})'.format(bug), failures)
145
232
    write_status('Failed', failed)
146
233
    if len(failed) > 0:
147
234
        return 1