Enforce the way we encapsulate a Validation
This patch ensures that the playbook is respecting the minimal structure of a validation. This patch also adds DocStrings to the Validation class and new unittests according to the enforcement changes. Change-Id: I0809163cbd661cbd24705ed348281c7818a944b4 Signed-off-by: Gael Chamoulaud (Strider) <gchamoul@redhat.com>
This commit is contained in:
parent
a6271afa38
commit
eb62054a33
|
@ -174,6 +174,18 @@ VALIDATIONS_DATA = {'Description': 'My Validation One Description',
|
|||
VALIDATIONS_STATS = {'Last execution date': '2019-11-25 13:40:14',
|
||||
'Number of execution': 'Total: 1, Passed: 0, Failed: 1'}
|
||||
|
||||
FAKE_WRONG_PLAYBOOK = [{
|
||||
'hosts': 'undercloud',
|
||||
'roles': ['advanced_format_512e_support'],
|
||||
'vars': {
|
||||
'nometadata': {
|
||||
'description': 'foo',
|
||||
'groups': ['prep', 'pre-deployment'],
|
||||
'name': 'Advanced Format 512e Support'
|
||||
}
|
||||
}
|
||||
}]
|
||||
|
||||
FAKE_PLAYBOOK = [{'hosts': 'undercloud',
|
||||
'roles': ['advanced_format_512e_support'],
|
||||
'vars': {'metadata': {'description': 'foo',
|
||||
|
@ -189,6 +201,14 @@ FAKE_PLAYBOOK2 = [{'hosts': 'undercloud',
|
|||
'Advanced Format 512e Support'},
|
||||
'foo': 'bar'}}]
|
||||
|
||||
FAKE_PLAYBOOK3 = [{'hosts': 'undercloud',
|
||||
'roles': ['advanced_format_512e_support'],
|
||||
'vars': {'metadata': {'description': 'foo',
|
||||
'name':
|
||||
'Advanced Format 512e Support'},
|
||||
'foo': 'bar'}}]
|
||||
|
||||
FAKE_VARS = {'foo': 'bar'}
|
||||
|
||||
FAKE_METADATA = {'id': 'foo',
|
||||
'description': 'foo',
|
||||
|
|
|
@ -42,6 +42,36 @@ class TestValidation(TestCase):
|
|||
data = val.get_metadata
|
||||
self.assertEquals(data, fakes.FAKE_METADATA)
|
||||
|
||||
@mock.patch('yaml.safe_load', return_value=fakes.FAKE_WRONG_PLAYBOOK)
|
||||
@mock.patch('six.moves.builtins.open')
|
||||
def test_get_metadata_wrong_playbook(self, mock_open, mock_yaml):
|
||||
with self.assertRaises(NameError) as exc_mgr:
|
||||
Validation('/tmp/foo').get_metadata
|
||||
self.assertEqual('No metadata found in validation foo',
|
||||
str(exc_mgr.exception))
|
||||
|
||||
@mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK2)
|
||||
@mock.patch('six.moves.builtins.open')
|
||||
def test_get_vars(self, mock_open, mock_yaml):
|
||||
val = Validation('/tmp/foo')
|
||||
data = val.get_vars
|
||||
self.assertEquals(data, fakes.FAKE_VARS)
|
||||
|
||||
@mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK)
|
||||
@mock.patch('six.moves.builtins.open')
|
||||
def test_get_vars_no_vars(self, mock_open, mock_yaml):
|
||||
val = Validation('/tmp/foo')
|
||||
data = val.get_vars
|
||||
self.assertEquals(data, {})
|
||||
|
||||
@mock.patch('yaml.safe_load', return_value=fakes.FAKE_WRONG_PLAYBOOK)
|
||||
@mock.patch('six.moves.builtins.open')
|
||||
def test_get_vars_no_metadata(self, mock_open, mock_yaml):
|
||||
with self.assertRaises(NameError) as exc_mgr:
|
||||
Validation('/tmp/foo').get_vars
|
||||
self.assertEqual('No metadata found in validation foo',
|
||||
str(exc_mgr.exception))
|
||||
|
||||
@mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK)
|
||||
@mock.patch('six.moves.builtins.open')
|
||||
def test_get_id(self, mock_open, mock_yaml):
|
||||
|
@ -58,6 +88,21 @@ class TestValidation(TestCase):
|
|||
groups = val.groups
|
||||
self.assertEquals(groups, ['prep', 'pre-deployment'])
|
||||
|
||||
@mock.patch('yaml.safe_load', return_value=fakes.FAKE_WRONG_PLAYBOOK)
|
||||
@mock.patch('six.moves.builtins.open')
|
||||
def test_groups_with_no_metadata(self, mock_open, mock_yaml):
|
||||
with self.assertRaises(NameError) as exc_mgr:
|
||||
Validation('/tmp/foo').groups
|
||||
self.assertEqual('No metadata found in validation foo',
|
||||
str(exc_mgr.exception))
|
||||
|
||||
@mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK3)
|
||||
@mock.patch('six.moves.builtins.open')
|
||||
def test_groups_with_no_existing_groups(self, mock_open, mock_yaml):
|
||||
val = Validation('/tmp/foo')
|
||||
groups = val.groups
|
||||
self.assertEquals(groups, [])
|
||||
|
||||
@mock.patch('yaml.safe_load', return_value=fakes.FAKE_PLAYBOOK)
|
||||
@mock.patch('six.moves.builtins.open')
|
||||
def test_get_ordered_dict(self, mock_open, mock_yaml):
|
||||
|
@ -72,6 +117,14 @@ class TestValidation(TestCase):
|
|||
data = val.get_formated_data
|
||||
self.assertEquals(data, fakes.FORMATED_DATA)
|
||||
|
||||
@mock.patch('yaml.safe_load', return_value=fakes.FAKE_WRONG_PLAYBOOK)
|
||||
@mock.patch('six.moves.builtins.open')
|
||||
def test_get_formated_data_no_metadata(self, mock_open, mock_yaml):
|
||||
with self.assertRaises(NameError) as exc_mgr:
|
||||
Validation('/tmp/foo').get_formated_data
|
||||
self.assertEqual('No metadata found in validation foo',
|
||||
str(exc_mgr.exception))
|
||||
|
||||
@mock.patch('six.moves.builtins.open')
|
||||
def test_validation_not_found(self, mock_open):
|
||||
mock_open.side_effect = IOError()
|
||||
|
|
|
@ -22,6 +22,52 @@ LOG = logging.getLogger(__name__ + ".validation")
|
|||
|
||||
|
||||
class Validation(object):
|
||||
"""An object for encapsulating a validation
|
||||
|
||||
Each validation is an `Ansible` playbook. Each playbook have some
|
||||
``metadata``. Here is what a minimal validation would look like:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
- hosts: webserver
|
||||
vars:
|
||||
metadata:
|
||||
name: Hello World
|
||||
description: This validation prints Hello World!
|
||||
roles:
|
||||
- hello-world
|
||||
|
||||
As shown here, the validation playbook requires three top-level
|
||||
directives:
|
||||
|
||||
``hosts``, ``vars -> metadata`` and ``roles``
|
||||
|
||||
``hosts`` specify which nodes to run the validation on.
|
||||
|
||||
The ``vars`` section serves for storing variables that are going to be
|
||||
available to the `Ansible` playbook. The validations API uses the
|
||||
``metadata`` section to read validation's name and description. These
|
||||
values are then reported by the API.
|
||||
|
||||
The validations can be grouped together by specifying a ``groups``
|
||||
metadata. Groups function similar to tags and a validation can thus be part
|
||||
of many groups. Here is, for example, how to have a validation be part of
|
||||
the `pre-deployment` and `hardware` groups.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
- hosts: webserver
|
||||
vars:
|
||||
metadata:
|
||||
name: Hello World
|
||||
description: This validation prints Hello World!
|
||||
groups:
|
||||
- pre-deployment
|
||||
- hardware
|
||||
roles:
|
||||
- hello-world
|
||||
|
||||
"""
|
||||
|
||||
_col_keys = ['ID', 'Name', 'Description', 'Groups']
|
||||
|
||||
|
@ -36,48 +82,211 @@ class Validation(object):
|
|||
except IOError:
|
||||
raise IOError("Validation playbook not found")
|
||||
|
||||
@property
|
||||
def has_vars_dict(self):
|
||||
"""Check the presence of the vars dictionary
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
- hosts: webserver
|
||||
vars: <====
|
||||
metadata:
|
||||
name: hello world
|
||||
description: this validation prints hello world!
|
||||
groups:
|
||||
- pre-deployment
|
||||
- hardware
|
||||
roles:
|
||||
- hello-world
|
||||
|
||||
:return: `true` if `vars` is found, `false` if not.
|
||||
:rtype: `boolean`
|
||||
"""
|
||||
return 'vars' in self.dict.keys()
|
||||
|
||||
@property
|
||||
def has_metadata_dict(self):
|
||||
"""Check the presence of the metadata dictionary
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
- hosts: webserver
|
||||
vars:
|
||||
metadata: <====
|
||||
name: hello world
|
||||
description: this validation prints hello world!
|
||||
groups:
|
||||
- pre-deployment
|
||||
- hardware
|
||||
roles:
|
||||
- hello-world
|
||||
|
||||
:return: `true` if `vars` and metadata are found, `false` if not.
|
||||
:rtype: `boolean`
|
||||
"""
|
||||
return self.has_vars_dict and 'metadata' in self.dict['vars'].keys()
|
||||
|
||||
@property
|
||||
def get_metadata(self):
|
||||
if self.dict['vars'].get('metadata'):
|
||||
"""Get the metadata of a validation
|
||||
|
||||
:return: The validation metadata
|
||||
:rtype: `dict` or `None` if no metadata has been found
|
||||
:raise: A `NameError` exception if no metadata has been found in the
|
||||
playbook
|
||||
|
||||
:Example:
|
||||
|
||||
>>> pl = '/foo/bar/val1.yaml'
|
||||
>>> val = Validation(pl)
|
||||
>>> print(val.get_metadata)
|
||||
{'description': 'Val1 desc.',
|
||||
'groups': ['group1', 'group2'],
|
||||
'id': 'val1',
|
||||
'name': 'The validation val1\'s name'}
|
||||
"""
|
||||
if self.has_metadata_dict:
|
||||
self.metadata = {'id': self.id}
|
||||
self.metadata.update(self.dict['vars'].get('metadata'))
|
||||
return self.metadata
|
||||
return self.metadata
|
||||
else:
|
||||
raise NameError(
|
||||
"No metadata found in validation {}".format(self.id)
|
||||
)
|
||||
|
||||
@property
|
||||
def get_vars(self):
|
||||
vars = self.dict['vars'].copy()
|
||||
if vars.get('metadata'):
|
||||
"""Get only the variables of a validation
|
||||
|
||||
:return: All the variables belonging to a validation
|
||||
:rtype: `dict` or `None` if no metadata has been found
|
||||
:raise: A `NameError` exception if no metadata has been found in the
|
||||
playbook
|
||||
|
||||
:Example:
|
||||
|
||||
>>> pl = '/foo/bar/val.yaml'
|
||||
>>> val = Validation(pl)
|
||||
>>> print(val.get_vars)
|
||||
{'var_name1': 'value1',
|
||||
'var_name2': 'value2'}
|
||||
"""
|
||||
if self.has_metadata_dict:
|
||||
vars = self.dict['vars'].copy()
|
||||
vars.pop('metadata')
|
||||
return vars
|
||||
return vars
|
||||
else:
|
||||
raise NameError(
|
||||
"No metadata found in validation {}".format(self.id)
|
||||
)
|
||||
|
||||
@property
|
||||
def get_data(self):
|
||||
"""Get the full contents of a validation playbook
|
||||
|
||||
:return: The full content of the playbook
|
||||
:rtype: `dict`
|
||||
|
||||
:Example:
|
||||
|
||||
>>> pl = '/foo/bar/val.yaml'
|
||||
>>> val = Validation(pl)
|
||||
>>> print(val.get_data)
|
||||
{'gather_facts': True,
|
||||
'hosts': 'all',
|
||||
'roles': ['val_role'],
|
||||
'vars': {'metadata': {'description': 'description of val ',
|
||||
'groups': ['group1', 'group2'],
|
||||
'name': 'validation one'},
|
||||
'var_name1': 'value1'}}
|
||||
"""
|
||||
return self.dict
|
||||
|
||||
@property
|
||||
def groups(self):
|
||||
return self.dict['vars']['metadata'].get('groups')
|
||||
"""Get the validation list of groups
|
||||
|
||||
:return: A list of groups for the validation
|
||||
:rtype: `list` or `None` if no metadata has been found
|
||||
:raise: A `NameError` exception if no metadata has been found in the
|
||||
playbook
|
||||
|
||||
:Example:
|
||||
|
||||
>>> pl = '/foo/bar/val.yaml'
|
||||
>>> val = Validation(pl)
|
||||
>>> print(val.groups)
|
||||
['group1', 'group2']
|
||||
"""
|
||||
if self.has_metadata_dict:
|
||||
groups = self.dict['vars']['metadata'].get('groups')
|
||||
if groups:
|
||||
return groups
|
||||
else:
|
||||
return []
|
||||
else:
|
||||
raise NameError(
|
||||
"No metadata found in validation {}".format(self.id)
|
||||
)
|
||||
|
||||
@property
|
||||
def get_id(self):
|
||||
"""Get the validation id
|
||||
|
||||
:return: The validation id
|
||||
:rtype: `string`
|
||||
|
||||
:Example:
|
||||
|
||||
>>> pl = '/foo/bar/check-cpu.yaml'
|
||||
>>> val = Validation(pl)
|
||||
>>> print(val.id)
|
||||
'check-cpu'
|
||||
"""
|
||||
return self.id
|
||||
|
||||
@property
|
||||
def get_ordered_dict(self):
|
||||
"""Get the full ordered content of a validation
|
||||
|
||||
:return: An `OrderedDict` with the full data of a validation
|
||||
:rtype: `OrderedDict`
|
||||
"""
|
||||
data = OrderedDict()
|
||||
data.update(self.dict)
|
||||
return data
|
||||
|
||||
@property
|
||||
def get_formated_data(self):
|
||||
"""Get basic information from a validation for output display
|
||||
|
||||
:return: Basic information of a validation including the `Description`,
|
||||
the list of `Groups`, the `ID` and the `Name`.
|
||||
:rtype: `dict`
|
||||
:raise: A `NameError` exception if no metadata has been found in the
|
||||
playbook
|
||||
|
||||
:Example:
|
||||
|
||||
>>> pl = '/foo/bar/val.yaml'
|
||||
>>> val = Validation(pl)
|
||||
>>> print(val.get_data)
|
||||
{'Description': 'description of val',
|
||||
'Groups': ['group1', 'group2'],
|
||||
'ID': 'val',
|
||||
'Name': 'validation one'}
|
||||
"""
|
||||
data = {}
|
||||
for key in self.get_metadata.keys():
|
||||
if key in map(str.lower, self._col_keys):
|
||||
for k in self._col_keys:
|
||||
if key == k.lower():
|
||||
output_key = k
|
||||
data[output_key] = self.get_metadata.get(key)
|
||||
else:
|
||||
# Get all other values:
|
||||
data[key] = self.get_metadata.get(key)
|
||||
metadata = self.get_metadata
|
||||
if metadata:
|
||||
for key in metadata.keys():
|
||||
if key in map(str.lower, self._col_keys):
|
||||
for k in self._col_keys:
|
||||
if key == k.lower():
|
||||
output_key = k
|
||||
data[output_key] = self.get_metadata.get(key)
|
||||
else:
|
||||
# Get all other values:
|
||||
data[key] = self.get_metadata.get(key)
|
||||
|
||||
return data
|
||||
|
|
Loading…
Reference in New Issue