Generate necessary network metadata for ironic port groups

In ironic it is now possible to attach neutron ports to ironic port
groups. To configure them on the instance side, we need to pass the
information about them to the configdrive, which is then read by
cloud-init.

This patch amends the network metadata generated by nova because:

* neutron port's MAC can be changed by ironic during attach call,
  metadata needs to contain an up-to-date address;

* metadata needs to include additional information about the port
  group, such as it's mode and any properties set on ironic side;

* metadata needs to contain information about ports that belong to the
  selected port group.

Implements: blueprint ironic-portgroups-support
Co-Authored-By: Sam Betts <sambetts@cisco.com>
Depends-On: I0dca2c2d98184e370c08c3e05aa3edadead869af
Depends-On: Id8afa902026ce4466e96cc7bfb7fb97447d65809
Change-Id: Ic9bf6ca9e3de0068a289cbe6760cd8c4526ec7e0
This commit is contained in:
Vladyslav Drok 2016-10-19 18:16:04 +03:00 committed by Matt Riedemann
parent c7b46c4778
commit 8856ad31a0
4 changed files with 138 additions and 11 deletions

View File

@ -1707,13 +1707,75 @@ class IronicDriverGenerateConfigDriveTestCase(test.NoDBTestCase):
request_context=None)
@mock.patch.object(FAKE_CLIENT.node, 'list_ports')
def test_generate_network_metadata_ports_only(
self, mock_ports, mock_cd_builder, mock_instance_meta):
@mock.patch.object(FAKE_CLIENT.portgroup, 'list')
def _test_generate_network_metadata(self, mock_portgroups, mock_ports,
address=None, vif_internal_info=True):
internal_info = ({'tenant_vif_port_id': utils.FAKE_VIF_UUID}
if vif_internal_info else {})
extra = ({'vif_port_id': utils.FAKE_VIF_UUID}
if not vif_internal_info else {})
portgroup = ironic_utils.get_test_portgroup(
node_uuid=self.node.uuid, address=address,
extra=extra, internal_info=internal_info,
properties={'bond_miimon': 100, 'xmit_hash_policy': 'layer3+4'}
)
port1 = ironic_utils.get_test_port(uuid=uuidutils.generate_uuid(),
node_uuid=self.node.uuid,
address='00:00:00:00:00:01',
portgroup_uuid=portgroup.uuid)
port2 = ironic_utils.get_test_port(uuid=uuidutils.generate_uuid(),
node_uuid=self.node.uuid,
address='00:00:00:00:00:02',
portgroup_uuid=portgroup.uuid)
mock_ports.return_value = [port1, port2]
mock_portgroups.return_value = [portgroup]
metadata = self.driver._get_network_metadata(self.node,
self.network_info)
pg_vif = metadata['links'][0]
self.assertEqual('bond', pg_vif['type'])
self.assertEqual('active-backup', pg_vif['bond_mode'])
self.assertEqual(address if address else utils.FAKE_VIF_MAC,
pg_vif['ethernet_mac_address'])
self.assertEqual('layer3+4',
pg_vif['bond_xmit_hash_policy'])
self.assertEqual(100, pg_vif['bond_miimon'])
self.assertEqual([port1.uuid, port2.uuid],
pg_vif['bond_links'])
self.assertEqual([{'id': port1.uuid, 'type': 'phy',
'ethernet_mac_address': port1.address},
{'id': port2.uuid, 'type': 'phy',
'ethernet_mac_address': port2.address}],
metadata['links'][1:])
# assert there are no duplicate links
link_ids = [link['id'] for link in metadata['links']]
self.assertEqual(len(set(link_ids)), len(link_ids),
'There are duplicate link IDs: %s' % link_ids)
def test_generate_network_metadata_with_pg_address(self, mock_cd_builder,
mock_instance_meta):
self._test_generate_network_metadata(address='00:00:00:00:00:00')
def test_generate_network_metadata_no_pg_address(self, mock_cd_builder,
mock_instance_meta):
self._test_generate_network_metadata()
def test_generate_network_metadata_vif_in_extra(self, mock_cd_builder,
mock_instance_meta):
self._test_generate_network_metadata(vif_internal_info=False)
@mock.patch.object(FAKE_CLIENT.node, 'list_ports')
@mock.patch.object(FAKE_CLIENT.portgroup, 'list')
def test_generate_network_metadata_ports_only(self, mock_portgroups,
mock_ports, mock_cd_builder,
mock_instance_meta):
address = self.network_info[0]['address']
port = ironic_utils.get_test_port(
node_uuid=self.node.uuid, address=address,
internal_info={'tenant_vif_port_id': utils.FAKE_VIF_UUID})
mock_ports.return_value = [port]
mock_portgroups.return_value = []
metadata = self.driver._get_network_metadata(self.node,
self.network_info)

View File

@ -59,6 +59,23 @@ def get_test_port(**kw):
'address': kw.get('address', 'FF:FF:FF:FF:FF:FF'),
'extra': kw.get('extra', {}),
'internal_info': kw.get('internal_info', {}),
'portgroup_uuid': kw.get('portgroup_uuid'),
'created_at': kw.get('created_at'),
'updated_at': kw.get('updated_at')})()
def get_test_portgroup(**kw):
return type('portgroup', (object,),
{'uuid': kw.get('uuid', 'deaffeed-1234-5678-9012-fedcbafedcba'),
'node_uuid': kw.get('node_uuid', get_test_node().uuid),
'address': kw.get('address', 'EE:EE:EE:EE:EE:EE'),
'extra': kw.get('extra', {}),
'internal_info': kw.get('internal_info', {}),
'properties': kw.get('properties', {}),
'mode': kw.get('mode', 'active-backup'),
'name': kw.get('name'),
'standalone_ports_supported': kw.get(
'standalone_ports_supported', True),
'created_at': kw.get('created_at'),
'updated_at': kw.get('updated_at')})()
@ -110,6 +127,12 @@ class FakePortClient(object):
pass
class FakePortgroupClient(object):
def list(self, node=None, detail=False):
pass
class FakeNodeClient(object):
def list(self, detail=False):
@ -147,3 +170,4 @@ class FakeClient(object):
node = FakeNodeClient()
port = FakePortClient()
portgroup = FakePortgroupClient()

View File

@ -694,25 +694,59 @@ class IronicDriver(virt_driver.ComputeDriver):
"""
base_metadata = netutils.get_network_metadata(network_info)
# TODO(vdrok): change to doing a single "detailed vif list" call,
# when added to ironic API, response to that will contain all
# necessary information. Then we will be able to avoid looking at
# internal_info/extra fields.
ports = self.ironicclient.call("node.list_ports",
node.uuid, detail=True)
portgroups = self.ironicclient.call("portgroup.list", node=node.uuid,
detail=True)
vif_id_to_objects = {'ports': {}, 'portgroups': {}}
for collection, name in ((ports, 'ports'), (portgroups, 'portgroups')):
for p in collection:
vif_id = (p.internal_info.get('tenant_vif_port_id') or
p.extra.get('vif_port_id'))
if vif_id:
vif_id_to_objects[name][vif_id] = p
# TODO(vsaienko) add support of portgroups
vif_id_to_objects = {'ports': {}}
for p in ports:
vif_id = (p.internal_info.get('tenant_vif_port_id') or
p.extra.get('vif_port_id'))
if vif_id:
vif_id_to_objects['ports'][vif_id] = p
additional_links = []
for link in base_metadata['links']:
vif_id = link['vif_id']
if vif_id in vif_id_to_objects['ports']:
if vif_id in vif_id_to_objects['portgroups']:
pg = vif_id_to_objects['portgroups'][vif_id]
pg_ports = [p for p in ports if p.portgroup_uuid == pg.uuid]
link.update({'type': 'bond', 'bond_mode': pg.mode,
'bond_links': []})
# If address is set on the portgroup, an (ironic) vif-attach
# call has already updated neutron with the port address;
# reflect it here. Otherwise, an address generated by neutron
# will be used instead (code is elsewhere to handle this case).
if pg.address:
link.update({'ethernet_mac_address': pg.address})
for prop in pg.properties:
# These properties are the bonding driver options described
# at https://www.kernel.org/doc/Documentation/networking/bonding.txt # noqa
# cloud-init checks the same way, parameter name has to
# start with bond
key = prop if prop.startswith('bond') else 'bond_%s' % prop
link[key] = pg.properties[prop]
for port in pg_ports:
# This won't cause any duplicates to be added. A port
# cannot be in more than one port group for the same
# node.
additional_links.append({
'id': port.uuid,
'type': 'phy', 'ethernet_mac_address': port.address,
})
link['bond_links'].append(port.uuid)
elif vif_id in vif_id_to_objects['ports']:
p = vif_id_to_objects['ports'][vif_id]
# Ironic updates neutron port's address during attachment
link.update({'ethernet_mac_address': p.address,
'type': 'phy'})
base_metadata['links'].extend(additional_links)
return base_metadata
def _generate_configdrive(self, context, instance, node, network_info,

View File

@ -0,0 +1,7 @@
---
features:
- |
Updates the network metadata that is passed to configdrive by the Ironic
virt driver. The metadata now includes network information about port
groups and their associated ports. It will be used to configure port
groups on the baremetal instance side.