Ironic: enable multitenant networking

This replaces the unused code (due to API versioning) in the ironic
driver's implementation of `network_binding_host_id`, to code that
actually enables the multitenant networking support in ironic.

While building this out, we changed direction and ended with the node's
field being called `network_interface` instead of `network_provider`.
This patch changes this to match, and bumps the ironic API version used
to 1.20, where this feature is introduced.

The old code also assumed ironic would have only two network interfaces,
"neutron" and "none". This became three - "neutron", "flat", and
"noop".[0] "neutron" is the only interface that requires returning None
from `network_binding_host_id`, while the others should match the old
behavior. Change the code to only do this for the neutron network
interface, instead of doing this for anything not called 'flat'.

[0] https://git.openstack.org/cgit/openstack/ironic/tree/setup.cfg#n90

Depends-On: I09a42c8e54d7782c591415e53fccade972ae8bdb
Depends-On: I3c135a4a2c79cfb0b9d63d9d31009330c2abb680
Change-Id: I9d036fd5d209ccd321fbd28117660494a7bcb74d
Implements: blueprint ironic-networks-support
Co-Authored-By: Hironori Shiina <shiina.hironori@jp.fujitsu.com>
This commit is contained in:
Jim Rollenhagen 2016-03-25 17:11:51 -07:00
parent 32b7526b3c
commit e55cf73890
6 changed files with 49 additions and 26 deletions

View File

@ -74,7 +74,7 @@ class IronicClientWrapperTestCase(test.NoDBTestCase):
'ironic_url': CONF.ironic.api_endpoint,
'max_retries': CONF.ironic.api_max_retries,
'retry_interval': CONF.ironic.api_retry_interval,
'os_ironic_api_version': '1.8'}
'os_ironic_api_version': '1.20'}
mock_ir_cli.assert_called_once_with(1, **expected)
@mock.patch.object(ironic_client, 'get_client')
@ -87,7 +87,7 @@ class IronicClientWrapperTestCase(test.NoDBTestCase):
'ironic_url': CONF.ironic.api_endpoint,
'max_retries': CONF.ironic.api_max_retries,
'retry_interval': CONF.ironic.api_retry_interval,
'os_ironic_api_version': '1.8'}
'os_ironic_api_version': '1.20'}
mock_ir_cli.assert_called_once_with(1, **expected)
@mock.patch.object(ironic_client, 'get_client')
@ -101,7 +101,7 @@ class IronicClientWrapperTestCase(test.NoDBTestCase):
'ironic_url': CONF.ironic.api_endpoint,
'max_retries': CONF.ironic.api_max_retries,
'retry_interval': CONF.ironic.api_retry_interval,
'os_ironic_api_version': '1.8',
'os_ironic_api_version': '1.20',
'os_cacert': 'fake-cafile',
'ca_file': 'fake-cafile'}
mock_ir_cli.assert_called_once_with(1, **expected)

View File

@ -1625,32 +1625,33 @@ class IronicDriverTestCase(test.NoDBTestCase):
detach_block_devices=None, attach_block_devices=None)
@mock.patch.object(FAKE_CLIENT.node, 'get')
def _test_network_binding_host_id(self, is_neutron, mock_get):
def _test_network_binding_host_id(self, network_interface, mock_get):
node_uuid = uuidutils.generate_uuid()
hostname = 'ironic-compute'
instance = fake_instance.fake_instance_obj(self.ctx,
node=node_uuid,
host=hostname)
if is_neutron:
provider = 'neutron'
if network_interface == 'neutron':
expected = None
else:
provider = 'none'
expected = hostname
node = ironic_utils.get_test_node(uuid=node_uuid,
instance_uuid=self.instance_uuid,
instance_type_id=5,
network_provider=provider)
network_interface=network_interface)
mock_get.return_value = node
host_id = self.driver.network_binding_host_id(self.ctx, instance)
self.assertEqual(expected, host_id)
def test_network_binding_host_id_neutron(self):
self._test_network_binding_host_id(True)
self._test_network_binding_host_id('neutron')
def test_network_binding_host_id_none(self):
self._test_network_binding_host_id(False)
def test_network_binding_host_id_flat(self):
self._test_network_binding_host_id('flat')
def test_network_binding_host_id_noop(self):
self._test_network_binding_host_id('noop')
@mock.patch.object(instance_metadata, 'InstanceMetadata')

View File

@ -45,7 +45,7 @@ def get_test_node(**kw):
'properties': kw.get('properties', {}),
'reservation': kw.get('reservation'),
'maintenance': kw.get('maintenance', False),
'network_provider': kw.get('network_provider'),
'network_interface': kw.get('network_interface'),
'extra': kw.get('extra', {}),
'updated_at': kw.get('created_at'),
'created_at': kw.get('updated_at')})()

View File

@ -29,7 +29,7 @@ CONF = cfg.CONF
ironic = None
# The API version required by the Ironic driver
IRONIC_API_VERSION = (1, 8)
IRONIC_API_VERSION = (1, 20)
class IronicClientWrapper(object):

View File

@ -1142,22 +1142,34 @@ class IronicDriver(virt_driver.ComputeDriver):
def network_binding_host_id(self, context, instance):
"""Get host ID to associate with network ports.
This defines the binding:host_id parameter to the port-create
calls for Neutron. If using a flat network, use the default behavior
and allow the port to bind immediately. If using separate networks
for the control plane and tenants, return None here to indicate
that the port should not yet be bound; Ironic will make a port-update
call to Neutron later to tell Neutron to bind the port.
This defines the binding:host_id parameter to the port-create calls for
Neutron. If using the neutron network interface (separate networks for
the control plane and tenants), return None here to indicate that the
port should not yet be bound; Ironic will make a port-update call to
Neutron later to tell Neutron to bind the port. Otherwise, use the
default behavior and allow the port to be bound immediately.
NOTE: the late binding is important for security. If an ML2 mechanism
manages to connect the tenant network to the baremetal machine before
deployment is done (e.g. port-create time), then the tenant potentially
has access to the deploy agent, which may contain firmware blobs or
secrets. ML2 mechanisms may be able to connect the port without the
switchport info that comes from ironic, if they store that switchport
info for some reason. As such, we should *never* pass binding:host_id
in the port-create call when using the 'neutron' network_interface,
because a null binding:host_id indicates to Neutron that it should
not connect the port yet.
:param context: request context
:param instance: nova.objects.instance.Instance that the network
ports will be associated with
:returns: a string representing the host ID
:returns: a string representing the host ID, or None
"""
node = self._get_node(instance.node)
if getattr(node, 'network_provider', 'none') == 'none':
# flat network, go ahead and allow the port to be bound
return super(IronicDriver, self).network_binding_host_id(
context, instance)
return None
node = self.ironicclient.call('node.get', instance.node,
fields=['network_interface'])
if node.network_interface == 'neutron':
return None
# flat network, go ahead and allow the port to be bound
return super(IronicDriver, self).network_binding_host_id(
context, instance)

View File

@ -0,0 +1,10 @@
---
features:
- Multitenant networking for the ironic compute driver is now supported.
To enable this feature, ironic nodes must be using the 'neutron'
network_interface.
upgrade:
- The ironic driver now requires python-ironicclient>=1.5.0 (previously
>=1.1.0), and requires the ironic service to support API version 1.20 or
higher. As usual, ironic should be upgraded before nova for a
smooth upgrade process.