Skip to content
This repository has been archived by the owner on Feb 29, 2024. It is now read-only.

Commit

Permalink
Add support for custom validations
Browse files Browse the repository at this point in the history
This patch introduces support for running custom validations by changing
the behavior of the validations actions ListValidationsAction,
ListGroupsAction and RunValidationAction.

Until now, these actions sourced validations from a directory on disk.
Now, these action are sourcing validations from the plan container
subdirectory (custom validations), or, if this is not available, from
the Swift container holding the default validations.

Change-Id: I9e9131b355312c53f12d154976d5d9cd706cc338
Implements: blueprint custom-validations
Depends-On: I338e139fa770ebb7bdcc1c0afb79eec062fada8b
  • Loading branch information
infraredgirl committed Aug 10, 2018
1 parent 0e009ec commit 8f88e78
Show file tree
Hide file tree
Showing 7 changed files with 274 additions and 79 deletions.
29 changes: 25 additions & 4 deletions tripleo_common/actions/validations.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@
# 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 six

from mistral_lib import actions
from mistralclient.api import base as mistralclient_api
from oslo_concurrency.processutils import ProcessExecutionError
from swiftclient import exceptions as swiftexceptions

from tripleo_common.actions import base
from tripleo_common import constants
Expand Down Expand Up @@ -82,19 +85,34 @@ def run(self, context):

class ListValidationsAction(base.TripleOAction):
"""Return a set of TripleO validations"""
def __init__(self, groups=None):
def __init__(self, plan=constants.DEFAULT_CONTAINER_NAME, groups=None):
super(ListValidationsAction, self).__init__()
self.groups = groups
self.plan = plan

def run(self, context):
return utils.load_validations(groups=self.groups)
swift = self.get_object_client(context)
try:
return utils.load_validations(
swift, plan=self.plan, groups=self.groups)
except swiftexceptions.ClientException as err:
msg = "Error loading validations from Swift: %s" % err
return actions.Result(error={"msg": six.text_type(msg)})


class ListGroupsAction(base.TripleOAction):
"""Return a set of TripleO validation groups"""
def __init__(self, plan=constants.DEFAULT_CONTAINER_NAME):
super(ListGroupsAction, self).__init__()
self.plan = plan

def run(self, context):
validations = utils.load_validations()
swift = self.get_object_client(context)
try:
validations = utils.load_validations(swift, plan=self.plan)
except swiftexceptions.ClientException as err:
msg = "Error loading validations from Swift: %s" % err
return actions.Result(error={"msg": six.text_type(msg)})
return {
group for validation in validations
for group in validation['groups']
Expand All @@ -110,13 +128,16 @@ def __init__(self, validation, plan=constants.DEFAULT_CONTAINER_NAME):

def run(self, context):
mc = self.get_workflow_client(context)
swift = self.get_object_client(context)

identity_file = None
try:
env = mc.environments.get('ssh_keys')
private_key = env.variables['private_key']
identity_file = utils.write_identity_file(private_key)

stdout, stderr = utils.run_validation(self.validation,
stdout, stderr = utils.run_validation(swift,
self.validation,
identity_file,
self.plan,
context)
Expand Down
3 changes: 3 additions & 0 deletions tripleo_common/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@
#: The default name to use for the container for validations
VALIDATIONS_CONTAINER_NAME = 'tripleo-validations'

#: The name of the plan subdirectory that holds custom validations
CUSTOM_VALIDATIONS_FOLDER = 'custom-validations'

#: The default key to use for updating parameters in plan environment.
DEFAULT_PLAN_ENV_KEY = 'parameter_defaults'

Expand Down
53 changes: 39 additions & 14 deletions tripleo_common/tests/actions/test_validations.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,54 +113,73 @@ def test_success_with_validations_disabled(self, get_workflow_client_mock,
class ListValidationsActionTest(base.TestCase):

@mock.patch('tripleo_common.utils.validations.load_validations')
def test_run_default(self, mock_load_validations):
@mock.patch('tripleo_common.actions.base.TripleOAction.get_object_client')
def test_run_default(self, mock_get_object_client, mock_load_validations):
mock_ctx = mock.MagicMock()
swiftclient = mock.MagicMock(url='http://swift:8080/v1/AUTH_test')
mock_get_object_client.return_value = swiftclient
mock_load_validations.return_value = 'list of validations'
action = validations.ListValidationsAction()

action = validations.ListValidationsAction(plan='overcloud')
self.assertEqual('list of validations', action.run(mock_ctx))
mock_load_validations.assert_called_once_with(groups=None)
mock_load_validations.assert_called_once_with(
mock_get_object_client(), plan='overcloud', groups=None)

@mock.patch('tripleo_common.utils.validations.load_validations')
def test_run_groups(self, mock_load_validations):
@mock.patch('tripleo_common.actions.base.TripleOAction.get_object_client')
def test_run_groups(self, mock_get_object_client, mock_load_validations):
mock_ctx = mock.MagicMock()
swiftclient = mock.MagicMock(url='http://swift:8080/v1/AUTH_test')
mock_get_object_client.return_value = swiftclient
mock_load_validations.return_value = 'list of validations'
action = validations.ListValidationsAction(groups=['group1',
'group2'])

action = validations.ListValidationsAction(
plan='overcloud', groups=['group1', 'group2'])
self.assertEqual('list of validations', action.run(mock_ctx))
mock_load_validations.assert_called_once_with(groups=['group1',
'group2'])
mock_load_validations.assert_called_once_with(
mock_get_object_client(), plan='overcloud',
groups=['group1', 'group2'])


class ListGroupsActionTest(base.TestCase):

@mock.patch('tripleo_common.utils.validations.load_validations')
def test_run(self, mock_load_validations):
@mock.patch('tripleo_common.actions.base.TripleOAction.get_object_client')
def test_run(self, mock_get_object_client, mock_load_validations):
mock_ctx = mock.MagicMock()
swiftclient = mock.MagicMock(url='http://swift:8080/v1/AUTH_test')
mock_get_object_client.return_value = swiftclient
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_ctx))
mock_load_validations.assert_called_once_with()

action = validations.ListGroupsAction(plan='overcloud')
self.assertEqual({'group1', 'group2'}, action.run(mock_ctx))
mock_load_validations.assert_called_once_with(
mock_get_object_client(), plan='overcloud')


class RunValidationActionTest(base.TestCase):

@mock.patch(
'tripleo_common.actions.base.TripleOAction.get_workflow_client')
@mock.patch('tripleo_common.actions.base.TripleOAction.get_object_client')
@mock.patch('tripleo_common.utils.validations.write_identity_file')
@mock.patch('tripleo_common.utils.validations.cleanup_identity_file')
@mock.patch('tripleo_common.utils.validations.run_validation')
def test_run(self, mock_run_validation, mock_cleanup_identity_file,
mock_write_identity_file, get_workflow_client_mock):
mock_write_identity_file, mock_get_object_client,
get_workflow_client_mock):
mock_ctx = mock.MagicMock()
mistral = mock.MagicMock()
get_workflow_client_mock.return_value = mistral
environment = collections.namedtuple('environment', ['variables'])
mistral.environments.get.return_value = environment(variables={
'private_key': 'shhhh'
})
swiftclient = mock.MagicMock(url='http://swift:8080/v1/AUTH_test')
mock_get_object_client.return_value = swiftclient
mock_write_identity_file.return_value = 'identity_file_path'
mock_run_validation.return_value = 'output', 'error'
action = validations.RunValidationAction('validation')
Expand All @@ -173,6 +192,7 @@ def test_run(self, mock_run_validation, mock_cleanup_identity_file,
self.assertEqual(expected, action.run(mock_ctx))
mock_write_identity_file.assert_called_once_with('shhhh')
mock_run_validation.assert_called_once_with(
mock_get_object_client(),
'validation',
'identity_file_path',
constants.DEFAULT_CONTAINER_NAME,
Expand All @@ -182,18 +202,22 @@ def test_run(self, mock_run_validation, mock_cleanup_identity_file,

@mock.patch(
'tripleo_common.actions.base.TripleOAction.get_workflow_client')
@mock.patch('tripleo_common.actions.base.TripleOAction.get_object_client')
@mock.patch('tripleo_common.utils.validations.write_identity_file')
@mock.patch('tripleo_common.utils.validations.cleanup_identity_file')
@mock.patch('tripleo_common.utils.validations.run_validation')
def test_run_failing(self, mock_run_validation, mock_cleanup_identity_file,
mock_write_identity_file, get_workflow_client_mock):
mock_write_identity_file, mock_get_object_client,
get_workflow_client_mock):
mock_ctx = mock.MagicMock()
mistral = mock.MagicMock()
get_workflow_client_mock.return_value = mistral
environment = collections.namedtuple('environment', ['variables'])
mistral.environments.get.return_value = environment(variables={
'private_key': 'shhhh'
})
swiftclient = mock.MagicMock(url='http://swift:8080/v1/AUTH_test')
mock_get_object_client.return_value = swiftclient
mock_write_identity_file.return_value = 'identity_file_path'
mock_run_validation.side_effect = ProcessExecutionError(
stdout='output', stderr='error')
Expand All @@ -207,6 +231,7 @@ def test_run_failing(self, mock_run_validation, mock_cleanup_identity_file,
self.assertEqual(expected, action.run(mock_ctx))
mock_write_identity_file.assert_called_once_with('shhhh')
mock_run_validation.assert_called_once_with(
mock_get_object_client(),
'validation',
'identity_file_path',
constants.DEFAULT_CONTAINER_NAME,
Expand Down
116 changes: 89 additions & 27 deletions tripleo_common/tests/utils/test_validations.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,28 @@
from tripleo_common.utils import validations


VALIDATION_DEFAULT = """---
- hosts: overcloud
vars:
metadata:
name: First validation
description: Default validation
tasks:
- name: Ping the nodes
ping:
"""

VALIDATION_CUSTOM = """---
- hosts: overcloud
vars:
metadata:
name: First validation
description: Custom validation
tasks:
- name: Ping the nodes
ping:
"""

VALIDATION_GROUP_1 = """---
- hosts: overcloud
vars:
Expand Down Expand Up @@ -138,45 +160,83 @@ def test_get_remaining_metadata_no_extra(self):
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]
@mock.patch('tripleo_common.actions.base.TripleOAction.get_object_client')
def test_load_validations_no_group(self, mock_get_object_client):
swiftclient = mock.MagicMock(url='http://swift:8080/v1/AUTH_test')
swiftclient.get_container.side_effect = (
({}, []), # no custom validations
({},
[{'name': 'VALIDATION_GROUP_1.yaml', 'groups': ['group1']},
{'name': 'VALIDATION_WITH_METADATA.yaml'}]))
swiftclient.get_object.side_effect = (
({}, VALIDATION_GROUP_1),
({}, VALIDATION_WITH_METADATA),
)
mock_get_object_client.return_value = swiftclient

with mock.patch('tripleo_common.utils.validations.open',
mock_open_context):
my_validations = validations.load_validations()
my_validations = validations.load_validations(
mock_get_object_client(), plan='overcloud')

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]
@mock.patch('tripleo_common.actions.base.TripleOAction.get_object_client')
def test_load_validations_group(self, mock_get_object_client):
swiftclient = mock.MagicMock(url='http://swift:8080/v1/AUTH_test')
swiftclient.get_container.side_effect = (
({}, []), # no custom validations
({},
[
{'name': 'VALIDATION_GROUPS_1_2.yaml',
'groups': ['group1', 'group2']},
{'name': 'VALIDATION_GROUP_1.yaml', 'groups': ['group1']},
{'name': 'VALIDATION_WITH_METADATA.yaml'}
]
)
)
swiftclient.get_object.side_effect = (
({}, VALIDATION_GROUPS_1_2),
({}, VALIDATION_GROUP_1),
({}, VALIDATION_WITH_METADATA),
)
mock_get_object_client.return_value = swiftclient

with mock.patch('tripleo_common.utils.validations.open',
mock_open_context):
my_validations = validations.load_validations(groups=['group1'])
my_validations = validations.load_validations(
mock_get_object_client(), plan='overcloud', groups=['group1'])

expected = [VALIDATION_GROUPS_1_2_PARSED, VALIDATION_GROUP_1_PARSED]
self.assertEqual(expected, my_validations)

@mock.patch('tripleo_common.actions.base.TripleOAction.get_object_client')
def test_load_validations_custom_gets_picked_over_default(
self, mock_get_object_client):
swiftclient = mock.MagicMock(url='http://swift:8080/v1/AUTH_test')
swiftclient.get_container.side_effect = (
({}, [{'name': 'FIRST_VALIDATION.yaml'}]),
({}, [{'name': 'FIRST_VALIDATION.yaml'}])
)
swiftclient.get_object.side_effect = (
({}, VALIDATION_CUSTOM),
({}, VALIDATION_DEFAULT)
)
mock_get_object_client.return_value = swiftclient

my_validations = validations.load_validations(
mock_get_object_client(), plan='overcloud')

self.assertEqual(len(my_validations), 1)
self.assertEqual('Custom validation', my_validations[0]['description'])


class RunValidationTest(base.TestCase):

@mock.patch('tripleo_common.utils.validations.find_validation')
@mock.patch('tripleo_common.actions.base.TripleOAction.get_object_client')
@mock.patch('tripleo_common.utils.validations.download_validation')
@mock.patch('oslo_concurrency.processutils.execute')
def test_run_validation(self, mock_execute,
mock_find_validation):
mock_download_validation, mock_get_object_client):
swiftclient = mock.MagicMock(url='http://swift:8080/v1/AUTH_test')
mock_get_object_client.return_value = swiftclient
Ctx = namedtuple('Ctx', 'auth_uri user_name auth_token project_name')
mock_ctx = Ctx(
auth_uri='auth_uri',
Expand All @@ -185,9 +245,10 @@ def test_run_validation(self, mock_execute,
project_name='project_name'
)
mock_execute.return_value = 'output'
mock_find_validation.return_value = 'validation_path'
mock_download_validation.return_value = 'validation_path'

result = validations.run_validation('validation', 'identity_file',
result = validations.run_validation(mock_get_object_client(),
'validation', 'identity_file',
'plan', mock_ctx)
self.assertEqual('output', result)
mock_execute.assert_called_once_with(
Expand All @@ -201,7 +262,8 @@ def test_run_validation(self, mock_execute,
'identity_file',
'plan'
)
mock_find_validation.assert_called_once_with('validation')
mock_download_validation.assert_called_once_with(
mock_get_object_client(), 'plan', 'validation')


class RunPatternValidatorTest(base.TestCase):
Expand Down
Loading

0 comments on commit 8f88e78

Please sign in to comment.