iRMC: Support volume boot for iRMC virtual media boot interface
This patch enables iRMC virtual media boot interface to configure remote boot. When a node is set up for boot-from-volume, this interface registers volume boot information via out-of-band network. This interface supports iSCSI and FibreChannel. For the configuration, some extra parameters of volume connectors are required by python-scciclient. These parameters should be set to node's driver_info by an operator. Partial-Bug: #1677436 Change-Id: I387ae9382ebc561bc721dcfed6416b25f4809183
This commit is contained in:
parent
cffe41c238
commit
70530f9a66
@ -41,6 +41,7 @@ from ironic.drivers.modules import pxe
|
|||||||
|
|
||||||
|
|
||||||
scci = importutils.try_import('scciclient.irmc.scci')
|
scci = importutils.try_import('scciclient.irmc.scci')
|
||||||
|
viom = importutils.try_import('scciclient.irmc.viom.client')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if CONF.debug:
|
if CONF.debug:
|
||||||
@ -57,7 +58,22 @@ REQUIRED_PROPERTIES = {
|
|||||||
"Required."),
|
"Required."),
|
||||||
}
|
}
|
||||||
|
|
||||||
COMMON_PROPERTIES = REQUIRED_PROPERTIES
|
OPTIONAL_PROPERTIES = {
|
||||||
|
'irmc_pci_physical_ids':
|
||||||
|
_("Physical IDs of PCI cards. A dictionary of pairs of resource UUID "
|
||||||
|
"and its physical ID like '<UUID>:<Physical ID>,...'. The resources "
|
||||||
|
"are Ports and Volume connectors. The Physical ID consists of card "
|
||||||
|
"type, slot No, and port No. The format is "
|
||||||
|
"{LAN|FC|CNA}<slot-No>-<Port-No>. This parameter is necessary for "
|
||||||
|
"booting a node from a remote volume. Optional."),
|
||||||
|
'irmc_storage_network_size':
|
||||||
|
_("Size of the network for iSCSI storage network. It should be a "
|
||||||
|
"positive integer. This is necessary for booting a node from a "
|
||||||
|
"remote iSCSI volume. Optional."),
|
||||||
|
}
|
||||||
|
|
||||||
|
COMMON_PROPERTIES = REQUIRED_PROPERTIES.copy()
|
||||||
|
COMMON_PROPERTIES.update(OPTIONAL_PROPERTIES)
|
||||||
|
|
||||||
|
|
||||||
def _parse_config_option():
|
def _parse_config_option():
|
||||||
@ -522,7 +538,329 @@ def check_share_fs_mounted():
|
|||||||
share=CONF.irmc.remote_image_share_root)
|
share=CONF.irmc.remote_image_share_root)
|
||||||
|
|
||||||
|
|
||||||
class IRMCVirtualMediaBoot(base.BootInterface):
|
class IRMCVolumeBootMixIn(object):
|
||||||
|
"""Mix-in class for volume boot configuration to iRMC
|
||||||
|
|
||||||
|
iRMC has a feature to set up remote boot to a server. This feature can be
|
||||||
|
used by VIOM (Virtual I/O Manager) library of SCCI client.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _validate_volume_boot(self, task):
|
||||||
|
"""Validate information for volume boot with this interface.
|
||||||
|
|
||||||
|
This interface requires physical information of connectors to
|
||||||
|
configure remote boot to iRMC. Physical information of LAN ports
|
||||||
|
is also required since VIOM feature manages all adapters.
|
||||||
|
|
||||||
|
:param task: a TaskManager instance containing the node to act on.
|
||||||
|
:raises: InvalidParameterValue: If invalid value is set to resources.
|
||||||
|
:raises: MissingParameterValue: If some value is not set to resources.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not deploy_utils.get_remote_boot_volume(task):
|
||||||
|
# No boot volume. Nothing to validate.
|
||||||
|
return
|
||||||
|
|
||||||
|
irmc_common.parse_driver_info(task.node)
|
||||||
|
|
||||||
|
for port in task.ports:
|
||||||
|
self._validate_lan_port(task.node, port)
|
||||||
|
|
||||||
|
for vt in task.volume_targets:
|
||||||
|
if vt.volume_type == 'iscsi':
|
||||||
|
self._validate_iscsi_connectors(task)
|
||||||
|
elif vt.volume_type == 'fibre_channel':
|
||||||
|
self._validate_fc_connectors(task)
|
||||||
|
# Unknown volume type is filtered in storage interface validation.
|
||||||
|
|
||||||
|
def _get_connector_physical_id(self, task, types):
|
||||||
|
"""Get physical ID of volume connector.
|
||||||
|
|
||||||
|
A physical ID of volume connector required by iRMC is registered in
|
||||||
|
"irmc_pci_physical_ids" of a Node's driver_info as a pair of resource
|
||||||
|
UUID and its physical ID. This method gets this ID from the parameter.
|
||||||
|
|
||||||
|
:param task: a TaskManager instance containing the node to act on.
|
||||||
|
:param types: a list of types of volume connectors required for the
|
||||||
|
target volume. One of connectors must have a physical ID.
|
||||||
|
:raises InvalidParameterValue if a physical ID is invalid.
|
||||||
|
:returns: A physical ID of a volume connector.
|
||||||
|
"""
|
||||||
|
for vc in task.volume_connectors:
|
||||||
|
if vc.type not in types:
|
||||||
|
continue
|
||||||
|
pid = task.node.driver_info['irmc_pci_physical_ids'].get(vc.uuid)
|
||||||
|
if pid:
|
||||||
|
try:
|
||||||
|
viom.validate_physical_port_id(pid)
|
||||||
|
except scci.SCCIInvalidInputError as e:
|
||||||
|
raise exception.InvalidParameterValue(
|
||||||
|
_('Physical port information of volume connector '
|
||||||
|
'%(connector)s is invalid: %(error)') %
|
||||||
|
{'connector': vc.uuid, 'error': e})
|
||||||
|
return pid
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _validate_iscsi_connectors(self, task):
|
||||||
|
"""Validate if volume connectors are properly registered for iSCSI.
|
||||||
|
|
||||||
|
For connecting a node to a iSCSI volume, volume connectors containing
|
||||||
|
an IQNN and an IP address are necessary. One of connectors must have
|
||||||
|
a physical ID of the PCI card. Network size of a storage network is
|
||||||
|
also required by iRMC. which should be registered in the node's
|
||||||
|
driver_info.
|
||||||
|
|
||||||
|
:param task: a TaskManager instance containing the node to act on.
|
||||||
|
:raises: InvalidParameterValue if a volume connector with a required
|
||||||
|
type is not registered.
|
||||||
|
:raises: InvalidParameterValue if a physical ID is not registered in
|
||||||
|
any volume connectors.
|
||||||
|
:raises: InvalidParameterValue if a physical ID is invalid.
|
||||||
|
"""
|
||||||
|
vc_dict = self._get_volume_connectors_by_type(task)
|
||||||
|
node = task.node
|
||||||
|
missing_types = []
|
||||||
|
for vc_type in ('iqn', 'ip'):
|
||||||
|
vc = vc_dict.get(vc_type)
|
||||||
|
if not vc:
|
||||||
|
missing_types.append(vc_type)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if missing_types:
|
||||||
|
raise exception.MissingParameterValue(
|
||||||
|
_('Failed to validate for node %(node)s because of missing '
|
||||||
|
'volume connector(s) with type(s) %(types)s') %
|
||||||
|
{'node': node.uuid,
|
||||||
|
'types': ', '.join(missing_types)})
|
||||||
|
|
||||||
|
if not self._get_connector_physical_id(task, ['iqn', 'ip']):
|
||||||
|
raise exception.MissingParameterValue(
|
||||||
|
_('Failed to validate for node %(node)s because of missing '
|
||||||
|
'physical port information for iSCSI connector. This '
|
||||||
|
'information must be set in "pci_physical_ids" parameter of '
|
||||||
|
'node\'s driver_info as <connector uuid>:<physical id>.') %
|
||||||
|
{'node': node.uuid})
|
||||||
|
self._get_network_size(node)
|
||||||
|
|
||||||
|
def _validate_fc_connectors(self, task):
|
||||||
|
"""Validate if volume connectors are properly registered for FC.
|
||||||
|
|
||||||
|
For connecting a node to a FC volume, one of connectors representing
|
||||||
|
wwnn and wwpn must have a physical ID of the PCI card.
|
||||||
|
|
||||||
|
:param task: a TaskManager instance containing the node to act on.
|
||||||
|
:raises: InvalidParameterValue if a physical ID is not registered in
|
||||||
|
any volume connectors.
|
||||||
|
:raises: InvalidParameterValue if a physical ID is invalid.
|
||||||
|
"""
|
||||||
|
node = task.node
|
||||||
|
if not self._get_connector_physical_id(task, ['wwnn', 'wwpn']):
|
||||||
|
raise exception.MissingParameterValue(
|
||||||
|
_('Failed to validate for node %(node)s because of missing '
|
||||||
|
'physical port information for FC connector. This '
|
||||||
|
'information must be set in "pci_physical_ids" parameter of '
|
||||||
|
'node\'s driver_info as <connector uuid>:<physical id>.') %
|
||||||
|
{'node': node.uuid})
|
||||||
|
|
||||||
|
def _validate_lan_port(self, node, port):
|
||||||
|
"""Validate ports for VIOM configuration.
|
||||||
|
|
||||||
|
Physical information of LAN ports must be registered to VIOM
|
||||||
|
configuration to activate them under VIOM management. The information
|
||||||
|
has to be set to "irmc_pci_physical_id" parameter in a nodes
|
||||||
|
driver_info.
|
||||||
|
|
||||||
|
:param node: an ironic node object
|
||||||
|
:param port: a port to be validated
|
||||||
|
:raises: MissingParameterValue if a physical ID of the port is not set.
|
||||||
|
:raises: InvalidParameterValue if a physical ID is invalid.
|
||||||
|
"""
|
||||||
|
physical_id = node.driver_info['irmc_pci_physical_ids'].get(port.uuid)
|
||||||
|
if not physical_id:
|
||||||
|
raise exception.MissingParameterValue(
|
||||||
|
_('Failed to validate for node %(node)s because of '
|
||||||
|
'missing physical port information of port %(port)s. '
|
||||||
|
'This information should be contained in '
|
||||||
|
'"pci_physical_ids" parameter of node\'s driver_info.') %
|
||||||
|
{'node': node.uuid,
|
||||||
|
'port': port.uuid})
|
||||||
|
try:
|
||||||
|
viom.validate_physical_port_id(physical_id)
|
||||||
|
except scci.SCCIInvalidInputError as e:
|
||||||
|
raise exception.InvalidParameterValue(
|
||||||
|
_('Failed to validate for node %(node)s because '
|
||||||
|
'the physical port ID for port %(port)s in node\'s'
|
||||||
|
' driver_info is invalid: %(reason)s') %
|
||||||
|
{'node': node.uuid,
|
||||||
|
'port': port.uuid,
|
||||||
|
'reason': e})
|
||||||
|
|
||||||
|
def _get_network_size(self, node):
|
||||||
|
"""Get network size of a storage network.
|
||||||
|
|
||||||
|
The network size of iSCSI network is required by iRMC for connecting
|
||||||
|
a node to an iSCSI volume. This network size is set to node's
|
||||||
|
driver_info as "irmc_storage_network_size" parameter in the form of
|
||||||
|
positive integer.
|
||||||
|
|
||||||
|
:param node: an ironic node object.
|
||||||
|
:raises: MissingParameterValue if the network size parameter is not
|
||||||
|
set.
|
||||||
|
:raises: InvalidParameterValue the network size is invalid.
|
||||||
|
"""
|
||||||
|
network_size = node.driver_info.get('irmc_storage_network_size')
|
||||||
|
if network_size is None:
|
||||||
|
raise exception.MissingParameterValue(
|
||||||
|
_('Failed to validate for node %(node)s because of '
|
||||||
|
'missing "irmc_storage_network_size" parameter in the '
|
||||||
|
'node\'s driver_info. This should be a positive integer '
|
||||||
|
'smaller than 32.') %
|
||||||
|
{'node': node.uuid})
|
||||||
|
try:
|
||||||
|
network_size = int(network_size)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
raise exception.InvalidParameterValue(
|
||||||
|
_('Failed to validate for node %(node)s because '
|
||||||
|
'"irmc_storage_network_size" parameter in the node\'s '
|
||||||
|
'driver_info is invalid. This should be a '
|
||||||
|
'positive integer smaller than 32.') %
|
||||||
|
{'node': node.uuid})
|
||||||
|
|
||||||
|
if network_size not in range(1, 32):
|
||||||
|
raise exception.InvalidParameterValue(
|
||||||
|
_('Failed to validate for node %(node)s because '
|
||||||
|
'"irmc_storage_network_size" parameter in the node\'s '
|
||||||
|
'driver_info is invalid. This should be a '
|
||||||
|
'positive integer smaller than 32.') %
|
||||||
|
{'node': node.uuid})
|
||||||
|
|
||||||
|
return network_size
|
||||||
|
|
||||||
|
def _get_volume_connectors_by_type(self, task):
|
||||||
|
"""Create a dictionary of volume connectors by types.
|
||||||
|
|
||||||
|
:param task: a TaskManager.
|
||||||
|
:returns: a volume connector dictionary whose key is a connector type.
|
||||||
|
"""
|
||||||
|
connectors = {}
|
||||||
|
for vc in task.volume_connectors:
|
||||||
|
if vc.type in ('ip', 'iqn', 'wwnn', 'wwpn'):
|
||||||
|
connectors[vc.type] = vc
|
||||||
|
else:
|
||||||
|
LOG.warning('Node %(node)s has a volume_connector (%(uuid)s) '
|
||||||
|
'defined with an unsupported type: %(type)s.',
|
||||||
|
{'node': task.node.uuid,
|
||||||
|
'uuid': vc.uuid,
|
||||||
|
'type': vc.type})
|
||||||
|
return connectors
|
||||||
|
|
||||||
|
def _register_lan_ports(self, viom_conf, task):
|
||||||
|
"""Register ports to VIOM configuration.
|
||||||
|
|
||||||
|
LAN ports information must be registered for VIOM configuration to
|
||||||
|
activate them under VIOM management.
|
||||||
|
|
||||||
|
:param viom_conf: a configurator for iRMC
|
||||||
|
:param task: a TaskManager instance containing the node to act on.
|
||||||
|
"""
|
||||||
|
for port in task.ports:
|
||||||
|
viom_conf.set_lan_port(
|
||||||
|
task.node.driver_info['irmc_pci_physical_ids'].get(port.uuid))
|
||||||
|
|
||||||
|
def _configure_boot_from_volume(self, task):
|
||||||
|
"""Set information for booting from a remote volume to iRMC.
|
||||||
|
|
||||||
|
:param task: a TaskManager instance containing the node to act on.
|
||||||
|
:raises: IRMCOperationError if iRMC operation failed
|
||||||
|
"""
|
||||||
|
|
||||||
|
irmc_info = irmc_common.parse_driver_info(task.node)
|
||||||
|
viom_conf = viom.VIOMConfiguration(irmc_info,
|
||||||
|
identification=task.node.uuid)
|
||||||
|
|
||||||
|
self._register_lan_ports(viom_conf, task)
|
||||||
|
|
||||||
|
for vt in task.volume_targets:
|
||||||
|
if vt.volume_type == 'iscsi':
|
||||||
|
self._set_iscsi_target(task, viom_conf, vt)
|
||||||
|
elif vt.volume_type == 'fibre_channel':
|
||||||
|
self._set_fc_target(task, viom_conf, vt)
|
||||||
|
|
||||||
|
try:
|
||||||
|
LOG.debug('Set VIOM configuration for node %(node)s: %(table)s',
|
||||||
|
{'node': task.node.uuid,
|
||||||
|
'table': viom_conf.dump_json()})
|
||||||
|
viom_conf.apply()
|
||||||
|
except scci.SCCIError as e:
|
||||||
|
LOG.error('iRMC failed to set VIOM configuration for node '
|
||||||
|
'%(node)s: %(error)s',
|
||||||
|
{'node': task.node.uuid,
|
||||||
|
'error': e})
|
||||||
|
raise exception.IRMCOperationError(
|
||||||
|
operation='Configure VIOM', error=e)
|
||||||
|
|
||||||
|
def _set_iscsi_target(self, task, viom_conf, target):
|
||||||
|
"""Set information for iSCSI boot to VIOM configuration."""
|
||||||
|
connectors = self._get_volume_connectors_by_type(task)
|
||||||
|
target_portal = target.properties['target_portal']
|
||||||
|
if ':' in target_portal:
|
||||||
|
target_host, target_port = target_portal.split(':')
|
||||||
|
else:
|
||||||
|
target_host = target_portal
|
||||||
|
target_port = None
|
||||||
|
if target.properties.get('auth_method') == 'CHAP':
|
||||||
|
chap_user = target.properties.get('auth_username')
|
||||||
|
chap_secret = target.properties.get('auth_password')
|
||||||
|
else:
|
||||||
|
chap_user = None
|
||||||
|
chap_secret = None
|
||||||
|
|
||||||
|
viom_conf.set_iscsi_volume(
|
||||||
|
self._get_connector_physical_id(task, ['iqn', 'ip']),
|
||||||
|
connectors['iqn'].connector_id,
|
||||||
|
initiator_ip=connectors['ip'].connector_id,
|
||||||
|
initiator_netmask=self._get_network_size(task.node),
|
||||||
|
target_iqn=target.properties['target_iqn'],
|
||||||
|
target_ip=target_host,
|
||||||
|
target_port=target_port,
|
||||||
|
target_lun=target.properties.get('target_lun'),
|
||||||
|
# Boot priority starts from 1 in the library.
|
||||||
|
boot_prio=target.boot_index + 1,
|
||||||
|
chap_user=chap_user,
|
||||||
|
chap_secret=chap_secret)
|
||||||
|
|
||||||
|
def _set_fc_target(self, task, viom_conf, target):
|
||||||
|
"""Set information for FC boot to VIOM configuration."""
|
||||||
|
wwn = target.properties['target_wwn']
|
||||||
|
if isinstance(wwn, list):
|
||||||
|
wwn = wwn[0]
|
||||||
|
viom_conf.set_fc_volume(
|
||||||
|
self._get_connector_physical_id(task, ['wwnn', 'wwpn']),
|
||||||
|
wwn,
|
||||||
|
target.properties['target_lun'],
|
||||||
|
# Boot priority starts from 1 in the library.
|
||||||
|
boot_prio=target.boot_index + 1)
|
||||||
|
|
||||||
|
def _cleanup_boot_from_volume(self, task, reboot=False):
|
||||||
|
"""Clear remote boot configuration.
|
||||||
|
|
||||||
|
:param task: a task from TaskManager.
|
||||||
|
:param reboot: True if reboot node soon
|
||||||
|
:raises: IRMCOperationError if iRMC operation failed
|
||||||
|
"""
|
||||||
|
irmc_info = irmc_common.parse_driver_info(task.node)
|
||||||
|
try:
|
||||||
|
viom_conf = viom.VIOMConfiguration(irmc_info, task.node.uuid)
|
||||||
|
viom_conf.terminate(reboot=reboot)
|
||||||
|
except scci.SCCIError as e:
|
||||||
|
LOG.error('iRMC failed to terminate VIOM configuration from '
|
||||||
|
'node %(node)s: %(error)s', {'node': task.node.uuid,
|
||||||
|
'error': e})
|
||||||
|
raise exception.IRMCOperationError(operation='Terminate VIOM',
|
||||||
|
error=e)
|
||||||
|
|
||||||
|
|
||||||
|
class IRMCVirtualMediaBoot(base.BootInterface, IRMCVolumeBootMixIn):
|
||||||
"""iRMC Virtual Media boot-related actions."""
|
"""iRMC Virtual Media boot-related actions."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -533,6 +871,7 @@ class IRMCVirtualMediaBoot(base.BootInterface):
|
|||||||
:raises: InvalidParameterValue, if config option has invalid value.
|
:raises: InvalidParameterValue, if config option has invalid value.
|
||||||
"""
|
"""
|
||||||
check_share_fs_mounted()
|
check_share_fs_mounted()
|
||||||
|
self.capabilities = ['iscsi_volume_boot', 'fc_volume_boot']
|
||||||
super(IRMCVirtualMediaBoot, self).__init__()
|
super(IRMCVirtualMediaBoot, self).__init__()
|
||||||
|
|
||||||
def get_properties(self):
|
def get_properties(self):
|
||||||
@ -553,6 +892,13 @@ class IRMCVirtualMediaBoot(base.BootInterface):
|
|||||||
"""
|
"""
|
||||||
check_share_fs_mounted()
|
check_share_fs_mounted()
|
||||||
|
|
||||||
|
self._validate_volume_boot(task)
|
||||||
|
if not task.driver.storage.should_write_image(task):
|
||||||
|
LOG.debug('Node %(node) skips image validation because of booting '
|
||||||
|
'from a remote volume.',
|
||||||
|
{'node': task.node.uuid})
|
||||||
|
return
|
||||||
|
|
||||||
d_info = _parse_deploy_info(task.node)
|
d_info = _parse_deploy_info(task.node)
|
||||||
if task.node.driver_internal_info.get('is_whole_disk_image'):
|
if task.node.driver_internal_info.get('is_whole_disk_image'):
|
||||||
props = []
|
props = []
|
||||||
@ -594,6 +940,12 @@ class IRMCVirtualMediaBoot(base.BootInterface):
|
|||||||
if task.node.provision_state == states.DEPLOYING:
|
if task.node.provision_state == states.DEPLOYING:
|
||||||
irmc_management.backup_bios_config(task)
|
irmc_management.backup_bios_config(task)
|
||||||
|
|
||||||
|
if not task.driver.storage.should_write_image(task):
|
||||||
|
LOG.debug('Node %(node) skips ramdisk preparation because of '
|
||||||
|
'booting from a remote volume.',
|
||||||
|
{'node': task.node.uuid})
|
||||||
|
return
|
||||||
|
|
||||||
deploy_nic_mac = deploy_utils.get_single_nic_with_vif_port_id(task)
|
deploy_nic_mac = deploy_utils.get_single_nic_with_vif_port_id(task)
|
||||||
ramdisk_params['BOOTIF'] = deploy_nic_mac
|
ramdisk_params['BOOTIF'] = deploy_nic_mac
|
||||||
|
|
||||||
@ -622,6 +974,13 @@ class IRMCVirtualMediaBoot(base.BootInterface):
|
|||||||
:param task: a task from TaskManager.
|
:param task: a task from TaskManager.
|
||||||
:returns: None
|
:returns: None
|
||||||
"""
|
"""
|
||||||
|
if task.node.driver_internal_info.get('boot_from_volume'):
|
||||||
|
LOG.debug('Node %(node) is configured for booting from a remote '
|
||||||
|
'volume.',
|
||||||
|
{'node': task.node.uuid})
|
||||||
|
self._configure_boot_from_volume(task)
|
||||||
|
return
|
||||||
|
|
||||||
_cleanup_vmedia_boot(task)
|
_cleanup_vmedia_boot(task)
|
||||||
|
|
||||||
node = task.node
|
node = task.node
|
||||||
@ -645,6 +1004,10 @@ class IRMCVirtualMediaBoot(base.BootInterface):
|
|||||||
:returns: None
|
:returns: None
|
||||||
:raises: IRMCOperationError if iRMC operation failed.
|
:raises: IRMCOperationError if iRMC operation failed.
|
||||||
"""
|
"""
|
||||||
|
if task.node.driver_internal_info.get('boot_from_volume'):
|
||||||
|
self._cleanup_boot_from_volume(task)
|
||||||
|
return
|
||||||
|
|
||||||
_remove_share_file(_get_boot_iso_name(task.node))
|
_remove_share_file(_get_boot_iso_name(task.node))
|
||||||
driver_internal_info = task.node.driver_internal_info
|
driver_internal_info = task.node.driver_internal_info
|
||||||
driver_internal_info.pop('irmc_boot_iso', None)
|
driver_internal_info.pop('irmc_boot_iso', None)
|
||||||
|
@ -23,6 +23,7 @@ import tempfile
|
|||||||
from ironic_lib import utils as ironic_utils
|
from ironic_lib import utils as ironic_utils
|
||||||
import mock
|
import mock
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
from oslo_utils import uuidutils
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from ironic.common import boot_devices
|
from ironic.common import boot_devices
|
||||||
@ -41,6 +42,8 @@ from ironic.drivers.modules import pxe
|
|||||||
from ironic.tests.unit.conductor import mgr_utils
|
from ironic.tests.unit.conductor import mgr_utils
|
||||||
from ironic.tests.unit.db import base as db_base
|
from ironic.tests.unit.db import base as db_base
|
||||||
from ironic.tests.unit.db import utils as db_utils
|
from ironic.tests.unit.db import utils as db_utils
|
||||||
|
from ironic.tests.unit.drivers import third_party_driver_mock_specs \
|
||||||
|
as mock_specs
|
||||||
from ironic.tests.unit.objects import utils as obj_utils
|
from ironic.tests.unit.objects import utils as obj_utils
|
||||||
|
|
||||||
if six.PY3:
|
if six.PY3:
|
||||||
@ -50,6 +53,19 @@ if six.PY3:
|
|||||||
|
|
||||||
INFO_DICT = db_utils.get_test_irmc_info()
|
INFO_DICT = db_utils.get_test_irmc_info()
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
PARSED_IFNO = {
|
||||||
|
'irmc_address': '1.2.3.4',
|
||||||
|
'irmc_port': 80,
|
||||||
|
'irmc_username': 'admin0',
|
||||||
|
'irmc_password': 'fake0',
|
||||||
|
'irmc_auth_method': 'digest',
|
||||||
|
'irmc_client_timeout': 60,
|
||||||
|
'irmc_snmp_community': 'public',
|
||||||
|
'irmc_snmp_port': 161,
|
||||||
|
'irmc_snmp_version': 'v2c',
|
||||||
|
'irmc_snmp_security': None,
|
||||||
|
'irmc_sensor_method': 'ipmitool',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class IRMCDeployPrivateMethodsTestCase(db_base.DbTestCase):
|
class IRMCDeployPrivateMethodsTestCase(db_base.DbTestCase):
|
||||||
@ -1215,3 +1231,439 @@ class IRMCPXEBootTestCase(db_base.DbTestCase):
|
|||||||
self.assertFalse(mock_set_secure_boot_mode.called)
|
self.assertFalse(mock_set_secure_boot_mode.called)
|
||||||
mock_clean_up_instance.assert_called_once_with(
|
mock_clean_up_instance.assert_called_once_with(
|
||||||
task.driver.boot, task)
|
task.driver.boot, task)
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch.object(irmc_boot, 'viom',
|
||||||
|
spec_set=mock_specs.SCCICLIENT_VIOM_SPEC)
|
||||||
|
class IRMCVirtualMediaBootWithVolumeTestCase(db_base.DbTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(IRMCVirtualMediaBootWithVolumeTestCase, self).setUp()
|
||||||
|
irmc_boot.check_share_fs_mounted_patcher.start()
|
||||||
|
self.addCleanup(irmc_boot.check_share_fs_mounted_patcher.stop)
|
||||||
|
self.config(enabled_hardware_types=['irmc'],
|
||||||
|
enabled_boot_interfaces=['irmc-virtual-media'],
|
||||||
|
enabled_deploy_interfaces=['direct'],
|
||||||
|
enabled_power_interfaces=['irmc'],
|
||||||
|
enabled_management_interfaces=['irmc'],
|
||||||
|
enabled_storage_interfaces=['cinder'])
|
||||||
|
driver_info = INFO_DICT
|
||||||
|
d_in_info = dict(boot_from_volume='volume-uuid')
|
||||||
|
self.node = obj_utils.create_test_node(self.context,
|
||||||
|
driver='irmc',
|
||||||
|
driver_info=driver_info,
|
||||||
|
storage_interface='cinder',
|
||||||
|
driver_internal_info=d_in_info)
|
||||||
|
|
||||||
|
def _create_mock_conf(self, mock_viom):
|
||||||
|
mock_conf = mock.Mock(spec_set=mock_specs.SCCICLIENT_VIOM_CONF_SPEC)
|
||||||
|
mock_viom.VIOMConfiguration.return_value = mock_conf
|
||||||
|
return mock_conf
|
||||||
|
|
||||||
|
def _add_pci_physical_id(self, uuid, physical_id):
|
||||||
|
driver_info = self.node.driver_info
|
||||||
|
ids = driver_info.get('irmc_pci_physical_ids', {})
|
||||||
|
ids[uuid] = physical_id
|
||||||
|
driver_info['irmc_pci_physical_ids'] = ids
|
||||||
|
self.node.driver_info = driver_info
|
||||||
|
self.node.save()
|
||||||
|
|
||||||
|
def _create_port(self, physical_id='LAN0-1', **kwargs):
|
||||||
|
uuid = uuidutils.generate_uuid()
|
||||||
|
obj_utils.create_test_port(self.context,
|
||||||
|
uuid=uuid,
|
||||||
|
node_id=self.node.id,
|
||||||
|
**kwargs)
|
||||||
|
if physical_id:
|
||||||
|
self._add_pci_physical_id(uuid, physical_id)
|
||||||
|
|
||||||
|
def _create_iscsi_iqn_connector(self, physical_id='CNA1-1'):
|
||||||
|
uuid = uuidutils.generate_uuid()
|
||||||
|
obj_utils.create_test_volume_connector(
|
||||||
|
self.context,
|
||||||
|
uuid=uuid,
|
||||||
|
type='iqn',
|
||||||
|
node_id=self.node.id,
|
||||||
|
connector_id='iqn.initiator')
|
||||||
|
if physical_id:
|
||||||
|
self._add_pci_physical_id(uuid, physical_id)
|
||||||
|
|
||||||
|
def _create_iscsi_ip_connector(self, physical_id=None, network_size='24'):
|
||||||
|
uuid = uuidutils.generate_uuid()
|
||||||
|
obj_utils.create_test_volume_connector(
|
||||||
|
self.context,
|
||||||
|
uuid=uuid,
|
||||||
|
type='ip',
|
||||||
|
node_id=self.node.id,
|
||||||
|
connector_id='192.168.11.11')
|
||||||
|
if physical_id:
|
||||||
|
self._add_pci_physical_id(uuid, physical_id)
|
||||||
|
if network_size:
|
||||||
|
driver_info = self.node.driver_info
|
||||||
|
driver_info['irmc_storage_network_size'] = network_size
|
||||||
|
self.node.driver_info = driver_info
|
||||||
|
self.node.save()
|
||||||
|
|
||||||
|
def _create_iscsi_target(self, target_info=None, boot_index=0, **kwargs):
|
||||||
|
target_properties = {
|
||||||
|
'target_portal': '192.168.22.22:3260',
|
||||||
|
'target_iqn': 'iqn.target',
|
||||||
|
'target_lun': 1,
|
||||||
|
}
|
||||||
|
if target_info:
|
||||||
|
target_properties.update(target_info)
|
||||||
|
obj_utils.create_test_volume_target(
|
||||||
|
self.context,
|
||||||
|
volume_type='iscsi',
|
||||||
|
node_id=self.node.id,
|
||||||
|
boot_index=boot_index,
|
||||||
|
properties=target_properties,
|
||||||
|
**kwargs)
|
||||||
|
|
||||||
|
def _create_iscsi_resources(self):
|
||||||
|
self._create_iscsi_iqn_connector()
|
||||||
|
self._create_iscsi_ip_connector()
|
||||||
|
self._create_iscsi_target()
|
||||||
|
|
||||||
|
def _create_fc_connector(self):
|
||||||
|
uuid = uuidutils.generate_uuid()
|
||||||
|
obj_utils.create_test_volume_connector(
|
||||||
|
self.context,
|
||||||
|
uuid=uuid,
|
||||||
|
type='wwnn',
|
||||||
|
node_id=self.node.id,
|
||||||
|
connector_id='11:22:33:44:55')
|
||||||
|
self._add_pci_physical_id(uuid, 'FC2-1')
|
||||||
|
obj_utils.create_test_volume_connector(
|
||||||
|
self.context,
|
||||||
|
uuid=uuidutils.generate_uuid(),
|
||||||
|
type='wwpn',
|
||||||
|
node_id=self.node.id,
|
||||||
|
connector_id='11:22:33:44:56')
|
||||||
|
|
||||||
|
def _create_fc_target(self):
|
||||||
|
target_properties = {
|
||||||
|
'target_wwn': 'aa:bb:cc:dd:ee',
|
||||||
|
'target_lun': 2,
|
||||||
|
}
|
||||||
|
obj_utils.create_test_volume_target(
|
||||||
|
self.context,
|
||||||
|
volume_type='fibre_channel',
|
||||||
|
node_id=self.node.id,
|
||||||
|
boot_index=0,
|
||||||
|
properties=target_properties)
|
||||||
|
|
||||||
|
def _create_fc_resources(self):
|
||||||
|
self._create_fc_connector()
|
||||||
|
self._create_fc_target()
|
||||||
|
|
||||||
|
def _call_validate(self):
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
task.driver.boot.validate(task)
|
||||||
|
|
||||||
|
def test_validate_iscsi(self, mock_viom):
|
||||||
|
self._create_port()
|
||||||
|
self._create_iscsi_resources()
|
||||||
|
self._call_validate()
|
||||||
|
self.assertEqual([mock.call('LAN0-1'), mock.call('CNA1-1')],
|
||||||
|
mock_viom.validate_physical_port_id.call_args_list)
|
||||||
|
|
||||||
|
def test_validate_no_physical_id_in_lan_port(self, mock_viom):
|
||||||
|
self._create_port(physical_id=None)
|
||||||
|
self._create_iscsi_resources()
|
||||||
|
self.assertRaises(exception.MissingParameterValue,
|
||||||
|
self._call_validate)
|
||||||
|
|
||||||
|
@mock.patch.object(irmc_boot, 'scci',
|
||||||
|
spec_set=mock_specs.SCCICLIENT_IRMC_SCCI_SPEC)
|
||||||
|
def test_validate_invalid_physical_id_in_lan_port(self, mock_scci,
|
||||||
|
mock_viom):
|
||||||
|
self._create_port(physical_id='wrong-id')
|
||||||
|
self._create_iscsi_resources()
|
||||||
|
|
||||||
|
mock_viom.validate_physical_port_id.side_effect = (
|
||||||
|
Exception('fake error'))
|
||||||
|
mock_scci.SCCIInvalidInputError = Exception
|
||||||
|
self.assertRaises(exception.InvalidParameterValue,
|
||||||
|
self._call_validate)
|
||||||
|
|
||||||
|
def test_validate_iscsi_connector_no_ip(self, mock_viom):
|
||||||
|
self._create_port()
|
||||||
|
self._create_iscsi_iqn_connector()
|
||||||
|
self._create_iscsi_target()
|
||||||
|
|
||||||
|
self.assertRaises(exception.MissingParameterValue,
|
||||||
|
self._call_validate)
|
||||||
|
|
||||||
|
def test_validate_iscsi_connector_no_iqn(self, mock_viom):
|
||||||
|
self._create_port()
|
||||||
|
self._create_iscsi_ip_connector(physical_id='CNA1-1')
|
||||||
|
self._create_iscsi_target()
|
||||||
|
|
||||||
|
self.assertRaises(exception.MissingParameterValue,
|
||||||
|
self._call_validate)
|
||||||
|
|
||||||
|
def test_validate_iscsi_connector_no_netmask(self, mock_viom):
|
||||||
|
self._create_port()
|
||||||
|
self._create_iscsi_iqn_connector()
|
||||||
|
self._create_iscsi_ip_connector(network_size=None)
|
||||||
|
self._create_iscsi_target()
|
||||||
|
|
||||||
|
self.assertRaises(exception.MissingParameterValue,
|
||||||
|
self._call_validate)
|
||||||
|
|
||||||
|
def test_validate_iscsi_connector_invalid_netmask(self, mock_viom):
|
||||||
|
self._create_port()
|
||||||
|
self._create_iscsi_iqn_connector()
|
||||||
|
self._create_iscsi_ip_connector(network_size='worng-netmask')
|
||||||
|
self._create_iscsi_target()
|
||||||
|
|
||||||
|
self.assertRaises(exception.InvalidParameterValue,
|
||||||
|
self._call_validate)
|
||||||
|
|
||||||
|
def test_validate_iscsi_connector_too_small_netmask(self, mock_viom):
|
||||||
|
self._create_port()
|
||||||
|
self._create_iscsi_iqn_connector()
|
||||||
|
self._create_iscsi_ip_connector(network_size='0')
|
||||||
|
self._create_iscsi_target()
|
||||||
|
|
||||||
|
self.assertRaises(exception.InvalidParameterValue,
|
||||||
|
self._call_validate)
|
||||||
|
|
||||||
|
def test_validate_iscsi_connector_too_large_netmask(self, mock_viom):
|
||||||
|
self._create_port()
|
||||||
|
self._create_iscsi_iqn_connector()
|
||||||
|
self._create_iscsi_ip_connector(network_size='32')
|
||||||
|
self._create_iscsi_target()
|
||||||
|
|
||||||
|
self.assertRaises(exception.InvalidParameterValue,
|
||||||
|
self._call_validate)
|
||||||
|
|
||||||
|
def test_validate_iscsi_connector_no_physical_id(self, mock_viom):
|
||||||
|
self._create_port()
|
||||||
|
self._create_iscsi_iqn_connector(physical_id=None)
|
||||||
|
self._create_iscsi_ip_connector()
|
||||||
|
self._create_iscsi_target()
|
||||||
|
|
||||||
|
self.assertRaises(exception.MissingParameterValue,
|
||||||
|
self._call_validate)
|
||||||
|
|
||||||
|
@mock.patch.object(deploy_utils, 'get_single_nic_with_vif_port_id')
|
||||||
|
def test_prepare_ramdisk_skip(self, mock_nic, mock_viom):
|
||||||
|
self._create_iscsi_resources()
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
task.node.provision_state = states.DEPLOYING
|
||||||
|
task.driver.boot.prepare_ramdisk(task, {})
|
||||||
|
mock_nic.assert_not_called()
|
||||||
|
|
||||||
|
@mock.patch.object(irmc_boot, '_cleanup_vmedia_boot')
|
||||||
|
def test_prepare_instance(self, mock_clean, mock_viom):
|
||||||
|
mock_conf = self._create_mock_conf(mock_viom)
|
||||||
|
self._create_port()
|
||||||
|
self._create_iscsi_resources()
|
||||||
|
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
task.driver.boot.prepare_instance(task)
|
||||||
|
mock_clean.assert_not_called()
|
||||||
|
|
||||||
|
mock_conf.set_iscsi_volume.assert_called_once_with(
|
||||||
|
'CNA1-1',
|
||||||
|
'iqn.initiator',
|
||||||
|
initiator_ip='192.168.11.11',
|
||||||
|
initiator_netmask=24,
|
||||||
|
target_iqn='iqn.target',
|
||||||
|
target_ip='192.168.22.22',
|
||||||
|
target_port='3260',
|
||||||
|
target_lun=1,
|
||||||
|
boot_prio=1,
|
||||||
|
chap_user=None,
|
||||||
|
chap_secret=None)
|
||||||
|
mock_conf.set_lan_port.assert_called_once_with('LAN0-1')
|
||||||
|
mock_viom.validate_physical_port_id.assert_called_once_with('CNA1-1')
|
||||||
|
self._assert_viom_apply(mock_viom, mock_conf)
|
||||||
|
|
||||||
|
def _call__configure_boot_from_volume(self):
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
task.driver.boot._configure_boot_from_volume(task)
|
||||||
|
|
||||||
|
def _assert_viom_apply(self, mock_viom, mock_conf):
|
||||||
|
mock_conf.apply.assert_called_once_with()
|
||||||
|
mock_conf.dump_json.assert_called_once_with()
|
||||||
|
mock_viom.VIOMConfiguration.assert_called_once_with(
|
||||||
|
PARSED_IFNO, identification=self.node.uuid)
|
||||||
|
|
||||||
|
def test__configure_boot_from_volume_iscsi(self, mock_viom):
|
||||||
|
mock_conf = self._create_mock_conf(mock_viom)
|
||||||
|
self._create_port()
|
||||||
|
self._create_iscsi_resources()
|
||||||
|
|
||||||
|
self._call__configure_boot_from_volume()
|
||||||
|
|
||||||
|
mock_conf.set_iscsi_volume.assert_called_once_with(
|
||||||
|
'CNA1-1',
|
||||||
|
'iqn.initiator',
|
||||||
|
initiator_ip='192.168.11.11',
|
||||||
|
initiator_netmask=24,
|
||||||
|
target_iqn='iqn.target',
|
||||||
|
target_ip='192.168.22.22',
|
||||||
|
target_port='3260',
|
||||||
|
target_lun=1,
|
||||||
|
boot_prio=1,
|
||||||
|
chap_user=None,
|
||||||
|
chap_secret=None)
|
||||||
|
mock_conf.set_lan_port.assert_called_once_with('LAN0-1')
|
||||||
|
mock_viom.validate_physical_port_id.assert_called_once_with('CNA1-1')
|
||||||
|
self._assert_viom_apply(mock_viom, mock_conf)
|
||||||
|
|
||||||
|
def test__configure_boot_from_volume_multi_lan_ports(self, mock_viom):
|
||||||
|
mock_conf = self._create_mock_conf(mock_viom)
|
||||||
|
self._create_port()
|
||||||
|
self._create_port(physical_id='LAN0-2',
|
||||||
|
address='52:54:00:cf:2d:32')
|
||||||
|
self._create_iscsi_resources()
|
||||||
|
|
||||||
|
self._call__configure_boot_from_volume()
|
||||||
|
|
||||||
|
mock_conf.set_iscsi_volume.assert_called_once_with(
|
||||||
|
'CNA1-1',
|
||||||
|
'iqn.initiator',
|
||||||
|
initiator_ip='192.168.11.11',
|
||||||
|
initiator_netmask=24,
|
||||||
|
target_iqn='iqn.target',
|
||||||
|
target_ip='192.168.22.22',
|
||||||
|
target_port='3260',
|
||||||
|
target_lun=1,
|
||||||
|
boot_prio=1,
|
||||||
|
chap_user=None,
|
||||||
|
chap_secret=None)
|
||||||
|
self.assertEqual([mock.call('LAN0-1'), mock.call('LAN0-2')],
|
||||||
|
mock_conf.set_lan_port.call_args_list)
|
||||||
|
mock_viom.validate_physical_port_id.assert_called_once_with('CNA1-1')
|
||||||
|
self._assert_viom_apply(mock_viom, mock_conf)
|
||||||
|
|
||||||
|
def test__configure_boot_from_volume_iscsi_no_portal_port(self, mock_viom):
|
||||||
|
mock_conf = self._create_mock_conf(mock_viom)
|
||||||
|
self._create_port()
|
||||||
|
self._create_iscsi_iqn_connector()
|
||||||
|
self._create_iscsi_ip_connector()
|
||||||
|
self._create_iscsi_target(
|
||||||
|
target_info=dict(target_portal='192.168.22.23'))
|
||||||
|
|
||||||
|
self._call__configure_boot_from_volume()
|
||||||
|
|
||||||
|
mock_conf.set_iscsi_volume.assert_called_once_with(
|
||||||
|
'CNA1-1',
|
||||||
|
'iqn.initiator',
|
||||||
|
initiator_ip='192.168.11.11',
|
||||||
|
initiator_netmask=24,
|
||||||
|
target_iqn='iqn.target',
|
||||||
|
target_ip='192.168.22.23',
|
||||||
|
target_port=None,
|
||||||
|
target_lun=1,
|
||||||
|
boot_prio=1,
|
||||||
|
chap_user=None,
|
||||||
|
chap_secret=None)
|
||||||
|
mock_conf.set_lan_port.assert_called_once_with('LAN0-1')
|
||||||
|
mock_viom.validate_physical_port_id.assert_called_once_with('CNA1-1')
|
||||||
|
self._assert_viom_apply(mock_viom, mock_conf)
|
||||||
|
|
||||||
|
def test__configure_boot_from_volume_iscsi_chap(self, mock_viom):
|
||||||
|
mock_conf = self._create_mock_conf(mock_viom)
|
||||||
|
self._create_port()
|
||||||
|
self._create_iscsi_iqn_connector()
|
||||||
|
self._create_iscsi_ip_connector()
|
||||||
|
self._create_iscsi_target(
|
||||||
|
target_info=dict(auth_method='CHAP',
|
||||||
|
auth_username='chapuser',
|
||||||
|
auth_password='chappass'))
|
||||||
|
|
||||||
|
self._call__configure_boot_from_volume()
|
||||||
|
|
||||||
|
mock_conf.set_iscsi_volume.assert_called_once_with(
|
||||||
|
'CNA1-1',
|
||||||
|
'iqn.initiator',
|
||||||
|
initiator_ip='192.168.11.11',
|
||||||
|
initiator_netmask=24,
|
||||||
|
target_iqn='iqn.target',
|
||||||
|
target_ip='192.168.22.22',
|
||||||
|
target_port='3260',
|
||||||
|
target_lun=1,
|
||||||
|
boot_prio=1,
|
||||||
|
chap_user='chapuser',
|
||||||
|
chap_secret='chappass')
|
||||||
|
mock_conf.set_lan_port.assert_called_once_with('LAN0-1')
|
||||||
|
mock_viom.validate_physical_port_id.assert_called_once_with('CNA1-1')
|
||||||
|
self._assert_viom_apply(mock_viom, mock_conf)
|
||||||
|
|
||||||
|
def test__configure_boot_from_volume_fc(self, mock_viom):
|
||||||
|
mock_conf = self._create_mock_conf(mock_viom)
|
||||||
|
self._create_port()
|
||||||
|
self._create_fc_connector()
|
||||||
|
self._create_fc_target()
|
||||||
|
|
||||||
|
self._call__configure_boot_from_volume()
|
||||||
|
|
||||||
|
mock_conf.set_fc_volume.assert_called_once_with(
|
||||||
|
'FC2-1',
|
||||||
|
'aa:bb:cc:dd:ee',
|
||||||
|
2,
|
||||||
|
boot_prio=1)
|
||||||
|
mock_conf.set_lan_port.assert_called_once_with('LAN0-1')
|
||||||
|
mock_viom.validate_physical_port_id.assert_called_once_with('FC2-1')
|
||||||
|
self._assert_viom_apply(mock_viom, mock_conf)
|
||||||
|
|
||||||
|
@mock.patch.object(irmc_boot, 'scci',
|
||||||
|
spec_set=mock_specs.SCCICLIENT_IRMC_SCCI_SPEC)
|
||||||
|
def test__configure_boot_from_volume_apply_error(self, mock_scci,
|
||||||
|
mock_viom):
|
||||||
|
mock_conf = self._create_mock_conf(mock_viom)
|
||||||
|
self._create_port()
|
||||||
|
self._create_fc_connector()
|
||||||
|
self._create_fc_target()
|
||||||
|
mock_conf.apply.side_effect = Exception('fake scci error')
|
||||||
|
mock_scci.SCCIError = Exception
|
||||||
|
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
self.assertRaises(exception.IRMCOperationError,
|
||||||
|
task.driver.boot._configure_boot_from_volume,
|
||||||
|
task)
|
||||||
|
|
||||||
|
mock_conf.set_fc_volume.assert_called_once_with(
|
||||||
|
'FC2-1',
|
||||||
|
'aa:bb:cc:dd:ee',
|
||||||
|
2,
|
||||||
|
boot_prio=1)
|
||||||
|
mock_conf.set_lan_port.assert_called_once_with('LAN0-1')
|
||||||
|
mock_viom.validate_physical_port_id.assert_called_once_with('FC2-1')
|
||||||
|
self._assert_viom_apply(mock_viom, mock_conf)
|
||||||
|
|
||||||
|
def test_clean_up_instance(self, mock_viom):
|
||||||
|
mock_conf = self._create_mock_conf(mock_viom)
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
task.driver.boot.clean_up_instance(task)
|
||||||
|
|
||||||
|
mock_viom.VIOMConfiguration.assert_called_once_with(PARSED_IFNO,
|
||||||
|
self.node.uuid)
|
||||||
|
mock_conf.terminate.assert_called_once_with(reboot=False)
|
||||||
|
|
||||||
|
def test_clean_up_instance_error(self, mock_viom):
|
||||||
|
mock_conf = self._create_mock_conf(mock_viom)
|
||||||
|
mock_conf.terminate.side_effect = Exception('fake error')
|
||||||
|
irmc_boot.scci.SCCIError = Exception
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
self.assertRaises(exception.IRMCOperationError,
|
||||||
|
task.driver.boot.clean_up_instance,
|
||||||
|
task)
|
||||||
|
|
||||||
|
mock_viom.VIOMConfiguration.assert_called_once_with(PARSED_IFNO,
|
||||||
|
self.node.uuid)
|
||||||
|
mock_conf.terminate.assert_called_once_with(reboot=False)
|
||||||
|
|
||||||
|
def test__cleanup_boot_from_volume(self, mock_viom):
|
||||||
|
mock_conf = self._create_mock_conf(mock_viom)
|
||||||
|
with task_manager.acquire(self.context, self.node.uuid) as task:
|
||||||
|
task.driver.boot._cleanup_boot_from_volume(task)
|
||||||
|
|
||||||
|
mock_viom.VIOMConfiguration.assert_called_once_with(PARSED_IFNO,
|
||||||
|
self.node.uuid)
|
||||||
|
mock_conf.terminate.assert_called_once_with(reboot=False)
|
||||||
|
@ -93,6 +93,7 @@ SCCICLIENT_IRMC_SCCI_SPEC = (
|
|||||||
'UNMOUNT_FD',
|
'UNMOUNT_FD',
|
||||||
'SCCIError',
|
'SCCIError',
|
||||||
'SCCIClientError',
|
'SCCIClientError',
|
||||||
|
'SCCIError',
|
||||||
'SCCIInvalidInputError',
|
'SCCIInvalidInputError',
|
||||||
'get_share_type',
|
'get_share_type',
|
||||||
'get_client',
|
'get_client',
|
||||||
@ -108,6 +109,20 @@ SCCICLIENT_IRMC_ELCM_SPEC = (
|
|||||||
'set_secure_boot_mode',
|
'set_secure_boot_mode',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
SCCICLIENT_VIOM_SPEC = (
|
||||||
|
'validate_physical_port_id',
|
||||||
|
'VIOMConfiguration',
|
||||||
|
)
|
||||||
|
|
||||||
|
SCCICLIENT_VIOM_CONF_SPEC = (
|
||||||
|
'set_lan_port',
|
||||||
|
'set_iscsi_volume',
|
||||||
|
'set_fc_volume',
|
||||||
|
'apply',
|
||||||
|
'dump_json',
|
||||||
|
'terminate',
|
||||||
|
)
|
||||||
|
|
||||||
ONEVIEWCLIENT_SPEC = (
|
ONEVIEWCLIENT_SPEC = (
|
||||||
'client',
|
'client',
|
||||||
'states',
|
'states',
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Adds support for booting from remote volumes to ``irmc-virtual-media``
|
||||||
|
boot interface. It enables boot configuration for iSCSI or FibreChannel
|
||||||
|
via out-of-band network.
|
||||||
|
|
||||||
|
In addition to the same configuration as generic boot-from-volume, this
|
||||||
|
interface requires the following settings.
|
||||||
|
|
||||||
|
* It is necessary to set a physical port ID to network ports and volume
|
||||||
|
connectors. All cards including those not used for volume boot should be
|
||||||
|
registered.
|
||||||
|
|
||||||
|
* A physical ID format is: ``<Card Type><Slot No>-<Port No>``
|
||||||
|
|
||||||
|
``<Card Type>``
|
||||||
|
LAN, FC or CNA
|
||||||
|
``<Slot No>``
|
||||||
|
0 indicates onboard slot. Use 1 to 9 for addon slots.
|
||||||
|
``<Port No>``
|
||||||
|
A port number starting from 1.
|
||||||
|
|
||||||
|
* Set the IDs to ``node.driver_info.pci_physical_ids``. This parameter
|
||||||
|
is a list of pair of UUID of a resource (Port or Volume connector)
|
||||||
|
and a physical ID like:
|
||||||
|
|
||||||
|
pci_physical_ids = 1ecd14ee-c191-4007-8413-16bb5d5a73a2:LAN0-1,1ecd14ee-c191-4007-8413-16bb5d5a73a2:CNA1-1
|
||||||
|
|
||||||
|
* For iSCSI, volume connectors with both type ``iqn`` and ``ip`` are
|
||||||
|
required. The configuration with DHCP is not supported yet.
|
||||||
|
|
||||||
|
* For iSCSI, a subnet mask of the storage network is necessary. It should
|
||||||
|
be set to ``node.driver_info.storage_network_size`` as integer.
|
||||||
|
|
||||||
|
This feature requires specific FC cards or CNAs (Converged Network Adapter).
|
||||||
|
See the documentation of iRMC driver for details.
|
Loading…
Reference in New Issue
Block a user