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:
parent
571579a005
commit
5894ff356e
@ -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()
|
||||
|
@ -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):
|
||||
|
@ -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 %}
|
||||
|
@ -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."""
|
||||
|
||||
|
@ -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")
|
||||
|
@ -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 %}
|
||||
|
@ -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):
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user