IloVirtualMediaAgent deploy driver

This commit introduces a new deploy driver which uses iLO virtual
media to boot up proliant baremetal nodes, and uses agent to deploy
the baremetal nodes.

This patch also changes agent pxe config template slightly, so that
the names of agent ramdisk parameters generated in code and those
expected by agent ramdisk are same.

Change-Id: Ia5677dff294bc146b864bed180fbda939cf9bb38
Implements: blueprint ilo-virtualmedia-ipa
This commit is contained in:
Ramakrishnan G 2014-08-08 23:18:59 +05:30 committed by Jim Rollenhagen
parent 571579a005
commit 5894ff356e
9 changed files with 219 additions and 7 deletions

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 power
from ironic.drivers.modules import ipmitool
@ -46,3 +47,26 @@ class IloVirtualMediaIscsiDriver(base.BaseDriver):
self.console = ipmitool.IPMIShellinaboxConsole()
self.management = ipmitool.IPMIManagement()
self.vendor = deploy.VendorPassthru()
class IloVirtualMediaAgentDriver(base.BaseDriver):
"""IloDriver using IloClient interface.
This driver implements the `core` functionality using
:class:ironic.drivers.modules.ilo.power.IloPower for power management
and
:class:ironic.drivers.modules.ilo.deploy.IloVirtualMediaAgentDriver for
deploy.
"""
def __init__(self):
if not importutils.try_import('proliantutils'):
raise exception.DriverLoadError(
driver=self.__class__.__name__,
reason=_("Unable to import proliantutils library"))
self.power = power.IloPower()
self.deploy = deploy.IloVirtualMediaAgentDeploy()
self.console = ipmitool.IPMIShellinaboxConsole()
self.management = ipmitool.IPMIManagement()
self.vendor = agent.AgentVendorInterface()

View File

@ -74,15 +74,38 @@ def _get_client():
return client
def _build_pxe_config_options(pxe_info):
def build_agent_options():
"""Build the options to be passed to the agent ramdisk.
:returns: a dictionary containing the parameters to be passed to
agent ramdisk.
"""
ironic_api = (CONF.conductor.api_url or
keystone.get_service_url()).rstrip('/')
return {
'ipa-api-url': ironic_api,
}
def _build_pxe_config_options(pxe_info):
"""Builds the pxe config options for booting agent.
This method builds the config options to be replaced on
the agent pxe config template.
:param pxe_info: A dict containing the 'deploy_kernel' and
'deploy_ramdisk' for the agent pxe config template.
:returns: a dict containing the options to be applied on
the agent pxe config template.
"""
agent_config_opts = {
'deployment_aki_path': pxe_info['deploy_kernel'][1],
'deployment_ari_path': pxe_info['deploy_ramdisk'][1],
'pxe_append_params': CONF.agent.agent_pxe_append_params,
'ipa_api_url': ironic_api,
}
agent_opts = build_agent_options()
agent_config_opts.update(agent_opts)
return agent_config_opts
def _get_tftp_image_info(node):
@ -162,8 +185,13 @@ def _cache_tftp_images(ctx, node, pxe_info):
_fetch_images(ctx, AgentTFTPImageCache(), pxe_info.values())
def _build_instance_info_for_deploy(task):
"""Build instance_info necessary for deploying to a node."""
def build_instance_info_for_deploy(task):
"""Build instance_info necessary for deploying to a node.
:param task: a TaskManager object containing the node
:returns: a dictionary containing the properties to be updated
in instance_info
"""
node = task.node
instance_info = node.instance_info
@ -248,7 +276,7 @@ class AgentDeploy(base.DeployInterface):
CONF.agent.agent_pxe_config_template)
_cache_tftp_images(task.context, node, pxe_info)
node.instance_info = _build_instance_info_for_deploy(task)
node.instance_info = build_instance_info_for_deploy(task)
node.save(task.context)
def clean_up(self, task):

View File

@ -2,4 +2,4 @@ default deploy
label deploy
kernel {{ pxe_options.deployment_aki_path }}
append initrd={{ pxe_options.deployment_ari_path }} text {{ pxe_options.pxe_append_params }} {% if pxe_options.ipa_api_url %}ipa-api-url={{ pxe_options.ipa_api_url }}{% endif %} {% if pxe_options.ipa_advertise_host %}ipa-advertise-host={{ pxe_options.ipa_advertise_host }}{% endif %}
append initrd={{ pxe_options.deployment_ari_path }} text {{ pxe_options.pxe_append_params }} {% if pxe_options.ipa-api-url %}ipa-api-url={{ pxe_options.ipa-api-url }}{% endif %} {% if pxe_options.ipa-advertise-host %}ipa-advertise-host={{ pxe_options.ipa-advertise-host }}{% endif %}

View File

@ -28,6 +28,7 @@ from ironic.common import swift
from ironic.conductor import task_manager
from ironic.conductor import utils as manager_utils
from ironic.drivers import base
from ironic.drivers.modules import agent
from ironic.drivers.modules import deploy_utils
from ironic.drivers.modules.ilo import common as ilo_common
from ironic.drivers.modules import iscsi_deploy
@ -306,6 +307,81 @@ class IloVirtualMediaIscsiDeploy(base.DeployInterface):
pass
class IloVirtualMediaAgentDeploy(base.DeployInterface):
"""Interface for deploy-related actions."""
def get_properties(self):
"""Return the properties of the interface.
:returns: dictionary of <property name>:<property description> entries.
"""
return COMMON_PROPERTIES
def validate(self, task):
"""Validate the driver-specific Node deployment info.
:param task: a TaskManager instance
:raises: MissingParameterValue if some parameters are missing.
"""
_parse_driver_info(task.node)
@task_manager.require_exclusive_lock
def deploy(self, task):
"""Perform a deployment to a node.
Prepares the options for the agent ramdisk and sets the node to boot
from virtual media cdrom.
:param task: a TaskManager instance.
:returns: states.DEPLOYWAIT
:raises: ImageCreationFailed, if it failed while creating the floppy
image.
:raises: IloOperationError, if some operation on iLO fails.
"""
deploy_ramdisk_opts = agent.build_agent_options()
deploy_iso_uuid = task.node.driver_info['ilo_deploy_iso']
deploy_iso = 'glance:' + deploy_iso_uuid
_reboot_into(task, deploy_iso, deploy_ramdisk_opts)
return states.DEPLOYWAIT
@task_manager.require_exclusive_lock
def tear_down(self, task):
"""Tear down a previous deployment on the task's node.
:param task: a TaskManager instance.
:returns: states.DELETED
"""
manager_utils.node_power_action(task, states.POWER_OFF)
return states.DELETED
def prepare(self, task):
"""Prepare the deployment environment for this node.
:param task: a TaskManager instance.
"""
node = task.node
node.instance_info = agent.build_instance_info_for_deploy(task)
node.save(task.context)
def clean_up(self, task):
"""Clean up the deployment environment for this node.
Ejects the attached virtual media from the iLO and also removes
the floppy image from Swift, if it exists.
:param task: a TaskManager instance.
"""
ilo_common.cleanup_vmedia_boot(task)
def take_over(self, task):
"""Take over management of this node from a dead conductor.
:param task: a TaskManager instance.
"""
pass
class VendorPassthru(base.VendorInterface):
"""Vendor-specific interfaces for iLO deploy drivers."""

View File

@ -2269,6 +2269,16 @@ class ManagerTestProperties(tests_db_base.DbTestCase):
'ipmi_target_address', 'ipmi_local_address']
self._check_driver_properties("iscsi_ilo", expected)
def test_driver_properties_agent_ilo(self):
expected = ['ilo_address', 'ilo_username', 'ilo_password',
'client_port', 'client_timeout', 'ilo_deploy_iso',
'ipmi_address', 'ipmi_terminal_port',
'ipmi_password', 'ipmi_priv_level',
'ipmi_username', 'ipmi_bridging', 'ipmi_transit_channel',
'ipmi_transit_address', 'ipmi_target_channel',
'ipmi_target_address', 'ipmi_local_address']
self._check_driver_properties("agent_ilo", expected)
def test_driver_properties_fail(self):
mgr_utils.mock_the_extension_manager()
self.driver = driver_factory.get_driver("fake")

View File

@ -2,4 +2,4 @@ default deploy
label deploy
kernel {{ pxe_options.deployment_aki_path }}
append initrd={{ pxe_options.deployment_ari_path }} root=squashfs: {% if pxe_options.pxe_append_params %}{{ pxe_options.pxe_append_params }}{% endif %} state=tmpfs: ipa-api-url={{ pxe_options.ipa_api_url }} {% if pxe_options.ipa_advertise_host %}ipa-advertise-host={{ pxe_options.ipa_advertise_host }}{% endif %}
append initrd={{ pxe_options.deployment_ari_path }} text root=squashfs: {% if pxe_options.pxe_append_params %}{{ pxe_options.pxe_append_params }}{% endif %} state=tmpfs: ipa-api-url={{ pxe_options.ipa-api-url }} {% if pxe_options.ipa-advertise-host %}ipa-advertise-host={{ pxe_options.ipa-advertise-host }}{% endif %}

View File

@ -27,6 +27,7 @@ from ironic.common import utils
from ironic.conductor import task_manager
from ironic.conductor import utils as manager_utils
from ironic.db import api as dbapi
from ironic.drivers.modules import agent
from ironic.drivers.modules import deploy_utils
from ironic.drivers.modules.ilo import common as ilo_common
from ironic.drivers.modules.ilo import deploy as ilo_deploy
@ -249,6 +250,59 @@ class IloVirtualMediaIscsiDeployTestCase(base.TestCase):
clean_up_boot_mock.assert_called_once_with(task.node)
class IloVirtualMediaAgentDeployTestCase(base.TestCase):
def setUp(self):
super(IloVirtualMediaAgentDeployTestCase, self).setUp()
self.dbapi = dbapi.get_instance()
self.context = context.get_admin_context()
mgr_utils.mock_the_extension_manager(driver="agent_ilo")
self.node = obj_utils.create_test_node(self.context,
driver='agent_ilo', driver_info=INFO_DICT)
@mock.patch.object(ilo_deploy, '_parse_driver_info')
def test_validate(self, parse_driver_info_mock):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.driver.deploy.validate(task)
parse_driver_info_mock.assert_called_once_with(task.node)
@mock.patch.object(ilo_deploy, '_reboot_into')
@mock.patch.object(agent, 'build_agent_options')
def test_deploy(self, build_options_mock, reboot_into_mock):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
deploy_opts = {'a': 'b'}
build_options_mock.return_value = deploy_opts
task.node.driver_info['ilo_deploy_iso'] = 'deploy-iso-uuid'
returned_state = task.driver.deploy.deploy(task)
build_options_mock.assert_called_once_with()
reboot_into_mock.assert_called_once_with(task,
'glance:deploy-iso-uuid',
deploy_opts)
self.assertEqual(states.DEPLOYWAIT, returned_state)
@mock.patch.object(manager_utils, 'node_power_action')
def test_tear_down(self, node_power_action_mock):
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
returned_state = task.driver.deploy.tear_down(task)
node_power_action_mock.assert_called_once_with(task,
states.POWER_OFF)
self.assertEqual(states.DELETED, returned_state)
@mock.patch.object(agent, 'build_instance_info_for_deploy')
def test_prepare(self, build_instance_info_mock):
deploy_opts = {'a': 'b'}
build_instance_info_mock.return_value = deploy_opts
with task_manager.acquire(self.context, self.node.uuid,
shared=False) as task:
task.driver.deploy.prepare(task)
self.assertEqual(deploy_opts, task.node.instance_info)
class VendorPassthruTestCase(base.TestCase):
def setUp(self):

View File

@ -17,6 +17,7 @@ from oslo.config import cfg
from ironic.common import dhcp_factory
from ironic.common import exception
from ironic.common import keystone
from ironic.common import pxe_utils
from ironic.common import states
from ironic.conductor import task_manager
@ -35,6 +36,24 @@ DRIVER_INFO = db_utils.get_test_agent_driver_info()
CONF = cfg.CONF
class TestAgentMethods(db_base.DbTestCase):
def setUp(self):
super(TestAgentMethods, self).setUp()
def test_build_agent_options_conf(self):
self.config(api_url='api-url', group='conductor')
options = agent.build_agent_options()
self.assertEqual('api-url', options['ipa-api-url'])
@mock.patch.object(keystone, 'get_service_url')
def test_build_agent_options_keystone(self, get_url_mock):
self.config(api_url=None, group='conductor')
get_url_mock.return_value = 'api-url'
options = agent.build_agent_options()
self.assertEqual('api-url', options['ipa-api-url'])
class TestAgentDeploy(db_base.DbTestCase):
def setUp(self):
super(TestAgentDeploy, self).setUp()

View File

@ -35,6 +35,7 @@ ironic.dhcp =
none = ironic.dhcp.none:NoneDHCPApi
ironic.drivers =
agent_ilo = ironic.drivers.ilo:IloVirtualMediaAgentDriver
agent_ipmitool = ironic.drivers.agent:AgentAndIPMIToolDriver
agent_pyghmi = ironic.drivers.agent:AgentAndIPMINativeDriver
agent_ssh = ironic.drivers.agent:AgentAndSSHDriver