~bac/charmworld/tag-constraints

« back to all changes in this revision

Viewing changes to charmworld/views/api/proof.py

  • Committer: Matthew Scott
  • Date: 2014-03-06 20:32:51 UTC
  • mfrom: (489 trunk)
  • mto: This revision was merged to the branch mainline in revision 490.
  • Revision ID: matthew.scott@canonical.com-20140306203251-ve388tpu1av0coqz
Merge with trunk to resolve conflicts.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2012-2014 Canonical Ltd.  This software is licensed under the GNU
 
2
# Affero General Public License version 3 (see the file LICENSE).
 
3
 
 
4
__metaclass__ = type
 
5
 
 
6
from charmworldlib.utils import (
 
7
    check_constraints,
 
8
    parse_constraints,
 
9
)
1
10
from deployer.relation import (
2
11
    Endpoint,
3
12
    EndpointPair,
4
13
)
 
14
 
5
15
from charmworld.lib.cdeployer import (
6
16
    get_flattened_deployment,
7
17
    parse_deployer,
22
32
 
23
33
 
24
34
def _build_proof_response(response_data):
25
 
    # Build up a root messages list of errors that can be used to help the
26
 
    # client go through them all easily.
 
35
    # Build up a root messages list of errors and warnings that can be used to
 
36
    # help the client go through them all easily.
27
37
    error_messages = []
 
38
    warning_messages = []
28
39
    if response_data['errors']:
29
40
        for bundle in response_data['errors']:
30
 
            prefix = bundle['name']
31
 
            for service_name, service in bundle['services'].items():
32
 
                for err in service:
33
 
                    msg = '%s: %s' % (prefix, err['message'])
 
41
            name = bundle['name']
 
42
            for service_name, errors in bundle['services'].items():
 
43
                for err in errors:
 
44
                    msg = '{}/{}: {}'.format(
 
45
                        name, service_name, err['message'])
34
46
                    error_messages.append(msg)
35
47
 
36
48
            for relation_error in bundle['relations']:
37
 
                msg = '%s: %s' % (prefix, relation_error['message'])
 
49
                msg = '{}: {}'.format(name, relation_error['message'])
38
50
                error_messages.append(msg)
39
51
 
 
52
            for service_name, warnings in bundle['warnings'].items():
 
53
                for warning in warnings:
 
54
                    msg = '{}/{}: {}'.format(
 
55
                        name, service_name, warning['message'])
 
56
                    warning_messages.append(msg)
 
57
 
40
58
    if error_messages:
41
59
        response_data['error_messages'].extend(error_messages)
42
 
    return response_data
 
60
 
 
61
    if warning_messages:
 
62
        response_data['warning_messages'].extend(warning_messages)
43
63
 
44
64
 
45
65
def _proof_bundles_charms_exist(db, bundle_info, services, series=None):
46
 
    """Verify the charms the bundle requests to deploy exist in the db."""
 
66
    """Verify the charms the bundle requests to deploy exist in the db.
 
67
 
 
68
    Modifies the bundle_info dictionary in place.
 
69
    """
47
70
    found_charms = {}
48
71
 
49
72
    # Verify we can find the charms at all.
56
79
            if not charm_found:
57
80
                raise ProofError(
58
81
                    charm_description.get_process(),
59
 
                    'Could not find charm for service: %s' % name)
 
82
                    'Could not find charm for service.')
60
83
            else:
61
84
                found_charms[name] = Charm(charm_found)
62
85
        except ProofError, exc:
63
86
            # We could not find the charm. No other verification can
64
87
            # happen.
65
 
            bundle_info['services'][name] = []
 
88
            bundle_info['services'].setdefault(name, [])
66
89
            bundle_info['services'][name].append({
67
90
                'message': exc.msg,
68
91
                'debug_info': exc.debug_info,
69
92
            })
70
 
    return found_charms, bundle_info
 
93
    return found_charms
71
94
 
72
95
 
73
96
def _proof_bundles_relations(bundle_info, bundle_config, charms):
79
102
    - Verify that if no specific interfaces are defined, we can find at least
80
103
      one common interface between the two services.
81
104
 
 
105
    Modifies the bundle_info dictionary in place.
82
106
    """
83
107
    deployment = CharmworldDeployment(bundle_config)
84
108
    relations = deployment.get_relations()
85
109
 
86
110
    if not relations:
87
 
        return bundle_info
 
111
        return
88
112
 
89
113
    # Begin the actual interface checks.
90
114
    for e_a, e_b in relations:
99
123
                bundle_info['relations'].append({
100
124
                    'message': ('Invalid relation to charm: %s not found.'
101
125
                                % ep.service),
102
 
                    'debug_info': []
 
126
                    'debug_info': [],
103
127
                })
104
128
                found = False
105
129
 
149
173
                    if not found and not common:
150
174
                        msg = "Invalid relation requested: {}:{}".format(
151
175
                            check_requires if check_requires else "",
152
 
                            check_provides if check_provides else ""
 
176
                            check_provides if check_provides else "",
153
177
                        )
154
178
                    else:
155
179
                        msg = "Invalid relation on interface: {}".format(
156
 
                            interface_name
 
180
                            interface_name,
157
181
                        )
158
182
 
159
183
                    bundle_info['relations'].append({
160
184
                        'message': msg,
161
 
                        'debug_info': [repr(EndpointPair(e_a, e_b))]
 
185
                        'debug_info': [repr(EndpointPair(e_a, e_b))],
162
186
                    })
163
187
 
164
188
            else:
166
190
                # sure we can find at least one common interface between them.
167
191
                common_interfaces = get_list_of_shared_interfaces(
168
192
                    charms[endpoints[0].service],
169
 
                    charms[endpoints[1].service]
 
193
                    charms[endpoints[1].service],
170
194
                )
171
195
 
172
196
                if len(common_interfaces) == 0:
176
200
                        'message': msg % (
177
201
                            endpoints[0].service,
178
202
                            endpoints[1].service),
179
 
                        'debug_info': [repr(EndpointPair(e_a, e_b))]
 
203
                        'debug_info': [repr(EndpointPair(e_a, e_b))],
180
204
                    })
181
205
 
182
 
    return bundle_info
183
 
 
184
 
 
185
 
def _proof_charm_config(bundle_info, bundle_config, charm_name, charm):
 
206
 
 
207
def _proof_charm_config(bundle_info, charm_data, charm_name, charm):
186
208
    """Given a charm, proof the config is valid for the charm found.
187
209
 
188
210
    If the deployer attempts to set illegal or non-existent config values to
189
211
    the charm treat these as proof errors.
 
212
 
 
213
    Modifies the bundle_info dictionary in place.
190
214
    """
191
 
    check_config = bundle_config['services'][charm_name].get('options')
192
 
    if not check_config:
 
215
    options = charm_data.get('options')
 
216
    if options is None:
193
217
        # There's no config specified to validate.
194
 
        return bundle_info
195
 
    for test_key, test_value in check_config.iteritems():
 
218
        return
 
219
    for test_key, test_value in options.iteritems():
196
220
        try:
197
221
            CharmProof.check_config(charm, test_key, test_value)
198
222
        except ProofError, exc:
199
 
            if charm_name not in bundle_info['services']:
200
 
                bundle_info['services'][charm_name] = []
201
 
 
 
223
            bundle_info['services'].setdefault(charm_name, [])
202
224
            bundle_info['services'][charm_name].append({
203
225
                'message': exc.msg,
204
226
                'debug_info': exc.debug_info,
205
227
            })
206
 
    return bundle_info
207
 
 
208
 
 
209
 
def proof_deployer(request):
210
 
    """Check the deployer for proof errors."""
 
228
    return
 
229
 
 
230
 
 
231
def _proof_charm_constraints(bundle_info, charm_data, charm_name):
 
232
    """Check the charm constraints.
 
233
 
 
234
    Modifies the bundle_info dictionary in place.
 
235
    """
 
236
    constraints = charm_data.get('constraints')
 
237
    if constraints is None:
 
238
        return
 
239
    if not check_constraints(constraints):
 
240
        # check_constraints returns False if the constraints are invalid or if
 
241
        # they are comma-separated.  So now we need to parse them to see if
 
242
        # they are invalid.  If they are invalid *and* comma-separated we
 
243
        # cannot discern that and just report the invalid constraints.
 
244
        try:
 
245
            parse_constraints(constraints)
 
246
        except ValueError as exc:
 
247
            bundle_info['services'].setdefault(charm_name, []).append(
 
248
                dict(message=exc.message))
 
249
        else:
 
250
            # Comma-separated constraints are just a warning.
 
251
            msg = ('The constraints are comma-separated, '
 
252
                   'which is deprecated.')
 
253
            bundle_info['warnings'].setdefault(charm_name, []).append(
 
254
                dict(message=msg))
 
255
 
 
256
 
 
257
def proof_deployer_file(request):
 
258
    """Check the bundle for proof errors."""
211
259
    # The top level is for general deployer file errors.
212
260
    # The errors here may be an error about the deployer file itself, or an
213
261
    # object that describes the issues found with a bundle within the deployer
218
266
        'error_messages': [],
219
267
        'debug_info': [],
220
268
        'services_info': {},
 
269
        'warning_messages': [],
221
270
    }
222
271
    deployer_string = request.params.get('deployer_file')
223
272
    deployer_format = request.params.get('deployer_format', 'yaml')
249
298
        bundle_info = {
250
299
            'name': bundle_name,
251
300
            'services': {},
 
301
            'warnings': {},
252
302
            'relations': [],
253
303
        }
254
304
 
255
305
        # First, verify that we can locate the charms specified in this bundle
256
306
        # in the charmworld database.
257
 
        found_charms, bundle_info = _proof_bundles_charms_exist(
 
307
        found_charms = _proof_bundles_charms_exist(
258
308
            request.db,
259
309
            bundle_info,
260
310
            bundle_config.get('services'),
264
314
        # For the charms we did find in the system, verify that their
265
315
        # config is valid for the values allowed by the charm we found.
266
316
        for name, charm in found_charms.iteritems():
267
 
            bundle_info = _proof_charm_config(
268
 
                bundle_info, bundle_config, name, charm)
 
317
            charm_data = bundle_config['services'][name]
 
318
            _proof_charm_config(
 
319
                bundle_info, charm_data, name, charm)
 
320
 
 
321
            _proof_charm_constraints(
 
322
                bundle_info, charm_data, name)
269
323
            response_data['services_info'][name] = {
270
324
                'series': charm.series,
271
325
                'name': charm.name,
274
328
 
275
329
        # Verify that the relations specified in the bundle are valid for the
276
330
        # interfaces exposed on the charms that were found.
277
 
        bundle_info = _proof_bundles_relations(
 
331
        _proof_bundles_relations(
278
332
            bundle_info, bundle_config, found_charms)
279
333
 
280
 
        # If there are errors in this bundles services or relation, make sure
 
334
        # If there are errors in this bundle's services or relation, make sure
281
335
        # we append the info to the response data that gets sent back to the
282
336
        # user.
283
 
        if bundle_info['services'].keys() or bundle_info['relations']:
 
337
        if (bundle_info['services'] or
 
338
            bundle_info['relations'] or
 
339
            bundle_info['warnings']):
284
340
            response_data['errors'].append(bundle_info)
285
341
 
286
342
    # After proofing each deployer file return with any failure info
287
343
    # found.
288
 
    return json_response(200, _build_proof_response(response_data))
 
344
    _build_proof_response(response_data)
 
345
    return json_response(200, response_data)