summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHironori Shiina <shiina.hironori@jp.fujitsu.com>2017-05-26 19:30:59 +0900
committerMatt Riedemann <mriedem.os@gmail.com>2018-01-25 14:10:02 -0500
commit23d935b3a60741ddb52f076ffeacde9c37f17c8c (patch)
tree27df6b13e198f9fea1a286e2ac75b69acc7e94e7
parent74deea4d8f66a85e66ec79c72c9f257f562d5afd (diff)
downloadnova-23d935b3a60741ddb52f076ffeacde9c37f17c8c.tar.gz
Ironic: Get IP address for volume connector
When storage network for booting an instance from iSCSI volume is managed by neutron, an IP address for volume connector cannot be registered by an operator in advance. A MAC address can be registered as a volume connector for an Ironic node. The IP address may be required depending on cinder backend drivers. This patch gets an IP address for a volume connector based on a MAC address assigned to a volume connector in ironic. To bind VIFs to ironic ports before connecting a volume, VIFs are attached earlier with a new virt driver API. Nova can get an IP address assigned to a VIF attached to an ironic port by retrieving the port with the MAC address. Co-Authored-By: Satoru Moriya <satoru.moriya.br@hitachi.com> Implements: blueprint ironic-volume-connector-ip Change-Id: I999bbfc0e28ec43390298deb59e2b6f6e10bf8ea
-rw-r--r--nova/compute/manager.py9
-rw-r--r--nova/tests/unit/compute/test_compute_mgr.py28
-rw-r--r--nova/tests/unit/virt/ironic/test_driver.py116
-rw-r--r--nova/tests/unit/virt/ironic/utils.py3
-rw-r--r--nova/virt/driver.py27
-rw-r--r--nova/virt/ironic/driver.py109
-rw-r--r--releasenotes/notes/bp-ironic-volume-connector-ip-467396a516dc668a.yaml11
7 files changed, 275 insertions, 28 deletions
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index 04b95f164f..2451a04df8 100644
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -2189,6 +2189,11 @@ class ComputeManager(manager.Manager):
reason=msg)
try:
+ # Depending on a virt driver, some network configuration is
+ # necessary before preparing block devices.
+ self.driver.prepare_networks_before_block_device_mapping(
+ instance, network_info)
+
# Verify that all the BDMs have a device_name set and assign a
# default to the ones missing it with the help of the driver.
self._default_block_device_names(instance, image_meta,
@@ -2209,11 +2214,14 @@ class ComputeManager(manager.Manager):
# Make sure the async call finishes
if network_info is not None:
network_info.wait(do_raise=False)
+ self.driver.clean_networks_preparation(instance,
+ network_info)
except (exception.UnexpectedTaskStateError,
exception.OverQuota, exception.InvalidBDM) as e:
# Make sure the async call finishes
if network_info is not None:
network_info.wait(do_raise=False)
+ self.driver.clean_networks_preparation(instance, network_info)
raise exception.BuildAbortException(instance_uuid=instance.uuid,
reason=e.format_message())
except Exception:
@@ -2222,6 +2230,7 @@ class ComputeManager(manager.Manager):
# Make sure the async call finishes
if network_info is not None:
network_info.wait(do_raise=False)
+ self.driver.clean_networks_preparation(instance, network_info)
msg = _('Failure prepping block device.')
raise exception.BuildAbortException(instance_uuid=instance.uuid,
reason=msg)
diff --git a/nova/tests/unit/compute/test_compute_mgr.py b/nova/tests/unit/compute/test_compute_mgr.py
index ee17f54f59..1934c7543b 100644
--- a/nova/tests/unit/compute/test_compute_mgr.py
+++ b/nova/tests/unit/compute/test_compute_mgr.py
@@ -5221,8 +5221,12 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase):
@mock.patch.object(objects.Instance, 'save')
@mock.patch.object(manager.ComputeManager, '_build_networks_for_instance')
@mock.patch.object(manager.ComputeManager, '_prep_block_device')
- def test_build_resources_reraises_on_failed_bdm_prep(self, mock_prep,
- mock_build, mock_save):
+ @mock.patch.object(virt_driver.ComputeDriver,
+ 'prepare_networks_before_block_device_mapping')
+ @mock.patch.object(virt_driver.ComputeDriver,
+ 'clean_networks_preparation')
+ def test_build_resources_reraises_on_failed_bdm_prep(
+ self, mock_clean, mock_prepnet, mock_prep, mock_build, mock_save):
mock_save.return_value = self.instance
mock_build.return_value = self.network_info
mock_prep.side_effect = test.TestingException
@@ -5240,6 +5244,8 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase):
self.requested_networks, self.security_groups)
mock_prep.assert_called_once_with(self.context, self.instance,
self.block_device_mapping)
+ mock_prepnet.assert_called_once_with(self.instance, self.network_info)
+ mock_clean.assert_called_once_with(self.instance, self.network_info)
@mock.patch('nova.virt.block_device.attach_block_devices',
side_effect=exception.VolumeNotCreated('oops!'))
@@ -5293,7 +5299,12 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase):
instance, hints)
mock_get.assert_called_once_with(self.context, uuids.group_hint)
- def test_failed_bdm_prep_from_delete_raises_unexpected(self):
+ @mock.patch.object(virt_driver.ComputeDriver,
+ 'prepare_networks_before_block_device_mapping')
+ @mock.patch.object(virt_driver.ComputeDriver,
+ 'clean_networks_preparation')
+ def test_failed_bdm_prep_from_delete_raises_unexpected(self, mock_clean,
+ mock_prepnet):
with test.nested(
mock.patch.object(self.compute,
'_build_networks_for_instance',
@@ -5319,6 +5330,8 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase):
self.requested_networks, self.security_groups)])
save.assert_has_calls([mock.call()])
+ mock_prepnet.assert_called_once_with(self.instance, self.network_info)
+ mock_clean.assert_called_once_with(self.instance, self.network_info)
@mock.patch.object(manager.ComputeManager, '_build_networks_for_instance')
def test_build_resources_aborts_on_failed_network_alloc(self, mock_build):
@@ -5430,8 +5443,13 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase):
@mock.patch(
'nova.compute.manager.ComputeManager._build_networks_for_instance')
@mock.patch('nova.objects.Instance.save')
+ @mock.patch.object(virt_driver.ComputeDriver,
+ 'prepare_networks_before_block_device_mapping')
+ @mock.patch.object(virt_driver.ComputeDriver,
+ 'clean_networks_preparation')
def test_build_resources_unexpected_task_error_before_yield(
- self, mock_save, mock_build_network, mock_info_wait):
+ self, mock_clean, mock_prepnet, mock_save, mock_build_network,
+ mock_info_wait):
mock_build_network.return_value = self.network_info
mock_save.side_effect = exception.UnexpectedTaskStateError(
instance_uuid=uuids.instance, expected={}, actual={})
@@ -5445,6 +5463,8 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase):
mock_build_network.assert_called_once_with(self.context, self.instance,
self.requested_networks, self.security_groups)
mock_info_wait.assert_called_once_with(do_raise=False)
+ mock_prepnet.assert_called_once_with(self.instance, self.network_info)
+ mock_clean.assert_called_once_with(self.instance, self.network_info)
@mock.patch('nova.network.model.NetworkInfoAsyncWrapper.wait')
@mock.patch(
diff --git a/nova/tests/unit/virt/ironic/test_driver.py b/nova/tests/unit/virt/ironic/test_driver.py
index 351f0bb1d5..cbaff6ed01 100644
--- a/nova/tests/unit/virt/ironic/test_driver.py
+++ b/nova/tests/unit/virt/ironic/test_driver.py
@@ -32,6 +32,7 @@ from nova.compute import vm_states
from nova.console import type as console_type
from nova import context as nova_context
from nova import exception
+from nova.network import model as network_model
from nova import objects
from nova.objects import fields
from nova import servicegroup
@@ -1030,9 +1031,8 @@ class IronicDriverTestCase(test.NoDBTestCase):
@mock.patch.object(ironic_driver.IronicDriver, '_wait_for_active')
@mock.patch.object(ironic_driver.IronicDriver,
'_add_instance_info_to_node')
- @mock.patch.object(ironic_driver.IronicDriver, '_plug_vifs')
@mock.patch.object(ironic_driver.IronicDriver, '_start_firewall')
- def _test_spawn(self, mock_sf, mock_pvifs, mock_aiitn, mock_wait_active,
+ def _test_spawn(self, mock_sf, mock_aiitn, mock_wait_active,
mock_avti, mock_node, mock_looping, mock_save):
node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
node = ironic_utils.get_test_node(driver='fake', uuid=node_uuid)
@@ -1059,7 +1059,6 @@ class IronicDriverTestCase(test.NoDBTestCase):
test.MatchType(objects.ImageMeta),
fake_flavor, block_device_info=None)
mock_avti.assert_called_once_with(self.ctx, instance, None)
- mock_pvifs.assert_called_once_with(node, instance, None)
mock_sf.assert_called_once_with(instance, None)
mock_node.set_provision_state.assert_called_once_with(node_uuid,
'active', configdrive=mock.ANY)
@@ -1099,11 +1098,10 @@ class IronicDriverTestCase(test.NoDBTestCase):
@mock.patch.object(ironic_driver.IronicDriver, '_wait_for_active')
@mock.patch.object(ironic_driver.IronicDriver,
'_add_instance_info_to_node')
- @mock.patch.object(ironic_driver.IronicDriver, '_plug_vifs')
@mock.patch.object(ironic_driver.IronicDriver, '_start_firewall')
- def test_spawn_destroyed_after_failure(self, mock_sf, mock_pvifs,
- mock_aiitn, mock_wait_active,
- mock_avti, mock_destroy, mock_node,
+ def test_spawn_destroyed_after_failure(self, mock_sf, mock_aiitn,
+ mock_wait_active, mock_avti,
+ mock_destroy, mock_node,
mock_looping, mock_required_by):
mock_required_by.return_value = False
node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
@@ -1356,10 +1354,9 @@ class IronicDriverTestCase(test.NoDBTestCase):
@mock.patch.object(FAKE_CLIENT, 'node')
@mock.patch.object(ironic_driver.IronicDriver, '_add_volume_target_info')
@mock.patch.object(ironic_driver.IronicDriver, '_start_firewall')
- @mock.patch.object(ironic_driver.IronicDriver, '_plug_vifs')
@mock.patch.object(ironic_driver.IronicDriver, '_cleanup_deploy')
def test_spawn_node_prepare_for_deploy_fail(self, mock_cleanup_deploy,
- mock_pvifs, mock_sf, mock_avti,
+ mock_sf, mock_avti,
mock_node, mock_required_by):
mock_required_by.return_value = False
node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
@@ -1389,9 +1386,8 @@ class IronicDriverTestCase(test.NoDBTestCase):
@mock.patch.object(ironic_driver.IronicDriver, '_add_volume_target_info')
@mock.patch.object(ironic_driver.IronicDriver, '_generate_configdrive')
@mock.patch.object(ironic_driver.IronicDriver, '_start_firewall')
- @mock.patch.object(ironic_driver.IronicDriver, '_plug_vifs')
def test_spawn_node_configdrive_fail(self,
- mock_pvifs, mock_sf, mock_configdrive,
+ mock_sf, mock_configdrive,
mock_avti, mock_node, mock_save,
mock_required_by):
mock_required_by.return_value = True
@@ -1422,10 +1418,9 @@ class IronicDriverTestCase(test.NoDBTestCase):
@mock.patch.object(FAKE_CLIENT, 'node')
@mock.patch.object(ironic_driver.IronicDriver, '_add_volume_target_info')
@mock.patch.object(ironic_driver.IronicDriver, '_start_firewall')
- @mock.patch.object(ironic_driver.IronicDriver, '_plug_vifs')
@mock.patch.object(ironic_driver.IronicDriver, '_cleanup_deploy')
def test_spawn_node_trigger_deploy_fail(self, mock_cleanup_deploy,
- mock_pvifs, mock_sf, mock_avti,
+ mock_sf, mock_avti,
mock_node, mock_required_by):
mock_required_by.return_value = False
node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
@@ -1451,10 +1446,9 @@ class IronicDriverTestCase(test.NoDBTestCase):
@mock.patch.object(FAKE_CLIENT, 'node')
@mock.patch.object(ironic_driver.IronicDriver, '_add_volume_target_info')
@mock.patch.object(ironic_driver.IronicDriver, '_start_firewall')
- @mock.patch.object(ironic_driver.IronicDriver, '_plug_vifs')
@mock.patch.object(ironic_driver.IronicDriver, '_cleanup_deploy')
def test_spawn_node_trigger_deploy_fail2(self, mock_cleanup_deploy,
- mock_pvifs, mock_sf, mock_avti,
+ mock_sf, mock_avti,
mock_node, mock_required_by):
mock_required_by.return_value = False
node_uuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
@@ -1481,10 +1475,9 @@ class IronicDriverTestCase(test.NoDBTestCase):
@mock.patch.object(FAKE_CLIENT, 'node')
@mock.patch.object(ironic_driver.IronicDriver, '_add_volume_target_info')
@mock.patch.object(ironic_driver.IronicDriver, '_start_firewall')
- @mock.patch.object(ironic_driver.IronicDriver, '_plug_vifs')
@mock.patch.object(ironic_driver.IronicDriver, 'destroy')
def test_spawn_node_trigger_deploy_fail3(self, mock_destroy,
- mock_pvifs, mock_sf, mock_avti,
+ mock_sf, mock_avti,
mock_node, mock_looping,
mock_required_by):
mock_required_by.return_value = False
@@ -1514,9 +1507,8 @@ class IronicDriverTestCase(test.NoDBTestCase):
@mock.patch.object(FAKE_CLIENT, 'node')
@mock.patch.object(ironic_driver.IronicDriver, '_add_volume_target_info')
@mock.patch.object(ironic_driver.IronicDriver, '_wait_for_active')
- @mock.patch.object(ironic_driver.IronicDriver, '_plug_vifs')
@mock.patch.object(ironic_driver.IronicDriver, '_start_firewall')
- def test_spawn_sets_default_ephemeral_device(self, mock_sf, mock_pvifs,
+ def test_spawn_sets_default_ephemeral_device(self, mock_sf,
mock_wait, mock_avti,
mock_node, mock_save,
mock_looping,
@@ -2192,8 +2184,13 @@ class IronicDriverTestCase(test.NoDBTestCase):
mock_node.list_volume_connectors.assert_called_once_with(
node_uuid, detail=True)
+ @mock.patch.object(objects.instance.Instance, 'get_network_info')
@mock.patch.object(FAKE_CLIENT, 'node')
- def test_get_volume_connector_no_ip(self, mock_node):
+ @mock.patch.object(FAKE_CLIENT.port, 'list')
+ @mock.patch.object(FAKE_CLIENT.portgroup, 'list')
+ def _test_get_volume_connector_no_ip(
+ self, mac_specified, mock_pgroup, mock_port, mock_node,
+ mock_nw_info, portgroup_exist=False):
node_uuid = uuids.node_uuid
node_props = {'cpu_arch': 'x86_64'}
node = ironic_utils.get_test_node(uuid=node_uuid,
@@ -2201,20 +2198,99 @@ class IronicDriverTestCase(test.NoDBTestCase):
connectors = [ironic_utils.get_test_volume_connector(
node_uuid=node_uuid, type='iqn',
connector_id='iqn.test')]
+ if mac_specified:
+ connectors.append(ironic_utils.get_test_volume_connector(
+ node_uuid=node_uuid, type='mac',
+ connector_id='11:22:33:44:55:66'))
+ fixed_ip = network_model.FixedIP(address='1.2.3.4', version=4)
+ subnet = network_model.Subnet(ips=[fixed_ip])
+ network = network_model.Network(subnets=[subnet])
+ vif = network_model.VIF(
+ id='aaaaaaaa-vv11-cccc-dddd-eeeeeeeeeeee', network=network)
+
expected_props = {'initiator': 'iqn.test',
+ 'ip': '1.2.3.4',
+ 'host': '1.2.3.4',
'multipath': False,
'os_type': 'baremetal',
'platform': 'x86_64'}
mock_node.get.return_value = node
mock_node.list_volume_connectors.return_value = connectors
+ mock_nw_info.return_value = [vif]
instance = fake_instance.fake_instance_obj(self.ctx, node=node_uuid)
+ port = ironic_utils.get_test_port(
+ node_uuid=node_uuid, address='11:22:33:44:55:66',
+ internal_info={'tenant_vif_port_id': vif['id']})
+ mock_port.return_value = [port]
+ if portgroup_exist:
+ portgroup = ironic_utils.get_test_portgroup(
+ node_uuid=node_uuid, address='11:22:33:44:55:66',
+ extra={'vif_port_id': vif['id']})
+ mock_pgroup.return_value = [portgroup]
+ else:
+ mock_pgroup.return_value = []
props = self.driver.get_volume_connector(instance)
self.assertEqual(expected_props, props)
mock_node.get.assert_called_once_with(node_uuid)
mock_node.list_volume_connectors.assert_called_once_with(
node_uuid, detail=True)
+ if mac_specified:
+ mock_pgroup.assert_called_once_with(
+ node=node_uuid, address='11:22:33:44:55:66', detail=True)
+ if not portgroup_exist:
+ mock_port.assert_called_once_with(
+ node=node_uuid, address='11:22:33:44:55:66', detail=True)
+ else:
+ mock_port.assert_not_called()
+ else:
+ mock_pgroup.assert_not_called()
+ mock_port.assert_not_called()
+
+ def test_get_volume_connector_no_ip_with_mac(self):
+ self._test_get_volume_connector_no_ip(True)
+
+ def test_get_volume_connector_no_ip_with_mac_with_portgroup(self):
+ self._test_get_volume_connector_no_ip(True, portgroup_exist=True)
+
+ def test_get_volume_connector_no_ip_without_mac(self):
+ self._test_get_volume_connector_no_ip(False)
+
+ @mock.patch.object(ironic_driver.IronicDriver, 'plug_vifs')
+ def test_prepare_networks_before_block_device_mapping(self, mock_pvifs):
+ instance = fake_instance.fake_instance_obj(self.ctx)
+ network_info = utils.get_test_network_info()
+ self.driver.prepare_networks_before_block_device_mapping(instance,
+ network_info)
+ mock_pvifs.assert_called_once_with(instance, network_info)
+
+ @mock.patch.object(ironic_driver.IronicDriver, 'plug_vifs')
+ def test_prepare_networks_before_block_device_mapping_error(self,
+ mock_pvifs):
+ instance = fake_instance.fake_instance_obj(self.ctx)
+ network_info = utils.get_test_network_info()
+ mock_pvifs.side_effect = ironic_exception.BadRequest('fake error')
+ self.assertRaises(
+ ironic_exception.BadRequest,
+ self.driver.prepare_networks_before_block_device_mapping,
+ instance, network_info)
+ mock_pvifs.assert_called_once_with(instance, network_info)
+
+ @mock.patch.object(ironic_driver.IronicDriver, 'unplug_vifs')
+ def test_clean_networks_preparation(self, mock_upvifs):
+ instance = fake_instance.fake_instance_obj(self.ctx)
+ network_info = utils.get_test_network_info()
+ self.driver.clean_networks_preparation(instance, network_info)
+ mock_upvifs.assert_called_once_with(instance, network_info)
+
+ @mock.patch.object(ironic_driver.IronicDriver, 'unplug_vifs')
+ def test_clean_networks_preparation_error(self, mock_upvifs):
+ instance = fake_instance.fake_instance_obj(self.ctx)
+ network_info = utils.get_test_network_info()
+ mock_upvifs.side_effect = ironic_exception.BadRequest('fake error')
+ self.driver.clean_networks_preparation(instance, network_info)
+ mock_upvifs.assert_called_once_with(instance, network_info)
@mock.patch.object(FAKE_CLIENT, 'node')
@mock.patch.object(ironic_driver.LOG, 'error')
diff --git a/nova/tests/unit/virt/ironic/utils.py b/nova/tests/unit/virt/ironic/utils.py
index 3066ec2d66..733dc43568 100644
--- a/nova/tests/unit/virt/ironic/utils.py
+++ b/nova/tests/unit/virt/ironic/utils.py
@@ -156,6 +156,9 @@ class FakeVolumeTargetClient(object):
class FakePortClient(object):
+ def list(self, address=None, node=None):
+ pass
+
def get(self, port_uuid):
pass
diff --git a/nova/virt/driver.py b/nova/virt/driver.py
index c7f96eabde..9f3219c2b5 100644
--- a/nova/virt/driver.py
+++ b/nova/virt/driver.py
@@ -505,6 +505,33 @@ class ComputeDriver(object):
"""
raise NotImplementedError()
+ def prepare_networks_before_block_device_mapping(self, instance,
+ network_info):
+ """Prepare networks before the block devices are mapped to instance.
+
+ Drivers who need network information for block device preparation can
+ do some network preparation necessary for block device preparation.
+
+ :param nova.objects.instance.Instance instance:
+ The instance whose networks are prepared.
+ :param nova.network.model.NetworkInfoAsyncWrapper network_info:
+ The network information of the given `instance`.
+ """
+ pass
+
+ def clean_networks_preparation(self, instance, network_info):
+ """Clean networks preparation when block device mapping is failed.
+
+ Drivers who need network information for block device preparaion should
+ clean the preparation when block device mapping is failed.
+
+ :param nova.objects.instance.Instance instance:
+ The instance whose networks are prepared.
+ :param nova.network.model.NetworkInfoAsyncWrapper network_info:
+ The network information of the given `instance`.
+ """
+ pass
+
def attach_interface(self, context, instance, image_meta, vif):
"""Use hotplug to add a network interface to a running instance.
diff --git a/nova/virt/ironic/driver.py b/nova/virt/ironic/driver.py
index 8d20740682..004a33f0ba 100644
--- a/nova/virt/ironic/driver.py
+++ b/nova/virt/ironic/driver.py
@@ -1062,7 +1062,6 @@ class IronicDriver(virt_driver.ComputeDriver):
# prepare for the deploy
try:
- self._plug_vifs(node, instance, network_info)
self._start_firewall(instance, network_info)
except Exception:
with excutils.save_and_reraise_exception():
@@ -1782,6 +1781,44 @@ class IronicDriver(virt_driver.ComputeDriver):
def need_legacy_block_device_info(self):
return False
+ def prepare_networks_before_block_device_mapping(self, instance,
+ network_info):
+ """Prepare networks before the block devices are mapped to instance.
+
+ Plug VIFs before block device preparation. In case where storage
+ network is managed by neutron and a MAC address is specified as a
+ volume connector to a node, we can get the IP address assigned to
+ the connector. An IP address of volume connector may be required by
+ some volume backend drivers. For getting the IP address, VIFs need to
+ be plugged before block device preparation so that a VIF is assigned to
+ a MAC address.
+ """
+
+ try:
+ self.plug_vifs(instance, network_info)
+ except Exception:
+ with excutils.save_and_reraise_exception():
+ LOG.error("Error preparing deploy for instance "
+ "%(instance)s on baremetal node %(node)s.",
+ {'instance': instance.uuid,
+ 'node': instance.node},
+ instance=instance)
+
+ def clean_networks_preparation(self, instance, network_info):
+ """Clean networks preparation when block device mapping is failed.
+
+ Unplug VIFs when block device preparation is failed.
+ """
+
+ try:
+ self.unplug_vifs(instance, network_info)
+ except Exception as e:
+ LOG.warning('Error detaching VIF from node %(node)s '
+ 'after deploy failed; %(reason)s',
+ {'node': instance.node,
+ 'reason': six.text_type(e)},
+ instance=instance)
+
def get_volume_connector(self, instance):
"""Get connector information for the instance for attaching to volumes.
@@ -1797,8 +1834,14 @@ class IronicDriver(virt_driver.ComputeDriver):
'host': hostname
}
+ An IP address is set if a volume connector with type ip is assigned to
+ a node. An IP address is also set if a node has a volume connector with
+ type mac. An IP address is got from a VIF attached to an ironic port
+ or portgroup with the MAC address. Otherwise, an IP address of one
+ of VIFs is used.
+
:param instance: nova instance
- :returns: A connector information dictionary
+ :return: A connector information dictionary
"""
node = self.ironicclient.call("node.get", instance.node)
properties = self._parse_node_properties(node)
@@ -1809,8 +1852,13 @@ class IronicDriver(virt_driver.ComputeDriver):
values.setdefault(conn.type, []).append(conn.connector_id)
props = {}
- if values.get('ip'):
- props['ip'] = props['host'] = values['ip'][0]
+ ip = self._get_volume_connector_ip(instance, node, values)
+ if ip:
+ LOG.debug('Volume connector IP address for node %(node)s is '
+ '%(ip)s.',
+ {'node': node.uuid, 'ip': ip},
+ instance=instance)
+ props['ip'] = props['host'] = ip
if values.get('iqn'):
props['initiator'] = values['iqn'][0]
if values.get('wwpn'):
@@ -1824,3 +1872,56 @@ class IronicDriver(virt_driver.ComputeDriver):
# we should at least set the value to False.
props['multipath'] = False
return props
+
+ def _get_volume_connector_ip(self, instance, node, values):
+ if values.get('ip'):
+ LOG.debug('Node %s has an IP address for volume connector',
+ node.uuid, instance=instance)
+ return values['ip'][0]
+
+ vif_id = self._get_vif_from_macs(node, values.get('mac', []), instance)
+
+ # retrieve VIF and get the IP address
+ nw_info = instance.get_network_info()
+ if vif_id:
+ fixed_ips = [ip for vif in nw_info if vif['id'] == vif_id
+ for ip in vif.fixed_ips()]
+ else:
+ fixed_ips = [ip for vif in nw_info for ip in vif.fixed_ips()]
+ fixed_ips_v4 = [ip for ip in fixed_ips if ip['version'] == 4]
+ if fixed_ips_v4:
+ return fixed_ips_v4[0]['address']
+ elif fixed_ips:
+ return fixed_ips[0]['address']
+ return None
+
+ def _get_vif_from_macs(self, node, macs, instance):
+ """Get a VIF from specified MACs.
+
+ Retrieve ports and portgroups which have specified MAC addresses and
+ return a UUID of a VIF attached to a port or a portgroup found first.
+
+ :param node: The node object.
+ :param mac: A list of MAC addresses of volume connectors.
+ :param instance: nova instance, used for logging.
+ :return: A UUID of a VIF assigned to one of the MAC addresses.
+ """
+ for mac in macs:
+ for method in ['portgroup.list', 'port.list']:
+ ports = self.ironicclient.call(method,
+ node=node.uuid,
+ address=mac,
+ detail=True)
+ for p in ports:
+ vif_id = (p.internal_info.get('tenant_vif_port_id') or
+ p.extra.get('vif_port_id'))
+ if vif_id:
+ LOG.debug('VIF %(vif)s for volume connector is '
+ 'retrieved with MAC %(mac)s of node '
+ '%(node)s',
+ {'vif': vif_id,
+ 'mac': mac,
+ 'node': node.uuid},
+ instance=instance)
+ return vif_id
+ return None
diff --git a/releasenotes/notes/bp-ironic-volume-connector-ip-467396a516dc668a.yaml b/releasenotes/notes/bp-ironic-volume-connector-ip-467396a516dc668a.yaml
new file mode 100644
index 0000000000..34ce36a78b
--- /dev/null
+++ b/releasenotes/notes/bp-ironic-volume-connector-ip-467396a516dc668a.yaml
@@ -0,0 +1,11 @@
+---
+features:
+ - |
+ When creating a baremetal instance with volumes, the ironic driver will
+ now pass an IP address of an iSCSI initiator to the block storage service
+ because the volume backend may require the IP address for access control.
+ If an IP address is set to an ironic node as a volume connector resource,
+ the address is used. If a node has MAC addresses as volume connector
+ resources, an IP address is retrieved from VIFs associated with the MAC
+ addresses. IPv4 addresses are given priority over IPv6 addresses if both
+ are available.