From 09f11aa3945e1a0b24b8097941259bf21fdf4b03 Mon Sep 17 00:00:00 2001 From: Marco Chiappero Date: Mon, 21 Nov 2016 17:44:29 +0000 Subject: [PATCH] Driver based model for kuryr-libnetwork This patch introduces drivers to perform deployment specific functions for libnetwork. This patch is based on and complementary to the driver model introduced in kuryr-lib for port binding, by enabling its use in kuryr-libnetwork. Initially the following two drivers will be available and included: * veth: this driver will exclusively make use of the the corresponding kuryr-lib veth driver. * nested: this driver is intended for nested containers and will enable the use of ipvlan/macvlan binding drivers. The driver will be selected by means of a new 'port_driver' option. NOTE: Due to https://bugs.launchpad.net/kuryr-libnetwork/+bug/1651015 When trying nested ports, it is important to use Kuryr to create the networks that will be used by Nova to create the instances, otherwise the nested ports will not be able to be cleaned. DocImpact Co-Authored-By: Luis Tomas Bolivar Co-Authored-By: Louise Daly Co-Authored-By: Gary Loughnane Implements: blueprint driver-binding-ipvlan Change-Id: I380fb017dc97a2cd54a404ec3c8154ecc2df80b8 --- kuryr_libnetwork/config.py | 5 +- kuryr_libnetwork/controllers.py | 45 +++- kuryr_libnetwork/port_driver/__init__.py | 0 kuryr_libnetwork/port_driver/base.py | 51 ++++ kuryr_libnetwork/port_driver/driver.py | 183 ++++++++++++++ .../port_driver/drivers/__init__.py | 0 .../port_driver/drivers/nested.py | 167 +++++++++++++ kuryr_libnetwork/port_driver/drivers/veth.py | 89 +++++++ kuryr_libnetwork/server.py | 1 + kuryr_libnetwork/tests/unit/base.py | 24 +- .../tests/unit/port_driver/__init__.py | 0 .../unit/port_driver/drivers/__init__.py | 0 .../unit/port_driver/drivers/test_nested.py | 201 +++++++++++++++ .../unit/port_driver/drivers/test_veth.py | 80 ++++++ .../tests/unit/port_driver/test_base.py | 78 ++++++ .../tests/unit/port_driver/test_driver.py | 113 +++++++++ kuryr_libnetwork/tests/unit/test_config.py | 3 + kuryr_libnetwork/tests/unit/test_kuryr.py | 235 +++++++++++------- .../tests/unit/test_kuryr_endpoint.py | 52 ++-- .../tests/unit/test_kuryr_network.py | 46 +++- 20 files changed, 1232 insertions(+), 141 deletions(-) create mode 100644 kuryr_libnetwork/port_driver/__init__.py create mode 100644 kuryr_libnetwork/port_driver/base.py create mode 100644 kuryr_libnetwork/port_driver/driver.py create mode 100644 kuryr_libnetwork/port_driver/drivers/__init__.py create mode 100644 kuryr_libnetwork/port_driver/drivers/nested.py create mode 100644 kuryr_libnetwork/port_driver/drivers/veth.py create mode 100644 kuryr_libnetwork/tests/unit/port_driver/__init__.py create mode 100644 kuryr_libnetwork/tests/unit/port_driver/drivers/__init__.py create mode 100644 kuryr_libnetwork/tests/unit/port_driver/drivers/test_nested.py create mode 100644 kuryr_libnetwork/tests/unit/port_driver/drivers/test_veth.py create mode 100644 kuryr_libnetwork/tests/unit/port_driver/test_base.py create mode 100644 kuryr_libnetwork/tests/unit/port_driver/test_driver.py diff --git a/kuryr_libnetwork/config.py b/kuryr_libnetwork/config.py index 3283405f..312248f4 100644 --- a/kuryr_libnetwork/config.py +++ b/kuryr_libnetwork/config.py @@ -23,7 +23,6 @@ import pbr.version from kuryr.lib._i18n import _ from kuryr.lib import config as lib_config - core_opts = [ cfg.StrOpt('pybasedir', default=os.path.abspath(os.path.join(os.path.dirname(__file__), @@ -37,13 +36,15 @@ core_opts = [ default=os.environ.get('CAPABILITY_SCOPE', 'local'), choices=['local', 'global'], help=_('Kuryr plugin scope reported to libnetwork.')), - cfg.StrOpt('local_default_address_space', default='no_address_space', help=_('There is no address-space by default in neutron')), cfg.StrOpt('global_default_address_space', default='no_address_space', help=_('There is no address-space by default in neutron')), + cfg.StrOpt('port_driver', + default='kuryr_libnetwork.port_driver.drivers.veth', + help=_('Driver for the desired deployment model')), ] CONF = cfg.CONF diff --git a/kuryr_libnetwork/controllers.py b/kuryr_libnetwork/controllers.py index e796d7ae..26080886 100644 --- a/kuryr_libnetwork/controllers.py +++ b/kuryr_libnetwork/controllers.py @@ -26,15 +26,14 @@ from oslo_config import cfg from oslo_log import log from oslo_utils import excutils -from kuryr.lib import binding from kuryr.lib import constants as lib_const from kuryr.lib import exceptions from kuryr.lib import utils as lib_utils from kuryr.lib._i18n import _LE, _LI, _LW -from kuryr.lib.binding.drivers import utils as driver_utils from kuryr_libnetwork import app from kuryr_libnetwork import config from kuryr_libnetwork import constants as const +from kuryr_libnetwork.port_driver import driver from kuryr_libnetwork import schemata from kuryr_libnetwork import utils @@ -92,6 +91,11 @@ def load_default_subnet_pools(): SUBNET_POOLS_V6 = [cfg.CONF.neutron.default_subnetpool_v6] +def load_port_driver(): + app.driver = driver.get_driver_instance() + app.logger.debug("Using port driver '%s'", str(app.driver)) + + def _cache_default_subnetpool_ids(app): """Caches IDs of the default subnetpools as app.DEFAULT_POOL_IDS.""" if not hasattr(app, 'DEFAULT_POOL_IDS'): @@ -589,6 +593,15 @@ def network_driver_create_network(): ("Specified pool name({0}) does not " "exist.").format(pool_name)) + # let the user override the driver default + if not neutron_uuid and not neutron_name: + try: + neutron_uuid = app.driver.get_default_network_id() + except n_exceptions.NeutronClientException as ex: + app.logger.error(_LE("Failed to retrieve the default driver " + "network due to Neutron error: %s"), ex) + raise + if not neutron_uuid and not neutron_name: network = app.neutron.create_network( {'network': {'name': neutron_network_name, @@ -820,12 +833,13 @@ def network_driver_create_endpoint(): neutron_network_id, endpoint_id, interface_cidrv4, interface_cidrv6, interface_mac) try: - ifname, peer_name, (stdout, stderr) = binding.port_bind( + (stdout, stderr) = app.driver.create_host_iface( endpoint_id, neutron_port, subnets, filtered_networks[0]) app.logger.debug(stdout) if stderr: app.logger.error(stderr) - except exceptions.VethCreationFailure as ex: + except (exceptions.VethCreationFailure, + exceptions.BindingNotSupportedFailure) as ex: with excutils.save_and_reraise_exception(): app.logger.error(_LE('Preparing the veth ' 'pair was failed: %s.'), ex) @@ -833,6 +847,10 @@ def network_driver_create_endpoint(): with excutils.save_and_reraise_exception(): app.logger.error(_LE( 'Could not bind the Neutron port to the veth endpoint.')) + except (exceptions.KuryrException, + n_exceptions.NeutronClientException) as ex: + with excutils.save_and_reraise_exception(): + app.logger.error(_LE('Failed to set up the interface: %s'), ex) if app.vif_plug_is_fatal: port_active = _port_active(neutron_port['id'], @@ -844,10 +862,10 @@ def network_driver_create_endpoint(): .format(neutron_port_name)) response_interface = {} - created_fixed_ips = neutron_port['fixed_ips'] subnets_dict_by_id = {subnet['id']: subnet for subnet in subnets} + if not interface_mac: response_interface['MacAddress'] = neutron_port['mac_address'] @@ -931,18 +949,23 @@ def network_driver_delete_endpoint(): neutron_port = filtered_ports[0] try: - stdout, stderr = binding.port_unbind(endpoint_id, neutron_port) + stdout, stderr = app.driver.delete_host_iface( + endpoint_id, neutron_port) app.logger.debug(stdout) if stderr: app.logger.error(stderr) except processutils.ProcessExecutionError: with excutils.save_and_reraise_exception(): - app.logger.error(_LE( - 'Could not unbind the Neutron port from the veth ' - 'endpoint.')) + app.logger.error(_LE('Could not unbind the Neutron port from' + 'the veth endpoint.')) except exceptions.VethDeletionFailure: with excutils.save_and_reraise_exception(): app.logger.error(_LE('Cleaning the veth pair up was failed.')) + except (exceptions.KuryrException, + n_exceptions.NeutronClientException) as ex: + with excutils.save_and_reraise_exception(): + app.logger.error(_LE('Error while removing the interface: %s'), + ex) return flask.jsonify(const.SCHEMA['SUCCESS']) @@ -1015,11 +1038,11 @@ def network_driver_join(): "Multiple Neutron subnets exist for the network_id={0} " .format(neutron_network_id)) - _, peer_name = driver_utils.get_veth_pair_names(neutron_port['id']) + iface_name = app.driver.get_container_iface_name(neutron_port['id']) join_response = { "InterfaceName": { - "SrcName": peer_name, + "SrcName": iface_name, "DstPrefix": config.CONF.binding.veth_dst_prefix }, "StaticRoutes": [] diff --git a/kuryr_libnetwork/port_driver/__init__.py b/kuryr_libnetwork/port_driver/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/kuryr_libnetwork/port_driver/base.py b/kuryr_libnetwork/port_driver/base.py new file mode 100644 index 00000000..a05e4dbe --- /dev/null +++ b/kuryr_libnetwork/port_driver/base.py @@ -0,0 +1,51 @@ +# 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 abc +import six + +from kuryr.lib.binding.drivers import utils +from kuryr.lib import exceptions +from kuryr_libnetwork import app +from kuryr_libnetwork import config +from kuryr_libnetwork.port_driver import driver + + +@six.add_metaclass(abc.ABCMeta) +class BaseNestedDriver(driver.Driver): + """Driver for container-in-VM deployments with MACVLAN and IPVLAN.""" + + def __init__(self): + self.link_iface = config.CONF.binding.link_iface + + def _get_port_from_host_iface(self, ifname): + """Returns the Neutron port associated to ifname or raises otherwise. + + Returns the Neutron port associated to ifname if such port exists, a + exceptions.KuryrException if it does not, + n_exceptions.NeutronClientException on errors. + + :returns: a Neutron port dictionary as returned by + python-neutronclient or None + :raises: exceptions.KuryrException + neutronclient.common.exceptions.NeutronClientException + """ + ip = utils.get_ipdb() + + mac_address = ip.interfaces.get(ifname, {}).get('address', None) + if mac_address: + ports = app.neutron.list_ports(mac_address=mac_address) + if ports['ports']: + return ports['ports'][0] + + raise exceptions.KuryrException("Cannot find a Neutron port " + "associated to interface name {0}".format(ifname)) diff --git a/kuryr_libnetwork/port_driver/driver.py b/kuryr_libnetwork/port_driver/driver.py new file mode 100644 index 00000000..a371cfcd --- /dev/null +++ b/kuryr_libnetwork/port_driver/driver.py @@ -0,0 +1,183 @@ +#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 abc +import six + +from oslo_utils import importutils + +from kuryr.lib import exceptions +from kuryr_libnetwork import config + + +@six.add_metaclass(abc.ABCMeta) +class Driver(object): + """Interface class for port drivers. + + In order to create compliant implementations subclasses must be named with + the 'Driver' suffix. + """ + + @abc.abstractmethod + def get_default_network_id(self): + """Returns a Neutron network ID as per driver logic, if any. + + Endpoints associated to certain type of drivers might need to join a + specific, possibly pre-existing, network to be able to work correctly. + For these drivers a Neutron network ID is returned or an exception + raised if failed, None returned in all the other cases. + + :returns: the Neutron network UUID as a string when available or None + :raises: exceptions.KuryrException + """ + raise NotImplementedError() + + @abc.abstractmethod + def create_host_iface(self, endpoint_id, neutron_port, subnets, + network=None): + """Instantiates a host interface and binds it to the host. + + A host interface will be created for the specific Neutron port and + bound to the related network subsystem on the host by delegating to the + pre-selected kuryr-lib driver. + + :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.BindingNotSupportedFailure + exceptions.KuryrException, + neutronclient.common.NeutronClientException, + processutils.ProcessExecutionError + """ + raise NotImplementedError() + + @abc.abstractmethod + def delete_host_iface(self, endpoint_id, neutron_port): + """Deletes a host interface after unbinding it from the host. + + The host interface associated to the Neutron port will be unbound from + its network subsystem and deleted by delegating to the selected + kuryr-lib driver. + + :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, + neutronclient.common.NeutronClientException, + processutils.ProcessExecutionError + """ + raise NotImplementedError() + + @abc.abstractmethod + 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 + """ + raise NotImplementedError() + + @abc.abstractmethod + def get_supported_bindings(self): + """Returns a tuple of supported binding driver names for the driver. + + :returns: a tuple of strings + """ + raise NotImplementedError() + + def __str__(self): + return self.__class__.__name__ + + +def get_driver_instance(): + """Instantiate a driver instance accordingly to the file configuration. + + :returns: a Driver instance + :raises: exceptions.KuryrException + """ + module, name, classname = _parse_port_driver_config() + + # TODO(apuimedo): switch to the openstack/stevedore plugin system + try: + driver = importutils.import_object("{0}.{1}".format(module, classname)) + except ImportError as ie: + raise exceptions.KuryrException( + "Cannot load port driver '{0}': {1}".format(module, ie)) + + _verify_port_driver_compliancy(driver, name) + _verify_binding_driver_compatibility(driver, name) + + return driver + + +def _parse_port_driver_config(): + """Read the port driver related config value and parse it. + + :returns: the provided full module path as per config file, the name of the + driver/module and the class name of the Driver class inside it + """ + config_value = config.CONF.port_driver + config_tokens = config_value.rsplit('.', 1) + if len(config_tokens) == 1: # not a path, just a name + name = config_tokens[0] + if len(name) == 0: + raise exceptions.KuryrException("No port driver provided") + else: + # Attempt to use the name only by prepending the default location + module = "kuryr_libnetwork.port_driver.drivers." + name + else: + module = config_value + name = config_tokens[1] + + classname = name.capitalize() + 'Driver' + + return module, name, classname + + +def _verify_port_driver_compliancy(driver, port_driver_name): + if not issubclass(driver.__class__, Driver): + raise exceptions.KuryrException("Cannot load port driver '{0}': " + "driver is not compliant with the {1} interface" + .format(port_driver_name, Driver.__name__)) + + +def _verify_binding_driver_compatibility(driver, port_driver_name): + tokens = config.CONF.binding.driver.rsplit('.', 1) + binding_driver_name = tokens[0] if len(tokens) == 1 else tokens[1] + binding_driver_name.lower() + + # TODO(mchiappe): find a clean way to test the binding driver + # is also loadable before we start + supported_bindings = driver.get_supported_bindings() + + if binding_driver_name not in supported_bindings: + raise exceptions.KuryrException("Configuration file error: " + "port driver '{0}' is not compatible with binding driver '{1}'" + .format(port_driver_name, binding_driver_name)) + + # Temporarily ban IPVLAN, to be removed in the future + if binding_driver_name == 'ipvlan': + raise exceptions.KuryrException("Configuration file error: " + "binding driver '{0}' is currently not supported with '{1}' " + "port driver".format(binding_driver_name, port_driver_name)) diff --git a/kuryr_libnetwork/port_driver/drivers/__init__.py b/kuryr_libnetwork/port_driver/drivers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/kuryr_libnetwork/port_driver/drivers/nested.py b/kuryr_libnetwork/port_driver/drivers/nested.py new file mode 100644 index 00000000..940a9d48 --- /dev/null +++ b/kuryr_libnetwork/port_driver/drivers/nested.py @@ -0,0 +1,167 @@ +# 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.binding.drivers import utils +from kuryr.lib import exceptions + +from kuryr_libnetwork import app +from kuryr_libnetwork.port_driver import base + +LOG = log.getLogger(__name__) + + +class NestedDriver(base.BaseNestedDriver): + """Driver for container-in-VM deployments with MACVLAN and IPVLAN.""" + + BINDING_DRIVERS = ('macvlan', 'ipvlan') + + def __init__(self): + super(NestedDriver, self).__init__() + + 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. + + Nested Endpoints need to join the same network as their Master + interface, this function will return its Neutron network UUID for the + Endpoint to join or throw in case of failure. + + :returns: the Neutron network UUID as a string + :raises: exceptions.KuryrException + """ + vm_port = self._get_port_from_host_iface(self.link_iface) + + return vm_port['network_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 also add the IP and MAC address pairs of the Endpoint + to the allowed_address_pairs list of the Neutron port associated to the + underlying host interface. + + :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_mac = neutron_port['mac_address'] + container_ips = neutron_port['fixed_ips'] + + if not container_ips: # The MAC address should be mandatory, no check + 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) + + _, _, (stdout, stderr) = binding.port_bind( + endpoint_id, neutron_port, subnets, network, vm_port) + self._add_to_allowed_address_pairs(vm_port, container_ips, + container_mac) + + 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 IP and MAC address pairs of the + Endpoint to the allowed_address_pairs list of the Neutron port + associated to the underlying host interface. + + :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) + container_ips = neutron_port['fixed_ips'] + + 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'] + for ip_entry in ip_addresses: + pair = {'ip_address': ip_entry['ip_address']} + if mac_address: + pair['mac_address'] = mac_address + address_pairs.append(pair) + + self._update_port_address_pairs(port['id'], address_pairs) + + def _remove_from_allowed_address_pairs(self, port, ip_addresses): + address_pairs = port['allowed_address_pairs'] + filter = frozenset(ip_entry['ip_address'] for ip_entry in ip_addresses) + updated_address_pairs = [] + + # filter allowed IPs by copying + for address_pair in address_pairs: + if address_pair['ip_address'] in filter: + continue + updated_address_pairs.append(address_pair) + + self._update_port_address_pairs(port['id'], updated_address_pairs) + + def _update_port_address_pairs(self, port_id, address_pairs): + try: + app.neutron.update_port( + port_id, + {'port': { + 'allowed_address_pairs': address_pairs + }}) + except n_exceptions.NeutronClientException as ex: + app.logger.error(_LE("Error happened during updating Neutron " + "port %(port_id)s: %(ex)s"), port_id, ex) + raise diff --git a/kuryr_libnetwork/port_driver/drivers/veth.py b/kuryr_libnetwork/port_driver/drivers/veth.py new file mode 100644 index 00000000..c69865e2 --- /dev/null +++ b/kuryr_libnetwork/port_driver/drivers/veth.py @@ -0,0 +1,89 @@ +# 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 kuryr.lib import binding +from kuryr.lib.binding.drivers import utils +from kuryr_libnetwork.port_driver import driver + + +class VethDriver(driver.Driver): + """Driver supporting veth on Bare Metal""" + + BINDING_DRIVERS = ('veth',) + + 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. + + This driver does not make use of any specific network and will thus + return None. + + :returns: None + """ + return None + + def create_host_iface(self, endpoint_id, neutron_port, subnets, + network=None): + """Instantiates a host interface and bind it to the host. + + The interface type will be veth and and bound to a virutal bridge. + + :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 + unbinding + :raises: kuryr.lib.exceptions.VethCreationFailure, + kuryr.lib.exceptions.BindingNotSupportedFailure + processutils.ProcessExecutionError + """ + _, _, (stdout, stderr) = binding.port_bind( + endpoint_id, neutron_port, subnets, network) + return (stdout, stderr) + + def delete_host_iface(self, endpoint_id, neutron_port): + """Deletes a host interface after unbinding it from the host. + + The host veth interface associated to the Neutron port will be unbound + from its vitual bridge and deleted by delegating to the selected + kuryr-lib driver. + + :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: kuryr.lib.exceptions.VethDeletionFailure, + processutils.ProcessExecutionError + """ + 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 diff --git a/kuryr_libnetwork/server.py b/kuryr_libnetwork/server.py index 7115597a..c8ae5cb6 100644 --- a/kuryr_libnetwork/server.py +++ b/kuryr_libnetwork/server.py @@ -27,6 +27,7 @@ def configure_app(): controllers.check_for_neutron_ext_support() controllers.check_for_neutron_ext_tag() controllers.load_default_subnet_pools() + controllers.load_port_driver() def start(): diff --git a/kuryr_libnetwork/tests/unit/base.py b/kuryr_libnetwork/tests/unit/base.py index 1b35c172..697445b3 100644 --- a/kuryr_libnetwork/tests/unit/base.py +++ b/kuryr_libnetwork/tests/unit/base.py @@ -10,16 +10,17 @@ # License for the specific language governing permissions and limitations # under the License. +import mock from mox3 import mox from neutronclient.v2_0 import client from oslotest import base -from kuryr.lib import binding from kuryr.lib import constants as lib_const from kuryr.lib import utils as lib_utils from kuryr_libnetwork import app from kuryr_libnetwork import constants as const from kuryr_libnetwork import controllers +from kuryr_libnetwork.port_driver import driver from kuryr_libnetwork import utils @@ -36,6 +37,7 @@ class TestCase(base.BaseTestCase): app.config['TESTING'] = True self.app = app.test_client() self.app.neutron = client.Client(token=TOKEN, endpoint_url=ENDURL) + app.driver = mock.Mock(spec=driver.Driver) app.tag = True @@ -53,26 +55,6 @@ class TestKuryrBase(TestCase): if hasattr(app, 'DEFAULT_POOL_IDS'): del app.DEFAULT_POOL_IDS - def _mock_out_binding(self, endpoint_id, neutron_port, - neutron_subnets, neutron_network=None): - self.mox.StubOutWithMock(binding, 'port_bind') - fake_binding_response = ( - 'fake-veth', 'fake-veth_c', ('fake stdout', '')) - binding.port_bind(endpoint_id, neutron_port, - neutron_subnets, - neutron_network).AndReturn( - fake_binding_response) - self.mox.ReplayAll() - return fake_binding_response - - def _mock_out_unbinding(self, endpoint_id, neutron_port): - self.mox.StubOutWithMock(binding, 'port_unbind') - fake_unbinding_response = ('fake stdout', '') - binding.port_unbind(endpoint_id, neutron_port).AndReturn( - fake_unbinding_response) - self.mox.ReplayAll() - return fake_unbinding_response - def _mock_out_network(self, neutron_network_id, docker_network_id, check_existing=False): no_networks_response = { diff --git a/kuryr_libnetwork/tests/unit/port_driver/__init__.py b/kuryr_libnetwork/tests/unit/port_driver/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/kuryr_libnetwork/tests/unit/port_driver/drivers/__init__.py b/kuryr_libnetwork/tests/unit/port_driver/drivers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/kuryr_libnetwork/tests/unit/port_driver/drivers/test_nested.py b/kuryr_libnetwork/tests/unit/port_driver/drivers/test_nested.py new file mode 100644 index 00000000..762b520f --- /dev/null +++ b/kuryr_libnetwork/tests/unit/port_driver/drivers/test_nested.py @@ -0,0 +1,201 @@ +# 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 utils as lib_utils +from kuryr_libnetwork.port_driver.drivers import nested +from kuryr_libnetwork.tests.unit import base + +mock_interface = mock.MagicMock() + + +class TestNestedDriver(base.TestKuryrBase): + """Unit tests for the NestedDriver port driver""" + + def test_get_supported_bindings(self): + nested_driver = nested.NestedDriver() + bindings = nested_driver.get_supported_bindings() + self.assertEqual(bindings, nested.NestedDriver.BINDING_DRIVERS) + + @mock.patch('kuryr_libnetwork.config.CONF') + @mock.patch.object(nested.NestedDriver, '_get_port_from_host_iface') + def test_get_default_network_id(self, mock_get_port_from_host, mock_conf): + mock_conf.binding.link_iface = 'eth0' + + 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 = 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)['port'] + mock_get_port_from_host.return_value = fake_vm_port + + nested_driver = nested.NestedDriver() + host_network_id = nested_driver.get_default_network_id() + mock_get_port_from_host.assert_called_with('eth0') + self.assertEqual(host_network_id, fake_vm_port['network_id']) + + @mock.patch('kuryr_libnetwork.config.CONF') + @mock.patch.object(binding, 'port_bind') + @mock.patch('kuryr_libnetwork.app.neutron.update_port') + @mock.patch.object(nested.NestedDriver, '_get_port_from_host_iface') + def test_create_host_iface(self, mock_get_port_from_host, + mock_update_port, mock_port_bind, mock_conf): + mock_conf.binding.link_iface = 'eth0' + + 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']}] + updated_allowed_pairs = fake_vm_port['allowed_address_pairs'] + updated_allowed_pairs.extend([ + {'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']}]) + + 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 + fake_exec_response = ('fake_stdout', '') + mock_port_bind.return_value = ('fake_host_ifname', + 'fake_container_ifname', fake_exec_response) + mock_get_port_from_host.return_value = fake_vm_port + + nested_driver = nested.NestedDriver() + response = nested_driver.create_host_iface(fake_endpoint_id, + fake_neutron_port, fake_subnets, fake_network) + + mock_get_port_from_host.assert_called_with('eth0') + mock_port_bind.assert_called_with(fake_endpoint_id, + fake_neutron_port, fake_subnets, fake_network, fake_vm_port) + mock_update_port.assert_called_with(fake_vm_port['id'], + {'port': { + 'allowed_address_pairs': updated_allowed_pairs + }}) + + self.assertEqual(response, fake_exec_response) + + @mock.patch('kuryr_libnetwork.config.CONF') + @mock.patch.object(binding, 'port_unbind') + @mock.patch('kuryr_libnetwork.app.neutron.update_port') + @mock.patch.object(nested.NestedDriver, '_get_port_from_host_iface') + def test_delete_host_iface(self, mock_get_port_from_host, + mock_update_port, mock_port_unbind, mock_conf): + mock_conf.binding.link_iface = 'eth0' + + 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.3', + 'mac_address': fake_neutron_port['mac_address']}, + {'ip_address': 'fe80::f816:3eff:fe1c:36a9', + 'mac_address': fake_neutron_port['mac_address']}] + updated_allowed_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_vm_port['allowed_address_pairs'].extend(updated_allowed_pairs) + + fake_unbind_response = ('fake_stdout', '') + mock_get_port_from_host.return_value = fake_vm_port + mock_port_unbind.return_value = fake_unbind_response + + nested_driver = nested.NestedDriver() + response = nested_driver.delete_host_iface(fake_endpoint_id, + fake_neutron_port) + + mock_get_port_from_host.assert_called_with('eth0') + mock_update_port.assert_called_with(fake_vm_port['id'], + {'port': { + 'allowed_address_pairs': updated_allowed_pairs + }}) + mock_port_unbind.assert_called_with(fake_endpoint_id, + fake_neutron_port) + + self.assertEqual(response, fake_unbind_response) + + @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): + nested_driver = nested.NestedDriver() + fake_neutron_port_id = uuidutils.generate_uuid() + response = nested_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") + + +class TestNestedDriverFailures(base.TestKuryrFailures): + """Unit tests for the NestedDriver port driver failures""" + + @mock.patch.object(nested.NestedDriver, '_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'] + + nested_driver = nested.NestedDriver() + self.assertRaises(exceptions.KuryrException, + nested_driver.create_host_iface, fake_endpoint_id, + fake_neutron_port, None) diff --git a/kuryr_libnetwork/tests/unit/port_driver/drivers/test_veth.py b/kuryr_libnetwork/tests/unit/port_driver/drivers/test_veth.py new file mode 100644 index 00000000..742ace75 --- /dev/null +++ b/kuryr_libnetwork/tests/unit/port_driver/drivers/test_veth.py @@ -0,0 +1,80 @@ +# 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 utils as lib_utils +from kuryr_libnetwork.port_driver.drivers import veth +from kuryr_libnetwork.tests.unit import base + + +class TestVethDriver(base.TestKuryrBase): + """Unit tests for veth driver""" + + def test_get_supported_bindings(self): + veth_driver = veth.VethDriver() + supported_bindings = veth_driver.get_supported_bindings() + self.assertEqual(supported_bindings, veth.VethDriver.BINDING_DRIVERS) + + def test_get_default_network_id(self): + veth_driver = veth.VethDriver() + host_network = veth_driver.get_default_network_id() + self.assertEqual(host_network, None) + + @mock.patch.object(binding, 'port_bind') + def test_create_host_iface(self, mock_port_bind): + veth_driver = veth.VethDriver() + fake_endpoint_id = lib_utils.get_hash() + fake_neutron_port = 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_subnets = self._get_fake_subnets( + fake_endpoint_id, fake_neutron_net_id, + fake_neutron_v4_subnet_id, fake_neutron_v6_subnet_id) + fake_network = mock.sentinel.binding_network + fake_exec_response = ('fake_stdout', '') + mock_port_bind.return_value = ('fake_host_ifname', + 'fake_container_ifname', fake_exec_response) + + response = veth_driver.create_host_iface(fake_endpoint_id, + fake_neutron_port, fake_subnets, fake_network) + mock_port_bind.assert_called_with(fake_endpoint_id, + fake_neutron_port, fake_subnets, fake_network) + self.assertEqual(response, fake_exec_response) + + @mock.patch.object(binding, 'port_unbind') + def test_delete_host_iface(self, mock_port_unbind): + veth_driver = veth.VethDriver() + fake_endpoint_id = lib_utils.get_hash() + fake_neutron_port = uuidutils.generate_uuid() + fake_unbind_response = ('fake_stdout', '') + mock_port_unbind.return_value = fake_unbind_response + + response = veth_driver.delete_host_iface(fake_endpoint_id, + fake_neutron_port) + mock_port_unbind.assert_called_with(fake_endpoint_id, + fake_neutron_port) + self.assertEqual(response, fake_unbind_response) + + @mock.patch.object(utils, 'get_veth_pair_names', + return_value=('fake_host_ifname', 'fake_container_ifname')) + def test_get_container_iface_name(self, mock_get_veth_pair_names): + veth_driver = veth.VethDriver() + fake_neutron_port_id = uuidutils.generate_uuid() + response = veth_driver.get_container_iface_name(fake_neutron_port_id) + mock_get_veth_pair_names.assert_called_with(fake_neutron_port_id) + self.assertEqual(response, "fake_container_ifname") diff --git a/kuryr_libnetwork/tests/unit/port_driver/test_base.py b/kuryr_libnetwork/tests/unit/port_driver/test_base.py new file mode 100644 index 00000000..82b70421 --- /dev/null +++ b/kuryr_libnetwork/tests/unit/port_driver/test_base.py @@ -0,0 +1,78 @@ +# 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 ddt +import mock + +from kuryr.lib import exceptions +from kuryr_libnetwork.port_driver import base as d_base +from kuryr_libnetwork.tests.unit import base + + +class TestBaseDriver(d_base.BaseNestedDriver): + def get_default_network_id(self): + pass + + def create_host_iface(self, endpoint_id, neutron_port, subnets, + network=None): + pass + + def delete_host_iface(self, endpoint_id, neutron_port): + pass + + def get_container_iface_name(self, neutron_port_id): + pass + + def get_supported_bindings(self): + pass + + +@ddt.ddt +class TestBaseNestedDriver(base.TestKuryrBase): + """Unit tests for the BaseNestedDriver port driver""" + + @mock.patch('kuryr_libnetwork.app.neutron.list_ports') + @mock.patch('kuryr.lib.binding.drivers.utils.get_ipdb') + @ddt.data(('fa:16:3e:20:57:c3'), (None)) + def test__get_port_from_host_iface(self, addr, m_get_ipdb, m_list_ports): + m_ip = mock.MagicMock() + m_iface = mock.MagicMock() + port = mock.sentinel.port + ports = {'ports': [port]} + + m_get_ipdb.return_value = m_ip + m_ip.interfaces.get.return_value = m_iface + m_iface.get.return_value = addr + m_list_ports.return_value = ports + + base_driver = TestBaseDriver() + if addr: + response = base_driver._get_port_from_host_iface('iface') + self.assertEqual(port, response) + m_list_ports.assert_called_with(mac_address=addr) + else: + self.assertRaises(exceptions.KuryrException, + base_driver._get_port_from_host_iface, 'iface') + + +class TestBaseNestedDriverFailures(base.TestKuryrFailures): + """Unit tests for the BaseNestedDriver port driver failures""" + + @mock.patch('kuryr.lib.binding.drivers.utils.get_ipdb') + def test__get_port_from_host_iface(self, m_get_ipdb): + m_ip = mock.MagicMock() + m_get_ipdb.return_value = m_ip + m_ip.interfaces.get.return_value = {} + + base_driver = TestBaseDriver() + self.assertRaises(exceptions.KuryrException, + base_driver._get_port_from_host_iface, '') diff --git a/kuryr_libnetwork/tests/unit/port_driver/test_driver.py b/kuryr_libnetwork/tests/unit/port_driver/test_driver.py new file mode 100644 index 00000000..8687de74 --- /dev/null +++ b/kuryr_libnetwork/tests/unit/port_driver/test_driver.py @@ -0,0 +1,113 @@ +# 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 ddt +import mock + +from oslo_utils import importutils + +from kuryr.lib import exceptions +from kuryr_libnetwork.port_driver import driver +from kuryr_libnetwork.tests.unit import base + + +@ddt.ddt +class TestDriver(base.TestKuryrBase): + """Unit tests for driver loading""" + + @mock.patch.object(driver, '_verify_binding_driver_compatibility') + @mock.patch.object(driver, '_verify_port_driver_compliancy') + @mock.patch.object(importutils, 'import_object') + @mock.patch.object(driver, '_parse_port_driver_config') + def test_get_driver_instance(self, mock_parse_config, mock_import_object, + mock_verify_compliancy, mock_verify_compatibility): + module = 'kuryr_libnetwork.port_driver.drivers.veth' + mock_parse_config.return_value = (module, 'veth', 'VethDriver') + fake_driver = mock.Mock(spec=driver.Driver) + mock_import_object.return_value = fake_driver + + response_driver = driver.get_driver_instance() + mock_parse_config.assert_called_once() + mock_import_object.assert_called_once_with(module + '.VethDriver') + mock_verify_compliancy.assert_called_once_with(fake_driver, 'veth') + mock_verify_compatibility.assert_called_once_with(fake_driver, 'veth') + self.assertEqual(response_driver, fake_driver) + + @mock.patch('kuryr_libnetwork.config.CONF') + @ddt.data('kuryr_libnetwork.port_driver.drivers.veth', 'veth') + def test__parse_port_driver_config(self, port_driver_value, mock_conf): + mock_conf.port_driver = port_driver_value + + module, name, classname = driver._parse_port_driver_config() + self.assertEqual(module, 'kuryr_libnetwork.port_driver.drivers.veth') + self.assertEqual(name, 'veth') + self.assertEqual(classname, 'VethDriver') + + def test__verify_port_driver_compliancy(self): + fake_driver = mock.Mock(spec=driver.Driver) + ret = driver._verify_port_driver_compliancy(fake_driver, 'driver') + self.assertIsNone(ret) + + @mock.patch('kuryr_libnetwork.config.CONF') + def test__verify_binding_driver_compatibility(self, mock_conf): + mock_conf.binding.driver = 'veth' + fake_driver = mock.Mock(spec=driver.Driver) + fake_driver.get_supported_bindings.return_value = ('veth',) + + ret = driver._verify_binding_driver_compatibility(fake_driver, 'veth') + fake_driver.get_supported_bindings.assert_called_once() + self.assertIsNone(ret) + + +class TestNestedDriverFailures(base.TestKuryrFailures): + """Unit tests for driver loading failures""" + + @mock.patch('kuryr_libnetwork.config.CONF') + def test__parse_port_driver_config_empty(self, mock_conf): + mock_conf.port_driver = '' + + self.assertRaisesRegex(exceptions.KuryrException, + "No port driver provided", driver._parse_port_driver_config) + + @mock.patch.object(importutils, 'import_object', side_effect=ImportError) + def test_get_driver_instance_import_error(self, mock_import_object): + self.assertRaises(exceptions.KuryrException, + driver.get_driver_instance) + + def test__verify_port_driver_compliancy(self): + class InvalidDriver(object): + pass + + self.assertRaises(exceptions.KuryrException, + driver._verify_port_driver_compliancy, InvalidDriver(), 'invalid') + + @mock.patch('kuryr_libnetwork.config.CONF') + def test__verify_binding_driver_compatibility_not_compatible(self, m_conf): + m_conf.binding.driver = 'macvlan' + message = "Configuration file error: port driver 'veth' is not " \ + "compatible with binding driver 'macvlan'" + + fake_driver = mock.Mock(spec=driver.Driver) + fake_driver.get_supported_bindings.return_value = ('veth',) + self.assertRaisesRegex(exceptions.KuryrException, message, + driver._verify_binding_driver_compatibility, fake_driver, 'veth') + + @mock.patch('kuryr_libnetwork.config.CONF') + def test__verify_binding_driver_compatibility_not_supported(self, m_conf): + m_conf.binding.driver = 'ipvlan' + message = "Configuration file error: binding driver 'ipvlan' is " \ + "currently not supported with 'nested' port driver" + + fake_driver = mock.Mock(spec=driver.Driver) + fake_driver.get_supported_bindings.return_value = ('ipvlan',) + self.assertRaisesRegex(exceptions.KuryrException, message, + driver._verify_binding_driver_compatibility, fake_driver, 'nested') diff --git a/kuryr_libnetwork/tests/unit/test_config.py b/kuryr_libnetwork/tests/unit/test_config.py index 27893c61..4c10829d 100644 --- a/kuryr_libnetwork/tests/unit/test_config.py +++ b/kuryr_libnetwork/tests/unit/test_config.py @@ -42,6 +42,9 @@ class ConfigurationTest(base.TestKuryrBase): self.assertEqual('kuryr6', config.CONF.neutron.default_subnetpool_v6) + self.assertEqual('kuryr_libnetwork.port_driver.drivers.veth', + config.CONF.port_driver) + @mock.patch.object(sys, 'argv', return_value='[]') @mock.patch('kuryr_libnetwork.controllers.check_for_neutron_ext_tag') @mock.patch('kuryr_libnetwork.controllers.check_for_neutron_ext_support') diff --git a/kuryr_libnetwork/tests/unit/test_kuryr.py b/kuryr_libnetwork/tests/unit/test_kuryr.py index 4e4e6502..b9f6fc25 100644 --- a/kuryr_libnetwork/tests/unit/test_kuryr.py +++ b/kuryr_libnetwork/tests/unit/test_kuryr.py @@ -15,8 +15,6 @@ import ddt from oslo_serialization import jsonutils from oslo_utils import uuidutils -from kuryr.lib import binding -from kuryr.lib.binding.drivers import utils as driver_utils from kuryr.lib import constants as lib_const from kuryr.lib import utils as lib_utils from kuryr_libnetwork import app @@ -58,16 +56,9 @@ class TestKuryr(base.TestKuryrBase): decoded_json = jsonutils.loads(response.data) self.assertEqual(expected, decoded_json) - def test_network_driver_create_network(self): + @ddt.data((True), (False)) + def test_network_driver_create_network(self, driver_default_net): docker_network_id = lib_utils.get_hash() - self.mox.StubOutWithMock(app.neutron, "create_network") - fake_request = { - "network": { - "name": utils.make_net_name(docker_network_id), - "admin_state_up": True - } - } - network_request = { 'NetworkID': docker_network_id, 'IPv4Data': [{ @@ -92,29 +83,55 @@ class TestKuryr(base.TestKuryrBase): app.neutron.list_subnetpools(name=fake_name).AndReturn( {'subnetpools': kuryr_subnetpools['subnetpools']}) - # The following fake response is retrieved from the Neutron doc: - # http://developer.openstack.org/api-ref-networking-v2.html#createNetwork # noqa fake_neutron_net_id = "4e8e5957-649f-477b-9e5b-f1f75b21c03c" - fake_response = { - "network": { - "status": "ACTIVE", - "subnets": [], - "name": utils.make_net_name(docker_network_id), - "admin_state_up": True, - "tenant_id": "9bacb3c5d39d41a79512987f338cf177", - "router:external": False, - "segments": [], - "shared": False, - "id": fake_neutron_net_id - } + driver_value = fake_neutron_net_id if driver_default_net else None + self.mox.StubOutWithMock(app.driver, "get_default_network_id") + app.driver.get_default_network_id().AndReturn(driver_value) + + fake_network = { + "status": "ACTIVE", + "subnets": [], + "admin_state_up": True, + "tenant_id": "9bacb3c5d39d41a79512987f338cf177", + "router:external": False, + "segments": [], + "shared": False, + "id": fake_neutron_net_id, } - app.neutron.create_network(fake_request).AndReturn(fake_response) + + if driver_value: + fake_existing_networks_response = { + "networks": [fake_network] + } + self.mox.StubOutWithMock(app.neutron, "list_networks") + app.neutron.list_networks(id=fake_neutron_net_id).AndReturn( + fake_existing_networks_response) + else: + fake_create_network_request = { + "network": { + "name": utils.make_net_name(docker_network_id), + "admin_state_up": True + } + } + fake_network['name'] = utils.make_net_name(docker_network_id) + # The following fake response is retrieved from the Neutron doc: + # http://developer.openstack.org/api-ref-networking-v2.html#createNetwork # noqa + fake_create_network_response = { + "network": fake_network + } + self.mox.StubOutWithMock(app.neutron, "create_network") + app.neutron.create_network(fake_create_network_request).AndReturn( + fake_create_network_response) self.mox.StubOutWithMock(app.neutron, "add_tag") tags = utils.create_net_tags(docker_network_id) for tag in tags: app.neutron.add_tag('networks', fake_neutron_net_id, tag) + if driver_value: + app.neutron.add_tag('networks', fake_neutron_net_id, + 'kuryr.net.existing') + self.mox.StubOutWithMock(app.neutron, 'list_subnets') fake_existing_subnets_response = { "subnets": [] @@ -361,7 +378,10 @@ class TestKuryr(base.TestKuryrBase): decoded_json = jsonutils.loads(response.data) self.assertEqual(constants.SCHEMA['SUCCESS'], decoded_json) - def test_network_driver_create_network_with_pool_name_option(self): + @ddt.data((True), (False)) + def test_network_driver_create_network_with_pool_name_option(self, + driver_default_net): + docker_network_id = lib_utils.get_hash() self.mox.StubOutWithMock(app.neutron, 'list_subnetpools') fake_kuryr_subnetpool_id = uuidutils.generate_uuid() @@ -370,37 +390,56 @@ class TestKuryr(base.TestKuryrBase): fake_kuryr_subnetpool_id, name=fake_name) app.neutron.list_subnetpools(name=fake_name).AndReturn( {'subnetpools': kuryr_subnetpools['subnetpools']}) - docker_network_id = lib_utils.get_hash() - self.mox.StubOutWithMock(app.neutron, "create_network") - fake_request = { - "network": { - "name": utils.make_net_name(docker_network_id), - "admin_state_up": True - } - } - # The following fake response is retrieved from the Neutron doc: - # http://developer.openstack.org/api-ref-networking-v2.html#createNetwork # noqa + fake_neutron_net_id = "4e8e5957-649f-477b-9e5b-f1f75b21c03c" - fake_response = { - "network": { - "status": "ACTIVE", - "subnets": [], - "name": utils.make_net_name(docker_network_id), - "admin_state_up": True, - "tenant_id": "9bacb3c5d39d41a79512987f338cf177", - "router:external": False, - "segments": [], - "shared": False, - "id": fake_neutron_net_id - } + driver_value = fake_neutron_net_id if driver_default_net else None + self.mox.StubOutWithMock(app.driver, "get_default_network_id") + app.driver.get_default_network_id().AndReturn(driver_value) + + fake_network = { + "status": "ACTIVE", + "subnets": [], + "admin_state_up": True, + "tenant_id": "9bacb3c5d39d41a79512987f338cf177", + "router:external": False, + "segments": [], + "shared": False, + "id": fake_neutron_net_id, } - app.neutron.create_network(fake_request).AndReturn(fake_response) + + if driver_value: + fake_existing_networks_response = { + "networks": [fake_network] + } + self.mox.StubOutWithMock(app.neutron, "list_networks") + app.neutron.list_networks(id=fake_neutron_net_id).AndReturn( + fake_existing_networks_response) + else: + fake_create_network_request = { + "network": { + "name": utils.make_net_name(docker_network_id), + "admin_state_up": True + } + } + fake_network['name'] = utils.make_net_name(docker_network_id) + # The following fake response is retrieved from the Neutron doc: + # http://developer.openstack.org/api-ref-networking-v2.html#createNetwork # noqa + fake_create_network_response = { + "network": fake_network + } + self.mox.StubOutWithMock(app.neutron, "create_network") + app.neutron.create_network(fake_create_network_request).AndReturn( + fake_create_network_response) self.mox.StubOutWithMock(app.neutron, "add_tag") tags = utils.create_net_tags(docker_network_id) for tag in tags: app.neutron.add_tag('networks', fake_neutron_net_id, tag) + if driver_value: + app.neutron.add_tag('networks', fake_neutron_net_id, + 'kuryr.net.existing') + self.mox.StubOutWithMock(app.neutron, 'list_subnets') fake_existing_subnets_response = { "subnets": [] @@ -463,7 +502,8 @@ class TestKuryr(base.TestKuryrBase): decoded_json = jsonutils.loads(response.data) self.assertEqual(constants.SCHEMA['SUCCESS'], decoded_json) - def test_network_driver_create_network_wo_gw(self): + @ddt.data((True), (False)) + def test_network_driver_create_network_wo_gw(self, driver_default_net): docker_network_id = lib_utils.get_hash() network_request = { 'NetworkID': docker_network_id, @@ -488,36 +528,55 @@ class TestKuryr(base.TestKuryrBase): app.neutron.list_subnetpools(name=fake_name).AndReturn( {'subnetpools': kuryr_subnetpools['subnetpools']}) - self.mox.StubOutWithMock(app.neutron, "create_network") - fake_request = { - "network": { - "name": utils.make_net_name(docker_network_id), - "admin_state_up": True - } - } - # The following fake response is retrieved from the Neutron doc: - # http://developer.openstack.org/api-ref-networking-v2.html#createNetwork # noqa fake_neutron_net_id = "4e8e5957-649f-477b-9e5b-f1f75b21c03c" - fake_response = { - "network": { - "status": "ACTIVE", - "subnets": [], - "name": utils.make_net_name(docker_network_id), - "admin_state_up": True, - "tenant_id": "9bacb3c5d39d41a79512987f338cf177", - "router:external": False, - "segments": [], - "shared": False, - "id": fake_neutron_net_id - } + driver_value = fake_neutron_net_id if driver_default_net else None + self.mox.StubOutWithMock(app.driver, "get_default_network_id") + app.driver.get_default_network_id().AndReturn(driver_value) + + fake_network = { + "status": "ACTIVE", + "subnets": [], + "admin_state_up": True, + "tenant_id": "9bacb3c5d39d41a79512987f338cf177", + "router:external": False, + "segments": [], + "shared": False, + "id": fake_neutron_net_id, } - app.neutron.create_network(fake_request).AndReturn(fake_response) + + if driver_value: + fake_existing_networks_response = { + "networks": [fake_network] + } + self.mox.StubOutWithMock(app.neutron, "list_networks") + app.neutron.list_networks(id=fake_neutron_net_id).AndReturn( + fake_existing_networks_response) + else: + fake_create_network_request = { + "network": { + "name": utils.make_net_name(docker_network_id), + "admin_state_up": True + } + } + # The following fake response is retrieved from the Neutron doc: + # http://developer.openstack.org/api-ref-networking-v2.html#createNetwork # noqa + fake_network['name'] = utils.make_net_name(docker_network_id) + fake_create_network_response = { + "network": fake_network + } + self.mox.StubOutWithMock(app.neutron, "create_network") + app.neutron.create_network(fake_create_network_request).AndReturn( + fake_create_network_response) self.mox.StubOutWithMock(app.neutron, "add_tag") tags = utils.create_net_tags(docker_network_id) for tag in tags: app.neutron.add_tag('networks', fake_neutron_net_id, tag) + if driver_value: + app.neutron.add_tag('networks', fake_neutron_net_id, + 'kuryr.net.existing') + self.mox.StubOutWithMock(app.neutron, 'list_subnets') fake_existing_subnets_response = { "subnets": [] @@ -795,9 +854,14 @@ class TestKuryr(base.TestKuryrBase): fake_neutron_subnets = [fake_v4_subnet['subnet'], fake_v6_subnet['subnet']] - _, fake_peer_name, _ = self._mock_out_binding( - fake_docker_endpoint_id, fake_updated_port, - fake_neutron_subnets, fake_neutron_network['networks'][0]) + + self.mox.StubOutWithMock(app.driver, 'create_host_iface') + fake_create_iface_response = ('fake stdout', '') + app.driver.create_host_iface(fake_docker_endpoint_id, + fake_updated_port, fake_neutron_subnets, + fake_neutron_network['networks'][0]).AndReturn( + fake_create_iface_response) + self.mox.ReplayAll() if vif_plug_is_fatal: self.mox.StubOutWithMock(app, "vif_plug_is_fatal") @@ -888,11 +952,11 @@ class TestKuryr(base.TestKuryrBase): self.assertEqual(fake_port_response['ports'][0]['status'], decoded_json['Value']['status']) - @mock.patch.object(binding, 'port_unbind') + @mock.patch('kuryr_libnetwork.controllers.app.driver.delete_host_iface') @mock.patch('kuryr_libnetwork.controllers.app.neutron.list_ports') @mock.patch('kuryr_libnetwork.controllers.app.neutron.list_networks') def test_network_driver_delete_endpoint(self, mock_list_networks, - mock_list_ports, mock_port_unbind): + mock_list_ports, mock_delete_host_iface): fake_docker_net_id = lib_utils.get_hash() fake_docker_endpoint_id = lib_utils.get_hash() @@ -901,7 +965,7 @@ class TestKuryr(base.TestKuryrBase): fake_neutron_v6_subnet_id = uuidutils.generate_uuid() fake_neutron_v4_subnet_id = uuidutils.generate_uuid() - fake_unbinding_response = ('fake stdout', '') + fake_iface_deletion_response = ('fake stdout', '') fake_neutron_ports_response = self._get_fake_ports( fake_docker_endpoint_id, fake_neutron_net_id, fake_neutron_port_id, lib_const.PORT_STATUS_ACTIVE, @@ -914,7 +978,7 @@ class TestKuryr(base.TestKuryrBase): mock_list_networks.return_value = self._get_fake_list_network( fake_neutron_net_id) mock_list_ports.return_value = fake_neutron_ports_response - mock_port_unbind.return_value = fake_unbinding_response + mock_delete_host_iface.return_value = fake_iface_deletion_response data = { 'NetworkID': fake_docker_net_id, @@ -927,7 +991,7 @@ class TestKuryr(base.TestKuryrBase): self.assertEqual(200, response.status_code) mock_list_networks.assert_called_with(tags=t) mock_list_ports.assert_called_with(name=neutron_port_name) - mock_port_unbind.assert_called_with(fake_docker_endpoint_id, + mock_delete_host_iface.assert_called_with(fake_docker_endpoint_id, fake_neutron_port) decoded_json = jsonutils.loads(response.data) self.assertEqual(constants.SCHEMA['SUCCESS'], decoded_json) @@ -959,9 +1023,10 @@ class TestKuryr(base.TestKuryrBase): app.neutron.list_subnets(network_id=fake_neutron_net_id).AndReturn( fake_neutron_subnets_response) fake_neutron_subnets = fake_neutron_subnets_response['subnets'] - self.mox.StubOutWithMock(driver_utils, 'get_veth_pair_names') - _, fake_peer_name = driver_utils.get_veth_pair_names( - fake_neutron_port_id).AndReturn(('fake-veth', "fake-veth_c")) + + self.mox.StubOutWithMock(app.driver, 'get_container_iface_name') + fake_iface_name = app.driver.get_container_iface_name( + fake_neutron_port_id).AndReturn('fake-name') self.mox.ReplayAll() fake_subnets_dict_by_id = {subnet['id']: subnet @@ -989,7 +1054,7 @@ class TestKuryr(base.TestKuryrBase): 'GatewayIPv6': fake_neutron_v6_subnet['gateway_ip'], 'InterfaceName': { 'DstPrefix': config.CONF.binding.veth_dst_prefix, - 'SrcName': fake_peer_name, + 'SrcName': fake_iface_name, }, 'StaticRoutes': [] } diff --git a/kuryr_libnetwork/tests/unit/test_kuryr_endpoint.py b/kuryr_libnetwork/tests/unit/test_kuryr_endpoint.py index be0e0cc5..4db45572 100644 --- a/kuryr_libnetwork/tests/unit/test_kuryr_endpoint.py +++ b/kuryr_libnetwork/tests/unit/test_kuryr_endpoint.py @@ -12,15 +12,14 @@ import ddt import mock -from neutronclient.common import exceptions +from neutronclient.common import exceptions as n_exceptions from oslo_concurrency import processutils from oslo_serialization import jsonutils from oslo_utils import uuidutils from werkzeug import exceptions as w_exceptions -from kuryr.lib import binding from kuryr.lib import constants as lib_const -from kuryr.lib import exceptions as kuryr_exceptions +from kuryr.lib import exceptions as k_exceptions from kuryr.lib import utils as lib_utils from kuryr_libnetwork.tests.unit import base from kuryr_libnetwork import utils @@ -54,8 +53,8 @@ class TestKuryrEndpointCreateFailures(base.TestKuryrFailures): @mock.patch('kuryr_libnetwork.controllers.app.neutron.list_networks') @mock.patch('kuryr_libnetwork.controllers.app.neutron.list_subnets') @mock.patch('kuryr_libnetwork.controllers.app.neutron.list_ports') - @ddt.data(exceptions.Unauthorized, exceptions.Forbidden, - exceptions.NotFound, exceptions.ServiceUnavailable) + @ddt.data(n_exceptions.Unauthorized, n_exceptions.Forbidden, + n_exceptions.NotFound, n_exceptions.ServiceUnavailable) def test_create_endpoint_port_failures(self, GivenException, mock_list_ports, mock_list_subnets, mock_list_networks, mock_create_port): @@ -105,16 +104,18 @@ class TestKuryrEndpointCreateFailures(base.TestKuryrFailures): self.assertIn('Err', decoded_json) self.assertEqual({'Err': GivenException.message}, decoded_json) - @mock.patch.object(binding, 'port_bind') + @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.neutron.list_subnets') @mock.patch('kuryr_libnetwork.controllers.app.neutron.list_ports') @mock.patch('kuryr_libnetwork.controllers.app.neutron.list_networks') - @ddt.data(kuryr_exceptions.VethCreationFailure, - processutils.ProcessExecutionError) - def test_create_veth_failures(self, GivenException, + @ddt.data(k_exceptions.VethCreationFailure, + processutils.ProcessExecutionError, + k_exceptions.KuryrException, + n_exceptions.NeutronClientException) + def test_create_host_iface_failures(self, GivenException, mock_list_networks, mock_list_ports, mock_list_subnets, - mock_update_port, mock_port_bind): + mock_update_port, mock_create_host_iface): fake_docker_network_id = lib_utils.get_hash() fake_docker_endpoint_id = lib_utils.get_hash() fake_neutron_network_id = uuidutils.generate_uuid() @@ -173,11 +174,15 @@ class TestKuryrEndpointCreateFailures(base.TestKuryrFailures): fake_docker_endpoint_id) mock_update_port.return_value = fake_port_response - fake_message = "fake message" - fake_exception = GivenException(fake_message) fake_neutron_subnets = [fake_v4_subnet['subnet'], fake_v6_subnet['subnet']] - mock_port_bind.side_effect = fake_exception + + fake_message = "fake message" + if GivenException == n_exceptions.NeutronClientException: + fake_exception = GivenException(fake_message, status_code=500) + else: + fake_exception = GivenException(fake_message) + mock_create_host_iface.side_effect = fake_exception response = self._invoke_create_request( fake_docker_network_id, fake_docker_endpoint_id) @@ -197,7 +202,7 @@ class TestKuryrEndpointCreateFailures(base.TestKuryrFailures): 'device_owner': lib_const.DEVICE_OWNER, 'device_id': fake_docker_endpoint_id }}) - mock_port_bind.assert_called_with( + mock_create_host_iface.assert_called_with( fake_docker_endpoint_id, fake_updated_port, fake_neutron_subnets, fake_neutron_network['networks'][0]) @@ -231,13 +236,15 @@ class TestKuryrEndpointDeleteFailures(base.TestKuryrFailures): data=jsonutils.dumps(data)) return response - @mock.patch.object(binding, 'port_unbind') + @mock.patch('kuryr_libnetwork.controllers.app.driver.delete_host_iface') @mock.patch('kuryr_libnetwork.controllers.app.neutron.list_ports') @mock.patch('kuryr_libnetwork.controllers.app.neutron.list_networks') - @ddt.data(kuryr_exceptions.VethDeletionFailure, + @ddt.data(k_exceptions.VethDeletionFailure, + k_exceptions.KuryrException, + n_exceptions.NeutronClientException, processutils.ProcessExecutionError) - def test_delete_endpoint_unbinding_failure(self, GivenException, - mock_list_networks, mock_list_ports, mock_port_unbind): + def test_delete_endpoint_delete_host_iface_failure(self, GivenException, + mock_list_networks, mock_list_ports, mock_delete_host_iface): fake_docker_network_id = lib_utils.get_hash() fake_docker_endpoint_id = lib_utils.get_hash() @@ -258,8 +265,11 @@ class TestKuryrEndpointDeleteFailures(base.TestKuryrFailures): fake_neutron_port = fake_neutron_ports_response['ports'][0] fake_message = "fake message" - fake_exception = GivenException(fake_message) - mock_port_unbind.side_effect = fake_exception + if GivenException == n_exceptions.NeutronClientException: + fake_exception = GivenException(fake_message, status_code=500) + else: + fake_exception = GivenException(fake_message) + mock_delete_host_iface.side_effect = fake_exception response = self._invoke_delete_request( fake_docker_network_id, fake_docker_endpoint_id) @@ -267,7 +277,7 @@ class TestKuryrEndpointDeleteFailures(base.TestKuryrFailures): w_exceptions.InternalServerError.code, response.status_code) mock_list_networks.assert_called_with(tags=t) mock_list_ports.assert_called_with(name=neutron_port_name) - mock_port_unbind.assert_called_with(fake_docker_endpoint_id, + mock_delete_host_iface.assert_called_with(fake_docker_endpoint_id, fake_neutron_port) decoded_json = jsonutils.loads(response.data) self.assertIn('Err', decoded_json) diff --git a/kuryr_libnetwork/tests/unit/test_kuryr_network.py b/kuryr_libnetwork/tests/unit/test_kuryr_network.py index 74a0b44c..c4a12348 100644 --- a/kuryr_libnetwork/tests/unit/test_kuryr_network.py +++ b/kuryr_libnetwork/tests/unit/test_kuryr_network.py @@ -36,9 +36,12 @@ class TestKuryrNetworkCreateFailures(base.TestKuryrFailures): return response @mock.patch('kuryr_libnetwork.controllers.app.neutron.create_network') + @mock.patch( + 'kuryr_libnetwork.controllers.app.driver.get_default_network_id', + return_value=None) @mock.patch('kuryr_libnetwork.controllers.app.neutron.list_subnetpools') def test_create_network_unauthorized(self, mock_list_subnetpools, - mock_create_network): + mock_get_default_network, mock_create_network): docker_network_id = lib_utils.get_hash() network_request = { 'NetworkID': docker_network_id, @@ -81,6 +84,47 @@ class TestKuryrNetworkCreateFailures(base.TestKuryrFailures): self.assertEqual( {'Err': exceptions.Unauthorized.message}, decoded_json) + @mock.patch( + 'kuryr_libnetwork.controllers.app.driver.get_default_network_id') + @mock.patch('kuryr_libnetwork.controllers.app.neutron.list_subnetpools') + def test_create_network_get_default_network_id_unauthorized(self, + mock_list_subnetpools, mock_get_default_network_id): + docker_network_id = lib_utils.get_hash() + network_request = { + 'NetworkID': docker_network_id, + 'IPv4Data': [{ + 'AddressSpace': 'foo', + 'Pool': '192.168.42.0/24', + 'Gateway': '192.168.42.1/24', + 'AuxAddresses': {} + }], + 'IPv6Data': [{ + 'AddressSpace': 'bar', + 'Pool': 'fe80::/64', + 'Gateway': 'fe80::f816:3eff:fe20:57c3/64', + 'AuxAddresses': {} + }], + 'Options': {} + } + + fake_subnetpool_name = lib_utils.get_neutron_subnetpool_name( + network_request['IPv4Data'][0]['Pool']) + fake_kuryr_subnetpool_id = uuidutils.generate_uuid() + kuryr_subnetpools = self._get_fake_v4_subnetpools( + fake_kuryr_subnetpool_id, name=fake_subnetpool_name) + mock_list_subnetpools.return_value = { + 'subnetpools': kuryr_subnetpools['subnetpools']} + + mock_get_default_network_id.side_effect = exceptions.Unauthorized + response = self._invoke_create_request(network_request) + self.assertEqual(401, response.status_code) + decoded_json = jsonutils.loads(response.data) + mock_list_subnetpools.assert_called_with(name=fake_subnetpool_name) + mock_get_default_network_id.assert_called() + self.assertIn('Err', decoded_json) + self.assertEqual( + {'Err': exceptions.Unauthorized.message}, decoded_json) + def test_create_network_bad_request(self): invalid_docker_network_id = 'id-should-be-hexdigits' network_request = {