diff --git a/promenade/control/validatedesign.py b/promenade/control/validatedesign.py index bb729a7c..3e378c78 100644 --- a/promenade/control/validatedesign.py +++ b/promenade/control/validatedesign.py @@ -11,44 +11,19 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -import json import logging -import falcon - from promenade.config import Configuration from promenade.control import base from promenade import exceptions from promenade import policy +from promenade.utils.validation_message import ValidationMessage from promenade import validation LOG = logging.getLogger(__name__) class ValidateDesignResource(base.BaseResource): - def _return_msg(self, resp, result): - if result['err_count'] == 0: - message = "Promenade validations succeeded." - status_code = falcon.HTTP_200 - status = "Success" - else: - message = "Promenade validations failed." - status_code = falcon.HTTP_400 - status = "Failure" - resp.body = json.dumps({ - "kind": "Status", - "apiVersion": "v1", - "metadata": {}, - "status": status, - "message": message, - "reason": "Validation", - "details": { - "errorCount": result['err_count'], - "messageList": result['msg'], - }, - "code": status_code, - }) - @policy.ApiEnforcer('kubernetes_provisioner:post_validatedesign') def on_post(self, req, resp): @@ -58,9 +33,12 @@ class ValidateDesignResource(base.BaseResource): config = Configuration.from_design_ref( href, allow_missing_substitutions=False) result = validation.check_design(config) - except exceptions.InvalidFormatError as e: - msg = "Invalid JSON Format: %s" % str(e) - result = {'msg': [msg], 'err_count': 1} - except exceptions.DeckhandException as e: - result = {'msg': [str(e)], 'err_count': 1} - return self._return_msg(resp, result) + except (exceptions.InvalidFormatError, + exceptions.DeckhandException) as e: + if isinstance(e, exceptions.InvalidFormatError): + msg = "Invalid JSON Format: %s" % str(e) + else: + msg = str(e) + result = ValidationMessage() + result.add_error_message(msg, name=e.title) + return result.get_output() diff --git a/promenade/utils/__init__.py b/promenade/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/promenade/utils/validation_message.py b/promenade/utils/validation_message.py new file mode 100644 index 00000000..a3fad97d --- /dev/null +++ b/promenade/utils/validation_message.py @@ -0,0 +1,89 @@ +# Copyright 2018 AT&T Intellectual Property. All other rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import falcon +import json + + +class ValidationMessage(object): + """ ValidationMessage per UCP convention: + https://github.com/att-comdev/ucp-integration/blob/master/docs/source/api-conventions.rst#output-structure # noqa + + Construction of ValidationMessage message: + + :param string message: Validation failure message. + :param boolean error: True or False, if this is an error message. + :param string name: Identifying name of the validation. + :param string level: The severity of validation result, as "Error", + "Warning", or "Info" + :param string schema: The schema of the document being validated. + :param string doc_name: The name of the document being validated. + :param string diagnostic: Information about what lead to the message, + or details for resolution. + """ + + def __init__(self): + self.error_count = 0 + self.details = {'errorCount': 0, 'messageList': []} + self.output = { + 'kind': 'Status', + 'apiVersion': 'v1.0', + 'metadata': {}, + 'reason': 'Validation', + 'details': self.details, + } + + def add_error_message(self, + msg, + name=None, + schema=None, + doc_name=None, + diagnostic=None): + new_error = { + 'message': msg, + 'error': True, + 'name': name, + 'documents': [], + 'level': "Error", + 'diagnostic': diagnostic, + 'kind': 'ValidationMessage' + } + if schema and doc_name: + self.output['documents'].append(dict(schema=schema, name=doc_name)) + self.details['errorCount'] += 1 + self.details['messageList'].append(new_error) + + def get_output(self, code=falcon.HTTP_400): + """ Return ValidationMessage message. + + :returns: The ValidationMessage for the Validation API response. + :rtype: dict + """ + if self.details['errorCount'] != 0: + self.output['code'] = code + self.output['message'] = 'Promenade validations failed' + self.output['status'] = 'Failure' + else: + self.output['code'] = falcon.HTTP_200 + self.output['message'] = 'Promenade validations succeeded' + self.output['status'] = 'Success' + return self.output + + def get_output_json(self): + """ Return ValidationMessage message as JSON. + + :returns: The ValidationMessage formatted in JSON, for logging. + :rtype: json + """ + return json.dumps(self.output, indent=2) diff --git a/promenade/validation.py b/promenade/validation.py index d2ad3e33..9454488d 100644 --- a/promenade/validation.py +++ b/promenade/validation.py @@ -14,42 +14,41 @@ from promenade import exceptions from promenade import logging -import copy +from promenade.utils.validation_message import ValidationMessage import jsonschema import os import pkg_resources import yaml __all__ = ['check_schema', 'check_schemas'] -result_template = {'msg': [], 'err_count': 0} LOG = logging.getLogger(__name__) def check_design(config): kinds = ['Docker', 'HostSystem', 'Kubelet', 'KubernetesNetwork'] - result = copy.deepcopy(result_template) + validation_msg = ValidationMessage() for kind in kinds: count = 0 for doc in config.documents: schema = doc.get('schema', None) if not schema: - result['msg'].append( - str( - exceptions.ValidationException( - '"schema" is a required document key.'))) - result['err_count'] += 1 - return result + msg = '"schema" is a required document key.' + validation_msg.add_error_message( + msg, name=exceptions.ValidationException(msg)) + return validation_msg name = schema.split('/')[1] if name == kind: count += 1 if count != 1: msg = ('There are {0} {1} documents. However, there should be one.' ).format(count, kind) - result['msg'].append( - str(exceptions.ValidationException(description=msg))) - result['err_count'] += 1 - return result + validation_msg.add_error_message( + msg, + name=exceptions.ValidationException(msg), + schema=schema, + doc_name=kind) + return validation_msg def check_schemas(documents, schemas=None):