From ade71e4f16cc1318af8bcc727b82e3ee0fa1e0bf Mon Sep 17 00:00:00 2001 From: Vasyl Saienko Date: Fri, 21 Jul 2017 12:16:03 +0300 Subject: [PATCH] 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 Change-Id: Iacda8180f644cc1a0986e8b1fc34c65263aabd59 --- ironic/common/neutron.py | 44 ++++++++++++++++++- ironic/drivers/modules/network/common.py | 11 +++-- ironic/tests/unit/common/test_neutron.py | 19 ++++++++ .../drivers/modules/network/test_neutron.py | 19 ++++++-- ..._settings_to_neutron-a6aec830a82c38a3.yaml | 5 +++ 5 files changed, 90 insertions(+), 8 deletions(-) create mode 100644 releasenotes/notes/pass_portgroup_settings_to_neutron-a6aec830a82c38a3.yaml diff --git a/ironic/common/neutron.py b/ironic/common/neutron.py index 8263cf1b8c..addfb01ce8 100644 --- a/ironic/common/neutron.py +++ b/ironic/common/neutron.py @@ -328,7 +328,14 @@ def get_node_portmap(task): """Extract the switch port information for the node. :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 = {} @@ -339,6 +346,41 @@ def get_node_portmap(task): # 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): """Attempts to delete any ports created by cleaning/provisioning diff --git a/ironic/drivers/modules/network/common.py b/ironic/drivers/modules/network/common.py index d5e5f4afac..41475acbfb 100644 --- a/ironic/drivers/modules/network/common.py +++ b/ironic/drivers/modules/network/common.py @@ -231,6 +231,7 @@ def plug_port_to_tenant_network(task, port_like_obj, client=None): node = task.node local_link_info = [] + local_group_info = {} client_id_opt = None 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] for port in pg_ports: local_link_info.append(port.local_link_connection) + local_group_info = neutron.get_local_group_information( + task, port_like_obj) else: # We iterate only on ports or portgroups, no need to check # that it is a port @@ -268,11 +271,13 @@ def plug_port_to_tenant_network(task, port_like_obj, client=None): 'port': { 'binding:vnic_type': 'baremetal', '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: body['port']['extra_dhcp_opts'] = [client_id_opt] diff --git a/ironic/tests/unit/common/test_neutron.py b/ironic/tests/unit/common/test_neutron.py index 1c6c87e8db..9b277a7ada 100644 --- a/ironic/tests/unit/common/test_neutron.py +++ b/ironic/tests/unit/common/test_neutron.py @@ -398,6 +398,25 @@ class TestNeutronNetworkActions(db_base.DbTestCase): 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') def test_rollback_ports(self, remove_mock): with task_manager.acquire(self.context, self.node.uuid) as task: diff --git a/ironic/tests/unit/drivers/modules/network/test_neutron.py b/ironic/tests/unit/drivers/modules/network/test_neutron.py index e4cee72d4d..c0f1125e5c 100644 --- a/ironic/tests/unit/drivers/modules/network/test_neutron.py +++ b/ironic/tests/unit/drivers/modules/network/test_neutron.py @@ -364,8 +364,11 @@ class NeutronInterfaceTestCase(db_base.DbTestCase): self.node.save() self._test_configure_tenant_networks(is_client_id=True) - @mock.patch.object(neutron_common, 'get_client') - def test_configure_tenant_networks_with_portgroups(self, client_mock): + @mock.patch.object(neutron_common, 'get_client', autospec=True) + @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( self.context, node_id=self.node.id, address='ff:54:00:cf:2d:32', extra={'vif_port_id': uuidutils.generate_uuid()}) @@ -387,6 +390,8 @@ class NeutronInterfaceTestCase(db_base.DbTestCase): ) upd_mock = mock.Mock() client_mock.return_value.update_port = upd_mock + local_group_info = {'a': 'b'} + glgi_mock.return_value = local_group_info expected_body = { 'port': { 'binding:vnic_type': 'baremetal', @@ -395,16 +400,22 @@ class NeutronInterfaceTestCase(db_base.DbTestCase): } call1_body = copy.deepcopy(expected_body) 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['port']['binding:profile'] = { '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: + # 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) client_mock.assert_called_once_with() + glgi_mock.assert_called_once_with(task, pg) upd_mock.assert_has_calls( [mock.call(self.port.extra['vif_port_id'], call1_body), mock.call(pg.extra['vif_port_id'], call2_body)] diff --git a/releasenotes/notes/pass_portgroup_settings_to_neutron-a6aec830a82c38a3.yaml b/releasenotes/notes/pass_portgroup_settings_to_neutron-a6aec830a82c38a3.yaml new file mode 100644 index 0000000000..8d960bad2a --- /dev/null +++ b/releasenotes/notes/pass_portgroup_settings_to_neutron-a6aec830a82c38a3.yaml @@ -0,0 +1,5 @@ +--- +features: + - Passes port group information (``portgroup.mode`` and + ``portgroup.properties``) to Neutron via Neutron ``port.binding:profile`` + field. \ No newline at end of file