fix: Add validation logic to check for duplicate documents in engine
This patch set adds validation logic to document_validation.py (in Deckhand's engine module) so that components that rely on Deckhand's engine for document rendering (such as Promenade or Pegleg) can fail fast when they provide Deckhand with a duplicate document. Must pass pre_validate=True to layering module which currently is the case for Promenade, et. al. Before this change, Deckand only supported this logic at the DB level (requiring service instantion); this is now no longer the case. Change-Id: I6d1c8214775aa0f3b5efb1049972cf847f74585b
This commit is contained in:
parent
0462b7b929
commit
2c4c5a9c63
@ -360,6 +360,31 @@ class DataSchemaValidator(GenericValidator):
|
|||||||
schema, document, error, root_path)
|
schema, document, error, root_path)
|
||||||
|
|
||||||
|
|
||||||
|
class DuplicateDocumentValidator(BaseValidator):
|
||||||
|
"""Validator used for guarding against duplicate documents."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(DuplicateDocumentValidator, self).__init__()
|
||||||
|
self._document_history = set()
|
||||||
|
self._diagnostic = ('Ensure that each raw document has a unique '
|
||||||
|
'combination of (name, schema, '
|
||||||
|
'metadata.layeringDefinition.layer).')
|
||||||
|
|
||||||
|
def validate(self, document, **kwargs):
|
||||||
|
"""Validates that duplicate document doesn't exist."""
|
||||||
|
if document.meta in self._document_history:
|
||||||
|
validation_message = vm.ValidationMessage(
|
||||||
|
message="Duplicate document exists",
|
||||||
|
doc_schema=document.schema,
|
||||||
|
doc_name=document.name,
|
||||||
|
doc_layer=document.layer,
|
||||||
|
diagnostic=self._diagnostic)
|
||||||
|
return [validation_message.format_message()]
|
||||||
|
else:
|
||||||
|
self._document_history.add(document.meta)
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
class DocumentValidation(object):
|
class DocumentValidation(object):
|
||||||
|
|
||||||
def __init__(self, documents, existing_data_schemas=None,
|
def __init__(self, documents, existing_data_schemas=None,
|
||||||
@ -425,12 +450,16 @@ class DocumentValidation(object):
|
|||||||
|
|
||||||
self._documents.append(document)
|
self._documents.append(document)
|
||||||
|
|
||||||
self._validators = [
|
|
||||||
DataSchemaValidator(self._external_data_schemas)
|
|
||||||
]
|
|
||||||
|
|
||||||
self._pre_validate = pre_validate
|
self._pre_validate = pre_validate
|
||||||
|
|
||||||
|
self._validators = [
|
||||||
|
DataSchemaValidator(self._external_data_schemas),
|
||||||
|
]
|
||||||
|
if self._pre_validate:
|
||||||
|
# Only perform this additional validation "offline". The controller
|
||||||
|
# need not call this as the db module will handle this validation.
|
||||||
|
self._validators.append(DuplicateDocumentValidator())
|
||||||
|
|
||||||
def _get_supported_schema_list(self):
|
def _get_supported_schema_list(self):
|
||||||
schema_list = []
|
schema_list = []
|
||||||
validator = self._validators[-1]
|
validator = self._validators[-1]
|
||||||
|
@ -98,6 +98,41 @@ class TestDocumentValidation(engine_test_base.TestDocumentValidationBase):
|
|||||||
str(validations[0]['errors'][-1]))
|
str(validations[0]['errors'][-1]))
|
||||||
self.assertNotIn('scary-secret.', str(validations[0]['errors'][-1]))
|
self.assertNotIn('scary-secret.', str(validations[0]['errors'][-1]))
|
||||||
|
|
||||||
|
def test_validation_document_duplication(self):
|
||||||
|
"""Validate that duplicate document fails when duplicate passed in."""
|
||||||
|
test_document = self._read_data('sample_document')
|
||||||
|
|
||||||
|
# Should only fail when pre_validate is True as the `db` module already
|
||||||
|
# handles this on behalf of the controller.
|
||||||
|
validations = document_validation.DocumentValidation(
|
||||||
|
[test_document] * 2, # Provide 2 of the same document.
|
||||||
|
pre_validate=True).validate_all()
|
||||||
|
|
||||||
|
expected_error = {
|
||||||
|
'diagnostic': mock.ANY,
|
||||||
|
'documents': [{
|
||||||
|
'layer': test_document['metadata']['layeringDefinition'][
|
||||||
|
'layer'],
|
||||||
|
'name': test_document['metadata']['name'],
|
||||||
|
'schema': test_document['schema']
|
||||||
|
}],
|
||||||
|
'error': True,
|
||||||
|
'kind': 'ValidationMessage',
|
||||||
|
'level': 'Error',
|
||||||
|
'message': 'Duplicate document exists',
|
||||||
|
'name': 'Deckhand validation error'
|
||||||
|
}
|
||||||
|
|
||||||
|
self.assertEqual(1, len(validations[1]['errors']))
|
||||||
|
self.assertEqual(expected_error,
|
||||||
|
validations[1]['errors'][0])
|
||||||
|
|
||||||
|
# With pre_validate=False the validation should skip.
|
||||||
|
validations = document_validation.DocumentValidation(
|
||||||
|
[test_document] * 2, # Provide 2 of the same document.
|
||||||
|
pre_validate=False).validate_all()
|
||||||
|
self.assertEmpty(validations[1]['errors'])
|
||||||
|
|
||||||
def test_validation_failure_sanitizes_message_secrets(self):
|
def test_validation_failure_sanitizes_message_secrets(self):
|
||||||
data_schema_factory = factories.DataSchemaFactory()
|
data_schema_factory = factories.DataSchemaFactory()
|
||||||
metadata_name = 'example/Doc/v1'
|
metadata_name = 'example/Doc/v1'
|
||||||
|
Loading…
Reference in New Issue
Block a user