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 <ltomasbo@redhat.com>
Co-Authored-By: Louise Daly <louise.m.daly@intel.com>
Co-Authored-By: Gary Loughnane <gary.loughnane@intel.com>
Implements: blueprint driver-binding-ipvlan

Change-Id: I380fb017dc97a2cd54a404ec3c8154ecc2df80b8
This commit is contained in:
Marco Chiappero 2016-11-21 17:44:29 +00:00
parent 351b7e07b1
commit 09f11aa394
20 changed files with 1232 additions and 141 deletions

View File

@ -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

View File

@ -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": []

View File

View File

@ -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))

View File

@ -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))

View File

@ -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

View File

@ -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

View File

@ -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():

View File

@ -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 = {

View File

@ -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)

View File

@ -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")

View File

@ -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, '')

View File

@ -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')

View File

@ -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')

View File

@ -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': []
}

View File

@ -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)

View File

@ -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 = {