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:
Julie Pichon 2016-08-16 14:33:24 +01:00
parent aaecd1c968
commit a9c8ffb655
3 changed files with 202 additions and 0 deletions

View File

@ -58,6 +58,7 @@ output_file = tripleo_common/locale/tripleo-common.pot
mistral.actions =
tripleo.upload_default_templates = tripleo_common.actions.templates:UploadTemplatesAction
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_plan = tripleo_common.actions.plan:CreatePlanAction
tripleo.deploy_config = tripleo_common.actions.deployment:OrchestrationDeployAction

View File

@ -16,6 +16,7 @@ import logging
from mistral.workflow import utils as mistral_workflow_utils
from tripleo_common.actions import base
from tripleo_common.utils import glance
from tripleo_common.utils import nodes
LOG = logging.getLogger(__name__)
@ -67,3 +68,66 @@ class RegisterOrUpdateNodes(base.TripleOAction):
"",
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)

View 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))