Add Mistral action for boot configuration
The boot configuration code mainly comes from the python-tripleoclient project. By moving it into a common Mistral action, it can be reused by other clients like the UI. This will also be used for the new 'overcloud node configure' command in the client. Change-Id: Ica936fb96cfca025b3cc29edb2cc81f2170ae4ed Related-Bug: #1595205
This commit is contained in:
parent
aaecd1c968
commit
a9c8ffb655
@ -58,6 +58,7 @@ output_file = tripleo_common/locale/tripleo-common.pot
|
|||||||
mistral.actions =
|
mistral.actions =
|
||||||
tripleo.upload_default_templates = tripleo_common.actions.templates:UploadTemplatesAction
|
tripleo.upload_default_templates = tripleo_common.actions.templates:UploadTemplatesAction
|
||||||
tripleo.process_templates = tripleo_common.actions.templates:ProcessTemplatesAction
|
tripleo.process_templates = tripleo_common.actions.templates:ProcessTemplatesAction
|
||||||
|
tripleo.baremetal.configure_boot = tripleo_common.actions.baremetal:ConfigureBootAction
|
||||||
tripleo.create_container = tripleo_common.actions.plan:CreateContainerAction
|
tripleo.create_container = tripleo_common.actions.plan:CreateContainerAction
|
||||||
tripleo.create_plan = tripleo_common.actions.plan:CreatePlanAction
|
tripleo.create_plan = tripleo_common.actions.plan:CreatePlanAction
|
||||||
tripleo.deploy_config = tripleo_common.actions.deployment:OrchestrationDeployAction
|
tripleo.deploy_config = tripleo_common.actions.deployment:OrchestrationDeployAction
|
||||||
|
@ -16,6 +16,7 @@ import logging
|
|||||||
|
|
||||||
from mistral.workflow import utils as mistral_workflow_utils
|
from mistral.workflow import utils as mistral_workflow_utils
|
||||||
from tripleo_common.actions import base
|
from tripleo_common.actions import base
|
||||||
|
from tripleo_common.utils import glance
|
||||||
from tripleo_common.utils import nodes
|
from tripleo_common.utils import nodes
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@ -67,3 +68,66 @@ class RegisterOrUpdateNodes(base.TripleOAction):
|
|||||||
"",
|
"",
|
||||||
err.message
|
err.message
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigureBootAction(base.TripleOAction):
|
||||||
|
"""Configure kernel and ramdisk.
|
||||||
|
|
||||||
|
:param node_uuid: an Ironic node UUID
|
||||||
|
:param kernel_name: Glance name of the kernel to use for the nodes.
|
||||||
|
:param ramdisk_name: Glance name of the ramdisk to use for the nodes.
|
||||||
|
:param instance_boot_option: Whether to set instances for booting from
|
||||||
|
local hard drive (local) or network (netboot).
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, node_uuid, kernel_name='bm-deploy-kernel',
|
||||||
|
ramdisk_name='bm-deploy-ramdisk', instance_boot_option=None):
|
||||||
|
super(ConfigureBootAction, self).__init__()
|
||||||
|
self.node_uuid = node_uuid
|
||||||
|
self.kernel_name = kernel_name
|
||||||
|
self.ramdisk_name = ramdisk_name
|
||||||
|
self.instance_boot_option = instance_boot_option
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
baremetal_client = self._get_baremetal_client()
|
||||||
|
image_client = self._get_image_client()
|
||||||
|
|
||||||
|
try:
|
||||||
|
image_ids = {'kernel': None, 'ramdisk': None}
|
||||||
|
if self.kernel_name is not None and self.ramdisk_name is not None:
|
||||||
|
image_ids = glance.create_or_find_kernel_and_ramdisk(
|
||||||
|
image_client, self.kernel_name, self.ramdisk_name)
|
||||||
|
|
||||||
|
node = baremetal_client.node.get(self.node_uuid)
|
||||||
|
|
||||||
|
capabilities = node.properties.get('capabilities', {})
|
||||||
|
capabilities = nodes.capabilities_to_dict(capabilities)
|
||||||
|
if self.instance_boot_option is not None:
|
||||||
|
capabilities['boot_option'] = self.instance_boot_option
|
||||||
|
else:
|
||||||
|
# Add boot option capability if it didn't exist
|
||||||
|
capabilities.setdefault(
|
||||||
|
'boot_option', self.instance_boot_option or 'local')
|
||||||
|
capabilities = nodes.dict_to_capabilities(capabilities)
|
||||||
|
|
||||||
|
baremetal_client.node.update(node.uuid, [
|
||||||
|
{
|
||||||
|
'op': 'add',
|
||||||
|
'path': '/properties/capabilities',
|
||||||
|
'value': capabilities,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'op': 'add',
|
||||||
|
'path': '/driver_info/deploy_ramdisk',
|
||||||
|
'value': image_ids['ramdisk'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'op': 'add',
|
||||||
|
'path': '/driver_info/deploy_kernel',
|
||||||
|
'value': image_ids['kernel'],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
LOG.debug("Configuring boot option for Node %s", self.node_uuid)
|
||||||
|
except Exception as err:
|
||||||
|
LOG.exception("Error configuring node boot options with Ironic.")
|
||||||
|
return mistral_workflow_utils.Result("", err)
|
||||||
|
137
tripleo_common/tests/actions/test_baremetal.py
Normal file
137
tripleo_common/tests/actions/test_baremetal.py
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# 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 mock
|
||||||
|
|
||||||
|
from glanceclient import exc as glance_exceptions
|
||||||
|
|
||||||
|
from tripleo_common.actions import baremetal
|
||||||
|
from tripleo_common.tests import base
|
||||||
|
|
||||||
|
|
||||||
|
class TestConfigureBootAction(base.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestConfigureBootAction, self).setUp()
|
||||||
|
self.node_update = [{'op': 'add',
|
||||||
|
'path': '/properties/capabilities',
|
||||||
|
'value': 'boot_option:local'},
|
||||||
|
{'op': 'add',
|
||||||
|
'path': '/driver_info/deploy_ramdisk',
|
||||||
|
'value': 'r_id'},
|
||||||
|
{'op': 'add',
|
||||||
|
'path': '/driver_info/deploy_kernel',
|
||||||
|
'value': 'k_id'}]
|
||||||
|
|
||||||
|
self.ironic = mock.MagicMock()
|
||||||
|
ironic_patcher = mock.patch(
|
||||||
|
'tripleo_common.actions.base.TripleOAction._get_baremetal_client',
|
||||||
|
return_value=self.ironic)
|
||||||
|
self.mock_ironic = ironic_patcher.start()
|
||||||
|
self.addCleanup(self.mock_ironic.stop)
|
||||||
|
|
||||||
|
self.glance = mock.MagicMock()
|
||||||
|
glance_patcher = mock.patch(
|
||||||
|
'tripleo_common.actions.base.TripleOAction._get_image_client',
|
||||||
|
return_value=self.glance)
|
||||||
|
self.mock_glance = glance_patcher.start()
|
||||||
|
self.addCleanup(self.mock_glance.stop)
|
||||||
|
|
||||||
|
def mock_find(name, disk_format):
|
||||||
|
if name == 'bm-deploy-kernel':
|
||||||
|
return mock.MagicMock(id='k_id')
|
||||||
|
elif name == 'bm-deploy-ramdisk':
|
||||||
|
return mock.MagicMock(id='r_id')
|
||||||
|
self.glance.images.find = mock_find
|
||||||
|
|
||||||
|
def test_run_instance_boot_option(self):
|
||||||
|
action = baremetal.ConfigureBootAction(node_uuid='MOCK_UUID',
|
||||||
|
instance_boot_option='netboot')
|
||||||
|
result = action.run()
|
||||||
|
self.assertEqual(result, None)
|
||||||
|
|
||||||
|
self.node_update[0].update({'value': 'boot_option:netboot'})
|
||||||
|
self.ironic.node.update.assert_called_once_with(mock.ANY,
|
||||||
|
self.node_update)
|
||||||
|
|
||||||
|
def test_run_instance_boot_option_not_set(self):
|
||||||
|
action = baremetal.ConfigureBootAction(node_uuid='MOCK_UUID')
|
||||||
|
result = action.run()
|
||||||
|
self.assertEqual(result, None)
|
||||||
|
|
||||||
|
self.node_update[0].update({'value': 'boot_option:local'})
|
||||||
|
self.ironic.node.update.assert_called_once_with(mock.ANY,
|
||||||
|
self.node_update)
|
||||||
|
|
||||||
|
def test_run_instance_boot_option_already_set_no_overwrite(self):
|
||||||
|
node_mock = mock.MagicMock()
|
||||||
|
node_mock.properties.get.return_value = ({'boot_option': 'netboot'})
|
||||||
|
self.ironic.node.get.return_value = node_mock
|
||||||
|
|
||||||
|
action = baremetal.ConfigureBootAction(node_uuid='MOCK_UUID')
|
||||||
|
result = action.run()
|
||||||
|
self.assertEqual(result, None)
|
||||||
|
|
||||||
|
self.node_update[0].update({'value': 'boot_option:netboot'})
|
||||||
|
self.ironic.node.update.assert_called_once_with(mock.ANY,
|
||||||
|
self.node_update)
|
||||||
|
|
||||||
|
def test_run_instance_boot_option_already_set_do_overwrite(self):
|
||||||
|
node_mock = mock.MagicMock()
|
||||||
|
node_mock.properties.get.return_value = ({'boot_option': 'netboot'})
|
||||||
|
self.ironic.node.get.return_value = node_mock
|
||||||
|
|
||||||
|
action = baremetal.ConfigureBootAction(node_uuid='MOCK_UUID',
|
||||||
|
instance_boot_option='local')
|
||||||
|
result = action.run()
|
||||||
|
self.assertEqual(result, None)
|
||||||
|
|
||||||
|
self.node_update[0].update({'value': 'boot_option:local'})
|
||||||
|
self.ironic.node.update.assert_called_once_with(mock.ANY,
|
||||||
|
self.node_update)
|
||||||
|
|
||||||
|
def test_run_new_kernel_and_ram_image(self):
|
||||||
|
image_ids = {'kernel': 'test_kernel_id', 'ramdisk': 'test_ramdisk_id'}
|
||||||
|
|
||||||
|
with mock.patch('tripleo_common.utils.glance.create_or_find_kernel_and'
|
||||||
|
'_ramdisk') as mock_find:
|
||||||
|
mock_find.return_value = image_ids
|
||||||
|
action = baremetal.ConfigureBootAction(node_uuid='MOCK_UUID',
|
||||||
|
kernel_name='test_kernel',
|
||||||
|
ramdisk_name='test_ramdisk')
|
||||||
|
result = action.run()
|
||||||
|
|
||||||
|
self.assertEqual(result, None)
|
||||||
|
|
||||||
|
self.node_update[1].update({'value': 'test_ramdisk_id'})
|
||||||
|
self.node_update[2].update({'value': 'test_kernel_id'})
|
||||||
|
self.ironic.node.update.assert_called_once_with(mock.ANY,
|
||||||
|
self.node_update)
|
||||||
|
|
||||||
|
def test_run_glance_ids_not_found(self):
|
||||||
|
self.glance.images.find = mock.Mock(
|
||||||
|
side_effect=glance_exceptions.NotFound)
|
||||||
|
|
||||||
|
action = baremetal.ConfigureBootAction(node_uuid='MOCK_UUID',
|
||||||
|
kernel_name='unknown_kernel',
|
||||||
|
ramdisk_name='unknown_ramdisk')
|
||||||
|
result = action.run()
|
||||||
|
self.assertIn("not found", str(result.error))
|
||||||
|
|
||||||
|
def test_run_exception_on_node_update(self):
|
||||||
|
self.ironic.node.update.side_effect = Exception("Update error")
|
||||||
|
|
||||||
|
action = baremetal.ConfigureBootAction(node_uuid='MOCK_UUID')
|
||||||
|
result = action.run()
|
||||||
|
|
||||||
|
self.assertIn("Update error", str(result.error))
|
Loading…
Reference in New Issue
Block a user