Add support for inband raid configuration agent ramdisk

This commit adds support for inband raid configuration
using agent ramdisk.  Agent ramdisk will route the call
to appropriate hardware manager which will create the
desired raid configuration. This must be explicitly
invoked via setting the provision to 'zap'.

This commit also updates the code to not touch
node.target_raid_config in raid.update_raid_info()
method.

Implements: blueprint inband-raid-configuration
Change-Id: Ifa640d4839b2f08410ceec57c2972e2f9dc69db3
This commit is contained in:
Ramakrishnan G 2015-06-30 00:45:40 -07:00
parent 45e55a0940
commit 5dd42ff66d
9 changed files with 336 additions and 7 deletions

View File

@ -105,7 +105,6 @@ def update_raid_info(node, raid_config):
current = raid_config.copy()
current['last_updated'] = str(datetime.datetime.utcnow())
node.raid_config = current
node.target_raid_config = None
# Current RAID configuration can have 0 or 1 root volumes. If there
# are > 1 root volumes, then it's invalid. We check for this condition

View File

@ -199,9 +199,10 @@ CLEANING_INTERFACE_PRIORITY = {
# by which interface is implementing the clean step. The clean step of the
# interface with the highest value here, will be executed first in that
# case.
'power': 3,
'management': 2,
'deploy': 1
'power': 4,
'management': 3,
'deploy': 2,
'raid': 1,
}
SYNC_EXCLUDED_STATES = (states.DEPLOYWAIT, states.CLEANWAIT, states.ENROLL)

View File

@ -54,6 +54,7 @@ class AgentAndIPMIToolDriver(base.BaseDriver):
self.vendor = utils.MixinVendorInterface(
self.mapping,
driver_passthru_mapping=self.driver_passthru_mapping)
self.raid = agent.AgentRAID()
class AgentAndIPMINativeDriver(base.BaseDriver):
@ -75,6 +76,7 @@ class AgentAndIPMINativeDriver(base.BaseDriver):
self.management = ipminative.NativeIPMIManagement()
self.console = ipminative.NativeIPMIShellinaboxConsole()
self.vendor = agent.AgentVendorInterface()
self.raid = agent.AgentRAID()
class AgentAndSSHDriver(base.BaseDriver):
@ -96,6 +98,7 @@ class AgentAndSSHDriver(base.BaseDriver):
self.deploy = agent.AgentDeploy()
self.management = ssh.SSHManagement()
self.vendor = agent.AgentVendorInterface()
self.raid = agent.AgentRAID()
class AgentAndVirtualBoxDriver(base.BaseDriver):
@ -121,6 +124,7 @@ class AgentAndVirtualBoxDriver(base.BaseDriver):
self.deploy = agent.AgentDeploy()
self.management = virtualbox.VirtualBoxManagement()
self.vendor = agent.AgentVendorInterface()
self.raid = agent.AgentRAID()
class AgentAndUcsDriver(base.BaseDriver):

View File

@ -139,6 +139,7 @@ class FakeAgentDriver(base.BaseDriver):
self.boot = pxe.PXEBoot()
self.deploy = agent.AgentDeploy()
self.vendor = agent.AgentVendorInterface()
self.raid = agent.AgentRAID()
class FakeIBootDriver(base.BaseDriver):

View File

@ -20,6 +20,7 @@ from oslo_utils import importutils
from ironic.common import exception
from ironic.common.i18n import _
from ironic.drivers import base
from ironic.drivers.modules import agent
from ironic.drivers.modules.ilo import deploy
from ironic.drivers.modules.ilo import inspect
from ironic.drivers.modules.ilo import management
@ -72,3 +73,4 @@ class IloVirtualMediaAgentDriver(base.BaseDriver):
self.management = management.IloManagement()
self.vendor = deploy.IloVirtualMediaAgentVendorInterface()
self.inspect = inspect.IloInspect()
self.raid = agent.AgentRAID()

View File

@ -27,6 +27,7 @@ from ironic.common.i18n import _LI
from ironic.common import image_service
from ironic.common import keystone
from ironic.common import paths
from ironic.common import raid
from ironic.common import states
from ironic.conductor import task_manager
from ironic.conductor import utils as manager_utils
@ -466,3 +467,138 @@ class AgentVendorInterface(agent_base_vendor.BaseAgentVendor):
# check once all in-tree drivers have a boot interface.
if task.driver.boot:
task.driver.boot.clean_up_ramdisk(task)
class AgentRAID(base.RAIDInterface):
"""Implementation of RAIDInterface which uses agent ramdisk."""
def get_properties(self):
"""Return the properties of the interface."""
return {}
@base.clean_step(priority=0)
def create_configuration(self, task,
create_root_volume=True,
create_nonroot_volumes=True):
"""Create a RAID configuration on a bare metal using agent ramdisk.
This method creates a RAID configuration on the given node.
:param task: a TaskManager instance.
:param create_root_volume: If True, a root volume is created
during RAID configuration. Otherwise, no root volume is
created. Default is True.
:param create_nonroot_volumes: If True, non-root volumes are
created. If False, no non-root volumes are created. Default
is True.
:returns: states.CLEANWAIT if operation was successfully invoked.
:raises: MissingParameterValue, if node.target_raid_config is missing
or was found to be empty after skipping root volume and/or non-root
volumes.
"""
node = task.node
LOG.debug("Agent RAID create_configuration invoked for node %(node)s "
"with create_root_volume=%(create_root_volume)s and "
"create_nonroot_volumes=%(create_nonroot_volumes)s with the "
"following target_raid_config: %(target_raid_config)s.",
{'node': node.uuid,
'create_root_volume': create_root_volume,
'create_nonroot_volumes': create_nonroot_volumes,
'target_raid_config': node.target_raid_config})
if not node.target_raid_config:
raise exception.MissingParameterValue(
_("Node %s has no target RAID configuration.") % node.uuid)
target_raid_config = node.target_raid_config.copy()
error_msg = None
error_msg_list = []
if not create_root_volume:
target_raid_config['logical_disks'] = [
x for x in target_raid_config['logical_disks']
if x.get('is_root_volume', False) is False]
error_msg_list.append(_("skipping root volume"))
if not create_nonroot_volumes:
error_msg_list.append(_("skipping non-root volumes"))
target_raid_config['logical_disks'] = [
x for x in target_raid_config['logical_disks']
if x.get('is_root_volume', False) is True]
if not target_raid_config['logical_disks']:
error_msg = _(' and ').join(error_msg_list)
raise exception.MissingParameterValue(
_("Node %(node)s has empty target RAID configuration "
"after %(msg)s.") % {'node': node.uuid, 'msg': error_msg})
# Rewrite it back to the node object, but no need to save it as
# we need to just send this to the agent ramdisk.
node.driver_internal_info['target_raid_config'] = target_raid_config
LOG.debug("Calling agent RAID create_configuration for node %(node)s "
"with the following target RAID configuration: %(target)s",
{'node': node.uuid, 'target': target_raid_config})
step = node.clean_step
return deploy_utils.agent_execute_clean_step(task, step)
@staticmethod
@agent_base_vendor.post_clean_step_hook(
interface='raid', step='create_configuration')
def _create_configuration_final(task, command):
"""Clean step hook after a RAID configuration was created.
This method is invoked as a post clean step hook by the Ironic
conductor once a create raid configuration is completed successfully.
The node (properties, capabilities, RAID information) will be updated
to reflect the actual RAID configuration that was created.
:param task: a TaskManager instance.
:param command: A command result structure of the RAID operation
returned from agent ramdisk on query of the status of command(s).
:raises: InvalidParameterValue, if 'current_raid_config' has more than
one root volume or if node.properties['capabilities'] is malformed.
:raises: IronicException, if clean_result couldn't be found within
the 'command' argument passed.
"""
try:
clean_result = command['command_result']['clean_result']
except KeyError:
raise exception.IronicException(
_("Agent ramdisk didn't return a proper command result while "
"cleaning %(node)s. It returned '%(result)s' after command "
"execution.") % {'node': task.node.uuid,
'result': command})
raid.update_raid_info(task.node, clean_result)
@base.clean_step(priority=0)
def delete_configuration(self, task):
"""Deletes RAID configuration on the given node.
:param task: a TaskManager instance.
:returns: states.CLEANWAIT if operation was successfully invoked
"""
LOG.debug("Agent RAID delete_configuration invoked for node %s.",
task.node.uuid)
step = task.node.clean_step
return deploy_utils.agent_execute_clean_step(task, step)
@staticmethod
@agent_base_vendor.post_clean_step_hook(
interface='raid', step='delete_configuration')
def _delete_configuration_final(task, command):
"""Clean step hook after RAID configuration was deleted.
This method is invoked as a post clean step hook by the Ironic
conductor once a delete raid configuration is completed successfully.
It sets node.raid_config to empty dictionary.
:param task: a TaskManager instance.
:param command: A command result structure of the RAID operation
returned from agent ramdisk on query of the status of command(s).
:returns: None
"""
task.node.raid_config = {}
task.node.save()

View File

@ -182,7 +182,9 @@ class RaidPublicMethodsTestCase(db_base.DbTestCase):
properties['capabilities'] = capabilities
del properties['local_gb']
node.properties = properties
node.save()
target_raid_config = json.loads(raid_constants.RAID_CONFIG_OKAY)
node.target_raid_config = target_raid_config
node.save()
raid.update_raid_info(node, current_config)
properties = node.properties
current = node.raid_config
@ -202,7 +204,8 @@ class RaidPublicMethodsTestCase(db_base.DbTestCase):
if capabilities:
self.assertNotIn('raid_level:1', properties['capabilities'])
self.assertEqual({}, target)
# Verify node.target_raid_config is preserved.
self.assertEqual(target_raid_config, target)
def test_update_raid_info_okay(self):
current_config = json.loads(raid_constants.CURRENT_RAID_CONFIG)

View File

@ -20,11 +20,14 @@ from oslo_config import cfg
from ironic.common import exception
from ironic.common import image_service
from ironic.common import keystone
from ironic.common import raid
from ironic.common import states
from ironic.conductor import task_manager
from ironic.conductor import utils as manager_utils
from ironic.drivers.modules import agent
from ironic.drivers.modules import agent_base_vendor
from ironic.drivers.modules import agent_client
from ironic.drivers.modules import deploy_utils
from ironic.drivers.modules import fake
from ironic.drivers.modules import pxe
from ironic.tests.conductor import utils as mgr_utils
@ -636,3 +639,183 @@ class TestAgentVendor(db_base.DbTestCase):
mock_get_cmd.return_value = [{'command_name': 'prepare_image',
'command_status': 'RUNNING'}]
self.assertFalse(self.passthru.deploy_is_done(task))
class AgentRAIDTestCase(db_base.DbTestCase):
def setUp(self):
super(AgentRAIDTestCase, self).setUp()
mgr_utils.mock_the_extension_manager(driver="fake_agent")
self.passthru = agent.AgentVendorInterface()
self.target_raid_config = {
"logical_disks": [
{'size_gb': 200, 'raid_level': 0, 'is_root_volume': True},
{'size_gb': 200, 'raid_level': 5}
]}
self.clean_step = {'step': 'create_configuration',
'interface': 'raid'}
n = {
'driver': 'fake_agent',
'instance_info': INSTANCE_INFO,
'driver_info': DRIVER_INFO,
'driver_internal_info': DRIVER_INTERNAL_INFO,
'target_raid_config': self.target_raid_config,
'clean_step': self.clean_step,
}
self.node = object_utils.create_test_node(self.context, **n)
@mock.patch.object(deploy_utils, 'agent_get_clean_steps', autospec=True)
def test_get_clean_steps(self, get_steps_mock):
get_steps_mock.return_value = [
{'step': 'create_configuration', 'interface': 'raid',
'priority': 1},
{'step': 'delete_configuration', 'interface': 'raid',
'priority': 2}]
with task_manager.acquire(self.context, self.node.uuid) as task:
ret = task.driver.raid.get_clean_steps(task)
self.assertEqual(0, ret[0]['priority'])
self.assertEqual(0, ret[1]['priority'])
@mock.patch.object(deploy_utils, 'agent_execute_clean_step',
autospec=True)
def test_create_configuration(self, execute_mock):
with task_manager.acquire(self.context, self.node.uuid) as task:
execute_mock.return_value = states.CLEANWAIT
return_value = task.driver.raid.create_configuration(task)
self.assertEqual(states.CLEANWAIT, return_value)
self.assertEqual(
self.target_raid_config,
task.node.driver_internal_info['target_raid_config'])
execute_mock.assert_called_once_with(task, self.clean_step)
@mock.patch.object(deploy_utils, 'agent_execute_clean_step',
autospec=True)
def test_create_configuration_skip_root(self, execute_mock):
with task_manager.acquire(self.context, self.node.uuid) as task:
execute_mock.return_value = states.CLEANWAIT
return_value = task.driver.raid.create_configuration(
task, create_root_volume=False)
self.assertEqual(states.CLEANWAIT, return_value)
execute_mock.assert_called_once_with(task, self.clean_step)
exp_target_raid_config = {
"logical_disks": [
{'size_gb': 200, 'raid_level': 5}
]}
self.assertEqual(
exp_target_raid_config,
task.node.driver_internal_info['target_raid_config'])
@mock.patch.object(deploy_utils, 'agent_execute_clean_step',
autospec=True)
def test_create_configuration_skip_nonroot(self, execute_mock):
with task_manager.acquire(self.context, self.node.uuid) as task:
execute_mock.return_value = states.CLEANWAIT
return_value = task.driver.raid.create_configuration(
task, create_nonroot_volumes=False)
self.assertEqual(states.CLEANWAIT, return_value)
execute_mock.assert_called_once_with(task, self.clean_step)
exp_target_raid_config = {
"logical_disks": [
{'size_gb': 200, 'raid_level': 0, 'is_root_volume': True},
]}
self.assertEqual(
exp_target_raid_config,
task.node.driver_internal_info['target_raid_config'])
@mock.patch.object(deploy_utils, 'agent_execute_clean_step',
autospec=True)
def test_create_configuration_no_target_raid_config_after_skipping(
self, execute_mock):
with task_manager.acquire(self.context, self.node.uuid) as task:
self.assertRaises(
exception.MissingParameterValue,
task.driver.raid.create_configuration,
task, create_root_volume=False,
create_nonroot_volumes=False)
self.assertFalse(execute_mock.called)
@mock.patch.object(deploy_utils, 'agent_execute_clean_step',
autospec=True)
def test_create_configuration_empty_target_raid_config(
self, execute_mock):
execute_mock.return_value = states.CLEANING
self.node.target_raid_config = {}
self.node.save()
with task_manager.acquire(self.context, self.node.uuid) as task:
self.assertRaises(exception.MissingParameterValue,
task.driver.raid.create_configuration,
task)
self.assertFalse(execute_mock.called)
@mock.patch.object(raid, 'update_raid_info', autospec=True)
def test__create_configuration_final(
self, update_raid_info_mock):
command = {'command_result': {'clean_result': 'foo'}}
with task_manager.acquire(self.context, self.node.uuid) as task:
raid_mgmt = agent.AgentRAID
raid_mgmt._create_configuration_final(task, command)
update_raid_info_mock.assert_called_once_with(task.node, 'foo')
@mock.patch.object(raid, 'update_raid_info', autospec=True)
def test__create_configuration_final_registered(
self, update_raid_info_mock):
self.node.clean_step = {'interface': 'raid',
'step': 'create_configuration'}
command = {'command_result': {'clean_result': 'foo'}}
create_hook = agent_base_vendor._get_post_clean_step_hook(self.node)
with task_manager.acquire(self.context, self.node.uuid) as task:
create_hook(task, command)
update_raid_info_mock.assert_called_once_with(task.node, 'foo')
@mock.patch.object(raid, 'update_raid_info', autospec=True)
def test__create_configuration_final_bad_command_result(
self, update_raid_info_mock):
command = {}
with task_manager.acquire(self.context, self.node.uuid) as task:
raid_mgmt = agent.AgentRAID
self.assertRaises(exception.IronicException,
raid_mgmt._create_configuration_final,
task, command)
self.assertFalse(update_raid_info_mock.called)
@mock.patch.object(deploy_utils, 'agent_execute_clean_step',
autospec=True)
def test_delete_configuration(self, execute_mock):
execute_mock.return_value = states.CLEANING
with task_manager.acquire(self.context, self.node.uuid) as task:
return_value = task.driver.raid.delete_configuration(task)
execute_mock.assert_called_once_with(task, self.clean_step)
self.assertEqual(states.CLEANING, return_value)
def test__delete_configuration_final(self):
command = {'command_result': {'clean_result': 'foo'}}
with task_manager.acquire(self.context, self.node.uuid) as task:
task.node.raid_config = {'foo': 'bar'}
raid_mgmt = agent.AgentRAID
raid_mgmt._delete_configuration_final(task, command)
self.node.refresh()
self.assertEqual({}, self.node.raid_config)
def test__delete_configuration_final_registered(
self):
self.node.clean_step = {'interface': 'raid',
'step': 'delete_configuration'}
self.node.raid_config = {'foo': 'bar'}
command = {'command_result': {'clean_result': 'foo'}}
delete_hook = agent_base_vendor._get_post_clean_step_hook(self.node)
with task_manager.acquire(self.context, self.node.uuid) as task:
delete_hook(task, command)
self.node.refresh()
self.assertEqual({}, self.node.raid_config)

View File

@ -1901,7 +1901,7 @@ class AgentCleaningTestCase(db_base.DbTestCase):
'step': 'update_firmware',
'priority': 30},
{'interface': 'raid',
'step': 'create_raid',
'step': 'create_configuration',
'priority': 10},
]
}