From d6c47ded71d7a7c5e9fa6550b720575bc44e9c17 Mon Sep 17 00:00:00 2001 From: Luis Tomas Bolivar Date: Fri, 25 Nov 2016 12:04:26 +0000 Subject: [PATCH] Nested-Containers: trunk subports management Add support to create/remove subports when vlan driver is used. Partially Implements blueprint containers-in-instances Change-Id: I26dfa04657c4904bcb46ecdc35ef7d76a1e81b16 --- README.rst | 42 +++ kuryr_libnetwork/controllers.py | 18 +- kuryr_libnetwork/port_driver/base.py | 9 + kuryr_libnetwork/port_driver/driver.py | 32 ++ .../port_driver/drivers/nested.py | 10 - kuryr_libnetwork/port_driver/drivers/veth.py | 1 + kuryr_libnetwork/port_driver/drivers/vlan.py | 172 ++++++++++ kuryr_libnetwork/tests/unit/base.py | 6 +- .../unit/port_driver/drivers/test_vlan.py | 319 ++++++++++++++++++ .../tests/unit/port_driver/test_base.py | 3 + kuryr_libnetwork/tests/unit/test_kuryr.py | 14 +- .../tests/unit/test_kuryr_endpoint.py | 13 +- 12 files changed, 592 insertions(+), 47 deletions(-) create mode 100644 kuryr_libnetwork/port_driver/drivers/vlan.py create mode 100644 kuryr_libnetwork/tests/unit/port_driver/drivers/test_vlan.py diff --git a/README.rst b/README.rst index b9ffd6f7..ff03c8b7 100644 --- a/README.rst +++ b/README.rst @@ -248,6 +248,48 @@ The bash script creates the following file if it is missing: Note the root privilege is required for creating and deleting the veth pairs with `pyroute2 `_ to run. + +How to try out nested-containers locally +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +1. Installing OpenStack running devstack with the desired local.conf file but + including the next to make use of OVS-firewall and enabling Trunk Ports:: + + [[post-config|/$Q_PLUGIN_CONF_FILE]] + + [DEFAULT] + service_plugins=trunk + + [securitygroup] + firewall_driver=openvswitch + +2. Launch a VM with `Neutron trunk port. + ` + +3. Inside the VM install kuryr and kuryr-libnetwork following the normal + installation steps (see above steps at `Installing Kuryr's libnetwork + driver`). + +4. Reconfigure kuryr inside the VM to point to the neutron server and to use the + vlan driver:: + - Configure `/etc/kuryr/kuryr.conf`:: + + [binding] + driver = kuryr.lib.binding.drivers.vlan + link_iface = eth0 # VM vNIC + + [neutron] + auth_url = http://KEYSTONE_SERVER_IP:35357/v3/ + username = admin + user_domain_name = Default + password = ADMIN_PASSWORD + project_name = service + project_domain_name = Default + auth_type = password + + - Restart kuryr service inside the VM + + Testing Kuryr ------------- diff --git a/kuryr_libnetwork/controllers.py b/kuryr_libnetwork/controllers.py index c897bfd1..3c49141d 100644 --- a/kuryr_libnetwork/controllers.py +++ b/kuryr_libnetwork/controllers.py @@ -208,22 +208,6 @@ def _create_port(endpoint_id, neutron_network_id, interface_mac, fixed_ips): return rcvd_port['port'] -def _update_port(port, endpoint_id): - port['name'] = utils.get_neutron_port_name(endpoint_id) - try: - response_port = app.neutron.update_port( - port['id'], - {'port': { - 'name': port['name'], - 'device_owner': lib_const.DEVICE_OWNER, - 'device_id': endpoint_id}}) - except n_exceptions.NeutronClientException as ex: - app.logger.error(_LE("Error happened during updating a " - "Neutron port: %s"), ex) - raise - return response_port['port'] - - def _get_fixed_ips_by_interface_cidr(subnets, interface_cidrv4, interface_cidrv6, fixed_ips): for subnet in subnets: @@ -273,7 +257,7 @@ def _create_or_update_port(neutron_network_id, endpoint_id, interface_mac, fixed_ips) elif num_port == 1: port = filtered_ports['ports'][0] - response_port = _update_port(port, endpoint_id) + response_port = app.driver.update_port(port, endpoint_id) else: raise n_exceptions.DuplicatedResourceException( "Multiple ports exist for the cidrs {0} and {1}" diff --git a/kuryr_libnetwork/port_driver/base.py b/kuryr_libnetwork/port_driver/base.py index a05e4dbe..fba99013 100644 --- a/kuryr_libnetwork/port_driver/base.py +++ b/kuryr_libnetwork/port_driver/base.py @@ -49,3 +49,12 @@ class BaseNestedDriver(driver.Driver): raise exceptions.KuryrException("Cannot find a Neutron port " "associated to interface name {0}".format(ifname)) + + def get_container_iface_name(self, neutron_port_id): + """Returns interface name of a container in the default namespace. + + :param neutron_port_id: The ID of a neutron port as string + :returns: interface name as string. + """ + _, container_iface_name = utils.get_veth_pair_names(neutron_port_id) + return container_iface_name diff --git a/kuryr_libnetwork/port_driver/driver.py b/kuryr_libnetwork/port_driver/driver.py index a371cfcd..1826e5aa 100644 --- a/kuryr_libnetwork/port_driver/driver.py +++ b/kuryr_libnetwork/port_driver/driver.py @@ -15,8 +15,14 @@ import six from oslo_utils import importutils +from neutronclient.common import exceptions as n_exceptions + +from kuryr.lib._i18n import _LE +from kuryr.lib import constants as lib_const from kuryr.lib import exceptions +from kuryr_libnetwork import app from kuryr_libnetwork import config +from kuryr_libnetwork import utils as libnet_utils @six.add_metaclass(abc.ABCMeta) @@ -106,6 +112,32 @@ class Driver(object): """ raise NotImplementedError() + def update_port(self, port, endpoint_id): + """Updates port information and performs extra driver-specific actions. + + It returns the updated port dictionary after the required actions + performed depending on the binding driver. + + :param port: a neutron port dictionary returned from + python-neutronclient + :param endpoint_id: the ID of the endpoint as string + :returns: the updated Neutron port id dictionary as returned by + python-neutronclient + """ + port['name'] = libnet_utils.get_neutron_port_name(endpoint_id) + try: + response_port = app.neutron.update_port(port['id'], + {'port': { + 'name': port['name'], + 'device_owner': lib_const.DEVICE_OWNER, + 'device_id': endpoint_id + }}) + except n_exceptions.NeutronClientException as ex: + app.logger.error(_LE("Error happened during updating a " + "Neutron port: %s"), ex) + raise + return response_port['port'] + def __str__(self): return self.__class__.__name__ diff --git a/kuryr_libnetwork/port_driver/drivers/nested.py b/kuryr_libnetwork/port_driver/drivers/nested.py index 940a9d48..7b14aa6b 100644 --- a/kuryr_libnetwork/port_driver/drivers/nested.py +++ b/kuryr_libnetwork/port_driver/drivers/nested.py @@ -15,7 +15,6 @@ from oslo_log import log from kuryr.lib._i18n import _LE from kuryr.lib import binding -from kuryr.lib.binding.drivers import utils from kuryr.lib import exceptions from kuryr_libnetwork import app @@ -121,15 +120,6 @@ class NestedDriver(base.BaseNestedDriver): self._remove_from_allowed_address_pairs(vm_port, container_ips) return binding.port_unbind(endpoint_id, neutron_port) - def get_container_iface_name(self, neutron_port_id): - """Returns interface name of a container in the default namespace. - - :param neutron_port_id: The ID of a neutron port as string - :returns: interface name as string - """ - _, container_iface_name = utils.get_veth_pair_names(neutron_port_id) - return container_iface_name - def _add_to_allowed_address_pairs(self, port, ip_addresses, mac_address=None): address_pairs = port['allowed_address_pairs'] diff --git a/kuryr_libnetwork/port_driver/drivers/veth.py b/kuryr_libnetwork/port_driver/drivers/veth.py index 94892298..204415cf 100644 --- a/kuryr_libnetwork/port_driver/drivers/veth.py +++ b/kuryr_libnetwork/port_driver/drivers/veth.py @@ -12,6 +12,7 @@ from kuryr.lib import binding from kuryr.lib.binding.drivers import utils + from kuryr_libnetwork.port_driver import driver diff --git a/kuryr_libnetwork/port_driver/drivers/vlan.py b/kuryr_libnetwork/port_driver/drivers/vlan.py new file mode 100644 index 00000000..e3c99074 --- /dev/null +++ b/kuryr_libnetwork/port_driver/drivers/vlan.py @@ -0,0 +1,172 @@ +# 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 neutronclient.common import exceptions as n_exceptions +from oslo_log import log + +from kuryr.lib._i18n import _LE +from kuryr.lib import binding +from kuryr.lib import exceptions +from kuryr.lib import segmentation_type_drivers as seg_driver + +from kuryr_libnetwork import app +from kuryr_libnetwork.port_driver import base + +LOG = log.getLogger(__name__) + + +class VlanDriver(base.BaseNestedDriver): + """Driver for container-in-VM deployments with Trunk Ports.""" + + BINDING_DRIVERS = ('vlan',) + + def __init__(self): + super(VlanDriver, self).__init__() + + self.port_vlan_dic = {} + self.trunk_port = self._get_port_from_host_iface(self.link_iface) + self._check_for_vlan_ids() + + def _check_for_vlan_ids(self): + """Gathers information about vlans already in use.""" + for subport in self.trunk_port['trunk_details']['sub_ports']: + self.port_vlan_dic[subport['port_id']] = subport['segmentation_id'] + + def get_supported_bindings(self): + """Returns a tuple of supported binding driver names for the driver. + + :returns: a tuple of strings + """ + return self.BINDING_DRIVERS + + def get_default_network_id(self): + """Returns a Neutron network ID as per driver logic, if any. + + :returns: the Neutron network UUID as a string + :raises: exceptions.KuryrException + """ + return None + + def update_port(self, port, endpoint_id): + segmentation_id = self._get_segmentation_id(port['id']) + self._attach_subport(self.trunk_port['trunk_details']['trunk_id'], + port['id'], + segmentation_id) + return super(VlanDriver, self).update_port(port, endpoint_id) + + def create_host_iface(self, endpoint_id, neutron_port, subnets, + network=None): + """Instantiates a host interface and binds it to the host. + + A host linked interface will be created for the specific Neutron port + by delegating to the pre-selected kuryr-lib driver. + This driver will attach the port to the trunk port as a subport by + using a segmentation id available. + + :param endpoint_id: the ID of the endpoint as string + :param neutron_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 + :returns: the tuple of stdout and stderr returned by + processutils.execute invoked + with the executable script for binding + :raises: exceptions.VethCreationFailure, + exceptions.KuryrException, + n_exceptions.NeutronClientException, + processutils.ProcessExecutionError + """ + container_ips = neutron_port['fixed_ips'] + + if not container_ips: + raise exceptions.KuryrException( + "Neutron port {0} does not have fixed_ips." + .format(neutron_port['id'])) + + vm_port = self._get_port_from_host_iface(self.link_iface) + + segmentation_id = self._get_segmentation_id(neutron_port['id']) + + _, _, (stdout, stderr) = binding.port_bind( + endpoint_id, neutron_port, subnets, network, vm_port, + segmentation_id) + + return (stdout, stderr) + + def delete_host_iface(self, endpoint_id, neutron_port): + """Deletes a host interface after unbinding it from the host. + + The host Slave interface associated to the Neutron port will be deleted + by delegating to the selected kuryr-lib driver. + This driver will also remove the subport attached to the trunk port + and will release its segmentation id + + :param endpoint_id: the ID of the Docker container as string + :param neutron_port: a port dictionary returned from + python-neutronclient + :returns: the tuple of stdout and stderr returned + by processutils.execute invoked with the executable script + for unbinding + :raises: exceptions.VethDeletionFailure, + exceptions.KuryrException, + n_exceptions.NeutronClientException, + processutils.ProcessExecutionError, + """ + vm_port = self._get_port_from_host_iface(self.link_iface) + + stdout, stderr = binding.port_unbind(endpoint_id, neutron_port) + + subports = [{'port_id': neutron_port['id']}] + try: + app.neutron.trunk_remove_subports( + vm_port['trunk_details']['trunk_id'], + {'sub_ports': subports}) + except n_exceptions.NeutronClientException as ex: + app.logger.error(_LE("Error happened during subport deletion " + "%(port_id)s: %(ex)s"), + {'port_id': neutron_port['id'], 'ex': ex}) + raise + self._release_segmentation_id(neutron_port['id']) + return stdout, stderr + + def _attach_subport(self, trunk_id, port_id, segmentation_id): + subport = [ + { + 'segmentation_id': segmentation_id, + 'port_id': port_id, + 'segmentation_type': 'vlan' + } + ] + try: + app.neutron.trunk_add_subports(trunk_id, {'sub_ports': subport}) + except n_exceptions.NeutronClientException as ex: + app.logger.error(_LE("Error happened adding subport %(port_id)s " + "to trunk port %(trunk_id)s: %(ex)s"), + port_id, trunk_id, ex) + raise + + def _get_segmentation_id(self, id): + if id in self.port_vlan_dic.keys(): + return self.port_vlan_dic[id] + seg_id = seg_driver.allocate_segmentation_id( + self.port_vlan_dic.values()) + self.port_vlan_dic[id] = seg_id + return seg_id + + def _release_segmentation_id(self, id): + seg_driver.release_segmentation_id(id) + del self.port_vlan_dic[id] + + def _get_port_vlan(self, port_id): + return self.port_vlan_dic[port_id] diff --git a/kuryr_libnetwork/tests/unit/base.py b/kuryr_libnetwork/tests/unit/base.py index ac792369..9d6ee6e0 100644 --- a/kuryr_libnetwork/tests/unit/base.py +++ b/kuryr_libnetwork/tests/unit/base.py @@ -158,7 +158,8 @@ class TestKuryrBase(TestCase): neutron_subnet_v6_id=None, neutron_subnet_v4_address="192.168.1.2", neutron_subnet_v6_address="fe80::f816:3eff:fe20:57c4", - device_owner=None): + device_owner=None, + neutron_trunk_id=None): # The following fake response is retrieved from the Neutron doc: # http://developer.openstack.org/api-ref-networking-v2.html#createPort # noqa fake_port = { @@ -188,6 +189,9 @@ class TestKuryrBase(TestCase): "subnet_id": neutron_subnet_v6_id, "ip_address": neutron_subnet_v6_address }) + if neutron_trunk_id: + fake_port['port']['trunk_details'] = {'trunk_id': neutron_trunk_id} + return fake_port @classmethod diff --git a/kuryr_libnetwork/tests/unit/port_driver/drivers/test_vlan.py b/kuryr_libnetwork/tests/unit/port_driver/drivers/test_vlan.py new file mode 100644 index 00000000..fd1a9cb3 --- /dev/null +++ b/kuryr_libnetwork/tests/unit/port_driver/drivers/test_vlan.py @@ -0,0 +1,319 @@ +# 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 import binding +from kuryr.lib.binding.drivers import utils +from kuryr.lib import constants as lib_const +from kuryr.lib import exceptions +from kuryr.lib import segmentation_type_drivers as seg_driver +from kuryr.lib import utils as lib_utils +from kuryr_libnetwork.port_driver.drivers import vlan +from kuryr_libnetwork.tests.unit import base +from kuryr_libnetwork import utils as libnet_utils + +mock_interface = mock.MagicMock() + + +class TestVlanDriver(base.TestKuryrBase): + """Unit tests for the VlanDriver port driver""" + + @mock.patch('kuryr_libnetwork.port_driver.drivers.vlan' + '.VlanDriver._check_for_vlan_ids') + @mock.patch('kuryr_libnetwork.port_driver.drivers.vlan' + '.VlanDriver._get_port_from_host_iface') + def test_get_supported_bindings(self, mock_trunk_port, mock_vlan_check): + mock_trunk_port.return_value = None + mock_vlan_check.return_value = None + vlan_driver = vlan.VlanDriver() + bindings = vlan_driver.get_supported_bindings() + self.assertEqual(bindings, vlan.VlanDriver.BINDING_DRIVERS) + + @mock.patch('kuryr_libnetwork.config.CONF') + @mock.patch('kuryr_libnetwork.port_driver.drivers.vlan' + '.VlanDriver._check_for_vlan_ids') + @mock.patch.object(binding, 'port_bind') + @mock.patch('kuryr_libnetwork.port_driver.drivers.vlan' + '.VlanDriver._get_segmentation_id') + @mock.patch('kuryr_libnetwork.port_driver.drivers.vlan' + '.VlanDriver._get_port_from_host_iface') + def test_create_host_iface(self, mock_get_port_from_host, + mock_segmentation_id, + mock_port_bind, mock_vlan_check, mock_conf): + mock_vlan_check.return_value = None + fake_endpoint_id = lib_utils.get_hash() + fake_neutron_port_id = uuidutils.generate_uuid() + fake_neutron_net_id = uuidutils.generate_uuid() + fake_neutron_v4_subnet_id = uuidutils.generate_uuid() + fake_neutron_v6_subnet_id = uuidutils.generate_uuid() + fake_vm_port_id = uuidutils.generate_uuid() + + fake_neutron_port = self._get_fake_port( + fake_endpoint_id, fake_neutron_net_id, + fake_neutron_port_id, lib_const.PORT_STATUS_ACTIVE, + fake_neutron_v4_subnet_id, fake_neutron_v6_subnet_id, + '192.168.1.3', 'fe80::f816:3eff:fe1c:36a9')['port'] + fake_neutron_port['mac_address'] = 'fa:16:3e:20:57:c3' + fake_vm_port = self._get_fake_port( + fake_endpoint_id, fake_neutron_net_id, + fake_vm_port_id, lib_const.PORT_STATUS_ACTIVE, + fake_neutron_v4_subnet_id, fake_neutron_v6_subnet_id, + '192.168.1.2', 'fe80::f816:3eff:fe20:57c4')['port'] + fake_vm_port['allowed_address_pairs'] = [ + {'ip_address': '192.168.1.2', + 'mac_address': fake_vm_port['mac_address']}, + {'ip_address': 'fe80::f816:3eff:fe20:57c4', + 'mac_address': fake_vm_port['mac_address']}] + + fake_subnets = self._get_fake_subnets( + fake_endpoint_id, fake_neutron_net_id, + fake_neutron_v4_subnet_id, fake_neutron_v6_subnet_id)['subnets'] + + fake_network = mock.sentinel.binding_network + mock_conf.binding.link_iface = 'eth0' + fake_exec_response = ('fake_stdout', '') + fake_segmentation_id = 1 + mock_port_bind.return_value = ('fake_host_ifname', + 'fake_container_ifname', fake_exec_response) + mock_segmentation_id.return_value = fake_segmentation_id + mock_get_port_from_host.return_value = fake_vm_port + + vlan_driver = vlan.VlanDriver() + + response = vlan_driver.create_host_iface(fake_endpoint_id, + fake_neutron_port, fake_subnets, fake_network) + + mock_get_port_from_host.assert_called_with( + mock_conf.binding.link_iface) + mock_port_bind.assert_called_with(fake_endpoint_id, + fake_neutron_port, fake_subnets, fake_network, fake_vm_port, + fake_segmentation_id) + mock_segmentation_id.assert_called_with(fake_neutron_port['id']) + + self.assertEqual(response, fake_exec_response) + + @mock.patch('kuryr_libnetwork.config.CONF') + @mock.patch('kuryr_libnetwork.port_driver.drivers.vlan' + '.VlanDriver._check_for_vlan_ids') + @mock.patch('kuryr_libnetwork.port_driver.drivers.vlan' + '.VlanDriver._release_segmentation_id') + @mock.patch.object(binding, 'port_unbind') + @mock.patch('kuryr_libnetwork.app.neutron.trunk_remove_subports') + @mock.patch('kuryr_libnetwork.port_driver.drivers.vlan' + '.VlanDriver._get_port_from_host_iface') + def test_delete_host_iface(self, mock_get_port_from_host, + mock_trunk_remove_subports, mock_port_unbind, + mock_release_seg_id, mock_vlan_check, + mock_conf): + mock_vlan_check.return_value = None + fake_endpoint_id = lib_utils.get_hash() + fake_neutron_port_id = uuidutils.generate_uuid() + fake_neutron_net_id = uuidutils.generate_uuid() + fake_neutron_trunk_id = uuidutils.generate_uuid() + fake_neutron_v4_subnet_id = uuidutils.generate_uuid() + fake_neutron_v6_subnet_id = uuidutils.generate_uuid() + fake_vm_port_id = uuidutils.generate_uuid() + + fake_neutron_port = self._get_fake_port( + fake_endpoint_id, fake_neutron_net_id, fake_neutron_port_id, + lib_const.PORT_STATUS_ACTIVE, + fake_neutron_v4_subnet_id, fake_neutron_v6_subnet_id, + '192.168.1.3', 'fe80::f816:3eff:fe1c:36a9')['port'] + fake_neutron_port['mac_address'] = 'fa:16:3e:20:57:c3' + fake_vm_port = self._get_fake_port( + fake_endpoint_id, fake_neutron_net_id, + fake_vm_port_id, lib_const.PORT_STATUS_ACTIVE, + fake_neutron_v4_subnet_id, fake_neutron_v6_subnet_id, + '192.168.1.2', 'fe80::f816:3eff:fe20:57c4', + None, fake_neutron_trunk_id)['port'] + fake_vm_port['allowed_address_pairs'] = [ + {'ip_address': '192.168.1.3', + 'mac_address': fake_neutron_port['mac_address']}, + {'ip_address': 'fe80::f816:3eff:fe1c:36a9', + 'mac_address': fake_neutron_port['mac_address']}] + + mock_conf.binding.link_iface = 'eth0' + fake_unbind_response = ('fake_stdout', '') + mock_get_port_from_host.return_value = fake_vm_port + mock_port_unbind.return_value = fake_unbind_response + + vlan_driver = vlan.VlanDriver() + + response = vlan_driver.delete_host_iface(fake_endpoint_id, + fake_neutron_port) + + mock_get_port_from_host.assert_called_with( + mock_conf.binding.link_iface) + mock_port_unbind.assert_called_with(fake_endpoint_id, + fake_neutron_port) + mock_trunk_remove_subports.assert_called_with(fake_neutron_trunk_id, + {'sub_ports': [{ + 'port_id': fake_neutron_port_id + }]}) + mock_release_seg_id.assert_called_with(fake_neutron_port_id) + + self.assertEqual(response, fake_unbind_response) + + @mock.patch('kuryr_libnetwork.port_driver.drivers.vlan' + '.VlanDriver._check_for_vlan_ids') + @mock.patch('kuryr_libnetwork.port_driver.drivers.vlan' + '.VlanDriver._get_port_from_host_iface') + @mock.patch.object(utils, 'get_veth_pair_names', + return_value=("fake_host_ifname", "fake_container_name")) + def test_get_container_iface_name(self, mock_get_pair_names, + mock_trunk_port, mock_vlan_check): + mock_trunk_port.return_value = None + mock_vlan_check.return_value = None + vlan_driver = vlan.VlanDriver() + fake_neutron_port_id = uuidutils.generate_uuid() + response = vlan_driver.get_container_iface_name(fake_neutron_port_id) + mock_get_pair_names.assert_called_with(fake_neutron_port_id) + self.assertEqual(response, "fake_container_name") + + @mock.patch('kuryr_libnetwork.port_driver.drivers.vlan' + '.VlanDriver._check_for_vlan_ids') + @mock.patch('kuryr_libnetwork.port_driver.drivers.vlan' + '.VlanDriver._get_port_from_host_iface') + @mock.patch('kuryr_libnetwork.app.neutron.trunk_add_subports') + def test_attach_subport(self, mock_trunk_add_subports, mock_trunk_port, + mock_vlan_check): + mock_trunk_port.return_value = None + mock_vlan_check.return_value = None + + fake_neutron_trunk_id = uuidutils.generate_uuid() + fake_neutron_port_id = uuidutils.generate_uuid() + fake_segmentation_id = 1 + fake_subport = [ + { + 'segmentation_id': fake_segmentation_id, + 'port_id': fake_neutron_port_id, + 'segmentation_type': 'vlan' + } + ] + + vlan_driver = vlan.VlanDriver() + + vlan_driver._attach_subport(fake_neutron_trunk_id, + fake_neutron_port_id, + fake_segmentation_id) + mock_trunk_add_subports.assert_called_with(fake_neutron_trunk_id, + {'sub_ports': fake_subport}) + + @mock.patch('kuryr_libnetwork.port_driver.drivers.vlan' + '.VlanDriver._check_for_vlan_ids') + @mock.patch('kuryr_libnetwork.port_driver.drivers.vlan' + '.VlanDriver._get_port_from_host_iface') + @mock.patch.object(seg_driver, 'allocate_segmentation_id') + def test_get_segmentation_id(self, mock_alloc_seg_id, mock_trunk_port, + mock_vlan_check): + mock_trunk_port.return_value = None + mock_vlan_check.return_value = None + fake_neutron_port1_id = uuidutils.generate_uuid() + fake_neutron_port2_id = uuidutils.generate_uuid() + mock_alloc_seg_id.side_effect = [1, 2] + + vlan_driver = vlan.VlanDriver() + + response = vlan_driver._get_segmentation_id(fake_neutron_port1_id) + mock_alloc_seg_id.assert_called_once() + self.assertEqual(response, 1) + + mock_alloc_seg_id.reset_mock() + response = vlan_driver._get_segmentation_id(fake_neutron_port1_id) + mock_alloc_seg_id.assert_not_called() + self.assertEqual(response, 1) + + response = vlan_driver._get_segmentation_id(fake_neutron_port2_id) + mock_alloc_seg_id.assert_called_once() + self.assertEqual(response, 2) + + @mock.patch('kuryr_libnetwork.port_driver.drivers.vlan' + '.VlanDriver._check_for_vlan_ids') + @mock.patch('kuryr_libnetwork.port_driver.drivers.vlan' + '.VlanDriver._get_port_from_host_iface') + @mock.patch('kuryr_libnetwork.app.neutron.update_port') + @mock.patch.object(libnet_utils, 'get_neutron_port_name') + @mock.patch('kuryr_libnetwork.port_driver.drivers.vlan' + '.VlanDriver._attach_subport') + @mock.patch('kuryr_libnetwork.port_driver.drivers.vlan' + '.VlanDriver._get_segmentation_id') + def test_update_port(self, mock_get_seg_id, mock_attach_subport, + mock_get_port_name, mock_update_port, + mock_get_port_from_host, mock_vlan_check): + fake_endpoint_id = lib_utils.get_hash() + fake_neutron_port_id = uuidutils.generate_uuid() + fake_neutron_net_id = uuidutils.generate_uuid() + fake_neutron_trunk_id = uuidutils.generate_uuid() + fake_neutron_v4_subnet_id = uuidutils.generate_uuid() + fake_neutron_v6_subnet_id = uuidutils.generate_uuid() + fake_vm_port_id = uuidutils.generate_uuid() + + fake_neutron_port = self._get_fake_port( + fake_endpoint_id, fake_neutron_net_id, + fake_neutron_port_id, lib_const.PORT_STATUS_ACTIVE, + fake_neutron_v4_subnet_id, fake_neutron_v6_subnet_id, + '192.168.1.3', 'fe80::f816:3eff:fe1c:36a9')['port'] + fake_neutron_port['mac_address'] = 'fa:16:3e:20:57:c3' + fake_vm_port = self._get_fake_port( + fake_endpoint_id, fake_neutron_net_id, + fake_vm_port_id, lib_const.PORT_STATUS_ACTIVE, + fake_neutron_v4_subnet_id, fake_neutron_v6_subnet_id, + '192.168.1.2', 'fe80::f816:3eff:fe20:57c4', + None, fake_neutron_trunk_id)['port'] + fake_segmentation_id = 1 + fake_port_name = 'port1' + + mock_get_seg_id.return_value = fake_segmentation_id + mock_get_port_name.return_value = fake_port_name + mock_get_port_from_host.return_value = fake_vm_port + mock_vlan_check.return_value = None + + vlan_driver = vlan.VlanDriver() + + vlan_driver.update_port(fake_neutron_port, fake_endpoint_id) + + mock_get_seg_id.assert_called_with(fake_neutron_port_id) + + mock_get_port_name.assert_called_with(fake_endpoint_id) + + mock_attach_subport.assert_called_with(fake_neutron_trunk_id, + fake_neutron_port_id, + fake_segmentation_id) + + mock_update_port.assert_called_with(fake_neutron_port_id, + {'port': { + 'name': fake_port_name, + 'device_owner': lib_const.DEVICE_OWNER, + 'device_id': fake_endpoint_id + }}) + + +class TestVlanDriverFailures(base.TestKuryrFailures): + """Unit tests for the VlanDriver port driver failures""" + + @mock.patch('kuryr_libnetwork.port_driver.drivers.vlan' + '.VlanDriver._get_port_from_host_iface') + def test_create_host_iface(self, mock_get_port_from_host): + fake_endpoint_id = lib_utils.get_hash() + fake_neutron_port_id = uuidutils.generate_uuid() + fake_neutron_net_id = uuidutils.generate_uuid() + + fake_neutron_port = self._get_fake_port( + fake_endpoint_id, fake_neutron_net_id, + fake_neutron_port_id, lib_const.PORT_STATUS_ACTIVE)['port'] + + vlan_driver = vlan.VlanDriver() + self.assertRaises(exceptions.KuryrException, + vlan_driver.create_host_iface, fake_endpoint_id, + fake_neutron_port, None) diff --git a/kuryr_libnetwork/tests/unit/port_driver/test_base.py b/kuryr_libnetwork/tests/unit/port_driver/test_base.py index 82b70421..90ca7d2d 100644 --- a/kuryr_libnetwork/tests/unit/port_driver/test_base.py +++ b/kuryr_libnetwork/tests/unit/port_driver/test_base.py @@ -35,6 +35,9 @@ class TestBaseDriver(d_base.BaseNestedDriver): def get_supported_bindings(self): pass + def update_port(self, neutron_port_id, endpoint_id): + pass + @ddt.ddt class TestBaseNestedDriver(base.TestKuryrBase): diff --git a/kuryr_libnetwork/tests/unit/test_kuryr.py b/kuryr_libnetwork/tests/unit/test_kuryr.py index 3c90d01b..613a2f39 100644 --- a/kuryr_libnetwork/tests/unit/test_kuryr.py +++ b/kuryr_libnetwork/tests/unit/test_kuryr.py @@ -849,7 +849,7 @@ class TestKuryr(base.TestKuryrBase): @mock.patch('kuryr_libnetwork.controllers.app.driver.create_host_iface') @mock.patch('kuryr_libnetwork.controllers.app.neutron.list_networks') @mock.patch('kuryr_libnetwork.controllers.app.neutron.show_port') - @mock.patch('kuryr_libnetwork.controllers.app.neutron.update_port') + @mock.patch('kuryr_libnetwork.controllers.app.driver.update_port') @mock.patch('kuryr_libnetwork.controllers.app.neutron.list_ports') @mock.patch('kuryr_libnetwork.controllers.app.neutron.list_subnets') @mock.patch('kuryr_libnetwork.controllers.app') @@ -922,7 +922,7 @@ class TestKuryr(base.TestKuryrBase): fake_updated_port = fake_port_response['port'] fake_updated_port['name'] = utils.get_neutron_port_name( fake_docker_endpoint_id) - mock_update_port.return_value = fake_port_response + mock_update_port.return_value = fake_port_response['port'] fake_neutron_subnets = [fake_v4_subnet['subnet'], fake_v6_subnet['subnet']] @@ -931,7 +931,6 @@ class TestKuryr(base.TestKuryrBase): mock_create_host_iface.return_value = fake_create_iface_response if vif_plug_is_fatal: - mock_vif.vif_plug_is_fatal = vif_plug_is_fatal fake_neutron_ports_response_2 = self._get_fake_port( fake_docker_endpoint_id, fake_neutron_net_id, fake_port_id, lib_const.PORT_STATUS_ACTIVE, @@ -958,13 +957,8 @@ class TestKuryr(base.TestKuryrBase): mock_list_subnets.assert_any_call( network_id=fake_neutron_net_id, cidr='fe80::/64') mock_list_ports.assert_called_with(fixed_ips=fake_fixed_ips) - mock_update_port.assert_called_with( - fake_updated_port['id'], - {'port': { - 'name': fake_updated_port['name'], - 'device_owner': lib_const.DEVICE_OWNER, - 'device_id': fake_docker_endpoint_id - }}) + mock_update_port.assert_called_with(fake_port_response['port'], + fake_docker_endpoint_id) mock_list_networks.assert_any_call(tags=t) mock_create_host_iface.assert_called_with(fake_docker_endpoint_id, fake_updated_port, fake_neutron_subnets, diff --git a/kuryr_libnetwork/tests/unit/test_kuryr_endpoint.py b/kuryr_libnetwork/tests/unit/test_kuryr_endpoint.py index 4db45572..2d682e74 100644 --- a/kuryr_libnetwork/tests/unit/test_kuryr_endpoint.py +++ b/kuryr_libnetwork/tests/unit/test_kuryr_endpoint.py @@ -105,7 +105,7 @@ class TestKuryrEndpointCreateFailures(base.TestKuryrFailures): self.assertEqual({'Err': GivenException.message}, decoded_json) @mock.patch('kuryr_libnetwork.controllers.app.driver.create_host_iface') - @mock.patch('kuryr_libnetwork.controllers.app.neutron.update_port') + @mock.patch('kuryr_libnetwork.controllers.app.driver.update_port') @mock.patch('kuryr_libnetwork.controllers.app.neutron.list_subnets') @mock.patch('kuryr_libnetwork.controllers.app.neutron.list_ports') @mock.patch('kuryr_libnetwork.controllers.app.neutron.list_networks') @@ -172,7 +172,7 @@ class TestKuryrEndpointCreateFailures(base.TestKuryrFailures): fake_updated_port = fake_port_response['port'] fake_updated_port['name'] = utils.get_neutron_port_name( fake_docker_endpoint_id) - mock_update_port.return_value = fake_port_response + mock_update_port.return_value = fake_port_response['port'] fake_neutron_subnets = [fake_v4_subnet['subnet'], fake_v6_subnet['subnet']] @@ -195,13 +195,8 @@ class TestKuryrEndpointCreateFailures(base.TestKuryrFailures): mock.call(cidr='fe80::/64', network_id=fake_neutron_network_id)] mock_list_subnets.assert_has_calls(expect_calls, any_order=True) mock_list_ports.assert_called_with(fixed_ips=fake_fixed_ips) - mock_update_port.assert_called_with( - fake_updated_port['id'], - {'port': { - 'name': fake_updated_port['name'], - 'device_owner': lib_const.DEVICE_OWNER, - 'device_id': fake_docker_endpoint_id - }}) + mock_update_port.assert_called_with(fake_port_response['port'], + fake_docker_endpoint_id) mock_create_host_iface.assert_called_with( fake_docker_endpoint_id, fake_updated_port, fake_neutron_subnets, fake_neutron_network['networks'][0])