Merge "binding: Add driver based subsystem"
This commit is contained in:
commit
dcebfd0c0a
1
.gitignore
vendored
1
.gitignore
vendored
@ -14,7 +14,6 @@ bin
|
||||
var
|
||||
sdist
|
||||
develop-eggs
|
||||
lib
|
||||
lib64
|
||||
|
||||
# Installer logs
|
||||
|
18
README.rst
18
README.rst
@ -76,13 +76,27 @@ Edit keystone section in `/etc/kuryr/kuryr.conf`, replace ADMIN_PASSWORD:
|
||||
admin_password = ADMIN_PASSWORD
|
||||
|
||||
|
||||
In the same file uncomment the `bindir` parameter with the path for the Kuryr vif binding
|
||||
executables:
|
||||
In the same file uncomment the `bindir` parameter with the path for the Kuryr
|
||||
vif binding executables:
|
||||
|
||||
::
|
||||
|
||||
bindir = /usr/local/libexec/kuryr
|
||||
|
||||
By default, Kuryr will use veth pairs for performing the binding. However, the
|
||||
Kuryr library ships with two other drivers that you can configure in the
|
||||
**binding** section::
|
||||
|
||||
[binding]
|
||||
#driver = kuryr.lib.binding.drivers.ipvlan
|
||||
#driver = kuryr.lib.binding.drivers.macvlan
|
||||
|
||||
Drivers may make use of other **binding** options. Both Kuryr library drivers in
|
||||
the previous snippet can be further configured setting the interface that will
|
||||
act as link interface for the virtual devices::
|
||||
|
||||
link_iface = enp4s0
|
||||
|
||||
|
||||
Running Kuryr
|
||||
-------------
|
||||
|
@ -1,204 +0,0 @@
|
||||
# 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 os
|
||||
|
||||
import ipaddress
|
||||
from oslo_concurrency import processutils
|
||||
from oslo_config import cfg
|
||||
from oslo_utils import excutils
|
||||
import pyroute2
|
||||
import six
|
||||
|
||||
from kuryr.lib import exceptions
|
||||
from kuryr.lib import utils
|
||||
|
||||
|
||||
BINDING_SUBCOMMAND = 'bind'
|
||||
DOWN = 'DOWN'
|
||||
FALLBACK_VIF_TYPE = 'unbound'
|
||||
FIXED_IP_KEY = 'fixed_ips'
|
||||
IFF_UP = 0x1 # The last bit represents if the interface is up
|
||||
IP_ADDRESS_KEY = 'ip_address'
|
||||
KIND_VETH = 'veth'
|
||||
MAC_ADDRESS_KEY = 'mac_address'
|
||||
SUBNET_ID_KEY = 'subnet_id'
|
||||
UNBINDING_SUBCOMMAND = 'unbind'
|
||||
VIF_TYPE_KEY = 'binding:vif_type'
|
||||
VIF_DETAILS_KEY = 'binding:vif_details'
|
||||
DEFAULT_NETWORK_MTU = 1500
|
||||
|
||||
_IPDB_CACHE = None
|
||||
_IPROUTE_CACHE = None
|
||||
|
||||
|
||||
def get_ipdb():
|
||||
"""Returns the already cached or a newly created IPDB instance.
|
||||
|
||||
IPDB reads the Linux specific file when it's instantiated. This behaviour
|
||||
prevents Mac OSX users from running unit tests. This function makes the
|
||||
loading IPDB lazyily and therefore it can be mocked after the import of
|
||||
modules that import this module.
|
||||
|
||||
:returns: The already cached or newly created ``pyroute2.IPDB`` instance
|
||||
"""
|
||||
global _IPDB_CACHE
|
||||
if not _IPDB_CACHE:
|
||||
_IPDB_CACHE = pyroute2.IPDB()
|
||||
return _IPDB_CACHE
|
||||
|
||||
|
||||
def get_iproute():
|
||||
"""Returns the already cached or a newly created IPRoute instance.
|
||||
|
||||
IPRoute reads the Linux specific file when it's instantiated. This
|
||||
behaviour prevents Mac OSX users from running unit tests. This function
|
||||
makes the loading IPDB lazyily and therefore it can be mocked after the
|
||||
import of modules that import this module.
|
||||
|
||||
:returns: The already cached or newly created ``pyroute2.IPRoute`` instance
|
||||
"""
|
||||
global _IPROUTE_CACHE
|
||||
if not _IPROUTE_CACHE:
|
||||
_IPROUTE_CACHE = pyroute2.IPRoute()
|
||||
return _IPROUTE_CACHE
|
||||
|
||||
|
||||
def _is_up(interface):
|
||||
flags = interface['flags']
|
||||
if not flags:
|
||||
return False
|
||||
return (flags & IFF_UP) == 1
|
||||
|
||||
|
||||
def cleanup_veth(ifname):
|
||||
"""Cleans the veth passed as an argument up.
|
||||
|
||||
:param ifname: the name of the veth endpoint
|
||||
:returns: the index of the interface which name is the given ifname if it
|
||||
exists, otherwise None
|
||||
:raises: pyroute2.NetlinkError
|
||||
"""
|
||||
ipr = get_iproute()
|
||||
|
||||
veths = ipr.link_lookup(ifname=ifname)
|
||||
if veths:
|
||||
host_veth_index = veths[0]
|
||||
ipr.link_remove(host_veth_index)
|
||||
return host_veth_index
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def port_bind(endpoint_id, neutron_port, neutron_subnets,
|
||||
neutron_network=None):
|
||||
"""Binds the Neutron port to the network interface on the host.
|
||||
|
||||
:param endpoint_id: the ID of the endpoint as string
|
||||
:param neutron_port: a port dictionary returned from
|
||||
python-neutronclient
|
||||
:param neutron_subnets: a list of all subnets under network to which this
|
||||
endpoint is trying to join
|
||||
:param neutron_network: network which this endpoint is trying to join
|
||||
:returns: the tuple of the names of the veth pair and the tuple of stdout
|
||||
and stderr returned by processutils.execute invoked with the
|
||||
executable script for binding
|
||||
:raises: kuryr.common.exceptions.VethCreationFailure,
|
||||
processutils.ProcessExecutionError
|
||||
"""
|
||||
ip = get_ipdb()
|
||||
|
||||
port_id = neutron_port['id']
|
||||
ifname, peer_name = utils.get_veth_pair_names(port_id)
|
||||
subnets_dict = {subnet['id']: subnet for subnet in neutron_subnets}
|
||||
if neutron_network is None:
|
||||
mtu = DEFAULT_NETWORK_MTU
|
||||
else:
|
||||
mtu = neutron_network.get('mtu', DEFAULT_NETWORK_MTU)
|
||||
|
||||
try:
|
||||
with ip.create(ifname=ifname, kind=KIND_VETH,
|
||||
reuse=True, peer=peer_name) as host_veth:
|
||||
if not _is_up(host_veth):
|
||||
host_veth.up()
|
||||
with ip.interfaces[peer_name] as peer_veth:
|
||||
fixed_ips = neutron_port.get(FIXED_IP_KEY, [])
|
||||
if not fixed_ips and (IP_ADDRESS_KEY in neutron_port):
|
||||
peer_veth.add_ip(neutron_port[IP_ADDRESS_KEY])
|
||||
for fixed_ip in fixed_ips:
|
||||
if IP_ADDRESS_KEY in fixed_ip and (SUBNET_ID_KEY in fixed_ip):
|
||||
subnet_id = fixed_ip[SUBNET_ID_KEY]
|
||||
subnet = subnets_dict[subnet_id]
|
||||
cidr = ipaddress.ip_network(six.text_type(subnet['cidr']))
|
||||
peer_veth.add_ip(fixed_ip[IP_ADDRESS_KEY], cidr.prefixlen)
|
||||
peer_veth.set_mtu(mtu)
|
||||
peer_veth.address = neutron_port[MAC_ADDRESS_KEY].lower()
|
||||
if not _is_up(peer_veth):
|
||||
peer_veth.up()
|
||||
except pyroute2.CreateException:
|
||||
raise exceptions.VethCreationFailure(
|
||||
'Creating the veth pair was failed.')
|
||||
except pyroute2.CommitException:
|
||||
raise exceptions.VethCreationFailure(
|
||||
'Could not configure the veth endpoint for the container.')
|
||||
|
||||
vif_type = neutron_port.get(VIF_TYPE_KEY, FALLBACK_VIF_TYPE)
|
||||
vif_details = utils.string_mappings(neutron_port.get(VIF_DETAILS_KEY))
|
||||
binding_exec_path = os.path.join(cfg.CONF.bindir, vif_type)
|
||||
if not os.path.exists(binding_exec_path):
|
||||
cleanup_veth(ifname)
|
||||
raise exceptions.BindingNotSupportedFailure(
|
||||
"vif_type({0}) is not supported. A binding script for "
|
||||
"this type can't be found.".format(vif_type))
|
||||
port_id = neutron_port['id']
|
||||
network_id = neutron_port['network_id']
|
||||
tenant_id = neutron_port['tenant_id']
|
||||
mac_address = neutron_port['mac_address']
|
||||
try:
|
||||
stdout, stderr = processutils.execute(
|
||||
binding_exec_path, BINDING_SUBCOMMAND, port_id, ifname,
|
||||
endpoint_id, mac_address, network_id, tenant_id, vif_details,
|
||||
run_as_root=True)
|
||||
except processutils.ProcessExecutionError:
|
||||
with excutils.save_and_reraise_exception():
|
||||
cleanup_veth(ifname)
|
||||
|
||||
return (ifname, peer_name, (stdout, stderr))
|
||||
|
||||
|
||||
def port_unbind(endpoint_id, neutron_port):
|
||||
"""Unbinds the Neutron port from the network interface on the host.
|
||||
|
||||
:param endpoint_id: the ID of the Docker container as string
|
||||
:param neutron_port: a port dictionary returned from python-neutronclient
|
||||
:returns: the tuple of stdout and stderr returned by processutils.execute
|
||||
invoked with the executable script for unbinding
|
||||
:raises: processutils.ProcessExecutionError, pyroute2.NetlinkError
|
||||
"""
|
||||
|
||||
vif_type = neutron_port.get(VIF_TYPE_KEY, FALLBACK_VIF_TYPE)
|
||||
vif_details = utils.string_mappings(neutron_port.get(VIF_DETAILS_KEY))
|
||||
unbinding_exec_path = os.path.join(cfg.CONF.bindir, vif_type)
|
||||
|
||||
port_id = neutron_port['id']
|
||||
ifname, _ = utils.get_veth_pair_names(port_id)
|
||||
|
||||
mac_address = neutron_port['mac_address']
|
||||
stdout, stderr = processutils.execute(
|
||||
unbinding_exec_path, UNBINDING_SUBCOMMAND, port_id, ifname,
|
||||
endpoint_id, mac_address, vif_details, run_as_root=True)
|
||||
try:
|
||||
cleanup_veth(ifname)
|
||||
except pyroute2.NetlinkError:
|
||||
raise exceptions.VethDeleteionFailure(
|
||||
'Deleting the veth pair failed.')
|
||||
return (stdout, stderr)
|
52
kuryr/lib/binding/__init__.py
Normal file
52
kuryr/lib/binding/__init__.py
Normal file
@ -0,0 +1,52 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from oslo_config import cfg
|
||||
from oslo_utils import importutils
|
||||
|
||||
|
||||
def port_bind(endpoint_id, port, subnets, network=None, nested_port=None):
|
||||
"""Binds the Neutron port to the network interface on the host.
|
||||
|
||||
:param endpoint_id: the ID of the endpoint as string
|
||||
:param port: the instance Neutron port dictionary as returned by
|
||||
python-neutronclient
|
||||
:param subnets: an iterable of all the Neutron subnets which the
|
||||
endpoint is trying to join
|
||||
:param network: the Neutron network which the endpoint is trying to
|
||||
join
|
||||
:param nested_port: the dictionary, as returned by python-neutronclient,
|
||||
of the port that that is used when running inside
|
||||
another instance (either ipvlan/macvlan or a subport)
|
||||
:returns: the tuple of the names of the veth pair and the tuple of stdout
|
||||
and stderr returned by processutils.execute invoked with the
|
||||
executable script for binding
|
||||
:raises: kuryr.common.exceptions.VethCreationFailure,
|
||||
processutils.ProcessExecutionError
|
||||
"""
|
||||
driver = importutils.import_module(cfg.CONF.binding.driver)
|
||||
|
||||
return driver.port_bind(endpoint_id, port, subnets, network=network,
|
||||
nested_port=nested_port)
|
||||
|
||||
|
||||
def port_unbind(endpoint_id, neutron_port):
|
||||
"""Unbinds the Neutron port from the network interface on the host.
|
||||
|
||||
:param endpoint_id: the ID of the Docker container as string
|
||||
:param neutron_port: a port dictionary returned from python-neutronclient
|
||||
:returns: the tuple of stdout and stderr returned by processutils.execute
|
||||
invoked with the executable script for unbinding
|
||||
:raises: processutils.ProcessExecutionError, pyroute2.NetlinkError
|
||||
"""
|
||||
driver = importutils.import_module(cfg.CONF.binding.driver)
|
||||
|
||||
return driver.port_unbind(endpoint_id, neutron_port)
|
0
kuryr/lib/binding/drivers/__init__.py
Normal file
0
kuryr/lib/binding/drivers/__init__.py
Normal file
57
kuryr/lib/binding/drivers/ipvlan.py
Normal file
57
kuryr/lib/binding/drivers/ipvlan.py
Normal file
@ -0,0 +1,57 @@
|
||||
# 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.
|
||||
"""For now it only supports container-in-vm deployments"""
|
||||
from pyroute2.netlink.rtnl.ifinfmsg import ifinfmsg
|
||||
|
||||
from kuryr.lib.binding.drivers import nested
|
||||
from kuryr.lib.binding.drivers import utils
|
||||
|
||||
KIND = 'ipvlan'
|
||||
# We use L2 to allow broadcast frames
|
||||
IPVLAN_MODE_L2 = ifinfmsg.ifinfo.ipvlan_data.modes['IPVLAN_MODE_L2']
|
||||
|
||||
|
||||
def port_bind(endpoint_id, port, subnets, network=None, nested_port=None):
|
||||
"""Binds the Neutron port to the network interface on the host.
|
||||
|
||||
:param endpoint_id: the ID of the endpoint as string
|
||||
:param port: the instance Neutron port dictionary as returned by
|
||||
python-neutronclient
|
||||
:param subnets: an iterable of all the Neutron subnets which the
|
||||
endpoint is trying to join
|
||||
:param network: the Neutron network which the endpoint is trying to
|
||||
join
|
||||
:param nested_port: the dictionary, as returned by python-neutronclient,
|
||||
of the port that that is used when running inside
|
||||
another instance (either ipvlan/macvlan or a subport)
|
||||
:returns: the tuple of the names of the veth pair and the tuple of stdout
|
||||
and stderr returned by processutils.execute invoked with the
|
||||
executable script for binding
|
||||
:raises: kuryr.common.exceptions.VethCreationFailure,
|
||||
processutils.ProcessExecutionError
|
||||
"""
|
||||
ip = utils.get_ipdb()
|
||||
port_id = port['id']
|
||||
_, devname = utils.get_veth_pair_names(port_id)
|
||||
link_iface = nested.get_link_iface(port)
|
||||
|
||||
with ip.create(ifname=devname, kind=KIND,
|
||||
link=ip.interfaces[link_iface],
|
||||
mode=IPVLAN_MODE_L2) as container_iface:
|
||||
utils._configure_container_iface(
|
||||
container_iface, subnets,
|
||||
fixed_ips=nested_port.get(utils.FIXED_IP_KEY))
|
||||
|
||||
return None, devname, ('', None)
|
||||
|
||||
|
||||
port_unbind = nested.port_unbind
|
56
kuryr/lib/binding/drivers/macvlan.py
Normal file
56
kuryr/lib/binding/drivers/macvlan.py
Normal file
@ -0,0 +1,56 @@
|
||||
# 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.
|
||||
"""For now it only supports container-in-vm deployments"""
|
||||
from kuryr.lib.binding.drivers import nested
|
||||
from kuryr.lib.binding.drivers import utils
|
||||
|
||||
KIND = 'macvlan'
|
||||
# We use the bridge mode for simplicity and proximity to the usual
|
||||
# container bridged networking
|
||||
MACVLAN_MODE_BRIDGE = 'bridge'
|
||||
|
||||
|
||||
def port_bind(endpoint_id, port, subnets, network=None, nested_port=None):
|
||||
"""Binds the Neutron port to the network interface on the host.
|
||||
|
||||
:param endpoint_id: the ID of the endpoint as string
|
||||
:param port: the instance Neutron port dictionary as returned by
|
||||
python-neutronclient
|
||||
:param subnets: an iterable of all the Neutron subnets which the
|
||||
endpoint is trying to join
|
||||
:param network: the Neutron network which the endpoint is trying to
|
||||
join
|
||||
:param nested_port: the dictionary, as returned by python-neutronclient,
|
||||
of the port that that is used when running inside
|
||||
another instance
|
||||
:returns: the tuple of the names of the veth pair and the tuple of stdout
|
||||
and stderr returned by processutils.execute invoked with the
|
||||
executable script for binding
|
||||
:raises: kuryr.common.exceptions.VethCreationFailure,
|
||||
processutils.ProcessExecutionError
|
||||
"""
|
||||
ip = utils.get_ipdb()
|
||||
port_id = port['id']
|
||||
_, devname = utils.get_veth_pair_names(port_id)
|
||||
link_iface = nested.get_link_iface(port)
|
||||
|
||||
with ip.create(ifname=devname, kind=KIND,
|
||||
link=ip.interfaces[link_iface],
|
||||
macvlan_mode=MACVLAN_MODE_BRIDGE) as container_iface:
|
||||
utils._configure_container_iface(
|
||||
container_iface, subnets,
|
||||
fixed_ips=nested_port.get(utils.FIXED_IP_KEY))
|
||||
|
||||
return None, devname, ('', None)
|
||||
|
||||
|
||||
port_unbind = nested.port_unbind
|
52
kuryr/lib/binding/drivers/nested.py
Normal file
52
kuryr/lib/binding/drivers/nested.py
Normal file
@ -0,0 +1,52 @@
|
||||
# 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.
|
||||
"""Helper module for bindings that usually happen inside OSt instances"""
|
||||
import pyroute2
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from kuryr.lib.binding.drivers import utils
|
||||
from kuryr.lib import exceptions
|
||||
|
||||
|
||||
def get_link_iface(port):
|
||||
"""Gets the name of the interface to link the container virtual devices"""
|
||||
link = cfg.CONF.binding.link_iface
|
||||
if not link:
|
||||
# Guess the name from the port hwaddr
|
||||
ip = utils.get_ipdb()
|
||||
for name, data in ip.interfaces.items():
|
||||
if data['address'] == port[utils.MAC_ADDRESS_KEY]:
|
||||
link = data['ifname']
|
||||
break
|
||||
return link
|
||||
|
||||
|
||||
def port_unbind(endpoint_id, neutron_port):
|
||||
"""Unbinds the Neutron port from the network interface on the host.
|
||||
|
||||
:param endpoint_id: the ID of the Docker container as string
|
||||
:param neutron_port: a port dictionary returned from python-neutronclient
|
||||
:returns: the tuple of stdout and stderr returned by processutils.execute
|
||||
invoked with the executable script for unbinding
|
||||
:raises: processutils.ProcessExecutionError, pyroute2.NetlinkError
|
||||
"""
|
||||
port_id = neutron_port['id']
|
||||
_, devname = utils.get_veth_pair_names(port_id)
|
||||
|
||||
try:
|
||||
utils.remove_device(devname)
|
||||
except pyroute2.NetlinkError:
|
||||
raise exceptions.VethDeleteionFailure(
|
||||
'Failed to delete the container device.')
|
||||
|
||||
return '', None
|
121
kuryr/lib/binding/drivers/utils.py
Normal file
121
kuryr/lib/binding/drivers/utils.py
Normal file
@ -0,0 +1,121 @@
|
||||
# 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 ipaddress
|
||||
|
||||
import pyroute2
|
||||
from pyroute2.netlink.rtnl import ifinfmsg
|
||||
import six
|
||||
|
||||
from kuryr.lib import constants
|
||||
|
||||
|
||||
_IPDB_CACHE = None
|
||||
_IPROUTE_CACHE = None
|
||||
|
||||
FIXED_IP_KEY = 'fixed_ips'
|
||||
IP_ADDRESS_KEY = 'ip_address'
|
||||
MAC_ADDRESS_KEY = 'mac_address'
|
||||
SUBNET_ID_KEY = 'subnet_id'
|
||||
|
||||
|
||||
def get_veth_pair_names(port_id):
|
||||
ifname = constants.VETH_PREFIX + port_id
|
||||
ifname = ifname[:constants.NIC_NAME_LEN]
|
||||
peer_name = constants.CONTAINER_VETH_PREFIX + port_id
|
||||
peer_name = peer_name[:constants.NIC_NAME_LEN]
|
||||
return ifname, peer_name
|
||||
|
||||
|
||||
def get_ipdb():
|
||||
"""Returns the already cached or a newly created IPDB instance.
|
||||
|
||||
IPDB reads the Linux specific file when it's instantiated. This behaviour
|
||||
prevents Mac OSX users from running unit tests. This function makes the
|
||||
loading IPDB lazyily and therefore it can be mocked after the import of
|
||||
modules that import this module.
|
||||
|
||||
:returns: The already cached or newly created ``pyroute2.IPDB`` instance
|
||||
"""
|
||||
global _IPDB_CACHE
|
||||
if not _IPDB_CACHE:
|
||||
_IPDB_CACHE = pyroute2.IPDB()
|
||||
return _IPDB_CACHE
|
||||
|
||||
|
||||
def get_iproute():
|
||||
"""Returns the already cached or a newly created IPRoute instance.
|
||||
|
||||
IPRoute reads the Linux specific file when it's instantiated. This
|
||||
behaviour prevents Mac OSX users from running unit tests. This function
|
||||
makes the loading IPDB lazyily and therefore it can be mocked after the
|
||||
import of modules that import this module.
|
||||
|
||||
:returns: The already cached or newly created ``pyroute2.IPRoute`` instance
|
||||
"""
|
||||
global _IPROUTE_CACHE
|
||||
if not _IPROUTE_CACHE:
|
||||
_IPROUTE_CACHE = pyroute2.IPRoute()
|
||||
return _IPROUTE_CACHE
|
||||
|
||||
|
||||
def remove_device(ifname):
|
||||
"""Removes the device with name ifname.
|
||||
|
||||
:param ifname: the name of the device to remove
|
||||
:returns: the index the device identified by ifname had if it
|
||||
exists, otherwise None
|
||||
:raises: pyroute2.NetlinkError
|
||||
"""
|
||||
ipr = get_iproute()
|
||||
|
||||
devices = ipr.link_lookup(ifname=ifname)
|
||||
if devices:
|
||||
dev_index = devices[0]
|
||||
ipr.link_remove(dev_index)
|
||||
return dev_index
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def is_up(interface):
|
||||
flags = interface['flags']
|
||||
if not flags:
|
||||
return False
|
||||
return (flags & ifinfmsg.IFF_UP) == 1
|
||||
|
||||
|
||||
def _configure_container_iface(iface, subnets, fixed_ips, mtu=None,
|
||||
hwaddr=None):
|
||||
"""Configures the interface that is placed in the container net ns
|
||||
|
||||
:param iface: the pyroute IPDB interface object to configure
|
||||
:param subnets: an iterable of all the Neutron subnets which the
|
||||
endpoint is trying to join
|
||||
:param fixed_ips: an iterable of fixed IPs to be set for the iface
|
||||
:param mtu: Maximum Transfer Unit to set for the iface
|
||||
:param hwaddr: Hardware address to set for the iface
|
||||
"""
|
||||
subnets_dict = {subnet['id']: subnet for subnet in subnets}
|
||||
# We assume containers always work with fixed ips, dhcp does not really
|
||||
# make a lot of sense
|
||||
for fixed_ip in fixed_ips:
|
||||
if IP_ADDRESS_KEY in fixed_ip and (SUBNET_ID_KEY in fixed_ip):
|
||||
subnet_id = fixed_ip[SUBNET_ID_KEY]
|
||||
subnet = subnets_dict[subnet_id]
|
||||
cidr = ipaddress.ip_network(six.text_type(subnet['cidr']))
|
||||
iface.add_ip(fixed_ip[IP_ADDRESS_KEY], cidr.prefixlen)
|
||||
if mtu is not None:
|
||||
iface.set_mtu(mtu)
|
||||
if hwaddr is not None:
|
||||
iface.set_address(hwaddr)
|
||||
if not is_up(iface):
|
||||
iface.up()
|
148
kuryr/lib/binding/drivers/veth.py
Normal file
148
kuryr/lib/binding/drivers/veth.py
Normal file
@ -0,0 +1,148 @@
|
||||
# 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 os
|
||||
|
||||
import pyroute2
|
||||
|
||||
from oslo_concurrency import processutils
|
||||
from oslo_config import cfg
|
||||
from oslo_utils import excutils
|
||||
|
||||
from kuryr.lib.binding.drivers import utils
|
||||
from kuryr.lib import exceptions
|
||||
from kuryr.lib import utils as lib_utils
|
||||
|
||||
|
||||
KIND = 'veth'
|
||||
|
||||
BINDING_SUBCOMMAND = 'bind'
|
||||
DEFAULT_NETWORK_MTU = 1500
|
||||
FALLBACK_VIF_TYPE = 'unbound'
|
||||
UNBINDING_SUBCOMMAND = 'unbind'
|
||||
VIF_DETAILS_KEY = 'binding:vif_details'
|
||||
VIF_TYPE_KEY = 'binding:vif_type'
|
||||
|
||||
|
||||
def port_bind(endpoint_id, port, subnets, network=None, nested_port=None):
|
||||
"""Binds the Neutron port to the network interface on the host.
|
||||
|
||||
:param endpoint_id: the ID of the endpoint as string
|
||||
:param port: the instance Neutron port dictionary as returned by
|
||||
python-neutronclient
|
||||
:param subnets: an iterable of all the Neutron subnets which the
|
||||
endpoint is trying to join
|
||||
:param network: the Neutron network which the endpoint is trying to
|
||||
join
|
||||
:param nested_port: the dictionary, as returned by python-neutronclient,
|
||||
of the port that that is used when running inside
|
||||
another instance (either ipvlan/macvlan or a subport)
|
||||
:returns: the tuple of the names of the veth pair and the tuple of stdout
|
||||
and stderr returned by processutils.execute invoked with the
|
||||
executable script for binding
|
||||
:raises: kuryr.common.exceptions.VethCreationFailure,
|
||||
processutils.ProcessExecutionError
|
||||
"""
|
||||
ip = utils.get_ipdb()
|
||||
port_id = port['id']
|
||||
host_ifname, container_ifname = utils.get_veth_pair_names(port_id)
|
||||
if network is None:
|
||||
mtu = DEFAULT_NETWORK_MTU
|
||||
else:
|
||||
mtu = network.get('mtu', DEFAULT_NETWORK_MTU)
|
||||
|
||||
try:
|
||||
with ip.create(ifname=host_ifname, kind=KIND,
|
||||
reuse=True, peer=container_ifname) as host_veth:
|
||||
if not utils.is_up(host_veth):
|
||||
host_veth.up()
|
||||
with ip.interfaces[container_ifname] as container_veth:
|
||||
utils._configure_container_iface(
|
||||
container_veth, subnets,
|
||||
fixed_ips=port.get(utils.FIXED_IP_KEY),
|
||||
mtu=mtu, hwaddr=port[utils.MAC_ADDRESS_KEY].lower())
|
||||
except pyroute2.CreateException:
|
||||
raise exceptions.VethCreationFailure(
|
||||
'Virtual device creation failed.')
|
||||
except pyroute2.CommitException:
|
||||
raise exceptions.VethCreationFailure(
|
||||
'Could not configure the container virtual device networking.')
|
||||
|
||||
try:
|
||||
stdout, stderr = _configure_host_iface(
|
||||
host_ifname, endpoint_id, port_id,
|
||||
port['network_id'], port['tenant_id'],
|
||||
port[utils.MAC_ADDRESS_KEY],
|
||||
kind=port.get(VIF_TYPE_KEY),
|
||||
details=port.get(VIF_DETAILS_KEY))
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
utils.remove_device(host_ifname)
|
||||
|
||||
return host_ifname, container_ifname, (stdout, stderr)
|
||||
|
||||
|
||||
def port_unbind(endpoint_id, neutron_port):
|
||||
"""Unbinds the Neutron port from the network interface on the host.
|
||||
|
||||
:param endpoint_id: the ID of the Docker container as string
|
||||
:param neutron_port: a port dictionary returned from python-neutronclient
|
||||
:returns: the tuple of stdout and stderr returned by processutils.execute
|
||||
invoked with the executable script for unbinding
|
||||
:raises: processutils.ProcessExecutionError, pyroute2.NetlinkError
|
||||
"""
|
||||
|
||||
vif_type = neutron_port.get(VIF_TYPE_KEY, FALLBACK_VIF_TYPE)
|
||||
vif_details = lib_utils.string_mappings(neutron_port.get(VIF_DETAILS_KEY))
|
||||
unbinding_exec_path = os.path.join(cfg.CONF.bindir, vif_type)
|
||||
|
||||
port_id = neutron_port['id']
|
||||
ifname, _ = utils.get_veth_pair_names(port_id)
|
||||
|
||||
mac_address = neutron_port['mac_address']
|
||||
stdout, stderr = processutils.execute(
|
||||
unbinding_exec_path, UNBINDING_SUBCOMMAND, port_id, ifname,
|
||||
endpoint_id, mac_address, vif_details, run_as_root=True)
|
||||
try:
|
||||
utils.remove_device(ifname)
|
||||
except pyroute2.NetlinkError:
|
||||
raise exceptions.VethDeleteionFailure(
|
||||
'Deleting the veth pair failed.')
|
||||
return (stdout, stderr)
|
||||
|
||||
|
||||
def _configure_host_iface(ifname, endpoint_id, port_id, net_id, project_id,
|
||||
hwaddr, kind=None, details=None):
|
||||
"""Configures the interface that is placed on the default net ns
|
||||
|
||||
:param ifname: the name of the interface to configure
|
||||
:param endpoint_id: the identifier of the endpoint
|
||||
:param port_id: the Neutron uuid of the port to which this interface
|
||||
is to be bound
|
||||
:param net_id: the Neutron uuid of the network the port is part of
|
||||
:param project_id: the Keystone project the binding is made for
|
||||
:param hwaddr: the interface hardware address
|
||||
:param kind: the Neutorn port vif_type
|
||||
:param details: Neutron vif details
|
||||
"""
|
||||
if kind is None:
|
||||
kind = FALLBACK_VIF_TYPE
|
||||
binding_exec_path = os.path.join(cfg.CONF.bindir, kind)
|
||||
if not os.path.exists(binding_exec_path):
|
||||
raise exceptions.BindingNotSupportedFailure(
|
||||
"vif_type({0}) is not supported. A binding script for this type "
|
||||
"can't be found".format(kind))
|
||||
stdout, stderr = processutils.execute(
|
||||
binding_exec_path, BINDING_SUBCOMMAND, port_id, ifname,
|
||||
endpoint_id, hwaddr, net_id, project_id,
|
||||
lib_utils.string_mappings(details),
|
||||
run_as_root=True)
|
||||
return stdout, stderr
|
@ -67,9 +67,22 @@ binding_opts = [
|
||||
cfg.StrOpt('veth_dst_prefix',
|
||||
default='eth',
|
||||
help=('The name prefix of the veth endpoint put inside the '
|
||||
'container.'))
|
||||
'container.')),
|
||||
cfg.StrOpt('driver',
|
||||
default='kuryr.lib.binding.drivers.veth',
|
||||
help=_('Driver to use for binding and unbinding ports.')),
|
||||
cfg.StrOpt('link_iface',
|
||||
default='',
|
||||
help=_('Specifies the name of the Nova instance interface to '
|
||||
'link the virtual devices to (only applicable to some '
|
||||
'binding drivers.')),
|
||||
]
|
||||
|
||||
binding_group = cfg.OptGroup(
|
||||
'binding',
|
||||
title='binding options',
|
||||
help=_('Configuration options for container interface binding.'))
|
||||
|
||||
|
||||
def register_neutron_opts(conf):
|
||||
conf.register_group(neutron_group)
|
||||
|
@ -34,7 +34,6 @@ _core_opts_with_logging += _options.generic_log_opts
|
||||
|
||||
_kuryr_opts = [
|
||||
(None, list(itertools.chain(_core_opts_with_logging))),
|
||||
('binding', config.binding_opts),
|
||||
]
|
||||
|
||||
|
||||
@ -70,4 +69,5 @@ def list_kuryr_opts():
|
||||
"""
|
||||
|
||||
return ([(k, copy.deepcopy(o)) for k, o in _kuryr_opts] +
|
||||
list_neutron_opts())
|
||||
list_neutron_opts() +
|
||||
[(config.binding_group, config.binding_opts)])
|
||||
|
@ -19,7 +19,6 @@ from neutronclient.v2_0 import client
|
||||
from oslo_config import cfg
|
||||
|
||||
from kuryr.lib import config as kuryr_config
|
||||
from kuryr.lib import constants as const
|
||||
|
||||
DOCKER_NETNS_BASE = '/var/run/docker/netns'
|
||||
PORT_POSTFIX = 'port'
|
||||
@ -41,14 +40,6 @@ def get_hostname():
|
||||
return socket.gethostname()
|
||||
|
||||
|
||||
def get_veth_pair_names(port_id):
|
||||
ifname = const.VETH_PREFIX + port_id
|
||||
ifname = ifname[:const.NIC_NAME_LEN]
|
||||
peer_name = const.CONTAINER_VETH_PREFIX + port_id
|
||||
peer_name = peer_name[:const.NIC_NAME_LEN]
|
||||
return ifname, peer_name
|
||||
|
||||
|
||||
def get_neutron_subnetpool_name(subnet_cidr):
|
||||
"""Returns a Neutron subnetpool name.
|
||||
|
||||
|
@ -24,7 +24,7 @@ class TestCase(base.BaseTestCase):
|
||||
super(TestCase, self).setUp()
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(config.core_opts)
|
||||
CONF.register_opts(config.binding_opts, 'binding')
|
||||
CONF.register_opts(config.binding_opts, group=config.binding_group)
|
||||
config.register_neutron_opts(CONF)
|
||||
|
||||
@staticmethod
|
||||
|
0
kuryr/tests/unit/binding/__init__.py
Normal file
0
kuryr/tests/unit/binding/__init__.py
Normal file
0
kuryr/tests/unit/binding/drivers/__init__.py
Normal file
0
kuryr/tests/unit/binding/drivers/__init__.py
Normal file
155
kuryr/tests/unit/binding/drivers/test_utils.py
Normal file
155
kuryr/tests/unit/binding/drivers/test_utils.py
Normal file
@ -0,0 +1,155 @@
|
||||
# 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
|
||||
import uuid
|
||||
|
||||
import pyroute2.ipdb.interface
|
||||
from pyroute2.netlink.rtnl import ifinfmsg
|
||||
|
||||
from kuryr.lib.binding.drivers import utils
|
||||
from kuryr.lib import constants
|
||||
from kuryr.tests.unit import base
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class BindingDriversUtilsTest(base.TestCase):
|
||||
"""Unit tests for binding drivers utils"""
|
||||
|
||||
def test_get_veth_pair_names(self):
|
||||
fake_neutron_port_id = str(uuid.uuid4())
|
||||
generated_ifname, generated_peer = utils.get_veth_pair_names(
|
||||
fake_neutron_port_id)
|
||||
|
||||
namelen = constants.NIC_NAME_LEN
|
||||
ifname_postlen = namelen - len(constants.VETH_PREFIX)
|
||||
peer_postlen = namelen - len(constants.CONTAINER_VETH_PREFIX)
|
||||
|
||||
self.assertEqual(namelen, len(generated_ifname))
|
||||
self.assertEqual(namelen, len(generated_peer))
|
||||
self.assertIn(constants.VETH_PREFIX, generated_ifname)
|
||||
self.assertIn(constants.CONTAINER_VETH_PREFIX, generated_peer)
|
||||
self.assertIn(fake_neutron_port_id[:ifname_postlen], generated_ifname)
|
||||
self.assertIn(fake_neutron_port_id[:peer_postlen], generated_peer)
|
||||
|
||||
@ddt.data((False), (True))
|
||||
def test_is_up(self, interface_flag):
|
||||
fake_interface = {'flags': 0x0}
|
||||
if interface_flag:
|
||||
fake_interface['flags'] = ifinfmsg.IFF_UP
|
||||
self.assertEqual(True, utils.is_up(fake_interface))
|
||||
else:
|
||||
self.assertEqual(False, utils.is_up(fake_interface))
|
||||
|
||||
@ddt.data(
|
||||
(['10.10.10.11'], ['384ac9fc-eefa-4399-8d88-1181433e33b1'], False,
|
||||
None, None),
|
||||
(['10.10.10.11'], ['384ac9fc-eefa-4399-8d88-1181433e33b1'],
|
||||
True, None, None),
|
||||
(['10.10.10.11', '10.11.0.10'],
|
||||
['384ac9fc-eefa-4399-8d88-1181433e33b1',
|
||||
'0a6eab28-9dc1-46c0-997c-cb9f66f6081f'],
|
||||
False, 1500, 'fa:16:3e:22:a3:3d'))
|
||||
@ddt.unpack
|
||||
@mock.patch.object(utils, 'is_up')
|
||||
def test__configure_container_iface(
|
||||
self, addrs, subnet_ids, already_up, mtu, mac, mock_is_up):
|
||||
subnets = [{
|
||||
'allocation_pools': [{'end': '10.11.0.254', 'start': '10.11.0.2'}],
|
||||
'cidr': '10.11.0.0/26',
|
||||
'created_at': '2016-09-27T07:55:12',
|
||||
'description': '',
|
||||
'dns_nameservers': [],
|
||||
'enable_dhcp': True,
|
||||
'gateway_ip': '10.11.0.1',
|
||||
'host_routes': [],
|
||||
'id': '0a6eab28-9dc1-46c0-997c-cb9f66f6081f',
|
||||
'ip_version': 4,
|
||||
'ipv6_address_mode': None,
|
||||
'ipv6_ra_mode': None,
|
||||
'name': 'subtest',
|
||||
'network_id': '90146ed2-c3ce-4001-866e-e97e513530a3',
|
||||
'revision': 2,
|
||||
'service_types': [],
|
||||
'subnetpool_id': None,
|
||||
'tenant_id': '0c0d1f46fa8d485d9534ea0e35f37bd3',
|
||||
'updated_at': '2016-09-27T07:55:12'
|
||||
}, {
|
||||
'allocation_pools': [{'end': '10.10.0.254', 'start': '10.10.0.2'}],
|
||||
'cidr': '10.10.0.0/24',
|
||||
'created_at': '2016-09-27T08:57:13',
|
||||
'description': '',
|
||||
'dns_nameservers': [],
|
||||
'enable_dhcp': True,
|
||||
'gateway_ip': '10.10.0.1',
|
||||
'host_routes': [],
|
||||
'id': '384ac9fc-eefa-4399-8d88-1181433e33b1',
|
||||
'ip_version': 4,
|
||||
'ipv6_address_mode': None,
|
||||
'ipv6_ra_mode': None,
|
||||
'name': '10.10.0.0/24',
|
||||
'network_id': 'bfb2f525-bedf-48ed-b125-102ee7920253',
|
||||
'revision': 2,
|
||||
'service_types': [],
|
||||
'subnetpool_id': None,
|
||||
'tenant_id': '51b66b97a12f42a990452967d2c555ac',
|
||||
'updated_at': '2016-09-27T08:57:13'}]
|
||||
|
||||
fake_iface = mock.Mock(spec=pyroute2.ipdb.interface.Interface)
|
||||
_set_mtu = mock.Mock()
|
||||
_set_address = mock.Mock()
|
||||
fake_iface.attach_mock(_set_mtu, 'set_mtu')
|
||||
fake_iface.attach_mock(_set_address, 'set_address')
|
||||
mock_is_up.return_value = already_up
|
||||
|
||||
fixed_ips = []
|
||||
for ip, subnet_id in zip(addrs, subnet_ids):
|
||||
fixed_ips.append({
|
||||
utils.IP_ADDRESS_KEY: ip,
|
||||
utils.SUBNET_ID_KEY: subnet_id})
|
||||
|
||||
utils._configure_container_iface(
|
||||
fake_iface,
|
||||
subnets,
|
||||
fixed_ips,
|
||||
mtu=mtu,
|
||||
hwaddr=mac)
|
||||
|
||||
subnets_prefix_by_id = dict(
|
||||
(subnet['id'], int(subnet['cidr'].split('/')[1])) for
|
||||
subnet in subnets)
|
||||
for ip, subnet_id in zip(addrs, subnet_ids):
|
||||
fake_iface.add_ip.assert_any_call(
|
||||
ip, subnets_prefix_by_id[subnet_id])
|
||||
|
||||
if already_up:
|
||||
fake_iface.up.assert_not_called()
|
||||
else:
|
||||
fake_iface.up.assert_called_once()
|
||||
|
||||
if mtu is None:
|
||||
fake_iface.set_mtu.assert_not_called()
|
||||
else:
|
||||
fake_iface.set_mtu.assert_called_with(mtu)
|
||||
|
||||
if mac is None:
|
||||
fake_iface.set_address.assert_not_called()
|
||||
else:
|
||||
fake_iface.set_address.assert_called_with(mac)
|
||||
|
||||
def test_get_ipdb(self):
|
||||
ip = utils.get_ipdb()
|
||||
self.assertEqual(ip, utils.get_ipdb())
|
||||
|
||||
def test_get_iproute(self):
|
||||
ipr = utils.get_iproute()
|
||||
self.assertEqual(ipr, utils.get_iproute())
|
@ -9,7 +9,6 @@
|
||||
# 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
|
||||
import uuid
|
||||
@ -28,15 +27,6 @@ mock_interface = mock.MagicMock()
|
||||
class BindingTest(base.TestCase):
|
||||
"""Unit tests for binding."""
|
||||
|
||||
@ddt.data((False), (True))
|
||||
def test_is_up(self, interface_flag):
|
||||
fake_interface = {'flags': 0x0}
|
||||
if interface_flag:
|
||||
fake_interface['flags'] = binding.IFF_UP
|
||||
self.assertEqual(True, binding._is_up(fake_interface))
|
||||
else:
|
||||
self.assertEqual(False, binding._is_up(fake_interface))
|
||||
|
||||
@mock.patch('os.path.exists', return_value=True)
|
||||
@mock.patch('oslo_concurrency.processutils.execute',
|
||||
return_value=('fake_stdout', 'fake_stderr'))
|
||||
@ -63,7 +53,8 @@ class BindingTest(base.TestCase):
|
||||
fake_network['networks'][0]['mtu'] = fake_mtu
|
||||
|
||||
binding.port_bind(fake_docker_endpoint_id, fake_port['port'],
|
||||
fake_subnets['subnets'], fake_network['networks'][0])
|
||||
fake_subnets['subnets'],
|
||||
fake_network['networks'][0])
|
||||
|
||||
expect_calls = [call.__enter__().set_mtu(fake_mtu),
|
||||
call.__enter__().up()]
|
||||
@ -71,10 +62,10 @@ class BindingTest(base.TestCase):
|
||||
mock_path_exists.assert_called_once()
|
||||
mock_execute.assert_called_once()
|
||||
|
||||
@mock.patch('kuryr.lib.binding.cleanup_veth')
|
||||
@mock.patch('kuryr.lib.binding.drivers.utils.remove_device')
|
||||
@mock.patch('oslo_concurrency.processutils.execute',
|
||||
return_value=('fake_stdout', 'fake_stderr'))
|
||||
def test_port_unbind(self, mock_execute, mock_cleanup_veth):
|
||||
def test_port_unbind(self, mock_execute, mock_remove_device):
|
||||
fake_docker_network_id = utils.get_hash()
|
||||
fake_docker_endpoint_id = utils.get_hash()
|
||||
fake_port_id = str(uuid.uuid4())
|
||||
@ -86,4 +77,4 @@ class BindingTest(base.TestCase):
|
||||
fake_neutron_v4_subnet_id, fake_neutron_v6_subnet_id)
|
||||
binding.port_unbind(fake_docker_endpoint_id, fake_port['port'])
|
||||
mock_execute.assert_called_once()
|
||||
mock_cleanup_veth.assert_called_once()
|
||||
mock_remove_device.assert_called_once()
|
@ -27,3 +27,5 @@ class ConfigurationTest(base.TestCase):
|
||||
cfg.CONF.neutron.endpoint_type)
|
||||
self.assertEqual('baremetal',
|
||||
cfg.CONF.deployment_type)
|
||||
self.assertEqual('kuryr.lib.binding.drivers.veth',
|
||||
cfg.CONF.binding.driver)
|
||||
|
@ -20,11 +20,18 @@ class OptsTest(base.TestCase):
|
||||
|
||||
_fake_kuryr_opts = [(None, 'fakevalue1'), ('Key1', 'fakevalue2')]
|
||||
_fake_neutron_opts = [('poolv4', 'swimming4'), ('poolv6', 'swimming6')]
|
||||
_fake_binding_group = 'binding_group'
|
||||
_fake_binding_opts = [('driver', 'my.ipvlan')]
|
||||
|
||||
@mock.patch.multiple(kuryr_opts, _kuryr_opts=_fake_kuryr_opts,
|
||||
@mock.patch.multiple(kuryr_opts.config,
|
||||
binding_group=_fake_binding_group,
|
||||
binding_opts=_fake_binding_opts)
|
||||
@mock.patch.multiple(kuryr_opts,
|
||||
_kuryr_opts=_fake_kuryr_opts,
|
||||
list_neutron_opts=mock.DEFAULT)
|
||||
def test_list_kuryr_opts(self, list_neutron_opts):
|
||||
list_neutron_opts.return_value = self._fake_neutron_opts
|
||||
|
||||
self.assertEqual(self._fake_kuryr_opts + self._fake_neutron_opts,
|
||||
self.assertEqual(self._fake_kuryr_opts + self._fake_neutron_opts +
|
||||
[(self._fake_binding_group, self._fake_binding_opts)],
|
||||
kuryr_opts.list_kuryr_opts())
|
||||
|
@ -13,11 +13,9 @@
|
||||
import ddt
|
||||
import mock
|
||||
import socket
|
||||
import uuid
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from kuryr.lib import constants as const
|
||||
from kuryr.lib import utils
|
||||
from kuryr.tests.unit import base
|
||||
|
||||
@ -30,22 +28,6 @@ class TestKuryrUtils(base.TestCase):
|
||||
self.fake_url = 'http://127.0.0.1:9696'
|
||||
self.fake_auth_url = 'http://127.0.0.1:35357/v2.0'
|
||||
|
||||
def test_get_veth_pair_names(self):
|
||||
fake_neutron_port_id = str(uuid.uuid4())
|
||||
generated_ifname, generated_peer = utils.get_veth_pair_names(
|
||||
fake_neutron_port_id)
|
||||
|
||||
namelen = const.NIC_NAME_LEN
|
||||
ifname_postlen = namelen - len(const.VETH_PREFIX)
|
||||
peer_postlen = namelen - len(const.CONTAINER_VETH_PREFIX)
|
||||
|
||||
self.assertEqual(namelen, len(generated_ifname))
|
||||
self.assertEqual(namelen, len(generated_peer))
|
||||
self.assertIn(const.VETH_PREFIX, generated_ifname)
|
||||
self.assertIn(const.CONTAINER_VETH_PREFIX, generated_peer)
|
||||
self.assertIn(fake_neutron_port_id[:ifname_postlen], generated_ifname)
|
||||
self.assertIn(fake_neutron_port_id[:peer_postlen], generated_peer)
|
||||
|
||||
def test_get_subnetpool_name(self):
|
||||
fake_subnet_cidr = "10.0.0.0/16"
|
||||
generated_neutron_subnetpool_name = utils.get_neutron_subnetpool_name(
|
||||
|
Loading…
Reference in New Issue
Block a user