Remove validations that moved to tripleo-validations

These validations are now in tripleo-validations, implemented in
Ansible. These can be removed from workflows.

Change-Id: I27121bac81c6e90004bb1b8b20b92bb2075b1303
Implements: blueprint workflow-move-validations
Depends-On: Id5c0692e3a1d3f551dcd55822de968ca8753a4eb
This commit is contained in:
Brad P. Crochet 2018-04-24 10:17:29 -04:00
parent ea44ebe886
commit 05a84782c4
4 changed files with 0 additions and 1524 deletions

View File

@ -128,17 +128,12 @@ mistral.actions =
tripleo.templates.process = tripleo_common.actions.templates:ProcessTemplatesAction
tripleo.templates.upload = tripleo_common.actions.templates:UploadTemplatesAction
tripleo.templates.upload_plan_environment = tripleo_common.actions.templates:UploadPlanEnvironmentAction
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.check_nodes_count = tripleo_common.actions.validations:CheckNodesCountAction
tripleo.validations.get_pubkey = tripleo_common.actions.validations:GetPubkeyAction
tripleo.validations.get_privkey = tripleo_common.actions.validations:GetPrivkeyAction
tripleo.validations.enabled = tripleo_common.actions.validations:Enabled
tripleo.validations.list_groups = tripleo_common.actions.validations:ListGroupsAction
tripleo.validations.list_validations = tripleo_common.actions.validations:ListValidationsAction
tripleo.validations.run_validation = tripleo_common.actions.validations:RunValidationAction
tripleo.validations.verify_profiles = tripleo_common.actions.validations:VerifyProfilesAction
tripleo.validations.upload = tripleo_common.actions.validations:UploadValidationsAction
tripleo.files.file_exists = tripleo_common.actions.files:FileExists
tripleo.files.make_temp_dir = tripleo_common.actions.files:MakeTempDir

View File

@ -18,7 +18,6 @@ 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
@ -141,347 +140,3 @@ class UploadValidationsAction(base.UploadDirectoryAction):
def __init__(self, container=constants.VALIDATIONS_CONTAINER_NAME,
dir_to_upload=constants.DEFAULT_VALIDATIONS_PATH):
super(UploadValidationsAction, self).__init__(container, dir_to_upload)
class CheckBootImagesAction(base.TripleOAction):
"""Validate boot images"""
# TODO(bcrochet): The validation actions are temporary. This logic should
# move to the tripleo-validations project eventually.
def __init__(self, images,
deploy_kernel_name=constants.DEFAULT_DEPLOY_KERNEL_NAME,
deploy_ramdisk_name=constants.DEFAULT_DEPLOY_RAMDISK_NAME):
super(CheckBootImagesAction, self).__init__()
self.images = images
self.deploy_kernel_name = deploy_kernel_name
self.deploy_ramdisk_name = deploy_ramdisk_name
def run(self, context):
messages = []
kernel_id = self._check_for_image(self.deploy_kernel_name, messages)
ramdisk_id = self._check_for_image(self.deploy_ramdisk_name, messages)
return_value = {
'kernel_id': kernel_id,
'ramdisk_id': ramdisk_id,
'errors': messages,
'warnings': []
}
if messages:
mistral_result = actions.Result(error=return_value)
else:
mistral_result = actions.Result(data=return_value)
return mistral_result
def _check_for_image(self, name, messages):
multiple_message = ("Please make sure there is only one image named "
"'{}' in glance.")
missing_message = ("No image with the name '{}' found - make sure you "
"have uploaded boot images.")
image_id = None
found_images = [item['id'] for item in self.images
if item['name'] == name]
if len(found_images) > 1:
messages.append(multiple_message.format(name))
elif len(found_images) == 0:
messages.append(missing_message.format(name))
else:
image_id = found_images[0]
return image_id
class CheckFlavorsAction(base.TripleOAction):
"""Validate and collect nova flavors in use.
Ensure that selected flavors (--ROLE-flavor) are valid in nova.
Issue a warning if local boot is not set for a flavor.
"""
# TODO(bcrochet): The validation actions are temporary. This logic should
# move to the tripleo-validations project eventually.
def __init__(self, roles_info):
super(CheckFlavorsAction, self).__init__()
self.roles_info = roles_info
def run(self, context):
"""Validate and collect nova flavors in use.
Ensure that selected flavors (--ROLE-flavor) are valid in nova.
Issue a warning if local boot is not set for a flavor.
:returns: dictionary flavor name -> (flavor object, scale)
"""
compute_client = self.get_compute_client(context)
flavors = {f.name: {'name': f.name, 'keys': f.get_keys()}
for f in compute_client.flavors.list()}
result = {}
warnings = []
errors = []
message = "Flavor '{1}' provided for the role '{0}', does not exist"
warning_message = (
'Flavor {0} "capabilities:boot_option" is set to '
'"netboot". Nodes will PXE boot from the ironic '
'conductor instead of using a local bootloader. '
'Make sure that enough nodes are marked with the '
'"boot_option" capability set to "netboot".')
for target, (flavor_name, scale) in self.roles_info.items():
if flavor_name is None or not scale:
continue
old_flavor_name, old_scale = result.get(flavor_name, (None, None))
if old_flavor_name:
result[flavor_name] = (old_flavor_name, old_scale + scale)
else:
flavor = flavors.get(flavor_name)
if flavor:
keys = flavor.get('keys', None)
if keys:
if keys.get('capabilities:boot_option', '') \
== 'netboot':
warnings.append(
warning_message.format(flavor_name))
result[flavor_name] = (flavor, scale)
else:
errors.append(message.format(target, flavor_name))
return_value = {
'flavors': result,
'errors': errors,
'warnings': warnings,
}
if errors:
mistral_result = {'error': return_value}
else:
mistral_result = {'data': return_value}
return actions.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, context):
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 actions.Result(**mistral_result)
class VerifyProfilesAction(base.TripleOAction):
"""Verify that the profiles have enough nodes"""
# TODO(bcrochet): The validation actions are temporary. This logic should
# move to the tripleo-validations project eventually.
def __init__(self, nodes, flavors):
super(VerifyProfilesAction, self).__init__()
self.nodes = nodes
self.flavors = flavors
def run(self, context):
errors = []
warnings = []
bm_nodes = {node['uuid']: node for node in self.nodes
if node['provision_state'] in ('available', 'active')}
free_node_caps = {uu: self._node_get_capabilities(node)
for uu, node in bm_nodes.items()}
profile_flavor_used = False
for flavor_name, (flavor, scale) in self.flavors.items():
if not scale:
continue
profile = None
keys = flavor.get('keys')
if keys:
profile = keys.get('capabilities:profile')
if not profile and len(self.flavors) > 1:
message = ('Error: The {flavor} flavor has no profile '
'associated.\n'
'Recommendation: assign a profile with openstack '
'flavor set --property '
'"capabilities:profile"="PROFILE_NAME" {flavor}')
errors.append(message.format(flavor=flavor_name))
continue
profile_flavor_used = True
assigned_nodes = [uu for uu, caps in free_node_caps.items()
if caps.get('profile') == profile]
required_count = scale - len(assigned_nodes)
if required_count < 0:
warnings.append('%d nodes with profile %s won\'t be used '
'for deployment now' % (-required_count,
profile))
required_count = 0
for uu in assigned_nodes:
free_node_caps.pop(uu)
if required_count > 0:
message = ('Error: only {total} of {scale} requested ironic '
'nodes are tagged to profile {profile} (for flavor '
'{flavor}).\n'
'Recommendation: tag more nodes using openstack '
'baremetal node set --property "capabilities='
'profile:{profile},boot_option:local" <NODE ID>')
errors.append(message.format(total=scale - required_count,
scale=scale,
profile=profile,
flavor=flavor_name))
nodes_without_profile = [uu for uu, caps in free_node_caps.items()
if not caps.get('profile')]
if nodes_without_profile and profile_flavor_used:
warnings.append("There are %d ironic nodes with no profile that "
"will not be used: %s" % (
len(nodes_without_profile),
', '.join(nodes_without_profile)))
return_value = {
'errors': errors,
'warnings': warnings,
}
if errors:
mistral_result = {'error': return_value}
else:
mistral_result = {'data': return_value}
return actions.Result(**mistral_result)
def _node_get_capabilities(self, node):
"""Get node capabilities."""
return nodeutils.capabilities_to_dict(
node['properties'].get('capabilities'))
class CheckNodesCountAction(base.TripleOAction):
"""Validate hypervisor statistics"""
# TODO(bcrochet): The validation actions are temporary. This logic should
# move to the tripleo-validations project eventually.
def __init__(self, statistics, stack, associated_nodes, available_nodes,
parameters, default_role_counts):
super(CheckNodesCountAction, self).__init__()
self.statistics = statistics
self.stack = stack
self.associated_nodes = associated_nodes
self.available_nodes = available_nodes
self.parameters = parameters
self.default_role_counts = default_role_counts
def run(self, context):
errors = []
warnings = []
requested_count = 0
for param, default in self.default_role_counts.items():
if self.stack:
try:
current = int(self.stack['parameters'][param])
except KeyError:
# We could be adding a new role on stack-update, so there's
# no assumption the parameter exists in the stack.
current = self.parameters.get(param, default)
requested_count += self.parameters.get(param, current)
else:
requested_count += self.parameters.get(param, default)
# We get number of nodes usable for the stack by getting already
# used (associated) nodes and number of nodes which can be used
# (not in maintenance mode).
# Assumption is that associated nodes are part of the stack (only
# one overcloud is supported).
associated = len(self.associated_nodes)
available = len(self.available_nodes)
available_count = associated + available
if requested_count > available_count:
errors.append('Not enough baremetal nodes - available: %d, '
'requested: %d' %
(available_count, requested_count))
if self.statistics['count'] < available_count:
errors.append('Only %d nodes are exposed to Nova of %d requests. '
'Check that enough nodes are in "available" state '
'with maintenance mode off.' %
(self.statistics['count'], available_count))
return_value = {
'errors': errors,
'warnings': warnings,
'result': {
'statistics': self.statistics,
'enough_nodes': True,
'requested_count': requested_count,
'available_count': available_count,
}
}
if errors:
return_value['result']['enough_nodes'] = False
mistral_result = {'error': return_value}
else:
mistral_result = {'data': return_value}
return actions.Result(**mistral_result)

View File

@ -14,7 +14,6 @@
# under the License.
import collections
import mock
from uuid import uuid4
from mistral_lib import actions
from oslo_concurrency.processutils import ProcessExecutionError
@ -23,7 +22,6 @@ from tripleo_common.actions import validations
from tripleo_common import constants
from tripleo_common.tests import base
from tripleo_common.tests.utils import test_validations
from tripleo_common.utils import nodes as nodeutils
class GetPubkeyActionTest(base.TestCase):
@ -215,667 +213,3 @@ class RunValidationActionTest(base.TestCase):
mock_ctx)
mock_cleanup_identity_file.assert_called_once_with(
'identity_file_path')
class TestCheckBootImagesAction(base.TestCase):
def setUp(self):
super(TestCheckBootImagesAction, self).setUp()
self.images = [
{'id': '67890', 'name': 'ramdisk'},
{'id': '12345', 'name': 'kernel'},
]
self.ctx = mock.MagicMock()
@mock.patch(
'tripleo_common.actions.validations.CheckBootImagesAction'
'._check_for_image')
def test_run(self, mock_check_for_image):
mock_check_for_image.side_effect = ['12345', '67890']
expected = actions.Result(
data={
'kernel_id': '12345',
'ramdisk_id': '67890',
'warnings': [],
'errors': []})
action_args = {
'images': self.images,
'deploy_kernel_name': 'kernel',
'deploy_ramdisk_name': 'ramdisk'
}
action = validations.CheckBootImagesAction(**action_args)
self.assertEqual(expected, action.run(self.ctx))
mock_check_for_image.assert_has_calls([
mock.call('kernel', []),
mock.call('ramdisk', [])
])
def test_check_for_image_success(self):
expected = '12345'
action_args = {
'images': self.images,
'deploy_kernel_name': 'kernel',
'deploy_ramdisk_name': 'ramdisk'
}
messages = mock.Mock()
action = validations.CheckBootImagesAction(**action_args)
self.assertEqual(expected, action._check_for_image('kernel', messages))
messages.assert_not_called()
def test_check_for_image_missing(self):
expected = None
deploy_kernel_name = 'missing'
action_args = {
'images': self.images,
'deploy_kernel_name': deploy_kernel_name
}
expected_message = ("No image with the name '%s' found - make sure "
"you have uploaded boot images."
% deploy_kernel_name)
messages = []
action = validations.CheckBootImagesAction(**action_args)
self.assertEqual(expected,
action._check_for_image(deploy_kernel_name, messages))
self.assertEqual(1, len(messages))
self.assertIn(expected_message, messages)
def test_check_for_image_too_many(self):
expected = None
deploy_ramdisk_name = 'toomany'
images = list(self.images)
images.append({'id': 'abcde', 'name': deploy_ramdisk_name})
images.append({'id': '45678', 'name': deploy_ramdisk_name})
action_args = {
'images': images,
'deploy_ramdisk_name': deploy_ramdisk_name
}
expected_message = ("Please make sure there is only one image named "
"'%s' in glance." % deploy_ramdisk_name)
messages = []
action = validations.CheckBootImagesAction(**action_args)
self.assertEqual(
expected, action._check_for_image(deploy_ramdisk_name, messages))
self.assertEqual(1, len(messages))
self.assertIn(expected_message, messages)
class FakeFlavor(object):
name = ''
uuid = ''
def __init__(self, name, keys={'capabilities:boot_option': 'local'}):
self.uuid = uuid4()
self.name = name
self.keys = keys
def get_keys(self):
return self.keys
class TestCheckFlavorsAction(base.TestCase):
def setUp(self):
super(TestCheckFlavorsAction, self).setUp()
self.compute = mock.MagicMock()
compute_patcher = mock.patch(
'tripleo_common.actions.base.TripleOAction.get_compute_client',
return_value=self.compute)
self.mock_compute = compute_patcher.start()
self.addCleanup(compute_patcher.stop)
self.mock_flavors = mock.Mock()
self.compute.attach_mock(self.mock_flavors, 'flavors')
self.mock_flavor_list = [
FakeFlavor('flavor1'),
FakeFlavor('flavor2',
keys={'capabilities:boot_option': 'netboot'}),
FakeFlavor('flavor3', None)
]
self.mock_flavors.attach_mock(
mock.Mock(return_value=self.mock_flavor_list), 'list')
self.ctx = mock.MagicMock()
def test_run_success(self):
roles_info = {
'role1': ('flavor1', 1),
}
expected = actions.Result(
data={
'flavors': {
'flavor1': (
{
'name': 'flavor1',
'keys': {'capabilities:boot_option': 'local'}
}, 1)
},
'warnings': [],
'errors': [],
}
)
action_args = {
'roles_info': roles_info
}
action = validations.CheckFlavorsAction(**action_args)
self.assertEqual(expected, action.run(self.ctx))
def test_run_boot_option_is_netboot(self):
roles_info = {
'role2': ('flavor2', 1),
'role3': ('flavor3', 1),
}
expected = actions.Result(
data={
'flavors': {
'flavor2': (
{
'name': 'flavor2',
'keys': {'capabilities:boot_option': 'netboot'}
}, 1),
'flavor3': (
{
'name': 'flavor3',
'keys': None
}, 1),
},
'warnings': [
('Flavor %s "capabilities:boot_option" is set to '
'"netboot". Nodes will PXE boot from the ironic '
'conductor instead of using a local bootloader. Make '
'sure that enough nodes are marked with the '
'"boot_option" capability set to "netboot".' % 'flavor2')
],
'errors': []
}
)
action_args = {
'roles_info': roles_info
}
action = validations.CheckFlavorsAction(**action_args)
result = action.run(self.ctx)
self.assertEqual(expected, result)
def test_run_flavor_does_not_exist(self):
roles_info = {
'role4': ('does_not_exist', 1),
}
expected = actions.Result(
error={
'errors': [
"Flavor '%s' provided for the role '%s', does not "
"exist" % ('does_not_exist', 'role4')
],
'warnings': [],
'flavors': {},
}
)
action_args = {
'roles_info': roles_info
}
action = validations.CheckFlavorsAction(**action_args)
self.assertEqual(expected, action.run(self.ctx))
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',
}
}
self.ctx = mock.MagicMock()
def test_run_success(self):
expected = actions.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(self.ctx))
def test_run_invalid_ramdisk(self):
expected = actions.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(self.ctx))
def test_no_boot_option_local(self):
expected = actions.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(self.ctx))
class TestVerifyProfilesAction(base.TestCase):
def setUp(self):
super(TestVerifyProfilesAction, self).setUp()
self.nodes = []
self.flavors = {name: (self._get_fake_flavor(name), 1)
for name in ('compute', 'control')}
self.ctx = mock.MagicMock()
def _get_fake_node(self, profile=None, possible_profiles=[],
provision_state='available'):
caps = {'%s_profile' % p: '1'
for p in possible_profiles}
if profile is not None:
caps['profile'] = profile
caps = nodeutils.dict_to_capabilities(caps)
return {
'uuid': str(uuid4()),
'properties': {'capabilities': caps},
'provision_state': provision_state,
}
def _get_fake_flavor(self, name, profile=''):
the_profile = profile or name
return {
'name': name,
'profile': the_profile,
'keys': {
'capabilities:boot_option': 'local',
'capabilities:profile': the_profile
}
}
def _test(self, expected_result):
action = validations.VerifyProfilesAction(self.nodes, self.flavors)
result = action.run(self.ctx)
self.assertEqual(expected_result, result)
def test_no_matching_without_scale(self):
self.flavors = {name: (object(), 0)
for name in self.flavors}
self.nodes[:] = [self._get_fake_node(profile='fake'),
self._get_fake_node(profile='fake')]
expected = actions.Result(
data={
'errors': [],
'warnings': [],
})
self._test(expected)
def test_exact_match(self):
self.nodes[:] = [self._get_fake_node(profile='compute'),
self._get_fake_node(profile='control')]
expected = actions.Result(
data={
'errors': [],
'warnings': [],
})
self._test(expected)
def test_nodes_with_no_profiles_present(self):
self.nodes[:] = [self._get_fake_node(profile='compute'),
self._get_fake_node(profile=None),
self._get_fake_node(profile='foobar'),
self._get_fake_node(profile='control')]
expected = actions.Result(
data={
'warnings': [
'There are 1 ironic nodes with no profile that will not '
'be used: %s' % self.nodes[1].get('uuid')
],
'errors': [],
})
self._test(expected)
def test_more_nodes_with_profiles_present(self):
self.nodes[:] = [self._get_fake_node(profile='compute'),
self._get_fake_node(profile='compute'),
self._get_fake_node(profile='compute'),
self._get_fake_node(profile='control')]
expected = actions.Result(
data={
'warnings': ["2 nodes with profile compute won't be used for "
"deployment now"],
'errors': [],
})
self._test(expected)
def test_no_nodes(self):
# One error per each flavor
expected = actions.Result(
error={'errors': ['Error: only 0 of 1 requested ironic nodes are '
'tagged to profile compute (for flavor '
'compute)\n'
'Recommendation: tag more nodes using openstack '
'baremetal node set --property '
'"capabilities=profile:compute,'
'boot_option:local" <NODE ID>',
'Error: only 0 of 1 requested ironic nodes are '
'tagged to profile control (for flavor '
'control).\n'
'Recommendation: tag more nodes using openstack '
'baremetal node set --property '
'"capabilities=profile:control,'
'boot_option:local" <NODE ID>'],
'warnings': []})
action = validations.VerifyProfilesAction(self.nodes, self.flavors)
result = action.run(self.ctx)
self.assertEqual(expected.error['errors'].sort(),
result.error['errors'].sort())
self.assertEqual(expected.error['warnings'], result.error['warnings'])
self.assertIsNone(result.data)
def test_not_enough_nodes(self):
self.nodes[:] = [self._get_fake_node(profile='compute')]
expected = actions.Result(
error={'errors': ['Error: only 0 of 1 requested ironic nodes are '
'tagged to profile control (for flavor '
'control).\n'
'Recommendation: tag more nodes using openstack '
'baremetal node set --property '
'"capabilities=profile:control,'
'boot_option:local" <NODE ID>'],
'warnings': []})
self._test(expected)
def test_scale(self):
# active nodes with assigned profiles are fine
self.nodes[:] = [self._get_fake_node(profile='compute',
provision_state='active'),
self._get_fake_node(profile='control')]
expected = actions.Result(
data={
'errors': [],
'warnings': [],
}
)
self._test(expected)
def test_assign_profiles_wrong_state(self):
# active nodes are not considered for assigning profiles
self.nodes[:] = [self._get_fake_node(possible_profiles=['compute'],
provision_state='active'),
self._get_fake_node(possible_profiles=['control'],
provision_state='cleaning'),
self._get_fake_node(profile='compute',
provision_state='error')]
expected = actions.Result(
error={
'warnings': [
'There are 1 ironic nodes with no profile that will not '
'be used: %s' % self.nodes[0].get('uuid')
],
'errors': [
'Error: only 0 of 1 requested ironic nodes are tagged to '
'profile control (for flavor control).\n'
'Recommendation: tag more nodes using openstack baremetal '
'node set --property "capabilities=profile:control,'
'boot_option:local" <NODE ID>',
'Error: only 0 of 1 requested ironic nodes are tagged to '
'profile compute (for flavor compute).\n'
'Recommendation: tag more nodes using openstack baremetal '
'node set --property "capabilities=profile:compute,'
'boot_option:local" <NODE ID>'
]
})
action = validations.VerifyProfilesAction(self.nodes, self.flavors)
result = action.run(self.ctx)
self.assertEqual(expected.error['errors'].sort(),
result.error['errors'].sort())
self.assertEqual(expected.error['warnings'], result.error['warnings'])
self.assertIsNone(result.data)
def test_no_spurious_warnings(self):
self.nodes[:] = [self._get_fake_node(profile=None)]
self.flavors = {'baremetal': (
self._get_fake_flavor('baremetal', None), 1)}
expected = actions.Result(
error={
'warnings': [
'There are 1 ironic nodes with no profile that will not '
'be used: %s' % self.nodes[0].get('uuid')
],
'errors': [
'Error: only 0 of 1 requested ironic nodes are tagged to '
'profile baremetal (for flavor baremetal).\n'
'Recommendation: tag more nodes using openstack baremetal '
'node set --property "capabilities=profile:baremetal,'
'boot_option:local" <NODE ID>'
]
})
self._test(expected)
class TestCheckNodesCountAction(base.TestCase):
def setUp(self):
super(TestCheckNodesCountAction, self).setUp()
self.defaults = {
'ControllerCount': 1,
'ComputeCount': 1,
'ObjectStorageCount': 0,
'BlockStorageCount': 0,
'CephStorageCount': 0,
}
self.stack = None
self.action_args = {
'stack': None,
'associated_nodes': self._ironic_node_list(True, False),
'available_nodes': self._ironic_node_list(False, True),
'parameters': {},
'default_role_counts': self.defaults,
'statistics': {'count': 3, 'memory_mb': 1, 'vcpus': 1},
}
self.ctx = mock.MagicMock()
def _ironic_node_list(self, associated, maintenance):
if associated:
nodes = range(2)
elif maintenance:
nodes = range(1)
return nodes
def test_run_check_hypervisor_stats(self):
action_args = self.action_args.copy()
action = validations.CheckNodesCountAction(**action_args)
result = action.run(self.ctx)
expected = actions.Result(
data={
'result': {
'requested_count': 2,
'available_count': 3,
'statistics': {'count': 3, 'vcpus': 1, 'memory_mb': 1},
'enough_nodes': True
},
'errors': [],
'warnings': [],
})
self.assertEqual(expected, result)
def test_run_check_hypervisor_stats_not_met(self):
statistics = {'count': 0, 'memory_mb': 0, 'vcpus': 0}
action_args = self.action_args.copy()
action_args.update({'statistics': statistics})
action = validations.CheckNodesCountAction(**action_args)
result = action.run(self.ctx)
expected = actions.Result(
error={
'errors': [
'Only 0 nodes are exposed to Nova of 3 requests. Check '
'that enough nodes are in "available" state with '
'maintenance mode off.'],
'warnings': [],
'result': {
'statistics': statistics,
'enough_nodes': False,
'requested_count': 2,
'available_count': 3,
}
})
self.assertEqual(expected, result)
def test_check_nodes_count_deploy_enough_nodes(self):
action_args = self.action_args.copy()
action_args['parameters'] = {'ControllerCount': 2}
action = validations.CheckNodesCountAction(**action_args)
result = action.run(self.ctx)
expected = actions.Result(
data={
'errors': [],
'warnings': [],
'result': {
'enough_nodes': True,
'requested_count': 3,
'available_count': 3,
'statistics': {'count': 3, 'memory_mb': 1, 'vcpus': 1}
}
})
self.assertEqual(expected, result)
def test_check_nodes_count_deploy_too_much(self):
action_args = self.action_args.copy()
action_args['parameters'] = {'ControllerCount': 3}
action = validations.CheckNodesCountAction(**action_args)
result = action.run(self.ctx)
expected = actions.Result(
error={
'errors': [
"Not enough baremetal nodes - available: 3, requested: 4"],
'warnings': [],
'result': {
'enough_nodes': False,
'requested_count': 4,
'available_count': 3,
'statistics': {'count': 3, 'memory_mb': 1, 'vcpus': 1}
}
})
self.assertEqual(expected, result)
def test_check_nodes_count_scale_enough_nodes(self):
action_args = self.action_args.copy()
action_args['parameters'] = {'ControllerCount': 2}
action_args['stack'] = {'parameters': self.defaults.copy()}
action = validations.CheckNodesCountAction(**action_args)
result = action.run(self.ctx)
expected = actions.Result(
data={
'errors': [],
'warnings': [],
'result': {
'enough_nodes': True,
'requested_count': 3,
'available_count': 3,
'statistics': {'count': 3, 'memory_mb': 1, 'vcpus': 1}
},
})
self.assertEqual(expected, result)
def test_check_nodes_count_scale_too_much(self):
action_args = self.action_args.copy()
action_args['parameters'] = {'ControllerCount': 3}
action_args['stack'] = {'parameters': self.defaults.copy()}
action = validations.CheckNodesCountAction(**action_args)
result = action.run(self.ctx)
expected = actions.Result(
error={
'errors': [
'Not enough baremetal nodes - available: 3, requested: 4'],
'warnings': [],
'result': {
'enough_nodes': False,
'requested_count': 4,
'available_count': 3,
'statistics': {'count': 3, 'memory_mb': 1, 'vcpus': 1}
}
})
self.assertEqual(expected, result)
def test_check_default_param_not_in_stack(self):
missing_param = 'CephStorageCount'
action_args = self.action_args.copy()
action_args['parameters'] = {'ControllerCount': 3}
action_args['stack'] = {'parameters': self.defaults.copy()}
del action_args['stack']['parameters'][missing_param]
action = validations.CheckNodesCountAction(**action_args)
result = action.run(self.ctx)
expected = actions.Result(
error={
'errors': [
'Not enough baremetal nodes - available: 3, requested: 4'],
'warnings': [],
'result': {
'enough_nodes': False,
'requested_count': 4,
'available_count': 3,
'statistics': {'count': 3, 'memory_mb': 1, 'vcpus': 1}
}
})
self.assertEqual(expected, result)

View File

@ -275,511 +275,3 @@ workflows:
config_name: copy_ssh_key
group: script
queue_name: <% $.queue_name %>
check_boot_images:
input:
- deploy_kernel_name: 'bm-deploy-kernel'
- deploy_ramdisk_name: 'bm-deploy-ramdisk'
- run_validations: true
- queue_name: tripleo
output:
errors: <% $.errors %>
warnings: <% $.warnings %>
kernel_id: <% $.kernel_id %>
ramdisk_id: <% $.ramdisk_id %>
tags:
- tripleo-common-managed
tasks:
check_run_validations:
on-complete:
- get_images: <% $.run_validations %>
- send_message: <% not $.run_validations %>
get_images:
action: glance.images_list
on-success: check_images
publish:
images: <% task().result %>
check_images:
action: tripleo.validations.check_boot_images
input:
images: <% $.images %>
deploy_kernel_name: <% $.deploy_kernel_name %>
deploy_ramdisk_name: <% $.deploy_ramdisk_name %>
on-success: send_message
publish:
kernel_id: <% task().result.kernel_id %>
ramdisk_id: <% task().result.ramdisk_id %>
warnings: <% task().result.warnings %>
errors: <% task().result.errors %>
on-error: send_message
publish-on-error:
kernel_id: <% task().result.kernel_id %>
ramdisk_id: <% task().result.ramdisk_id %>
warnings: <% task().result.warnings %>
errors: <% task().result.errors %>
status: FAILED
message: <% task().result %>
send_message:
workflow: tripleo.messaging.v1.send
input:
queue_name: <% $.queue_name %>
type: <% execution().name %>
status: <% $.get('status', 'SUCCESS') %>
message: <% $.get('message', '') %>
execution: <% execution() %>
payload:
kernel_id: <% $.kernel_id %>
ramdisk_id: <% $.ramdisk_id %>
errors: <% $.errors %>
warnings: <% $.warnings %>
collect_flavors:
input:
- roles_info: {}
- run_validations: true
- queue_name: tripleo
output:
errors: <% $.errors %>
warnings: <% $.warnings %>
flavors: <% $.flavors %>
tags:
- tripleo-common-managed
tasks:
check_run_validations:
on-complete:
- check_flavors: <% $.run_validations %>
- send_message: <% not $.run_validations %>
check_flavors:
action: tripleo.validations.check_flavors
input:
roles_info: <% $.roles_info %>
on-success: send_message
publish:
flavors: <% task().result.flavors %>
errors: <% task().result.errors %>
warnings: <% task().result.warnings %>
on-error: send_message
publish-on-error:
flavors: {}
errors: <% task().result.errors %>
warnings: <% task().result.warnings %>
status: FAILED
message: <% task().result %>
send_message:
workflow: tripleo.messaging.v1.send
input:
queue_name: <% $.queue_name %>
type: <% execution().name %>
status: <% $.get('status', 'SUCCESS') %>
message: <% $.get('message', '') %>
execution: <% execution() %>
payload:
flavors: <% $.flavors %>
errors: <% $.errors %>
warnings: <% $.warnings %>
check_ironic_boot_configuration:
input:
- kernel_id: null
- ramdisk_id: null
- run_validations: true
- queue_name: tripleo
output:
errors: <% $.errors %>
warnings: <% $.warnings %>
tags:
- tripleo-common-managed
tasks:
check_run_validations:
on-complete:
- get_ironic_nodes: <% $.run_validations %>
- send_message: <% not $.run_validations %>
get_ironic_nodes:
action: ironic.node_list
input:
provision_state: available
maintenance: false
detail: true
on-success: check_node_boot_configuration
publish:
nodes: <% task().result %>
on-error: send_message
publish-on-error:
status: FAILED
message: <% task().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
publish:
errors: <% task().result.errors.flatten() %>
warnings: <% task().result.warnings.flatten() %>
on-error: send_message
publish-on-error:
errors: <% task().result.errors.flatten() %>
warnings: <% task().result.warnings.flatten() %>
status: FAILED
message: <% task().result %>
send_message:
workflow: tripleo.messaging.v1.send
input:
queue_name: <% $.queue_name %>
type: <% execution().name %>
status: <% $.get('status', 'SUCCESS') %>
message: <% $.get('message', '') %>
execution: <% execution() %>
payload:
errors: <% $.errors %>
warnings: <% $.warnings %>
verify_profiles:
input:
- flavors: []
- run_validations: true
- queue_name: tripleo
output:
errors: <% $.errors %>
warnings: <% $.warnings %>
tags:
- tripleo-common-managed
tasks:
check_run_validations:
on-complete:
- get_ironic_nodes: <% $.run_validations %>
- send_message: <% not $.run_validations %>
get_ironic_nodes:
action: ironic.node_list
input:
maintenance: false
detail: true
on-success: verify_profiles
publish:
nodes: <% task().result %>
on-error: send_message
publish-on-error:
status: FAILED
message: <% task().result %>
verify_profiles:
action: tripleo.validations.verify_profiles
input:
nodes: <% $.nodes %>
flavors: <% $.flavors %>
on-success: send_message
publish:
errors: <% task().result.errors %>
warnings: <% task().result.warnings %>
on-error: send_message
publish-on-error:
errors: <% task().result.errors %>
warnings: <% task().result.warnings %>
status: FAILED
message: <% task().result %>
send_message:
workflow: tripleo.messaging.v1.send
input:
queue_name: <% $.queue_name %>
type: <% execution().name %>
status: <% $.get('status', 'SUCCESS') %>
message: <% $.get('message', '') %>
execution: <% execution() %>
payload:
errors: <% $.errors %>
warnings: <% $.warnings %>
check_default_nodes_count:
input:
- stack_id: overcloud
- parameters: {}
- default_role_counts: {}
- run_validations: true
- queue_name: tripleo
output:
statistics: <% $.statistics %>
errors: <% $.errors %>
warnings: <% $.warnings %>
tags:
- tripleo-common-managed
tasks:
check_run_validations:
on-complete:
- get_hypervisor_statistics: <% $.run_validations %>
- send_message: <% not $.run_validations %>
get_hypervisor_statistics:
action: nova.hypervisors_statistics
on-success: get_stack
publish:
statistics: <% task().result %>
on-error: send_message
publish-on-error:
status: FAILED
message: <% task().result %>
errors: []
warnings: []
statistics: null
get_stack:
action: heat.stacks_get
input:
stack_id: <% $.stack_id %>
resolve_outputs: false
on-success: get_associated_nodes
publish:
stack: <% task().result %>
on-error: get_associated_nodes
publish-on-error:
stack: null
get_associated_nodes:
action: ironic.node_list
input:
associated: true
on-success: get_available_nodes
publish:
associated_nodes: <% task().result %>
on-error: send_message
publish-on-error:
status: FAILED
message: <% task().result %>
errors: []
warnings: []
get_available_nodes:
action: ironic.node_list
input:
provision_state: available
associated: false
maintenance: false
on-success: check_nodes_count
publish:
available_nodes: <% task().result %>
on-error: send_message
publish-on-error:
status: FAILED
message: <% task().result %>
errors: []
warnings: []
check_nodes_count:
action: tripleo.validations.check_nodes_count
input:
statistics: <% $.statistics %>
stack: <% $.stack %>
associated_nodes: <% $.associated_nodes %>
available_nodes: <% $.available_nodes %>
parameters: <% $.parameters %>
default_role_counts: <% $.default_role_counts %>
on-success: send_message
publish:
errors: <% task().result.errors %>
warnings: <% task().result.warnings %>
on-error: send_message
publish-on-error:
status: FAILED
message: <% task().result %>
statistics: null
errors: <% task().result.errors %>
warnings: <% task().result.warnings %>
send_message:
workflow: tripleo.messaging.v1.send
input:
queue_name: <% $.queue_name %>
type: <% execution().name %>
status: <% $.get('status', 'SUCCESS') %>
message: <% $.get('message', '') %>
execution: <% execution() %>
payload:
statistics: <% $.statistics %>
errors: <% $.errors %>
warnings: <% $.warnings %>
check_pre_deployment_validations:
input:
- deploy_kernel_name: 'bm-deploy-kernel'
- deploy_ramdisk_name: 'bm-deploy-ramdisk'
- roles_info: {}
- stack_id: overcloud
- parameters: {}
- default_role_counts: {}
- run_validations: true
- queue_name: tripleo
output:
errors: <% $.errors %>
warnings: <% $.warnings %>
kernel_id: <% $.kernel_id %>
ramdisk_id: <% $.ramdisk_id %>
flavors: <% $.flavors %>
statistics: <% $.statistics %>
tags:
- tripleo-common-managed
tasks:
init_messages:
on-success: check_boot_images
publish:
errors: []
warnings: []
check_boot_images:
workflow: check_boot_images
input:
deploy_kernel_name: <% $.deploy_kernel_name %>
deploy_ramdisk_name: <% $.deploy_ramdisk_name %>
run_validations: <% $.run_validations %>
queue_name: <% $.queue_name %>
publish:
errors: <% $.errors + task().result.get('errors', []) %>
warnings: <% $.warnings + task().result.get('warnings', []) %>
kernel_id: <% task().result.get('kernel_id') %>
ramdisk_id: <% task().result.get('ramdisk_id') %>
publish-on-error:
errors: <% $.errors + task().result.get('errors', []) %>
warnings: <% $.warnings + task().result.get('warnings', []) %>
kernel_id: <% task().result.get('kernel_id') %>
ramdisk_id: <% task().result.get('ramdisk_id') %>
status: FAILED
on-success: collect_flavors
on-error: collect_flavors
collect_flavors:
workflow: collect_flavors
input:
roles_info: <% $.roles_info %>
run_validations: <% $.run_validations %>
queue_name: <% $.queue_name %>
publish:
errors: <% $.errors + task().result.get('errors', []) %>
warnings: <% $.warnings + task().result.get('warnings', []) %>
flavors: <% task().result.get('flavors') %>
publish-on-error:
errors: <% $.errors + task().result.get('errors', []) %>
warnings: <% $.warnings + task().result.get('warnings', []) %>
flavors: <% task().result.get('flavors') %>
status: FAILED
on-success: check_ironic_boot_configuration
on-error: check_ironic_boot_configuration
check_ironic_boot_configuration:
workflow: check_ironic_boot_configuration
input:
kernel_id: <% $.kernel_id %>
ramdisk_id: <% $.ramdisk_id %>
run_validations: <% $.run_validations %>
queue_name: <% $.queue_name %>
publish:
errors: <% $.errors + task().result.get('errors', []) %>
warnings: <% $.warnings + task().result.get('warnings', []) %>
publish-on-error:
errors: <% $.errors + task().result.get('errors', []) %>
warnings: <% $.warnings + task().result.get('warnings', []) %>
status: FAILED
on-success: check_default_nodes_count
on-error: check_default_nodes_count
check_default_nodes_count:
workflow: check_default_nodes_count
# ironic-nova sync happens once in two minutes
retry: count=12 delay=10
input:
stack_id: <% $.stack_id %>
parameters: <% $.parameters %>
default_role_counts: <% $.default_role_counts %>
run_validations: <% $.run_validations %>
queue_name: <% $.queue_name %>
publish:
errors: <% $.errors + task().result.get('errors', []) %>
warnings: <% $.warnings + task().result.get('warnings', []) %>
statistics: <% task().result.get('statistics') %>
publish-on-error:
errors: <% $.errors + task().result.get('errors', []) %>
warnings: <% $.warnings + task().result.get('warnings', []) %>
statistics: <% task().result.get('statistics') %>
status: FAILED
on-success: verify_profiles
# Do not confuse user with info about profiles if the nodes
# count is off in the first place. Skip directly to
# send_message. (bug 1703942)
on-error: send_message
verify_profiles:
workflow: verify_profiles
input:
flavors: <% $.flavors %>
run_validations: <% $.run_validations %>
queue_name: <% $.queue_name %>
publish:
errors: <% $.errors + task().result.get('errors', []) %>
warnings: <% $.warnings + task().result.get('warnings', []) %>
publish-on-error:
errors: <% $.errors + task().result.get('errors', []) %>
warnings: <% $.warnings + task().result.get('warnings', []) %>
status: FAILED
on-success: send_message
on-error: send_message
send_message:
workflow: tripleo.messaging.v1.send
input:
queue_name: <% $.queue_name %>
type: <% execution().name %>
status: <% $.get('status', 'SUCCESS') %>
message: <% $.get('message', '') %>
execution: <% execution() %>
payload:
kernel_id: <% $.kernel_id %>
ramdisk_id: <% $.ramdisk_id %>
flavors: <% $.flavors %>
statistics: <% $.statistics %>
errors: <% $.errors %>
warnings: <% $.warnings %>
upload_validations:
tags:
- tripleo-common-managed
input:
- queue_name: tripleo
tasks:
upload_validations:
action: tripleo.validations.upload
on-complete: send_message
publish-on-error:
status: FAILED
message: <% task().result %>
publish:
status: SUCCESS
message: <% task().result %>
send_message:
workflow: tripleo.messaging.v1.send
input:
queue_name: <% $.queue_name %>
type: <% execution().name %>
status: <% $.status %>
execution: <% execution() %>
message: <% $.get('message', '') %>