From 48e3f4f91d16aa518433e712469d82a8254146aa Mon Sep 17 00:00:00 2001 From: Hongbin Lu Date: Sun, 3 Sep 2017 21:54:33 -0400 Subject: [PATCH] Introduce a SR-IOV binding driver The driver is for binding/unbinding SR-IOV port. Basically, what it does is setting the vlan id for the VF interface. Change-Id: Ife43b57a11c9aac9c0bece84adf719e62f708fda Partial-Implements: blueprint sriov-binding --- kuryr/lib/binding/__init__.py | 11 ++- kuryr/lib/binding/drivers/hw_veb.py | 74 +++++++++++++++ kuryr/lib/binding/drivers/ipvlan.py | 3 +- kuryr/lib/binding/drivers/macvlan.py | 3 +- kuryr/lib/binding/drivers/nested.py | 3 +- kuryr/lib/binding/drivers/veth.py | 8 +- kuryr/lib/binding/drivers/vlan.py | 3 +- kuryr/lib/constants.py | 1 + .../tests/unit/binding/drivers/test_hw_veb.py | 94 +++++++++++++++++++ .../bp-sriov-binding-7cebeae1c9f27ea4.yaml | 5 + 10 files changed, 194 insertions(+), 11 deletions(-) create mode 100644 kuryr/lib/binding/drivers/hw_veb.py create mode 100644 kuryr/tests/unit/binding/drivers/test_hw_veb.py create mode 100644 releasenotes/notes/bp-sriov-binding-7cebeae1c9f27ea4.yaml diff --git a/kuryr/lib/binding/__init__.py b/kuryr/lib/binding/__init__.py index 8953b973..6834ef1f 100644 --- a/kuryr/lib/binding/__init__.py +++ b/kuryr/lib/binding/__init__.py @@ -14,7 +14,7 @@ from oslo_utils import importutils def port_bind(endpoint_id, port, subnets, network=None, vm_port=None, - segmentation_id=None): + segmentation_id=None, **kwargs): """Binds the Neutron port to the network interface on the host. :param endpoint_id: the ID of the endpoint as string @@ -29,6 +29,7 @@ def port_bind(endpoint_id, port, subnets, network=None, vm_port=None, port of a container which is running inside this Nova instance (either ipvlan/macvlan or a subport). :param segmentation_id: ID of the segment for container traffic isolation) + :param kwargs: Additional driver-specific arguments :returns: the tuple of the names of the veth pair and the tuple of stdout and stderr returned by processutils.execute invoked with the executable script for binding @@ -38,18 +39,20 @@ def port_bind(endpoint_id, port, subnets, network=None, vm_port=None, driver = importutils.import_module(cfg.CONF.binding.driver) return driver.port_bind(endpoint_id, port, subnets, network=network, vm_port=vm_port, - segmentation_id=segmentation_id) + segmentation_id=segmentation_id, + **kwargs) -def port_unbind(endpoint_id, neutron_port): +def port_unbind(endpoint_id, neutron_port, **kwargs): """Unbinds the Neutron port from the network interface on the host. :param endpoint_id: the ID of the Docker container as string :param neutron_port: a port dictionary returned from python-neutronclient + :param kwargs: Additional driver-specific arguments :returns: the tuple of stdout and stderr returned by processutils.execute invoked with the executable script for unbinding :raises: processutils.ProcessExecutionError, pyroute2.NetlinkError """ driver = importutils.import_module(cfg.CONF.binding.driver) - return driver.port_unbind(endpoint_id, neutron_port) + return driver.port_unbind(endpoint_id, neutron_port, **kwargs) diff --git a/kuryr/lib/binding/drivers/hw_veb.py b/kuryr/lib/binding/drivers/hw_veb.py new file mode 100644 index 00000000..7188ac89 --- /dev/null +++ b/kuryr/lib/binding/drivers/hw_veb.py @@ -0,0 +1,74 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from oslo_concurrency import processutils + +from kuryr.lib.binding.drivers import utils +from kuryr.lib import constants + + +def port_bind(endpoint_id, port, subnets, network=None, vm_port=None, + segmentation_id=None, **kwargs): + """Binds the Neutron port to the network interface on the host. + + :param endpoint_id: the ID of the endpoint as string + :param port: the container Neutron port dictionary as returned by + python-neutronclient + :param subnets: an iterable of all the Neutron subnets which the + endpoint is trying to join + :param network: the Neutron network which the endpoint is trying to + join + :param vm_port: the Nova instance port dictionary, as returned by + python-neutronclient. Container port under binding is + running inside this instance (either ipvlan/macvlan or + a subport) + :param segmentation_id: ID of the segment for container traffic isolation) + :param kwargs: Additional driver-specific arguments + :returns: the tuple of the names of the veth pair and the tuple of stdout + and stderr returned by processutils.execute invoked with the + executable script for binding + :raises: kuryr.common.exceptions.VethCreationFailure, + processutils.ProcessExecutionError + """ + pf_ifname = kwargs['pf_ifname'] + vf_num = kwargs['vf_num'] + mac_addr = port[utils.MAC_ADDRESS_KEY] + vlan = port[constants.VIF_DETAILS_KEY][constants.VIF_DETAILS_VLAN_KEY] + _set_vf_interface_vlan(pf_ifname, vf_num, mac_addr, vlan) + return None, None, ('', None) + + +def port_unbind(endpoint_id, neutron_port, **kwargs): + """Unbinds the Neutron port from the network interface on the host. + + :param endpoint_id: the ID of the Docker container as string + :param neutron_port: a port dictionary returned from python-neutronclient + :param kwargs: Additional driver-specific arguments + :returns: the tuple of stdout and stderr returned by processutils.execute + invoked with the executable script for unbinding + :raises: processutils.ProcessExecutionError, pyroute2.NetlinkError + """ + pf_ifname = kwargs['pf_ifname'] + vf_num = kwargs['vf_num'] + mac_addr = neutron_port[utils.MAC_ADDRESS_KEY] + _set_vf_interface_vlan(pf_ifname, vf_num, mac_addr) + return '', None + + +def _set_vf_interface_vlan(pf_ifname, vf_num, mac_addr, vlan=0): + exit_code = [0, 2, 254] + processutils.execute('ip', 'link', 'set', pf_ifname, + 'vf', vf_num, + 'mac', mac_addr, + 'vlan', vlan, + run_as_root=True, + check_exit_code=exit_code) diff --git a/kuryr/lib/binding/drivers/ipvlan.py b/kuryr/lib/binding/drivers/ipvlan.py index a76e6dc8..43e71223 100644 --- a/kuryr/lib/binding/drivers/ipvlan.py +++ b/kuryr/lib/binding/drivers/ipvlan.py @@ -21,7 +21,7 @@ IPVLAN_MODE_L2 = ifinfmsg.ifinfo.data_map['ipvlan'].modes['IPVLAN_MODE_L2'] def port_bind(endpoint_id, port, subnets, network=None, vm_port=None, - segmentation_id=None): + segmentation_id=None, **kwargs): """Binds the Neutron port to the network interface on the host. :param endpoint_id: the ID of the endpoint as string @@ -35,6 +35,7 @@ def port_bind(endpoint_id, port, subnets, network=None, vm_port=None, python-neutronclient. Container is running inside this instance (either ipvlan/macvlan or a subport) :param segmentation_id: ID of the segment for container traffic isolation) + :param kwargs: Additional driver-specific arguments :returns: the tuple of the names of the veth pair and the tuple of stdout and stderr returned by processutils.execute invoked with the executable script for binding diff --git a/kuryr/lib/binding/drivers/macvlan.py b/kuryr/lib/binding/drivers/macvlan.py index c0fbe0e8..7d005fdc 100644 --- a/kuryr/lib/binding/drivers/macvlan.py +++ b/kuryr/lib/binding/drivers/macvlan.py @@ -20,7 +20,7 @@ MACVLAN_MODE_BRIDGE = 'bridge' def port_bind(endpoint_id, port, subnets, network=None, vm_port=None, - segmentation_id=None): + segmentation_id=None, **kwargs): """Binds the Neutron port to the network interface on the host. :param endpoint_id: the ID of the endpoint as string @@ -34,6 +34,7 @@ def port_bind(endpoint_id, port, subnets, network=None, vm_port=None, python-neutronclient. Container is running inside instance. :param segmentation_id: ID of the segment for container traffic isolation) + :param kwargs: Additional driver-specific arguments :returns: the tuple of the names of the veth pair and the tuple of stdout and stderr returned by processutils.execute invoked with the executable script for binding diff --git a/kuryr/lib/binding/drivers/nested.py b/kuryr/lib/binding/drivers/nested.py index 3213e6de..3604cdd4 100644 --- a/kuryr/lib/binding/drivers/nested.py +++ b/kuryr/lib/binding/drivers/nested.py @@ -31,11 +31,12 @@ def get_link_iface(port): return link -def port_unbind(endpoint_id, neutron_port): +def port_unbind(endpoint_id, neutron_port, **kwargs): """Unbinds the Neutron port from the network interface on the host. :param endpoint_id: the ID of the Docker container as string :param neutron_port: a port dictionary returned from python-neutronclient + :param kwargs: Additional driver-specific arguments :returns: the tuple of stdout and stderr returned by processutils.execute invoked with the executable script for unbinding :raises: processutils.ProcessExecutionError, pyroute2.NetlinkError diff --git a/kuryr/lib/binding/drivers/veth.py b/kuryr/lib/binding/drivers/veth.py index 9f79d1e1..778b9a33 100644 --- a/kuryr/lib/binding/drivers/veth.py +++ b/kuryr/lib/binding/drivers/veth.py @@ -27,7 +27,7 @@ KIND = 'veth' def port_bind(endpoint_id, port, subnets, network=None, vm_port=None, - segmentation_id=None): + segmentation_id=None, **kwargs): """Binds the Neutron port to the network interface on the host. :param endpoint_id: the ID of the endpoint as string @@ -37,11 +37,12 @@ def port_bind(endpoint_id, port, subnets, network=None, vm_port=None, endpoint is trying to join :param network: the Neutron network which the endpoint is trying to join - :param vm_port: the Nova instance dictionary, as returned by + :param vm_port: the Nova instance port dictionary, as returned by python-neutronclient. Container port under binding is running inside this instance (either ipvlan/macvlan or a subport) :param segmentation_id: ID of the segment for container traffic isolation) + :param kwargs: Additional driver-specific arguments :returns: the tuple of the names of the veth pair and the tuple of stdout and stderr returned by processutils.execute invoked with the executable script for binding @@ -84,11 +85,12 @@ def port_bind(endpoint_id, port, subnets, network=None, vm_port=None, return host_ifname, container_ifname, (stdout, stderr) -def port_unbind(endpoint_id, neutron_port): +def port_unbind(endpoint_id, neutron_port, **kwargs): """Unbinds the Neutron port from the network interface on the host. :param endpoint_id: the ID of the Docker container as string :param neutron_port: a port dictionary returned from python-neutronclient + :param kwargs: Additional driver-specific arguments :returns: the tuple of stdout and stderr returned by processutils.execute invoked with the executable script for unbinding :raises: processutils.ProcessExecutionError, pyroute2.NetlinkError diff --git a/kuryr/lib/binding/drivers/vlan.py b/kuryr/lib/binding/drivers/vlan.py index 7fa470d2..680b99ae 100644 --- a/kuryr/lib/binding/drivers/vlan.py +++ b/kuryr/lib/binding/drivers/vlan.py @@ -18,7 +18,7 @@ KIND = 'vlan' def port_bind(endpoint_id, port, subnets, network=None, - vm_port=None, segmentation_id=None): + vm_port=None, segmentation_id=None, **kwargs): """Binds the Neutron port to the network interface on the host. :param endpoint_id: the ID of the endpoint as string @@ -32,6 +32,7 @@ def port_bind(endpoint_id, port, subnets, network=None, python-neutronclient. Container is running inside this instance (either ipvlan/macvlan or a subport) :param segmentation_id: ID of the segment for container traffic isolation) + :param kwargs: Additional driver-specific arguments :returns: the tuple of the names of the veth pair and the tuple of stdout and stderr returned by processutils.execute invoked with the executable script for binding diff --git a/kuryr/lib/constants.py b/kuryr/lib/constants.py index 25c95598..06a306df 100644 --- a/kuryr/lib/constants.py +++ b/kuryr/lib/constants.py @@ -28,4 +28,5 @@ DEFAULT_NETWORK_MTU = 1500 FALLBACK_VIF_TYPE = 'unbound' UNBINDING_SUBCOMMAND = 'unbind' VIF_DETAILS_KEY = 'binding:vif_details' +VIF_DETAILS_VLAN_KEY = 'vlan' VIF_TYPE_KEY = 'binding:vif_type' diff --git a/kuryr/tests/unit/binding/drivers/test_hw_veb.py b/kuryr/tests/unit/binding/drivers/test_hw_veb.py new file mode 100644 index 00000000..447d204a --- /dev/null +++ b/kuryr/tests/unit/binding/drivers/test_hw_veb.py @@ -0,0 +1,94 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +import mock +from oslo_utils import uuidutils + +from kuryr.lib.binding.drivers import hw_veb +from kuryr.lib import constants +from kuryr.lib import utils +from kuryr.tests.unit import base + + +mock_create = mock.MagicMock() +mock_interface = mock.MagicMock() + + +class TestHwVebDriver(base.TestCase): + """Unit tests for hw_veb driver""" + + @mock.patch('oslo_concurrency.processutils.execute', + return_value=('fake_stdout', 'fake_stderr')) + def test_port_bind(self, mock_execute): + fake_docker_endpoint_id = utils.get_hash() + fake_docker_network_id = utils.get_hash() + fake_port_id = uuidutils.generate_uuid() + fake_neutron_v4_subnet_id = uuidutils.generate_uuid() + fake_neutron_v6_subnet_id = uuidutils.generate_uuid() + fake_vlan_id = 100 + fake_vif_details = {constants.VIF_DETAILS_VLAN_KEY: fake_vlan_id} + fake_vif_type = "ovs" + fake_port = self._get_fake_port( + fake_docker_endpoint_id, fake_docker_network_id, + fake_port_id, constants.PORT_STATUS_ACTIVE, + fake_neutron_v4_subnet_id, fake_neutron_v6_subnet_id, + vif_details=fake_vif_details, vif_type=fake_vif_type) + fake_subnets = self._get_fake_subnets( + fake_docker_endpoint_id, fake_docker_network_id, + fake_neutron_v4_subnet_id, fake_neutron_v6_subnet_id) + fake_network = self._get_fake_networks(fake_docker_network_id) + fake_pf_ifname = 'eth13' + fake_vf_num = 1 + + hw_veb.port_bind(fake_docker_endpoint_id, + fake_port['port'], + fake_subnets['subnets'], + fake_network['networks'][0], + pf_ifname=fake_pf_ifname, + vf_num=fake_vf_num) + + mock_execute.assert_called_once_with( + 'ip', 'link', 'set', fake_pf_ifname, + 'vf', fake_vf_num, + 'mac', fake_port['port']['mac_address'], + 'vlan', fake_vlan_id, + run_as_root=True, + check_exit_code=[0, 2, 254]) + + @mock.patch('oslo_concurrency.processutils.execute', + return_value=('fake_stdout', 'fake_stderr')) + def test_port_unbind(self, mock_execute): + fake_docker_endpoint_id = utils.get_hash() + fake_docker_network_id = utils.get_hash() + fake_port_id = uuidutils.generate_uuid() + fake_neutron_v4_subnet_id = uuidutils.generate_uuid() + fake_neutron_v6_subnet_id = uuidutils.generate_uuid() + fake_vif_type = "ovs" + fake_port = self._get_fake_port( + fake_docker_endpoint_id, fake_docker_network_id, + fake_port_id, constants.PORT_STATUS_ACTIVE, + fake_neutron_v4_subnet_id, fake_neutron_v6_subnet_id, + vif_type=fake_vif_type) + fake_pf_ifname = 'eth13' + fake_vf_num = 1 + hw_veb.port_unbind(fake_docker_endpoint_id, fake_port['port'], + pf_ifname=fake_pf_ifname, + vf_num=fake_vf_num) + mock_execute.assert_called_once() + mock_execute.assert_called_once_with( + 'ip', 'link', 'set', fake_pf_ifname, + 'vf', fake_vf_num, + 'mac', fake_port['port']['mac_address'], + 'vlan', 0, + run_as_root=True, + check_exit_code=[0, 2, 254]) diff --git a/releasenotes/notes/bp-sriov-binding-7cebeae1c9f27ea4.yaml b/releasenotes/notes/bp-sriov-binding-7cebeae1c9f27ea4.yaml new file mode 100644 index 00000000..7f7de0a6 --- /dev/null +++ b/releasenotes/notes/bp-sriov-binding-7cebeae1c9f27ea4.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Introduce hw_veb binding driver. This driver can perform binding + of SR-IOV neutron port.