Start passing portgroup information to Neutron

With this patch ironic starts passing portgroup information to Neutron
via 'binding:profile'. This will allow to configure portgroup on the
switch by appropriate ML2 driver during deployment.
The example of binding:profile dictionary is:

{
    'local_link_information':[
        {
            'switch_id': 'aa:bb:cc:dd:ee:ff',
            'port_id': 'Gig0/1'
        },
        {
            'switch_id': 'aa:bb:cc:dd:ee:ff',
            'port_id: 'Gig0/2'
        }
    ],
    'local_group_information': {
            'id': portgroup.uuid,
            'name': portgroup.name,
            'bond_mode': portgroup.mode,
            'bond_properties': {
                'bond_propertyA': 'valueA',
                'bond_propertyB': 'valueB',
             }
    }
}

Partial-Bug: #1652630
Co-Authored-By: John L. Villalovos <john.l.villalovos@intel.com>
Change-Id: Iacda8180f644cc1a0986e8b1fc34c65263aabd59
This commit is contained in:
Vasyl Saienko 2017-07-21 12:16:03 +03:00 committed by Vladyslav Drok
parent 5ea8d9f354
commit ade71e4f16
5 changed files with 90 additions and 8 deletions

View File

@ -328,7 +328,14 @@ def get_node_portmap(task):
"""Extract the switch port information for the node. """Extract the switch port information for the node.
:param task: a task containing the Node object. :param task: a task containing the Node object.
:returns: a dictionary in the form {port.uuid: port.local_link_connection} :returns: a dictionary in the form
{
port.uuid: {
'switch_id': 'abc',
'port_id': 'Po0/1',
'other_llc_key': 'val'
}
}
""" """
portmap = {} portmap = {}
@ -339,6 +346,41 @@ def get_node_portmap(task):
# necessary info? (probably) # necessary info? (probably)
def get_local_group_information(task, portgroup):
"""Extract the portgroup information.
:param task: a task containing the Node object.
:param portgroup: Ironic portgroup object to extract data for.
:returns: a dictionary in the form:
{
'id': portgroup.uuid,
'name': portgroup.name,
'bond_mode': portgroup.mode,
'bond_properties': {
'bond_propertyA': 'valueA',
'bond_propertyB': 'valueB',
}
{
"""
portgroup_properties = {}
for prop, value in portgroup.properties.items():
# These properties are the bonding driver options described
# at https://www.kernel.org/doc/Documentation/networking/bonding.txt .
# cloud-init checks the same way, parameter name has to start with
# 'bond'. Keep this structure when passing properties to neutron ML2
# drivers.
key = prop if prop.startswith('bond') else 'bond_%s' % prop
portgroup_properties[key] = value
return {
'id': portgroup.uuid,
'name': portgroup.name,
'bond_mode': portgroup.mode,
'bond_properties': portgroup_properties
}
def rollback_ports(task, network_uuid): def rollback_ports(task, network_uuid):
"""Attempts to delete any ports created by cleaning/provisioning """Attempts to delete any ports created by cleaning/provisioning

View File

@ -231,6 +231,7 @@ def plug_port_to_tenant_network(task, port_like_obj, client=None):
node = task.node node = task.node
local_link_info = [] local_link_info = []
local_group_info = {}
client_id_opt = None client_id_opt = None
vif_id = ( vif_id = (
@ -253,6 +254,8 @@ def plug_port_to_tenant_network(task, port_like_obj, client=None):
if p.portgroup_id == port_like_obj.id] if p.portgroup_id == port_like_obj.id]
for port in pg_ports: for port in pg_ports:
local_link_info.append(port.local_link_connection) local_link_info.append(port.local_link_connection)
local_group_info = neutron.get_local_group_information(
task, port_like_obj)
else: else:
# We iterate only on ports or portgroups, no need to check # We iterate only on ports or portgroups, no need to check
# that it is a port # that it is a port
@ -268,11 +271,13 @@ def plug_port_to_tenant_network(task, port_like_obj, client=None):
'port': { 'port': {
'binding:vnic_type': 'baremetal', 'binding:vnic_type': 'baremetal',
'binding:host_id': node.uuid, 'binding:host_id': node.uuid,
'binding:profile': {
'local_link_information': local_link_info,
},
} }
} }
binding_profile = {'local_link_information': local_link_info}
if local_group_info:
binding_profile['local_group_information'] = local_group_info
body['port']['binding:profile'] = binding_profile
if client_id_opt: if client_id_opt:
body['port']['extra_dhcp_opts'] = [client_id_opt] body['port']['extra_dhcp_opts'] = [client_id_opt]

View File

@ -398,6 +398,25 @@ class TestNeutronNetworkActions(db_base.DbTestCase):
portmap portmap
) )
def test_get_local_group_information(self):
pg = object_utils.create_test_portgroup(
self.context, node_id=self.node.id,
uuid=uuidutils.generate_uuid(),
address='52:54:55:cf:2d:32',
mode='802.3ad', properties={'bond_opt1': 'foo',
'opt2': 'bar'},
name='test-pg'
)
expected = {
'id': pg.uuid,
'name': pg.name,
'bond_mode': pg.mode,
'bond_properties': {'bond_opt1': 'foo', 'bond_opt2': 'bar'},
}
with task_manager.acquire(self.context, self.node.uuid) as task:
res = neutron.get_local_group_information(task, pg)
self.assertEqual(expected, res)
@mock.patch.object(neutron, 'remove_ports_from_network') @mock.patch.object(neutron, 'remove_ports_from_network')
def test_rollback_ports(self, remove_mock): def test_rollback_ports(self, remove_mock):
with task_manager.acquire(self.context, self.node.uuid) as task: with task_manager.acquire(self.context, self.node.uuid) as task:

View File

@ -364,8 +364,11 @@ class NeutronInterfaceTestCase(db_base.DbTestCase):
self.node.save() self.node.save()
self._test_configure_tenant_networks(is_client_id=True) self._test_configure_tenant_networks(is_client_id=True)
@mock.patch.object(neutron_common, 'get_client') @mock.patch.object(neutron_common, 'get_client', autospec=True)
def test_configure_tenant_networks_with_portgroups(self, client_mock): @mock.patch.object(neutron_common, 'get_local_group_information',
autospec=True)
def test_configure_tenant_networks_with_portgroups(
self, glgi_mock, client_mock):
pg = utils.create_test_portgroup( pg = utils.create_test_portgroup(
self.context, node_id=self.node.id, address='ff:54:00:cf:2d:32', self.context, node_id=self.node.id, address='ff:54:00:cf:2d:32',
extra={'vif_port_id': uuidutils.generate_uuid()}) extra={'vif_port_id': uuidutils.generate_uuid()})
@ -387,6 +390,8 @@ class NeutronInterfaceTestCase(db_base.DbTestCase):
) )
upd_mock = mock.Mock() upd_mock = mock.Mock()
client_mock.return_value.update_port = upd_mock client_mock.return_value.update_port = upd_mock
local_group_info = {'a': 'b'}
glgi_mock.return_value = local_group_info
expected_body = { expected_body = {
'port': { 'port': {
'binding:vnic_type': 'baremetal', 'binding:vnic_type': 'baremetal',
@ -395,16 +400,22 @@ class NeutronInterfaceTestCase(db_base.DbTestCase):
} }
call1_body = copy.deepcopy(expected_body) call1_body = copy.deepcopy(expected_body)
call1_body['port']['binding:profile'] = { call1_body['port']['binding:profile'] = {
'local_link_information': [self.port.local_link_connection] 'local_link_information': [self.port.local_link_connection],
} }
call2_body = copy.deepcopy(expected_body) call2_body = copy.deepcopy(expected_body)
call2_body['port']['binding:profile'] = { call2_body['port']['binding:profile'] = {
'local_link_information': [port1.local_link_connection, 'local_link_information': [port1.local_link_connection,
port2.local_link_connection] port2.local_link_connection],
'local_group_information': local_group_info
} }
with task_manager.acquire(self.context, self.node.id) as task: with task_manager.acquire(self.context, self.node.id) as task:
# Override task.portgroups here, to have ability to check
# that mocked get_local_group_information was called with
# this portgroup object.
task.portgroups = [pg]
self.interface.configure_tenant_networks(task) self.interface.configure_tenant_networks(task)
client_mock.assert_called_once_with() client_mock.assert_called_once_with()
glgi_mock.assert_called_once_with(task, pg)
upd_mock.assert_has_calls( upd_mock.assert_has_calls(
[mock.call(self.port.extra['vif_port_id'], call1_body), [mock.call(self.port.extra['vif_port_id'], call1_body),
mock.call(pg.extra['vif_port_id'], call2_body)] mock.call(pg.extra['vif_port_id'], call2_body)]

View File

@ -0,0 +1,5 @@
---
features:
- Passes port group information (``portgroup.mode`` and
``portgroup.properties``) to Neutron via Neutron ``port.binding:profile``
field.