From ceecdadbf46bef8c910aa251deb2f90555d4942f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Andr=C3=A9?= Date: Wed, 10 Aug 2016 14:48:35 +0200 Subject: [PATCH] Workflows to load validations This commit adds workflow to list available validations and validation groups. Change-Id: I85fb6c1939e711887b2ee91c9cefa41a3da681a3 --- setup.cfg | 2 + tripleo_common/actions/validations.py | 23 ++++ tripleo_common/constants.py | 4 + .../tests/actions/test_validations.py | 33 +++++ .../tests/utils/test_validations.py | 128 +++++++++++++++++- tripleo_common/utils/validations.py | 52 +++++++ workbooks/validations.yaml | 14 ++ 7 files changed, 255 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 7de1d4b6c..df3439e63 100644 --- a/setup.cfg +++ b/setup.cfg @@ -72,3 +72,5 @@ mistral.actions = tripleo.reset_parameters = tripleo_common.actions.parameters:ResetParametersAction tripleo.deploy = tripleo_common.actions.deployment:DeployStackAction tripleo.validations.get_pubkey = tripleo_common.actions.validations:GetPubkeyAction + tripleo.validations.list_validations = tripleo_common.actions.validations:ListValidationsAction + tripleo.validations.list_groups = tripleo_common.actions.validations:ListGroupsAction diff --git a/tripleo_common/actions/validations.py b/tripleo_common/actions/validations.py index c16ac5d81..0cdc52118 100644 --- a/tripleo_common/actions/validations.py +++ b/tripleo_common/actions/validations.py @@ -53,3 +53,26 @@ class GetPubkeyAction(base.TripleOAction): mc.environments.create(**workflow_env) return public_key + + +class ListValidationsAction(base.TripleOAction): + """Return a set of TripleO validations""" + def __init__(self, groups=None): + super(ListValidationsAction, self).__init__() + self.groups = groups + + def run(self): + return utils.load_validations(groups=self.groups) + + +class ListGroupsAction(base.TripleOAction): + """Return a set of TripleO validation groups""" + def __init__(self): + super(ListGroupsAction, self).__init__() + + def run(self): + validations = utils.load_validations() + return { + group for validation in validations + for group in validation['groups'] + } diff --git a/tripleo_common/constants.py b/tripleo_common/constants.py index ef9ed684d..8a39f9e12 100644 --- a/tripleo_common/constants.py +++ b/tripleo_common/constants.py @@ -32,6 +32,10 @@ DEFAULT_CONTAINER_NAME = 'overcloud' #: The path to the tripleo heat templates installed on the undercloud DEFAULT_TEMPLATES_PATH = '/usr/share/openstack-tripleo-heat-templates/' +# The path to the tripleo validations installed on the undercloud +DEFAULT_VALIDATIONS_PATH = \ + '/usr/share/openstack-tripleo-validations/validations/' + # TRIPLEO_META_USAGE_KEY is inserted into metadata for containers created in # Swift via SwiftPlanStorageBackend to identify them from other containers TRIPLEO_META_USAGE_KEY = 'x-container-meta-usage-tripleo' diff --git a/tripleo_common/tests/actions/test_validations.py b/tripleo_common/tests/actions/test_validations.py index 34045924a..59f212337 100644 --- a/tripleo_common/tests/actions/test_validations.py +++ b/tripleo_common/tests/actions/test_validations.py @@ -17,6 +17,7 @@ import mock from tripleo_common.actions import validations from tripleo_common.tests import base +from tripleo_common.tests.utils import test_validations class GetPubkeyActionTest(base.TestCase): @@ -55,3 +56,35 @@ class GetPubkeyActionTest(base.TestCase): mock_mkdtemp.assert_called_once() mock_create_keypair.assert_called_once_with('/tmp_path/id_rsa') mock_rmtree.asser_called_once_with('/tmp_path') + + +class ListValidationsActionTest(base.TestCase): + + @mock.patch('tripleo_common.utils.validations.load_validations') + def test_run_default(self, mock_load_validations): + mock_load_validations.return_value = 'list of validations' + action = validations.ListValidationsAction() + self.assertEqual('list of validations', action.run()) + mock_load_validations.assert_called_once_with(groups=None) + + @mock.patch('tripleo_common.utils.validations.load_validations') + def test_run_groups(self, mock_load_validations): + mock_load_validations.return_value = 'list of validations' + action = validations.ListValidationsAction(groups=['group1', + 'group2']) + self.assertEqual('list of validations', action.run()) + mock_load_validations.assert_called_once_with(groups=['group1', + 'group2']) + + +class ListGroupsActionTest(base.TestCase): + + @mock.patch('tripleo_common.utils.validations.load_validations') + def test_run(self, mock_load_validations): + mock_load_validations.return_value = [ + test_validations.VALIDATION_GROUPS_1_2_PARSED, + test_validations.VALIDATION_GROUP_1_PARSED, + test_validations.VALIDATION_WITH_METADATA_PARSED] + action = validations.ListGroupsAction() + self.assertEqual(set(['group1', 'group2']), action.run()) + mock_load_validations.assert_called_once_with() diff --git a/tripleo_common/tests/utils/test_validations.py b/tripleo_common/tests/utils/test_validations.py index 4069c8d19..471c0cc96 100644 --- a/tripleo_common/tests/utils/test_validations.py +++ b/tripleo_common/tests/utils/test_validations.py @@ -14,12 +14,78 @@ # limitations under the License. import mock +import yaml from tripleo_common.tests import base from tripleo_common.utils import validations -class ValidationsTest(base.TestCase): +VALIDATION_GROUP_1 = """--- +- hosts: overcloud + vars: + metadata: + name: First validation + description: A validation belonging to group1 + groups: + - group1 + tasks: + - name: Ping the nodes + ping: +""" + +VALIDATION_WITH_METADATA = """--- +- hosts: undercloud + vars: + metadata: + name: Validation with metadata + description: A validation with extra metadata + foo: a foo metadata + bar: 42 + tasks: + - name: Do something useful + watch_tv: +""" + +VALIDATION_GROUPS_1_2 = """--- +- hosts: undercloud + vars: + metadata: + name: Validation from many groups + description: A validation belonging to groups 1 and 2 + groups: + - group1 + - group2 + tasks: + - name: Do something useful + watch_tv: +""" + +VALIDATION_GROUP_1_PARSED = { + 'description': 'A validation belonging to group1', + 'groups': ['group1'], + 'id': 'VALIDATION_GROUP_1', + 'metadata': {}, + 'name': 'First validation', +} + +VALIDATION_WITH_METADATA_PARSED = { + 'description': 'A validation with extra metadata', + 'groups': [], + 'id': 'VALIDATION_WITH_METADATA', + 'metadata': {'foo': 'a foo metadata', 'bar': 42}, + 'name': 'Validation with metadata', +} + +VALIDATION_GROUPS_1_2_PARSED = { + 'description': 'A validation belonging to groups 1 and 2', + 'groups': ['group1', 'group2'], + 'id': 'VALIDATION_GROUPS_1_2', + 'metadata': {}, + 'name': 'Validation from many groups', +} + + +class ValidationsKeyTest(base.TestCase): @mock.patch("oslo_concurrency.processutils.execute") def test_create_ssh_keypair(self, mock_execute): @@ -27,3 +93,63 @@ class ValidationsTest(base.TestCase): mock_execute.assert_called_once_with( '/usr/bin/ssh-keygen', '-t', 'rsa', '-N', '', '-f', '/path/to/key', '-C', 'tripleo-validations') + + +class LoadValidationsTest(base.TestCase): + + def test_get_validation_metadata(self): + validation = yaml.safe_load(VALIDATION_GROUP_1) + value = validations.get_validation_metadata(validation, 'name') + self.assertEqual('First validation', value) + + @mock.patch('tripleo_common.utils.validations.DEFAULT_METADATA') + def test_get_validation_metadata_default_value(self, mock_metadata): + mock_metadata.get.return_value = 'default_value' + value = validations.get_validation_metadata({}, 'missing') + self.assertEqual('default_value', value) + + def test_get_remaining_metadata(self): + validation = yaml.safe_load(VALIDATION_WITH_METADATA) + value = validations.get_remaining_metadata(validation) + expected = { + 'foo': 'a foo metadata', + 'bar': 42 + } + self.assertEqual(expected, value) + + def test_get_remaining_metadata_no_extra(self): + validation = yaml.safe_load(VALIDATION_GROUP_1) + value = validations.get_remaining_metadata(validation) + self.assertEqual({}, value) + + @mock.patch('glob.glob') + def test_load_validations_no_group(self, mock_glob): + mock_glob.return_value = ['VALIDATION_GROUP_1', + 'VALIDATION_WITH_METADATA'] + mock_open_context = mock.mock_open() + mock_open_context().read.side_effect = [VALIDATION_GROUP_1, + VALIDATION_WITH_METADATA] + + with mock.patch('tripleo_common.utils.validations.open', + mock_open_context): + my_validations = validations.load_validations() + + expected = [VALIDATION_GROUP_1_PARSED, VALIDATION_WITH_METADATA_PARSED] + self.assertEqual(expected, my_validations) + + @mock.patch('glob.glob') + def test_load_validations_group(self, mock_glob): + mock_glob.return_value = ['VALIDATION_GROUPS_1_2', + 'VALIDATION_GROUP_1', + 'VALIDATION_WITH_METADATA'] + mock_open_context = mock.mock_open() + mock_open_context().read.side_effect = [VALIDATION_GROUPS_1_2, + VALIDATION_GROUP_1, + VALIDATION_WITH_METADATA] + + with mock.patch('tripleo_common.utils.validations.open', + mock_open_context): + my_validations = validations.load_validations(groups=['group1']) + + expected = [VALIDATION_GROUPS_1_2_PARSED, VALIDATION_GROUP_1_PARSED] + self.assertEqual(expected, my_validations) diff --git a/tripleo_common/utils/validations.py b/tripleo_common/utils/validations.py index a4287d4f4..edfa8a46f 100644 --- a/tripleo_common/utils/validations.py +++ b/tripleo_common/utils/validations.py @@ -12,12 +12,64 @@ # 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 glob import logging +import os +import yaml from oslo_concurrency import processutils +from tripleo_common import constants + LOG = logging.getLogger(__name__) +DEFAULT_METADATA = { + 'name': 'Unnamed', + 'description': 'No description', + 'stage': 'No stage', + 'groups': [], +} + + +def get_validation_metadata(validation, key): + try: + return validation[0]['vars']['metadata'][key] + except KeyError: + return DEFAULT_METADATA.get(key) + except TypeError: + LOG.exception("Failed to get validation metadata.") + + +def load_validations(groups=None): + '''Loads all validations.''' + paths = glob.glob('{}/*.yaml'.format(constants.DEFAULT_VALIDATIONS_PATH)) + results = [] + for validation_path in sorted(paths): + with open(validation_path) as f: + validation = yaml.safe_load(f.read()) + validation_groups = get_validation_metadata(validation, 'groups') \ + or [] + if not groups or \ + set.intersection(set(groups), set(validation_groups)): + results.append({ + 'id': os.path.splitext( + os.path.basename(validation_path))[0], + 'name': get_validation_metadata(validation, 'name'), + 'groups': get_validation_metadata(validation, 'groups'), + 'description': get_validation_metadata(validation, + 'description'), + 'metadata': get_remaining_metadata(validation) + }) + return results + + +def get_remaining_metadata(validation): + try: + return {k: v for k, v in validation[0]['vars']['metadata'].items() + if k not in ['name', 'description', 'groups']} + except KeyError: + return dict() + def create_ssh_keypair(key_path): """Create SSH keypair""" diff --git a/workbooks/validations.yaml b/workbooks/validations.yaml index c0fe67806..245ad56a8 100644 --- a/workbooks/validations.yaml +++ b/workbooks/validations.yaml @@ -5,6 +5,20 @@ description: TripleO Validations Workflows v1 workflows: + list: + type: direct + input: + - group_names: [] + tasks: + find_validations: + action: tripleo.validations.list_validations groups=<% $.group_names %> + + list_groups: + type: direct + tasks: + find_groups: + action: tripleo.validations.list_groups + copy_ssh_key: type: direct input: