diff --git a/releasenotes/notes/ironic-boot-config-77addfde192cee0f.yaml b/releasenotes/notes/ironic-boot-config-77addfde192cee0f.yaml new file mode 100644 index 000000000..167818e88 --- /dev/null +++ b/releasenotes/notes/ironic-boot-config-77addfde192cee0f.yaml @@ -0,0 +1,4 @@ +--- +features: + - Adds an action and workflow used to check the ironic boot + configuration. diff --git a/setup.cfg b/setup.cfg index ce3e442fe..97cf0b0c1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -97,6 +97,7 @@ mistral.actions = tripleo.templates.upload = tripleo_common.actions.templates:UploadTemplatesAction tripleo.validations.check_boot_images = tripleo_common.actions.validations:CheckBootImagesAction tripleo.validations.check_flavors = tripleo_common.actions.validations:CheckFlavorsAction + tripleo.validations.check_node_boot_configuration = tripleo_common.actions.validations:CheckNodeBootConfigurationAction tripleo.validations.get_pubkey = tripleo_common.actions.validations:GetPubkeyAction tripleo.validations.enabled = tripleo_common.actions.validations:Enabled tripleo.validations.list_groups = tripleo_common.actions.validations:ListGroupsAction diff --git a/tripleo_common/actions/validations.py b/tripleo_common/actions/validations.py index 49b18a9a5..4c2c52982 100644 --- a/tripleo_common/actions/validations.py +++ b/tripleo_common/actions/validations.py @@ -18,6 +18,7 @@ from oslo_concurrency.processutils import ProcessExecutionError from tripleo_common.actions import base from tripleo_common import constants +from tripleo_common.utils import nodes as nodeutils from tripleo_common.utils import passwords as password_utils from tripleo_common.utils import validations as utils @@ -240,3 +241,57 @@ class CheckFlavorsAction(base.TripleOAction): mistral_result = {'data': return_value} return mistral_workflow_utils.Result(**mistral_result) + + +class CheckNodeBootConfigurationAction(base.TripleOAction): + """Check the boot configuration of the baremetal nodes""" + + # TODO(bcrochet): The validation actions are temporary. This logic should + # move to the tripleo-validations project eventually. + def __init__(self, node, kernel_id, ramdisk_id): + super(CheckNodeBootConfigurationAction, self).__init__() + + self.node = node + self.kernel_id = kernel_id + self.ramdisk_id = ramdisk_id + + def run(self): + warnings = [] + errors = [] + message = ("Node {uuid} has an incorrectly configured " + "{property}. Expected \"{expected}\" but got " + "\"{actual}\".") + if self.node['driver_info'].get('deploy_ramdisk') != self.ramdisk_id: + errors.append(message.format( + uuid=self.node['uuid'], + property='driver_info/deploy_ramdisk', + expected=self.ramdisk_id, + actual=self.node['driver_info'].get('deploy_ramdisk') + )) + if self.node['driver_info'].get('deploy_kernel') != self.kernel_id: + errors.append(message.format( + uuid=self.node['uuid'], + property='driver_info/deploy_kernel', + expected=self.kernel_id, + actual=self.node['driver_info'].get('deploy_kernel') + )) + capabilities = nodeutils.capabilities_to_dict( + self.node['properties'].get('capabilities', '')) + if capabilities.get('boot_option') != 'local': + boot_option_message = ("Node {uuid} is not configured to use " + "boot_option:local in capabilities. It " + "will not be used for deployment with " + "flavors that require boot_option:local.") + + warnings.append(boot_option_message.format(uuid=self.node['uuid'])) + + return_value = { + 'errors': errors, + 'warnings': warnings + } + if errors: + mistral_result = {'error': return_value} + else: + mistral_result = {'data': return_value} + + return mistral_workflow_utils.Result(**mistral_result) diff --git a/tripleo_common/tests/actions/test_validations.py b/tripleo_common/tests/actions/test_validations.py index 419f6af3e..e55c2e854 100644 --- a/tripleo_common/tests/actions/test_validations.py +++ b/tripleo_common/tests/actions/test_validations.py @@ -380,3 +380,78 @@ class TestCheckFlavorsAction(base.TestCase): } action = validations.CheckFlavorsAction(**action_args) self.assertEqual(expected, action.run()) + + +class TestCheckNodeBootConfigurationAction(base.TestCase): + def setUp(self): + super(TestCheckNodeBootConfigurationAction, self).setUp() + self.kernel_id = '12345' + self.ramdisk_id = '67890' + self.node = { + 'uuid': '100f2cf6-06de-480e-a73e-6fdf6c9962b7', + 'driver_info': { + 'deploy_kernel': '12345', + 'deploy_ramdisk': '67890', + }, + 'properties': { + 'capabilities': 'boot_option:local', + } + } + + def test_run_success(self): + expected = mistral_workflow_utils.Result( + data={'errors': [], 'warnings': []} + ) + + action_args = { + 'node': self.node, + 'kernel_id': self.kernel_id, + 'ramdisk_id': self.ramdisk_id, + } + action = validations.CheckNodeBootConfigurationAction(**action_args) + self.assertEqual(expected, action.run()) + + def test_run_invalid_ramdisk(self): + expected = mistral_workflow_utils.Result( + error={ + 'errors': [ + 'Node 100f2cf6-06de-480e-a73e-6fdf6c9962b7 has an ' + 'incorrectly configured driver_info/deploy_ramdisk. ' + 'Expected "67890" but got "98760".' + ], + 'warnings': []}) + + node = self.node.copy() + node['driver_info']['deploy_ramdisk'] = '98760' + action_args = { + 'node': node, + 'kernel_id': self.kernel_id, + 'ramdisk_id': self.ramdisk_id, + } + action = validations.CheckNodeBootConfigurationAction(**action_args) + self.assertEqual(expected, action.run()) + + def test_no_boot_option_local(self): + expected = mistral_workflow_utils.Result( + data={ + 'errors': [], + 'warnings': [ + 'Node 100f2cf6-06de-480e-a73e-6fdf6c9962b7 is not ' + 'configured to use boot_option:local in capabilities. ' + 'It will not be used for deployment with flavors that ' + 'require boot_option:local.' + ] + } + ) + + node = self.node.copy() + node['properties']['capabilities'] = 'boot_option:not_local' + + action_args = { + 'node': node, + 'kernel_id': self.kernel_id, + 'ramdisk_id': self.ramdisk_id, + } + + action = validations.CheckNodeBootConfigurationAction(**action_args) + self.assertEqual(expected, action.run()) diff --git a/workbooks/validations.yaml b/workbooks/validations.yaml index cbb5ef83f..d009db080 100644 --- a/workbooks/validations.yaml +++ b/workbooks/validations.yaml @@ -378,3 +378,74 @@ workflows: warnings: <% $.warnings %> on-success: - fail: <% $.get('status') = "FAILED" %> + + check_ironic_boot_configuration: + input: + - kernel_id: null + - ramdisk_id: null + - run_validations: true + - queue_name: tripleo + output: + errors: <% $.errors %> + warnings: <% $.warnings %> + + tasks: + check_run_validations: + on-complete: + - get_ironic_nodes: <% $.run_validations %> + - send_message: <% not $.run_validations %> + + get_ironic_nodes: + action: ironic.node_list + on-success: check_node_boot_configuration + on-error: failed_get_ironic_nodes + input: + maintenance: false + detail: true + publish: + nodes: <% task(get_ironic_nodes).result %> + + failed_get_ironic_nodes: + on-success: send_message + publish: + status: FAILED + message: <% task(get_ironic_nodes).result %> + + check_node_boot_configuration: + action: tripleo.validations.check_node_boot_configuration + input: + node: <% $.node %> + kernel_id: <% $.kernel_id %> + ramdisk_id: <% $.ramdisk_id %> + with-items: node in <% $.nodes %> + on-success: send_message + on-error: fail_check_node_boot_configuration + publish: + errors: <% task(check_node_boot_configuration).result.errors.flatten() %> + warnings: <% task(check_node_boot_configuration).result.warnings.flatten() %> + publish-on-error: + errors: <% task(check_node_boot_configuration).result.errors.flatten() %> + warnings: <% task(check_node_boot_configuration).result.warnings.flatten() %> + + fail_check_node_boot_configuration: + on-success: send_message + publish: + status: FAILED + message: <% task(check_node_boot_configuration).result %> + + send_message: + action: zaqar.queue_post + retry: count=5 delay=1 + input: + queue_name: <% $.queue_name %> + messages: + body: + type: tripleo.validations.v1.check_ironic_boot_configuration + payload: + status: <% $.get('status', 'SUCCESS') %> + message: <% $.get('message', '') %> + execution: <% execution() %> + errors: <% $.errors %> + warnings: <% $.warnings %> + on-success: + - fail: <% $.get('status') = "FAILED" %>