Adds modules for managing network interfaces for generic driver
Adds modules for creating and managing logical interfaces. This modules create logical interface and attach it to OVS or Linux bridge to associate with neutron port. Module manila.network.linux.ip_lib.py executes linux 'ip' command for logical interface setup. Module manila.network.linux.interface.py is responsible for plugging logical interface into OVS or Linux bridge. Class in module manila.network.linux.ovs_lib.py represents OVS bridge and allows to manage it. Partially implements: bp generic-driver Change-Id: Iaa97e961f1670479a59a2f9adba5953d271b1818
This commit is contained in:
parent
61a52b88d1
commit
ce5eeb7364
|
@ -44,3 +44,9 @@ rm: CommandFilter, /usr/bin/rm, root
|
|||
# manila/share/drivers/glusterfs.py: 'gluster', '--xml', 'volume', 'info', '%s'
|
||||
# manila/share/drivers/glusterfs.py: 'gluster', 'volume', 'set', '%s', 'nfs.export-dir', '%s'
|
||||
gluster: CommandFilter, /usr/sbin/gluster, root
|
||||
|
||||
# manila/network/linux/ip_lib.py: 'ip', 'netns', 'exec', '%s', '%s'
|
||||
ip: CommandFilter, /sbin/ip, root
|
||||
|
||||
# manila/network/linux/interface.py: 'ovs-vsctl', 'add-port', '%s', '%s'
|
||||
ovs-vsctl: CommandFilter, /usr/bin/ovs-vsctl, root
|
||||
|
|
|
@ -541,3 +541,7 @@ class VolumeSnapshotNotFound(NotFound):
|
|||
|
||||
class InstanceNotFound(NotFound):
|
||||
message = _("Instance %(instance_id)s could not be found.")
|
||||
|
||||
|
||||
class BridgeDoesNotExist(ManilaException):
|
||||
message = _("Bridge %(bridge)s does not exist.")
|
||||
|
|
|
@ -0,0 +1,194 @@
|
|||
# Copyright 2014 Mirantis Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 netaddr
|
||||
from oslo.config import cfg
|
||||
|
||||
from manila import exception
|
||||
from manila.network.linux import ip_lib
|
||||
from manila.network.linux import ovs_lib
|
||||
from manila.openstack.common import log as logging
|
||||
from manila import utils
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
OPTS = [
|
||||
cfg.StrOpt('ovs_integration_bridge',
|
||||
default='br-int',
|
||||
help=_('Name of Open vSwitch bridge to use')),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(OPTS)
|
||||
|
||||
|
||||
class LinuxInterfaceDriver(object):
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
# from linux IF_NAMESIZE
|
||||
DEV_NAME_LEN = 14
|
||||
DEV_NAME_PREFIX = 'tap'
|
||||
|
||||
def __init__(self):
|
||||
self.conf = CONF
|
||||
|
||||
def init_l3(self, device_name, ip_cidrs, namespace=None):
|
||||
"""Set the L3 settings for the interface using data from the port.
|
||||
|
||||
ip_cidrs: list of 'X.X.X.X/YY' strings
|
||||
"""
|
||||
device = ip_lib.IPDevice(device_name,
|
||||
namespace=namespace)
|
||||
|
||||
previous = {}
|
||||
for address in device.addr.list(scope='global', filters=['permanent']):
|
||||
previous[address['cidr']] = address['ip_version']
|
||||
|
||||
# add new addresses
|
||||
for ip_cidr in ip_cidrs:
|
||||
|
||||
net = netaddr.IPNetwork(ip_cidr)
|
||||
if ip_cidr in previous:
|
||||
del previous[ip_cidr]
|
||||
continue
|
||||
|
||||
device.addr.add(net.version, ip_cidr, str(net.broadcast))
|
||||
|
||||
# clean up any old addresses
|
||||
for ip_cidr, ip_version in previous.items():
|
||||
device.addr.delete(ip_version, ip_cidr)
|
||||
|
||||
def check_bridge_exists(self, bridge):
|
||||
if not ip_lib.device_exists(bridge):
|
||||
raise exception.BridgeDoesNotExist(bridge=bridge)
|
||||
|
||||
def get_device_name(self, port):
|
||||
return (self.DEV_NAME_PREFIX + port['id'])[:self.DEV_NAME_LEN]
|
||||
|
||||
@abc.abstractmethod
|
||||
def plug(self, network_id, port_id, device_name, mac_address,
|
||||
bridge=None, namespace=None, prefix=None):
|
||||
"""Plug in the interface."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def unplug(self, device_name, bridge=None, namespace=None, prefix=None):
|
||||
"""Unplug the interface."""
|
||||
|
||||
|
||||
class OVSInterfaceDriver(LinuxInterfaceDriver):
|
||||
"""Driver for creating an internal interface on an OVS bridge."""
|
||||
|
||||
DEV_NAME_PREFIX = 'tap'
|
||||
|
||||
def _get_tap_name(self, dev_name):
|
||||
return dev_name
|
||||
|
||||
def _ovs_add_port(self, bridge, device_name, port_id, mac_address,
|
||||
internal=True):
|
||||
cmd = ['ovs-vsctl', '--', '--may-exist',
|
||||
'add-port', bridge, device_name]
|
||||
if internal:
|
||||
cmd += ['--', 'set', 'Interface', device_name, 'type=internal']
|
||||
cmd += ['--', 'set', 'Interface', device_name,
|
||||
'external-ids:iface-id=%s' % port_id,
|
||||
'--', 'set', 'Interface', device_name,
|
||||
'external-ids:iface-status=active',
|
||||
'--', 'set', 'Interface', device_name,
|
||||
'external-ids:attached-mac=%s' % mac_address]
|
||||
utils.execute(*cmd, run_as_root=True)
|
||||
|
||||
def plug(self, port_id, device_name, mac_address,
|
||||
bridge=None, namespace=None, prefix=None):
|
||||
"""Plug in the interface."""
|
||||
if not bridge:
|
||||
bridge = self.conf.ovs_integration_bridge
|
||||
|
||||
self.check_bridge_exists(bridge)
|
||||
ip = ip_lib.IPWrapper()
|
||||
ns_dev = ip.device(device_name)
|
||||
|
||||
if not ip_lib.device_exists(device_name,
|
||||
namespace=namespace):
|
||||
|
||||
tap_name = self._get_tap_name(device_name)
|
||||
self._ovs_add_port(bridge, tap_name, port_id, mac_address)
|
||||
ns_dev.link.set_address(mac_address)
|
||||
|
||||
# Add an interface created by ovs to the namespace.
|
||||
if namespace:
|
||||
namespace_obj = ip.ensure_namespace(namespace)
|
||||
namespace_obj.add_device_to_namespace(ns_dev)
|
||||
|
||||
else:
|
||||
LOG.warn(_("Device %s already exists"), device_name)
|
||||
ns_dev.link.set_up()
|
||||
|
||||
def unplug(self, device_name, bridge=None, namespace=None, prefix=None):
|
||||
"""Unplug the interface."""
|
||||
if not bridge:
|
||||
bridge = self.conf.ovs_integration_bridge
|
||||
|
||||
tap_name = self._get_tap_name(device_name)
|
||||
self.check_bridge_exists(bridge)
|
||||
ovs = ovs_lib.OVSBridge(bridge)
|
||||
|
||||
try:
|
||||
ovs.delete_port(tap_name)
|
||||
except RuntimeError:
|
||||
LOG.error(_("Failed unplugging interface '%s'"),
|
||||
device_name)
|
||||
|
||||
|
||||
class BridgeInterfaceDriver(LinuxInterfaceDriver):
|
||||
"""Driver for creating bridge interfaces."""
|
||||
|
||||
DEV_NAME_PREFIX = 'ns-'
|
||||
|
||||
def plug(self, port_id, device_name, mac_address,
|
||||
bridge=None, namespace=None, prefix=None):
|
||||
"""Plugin the interface."""
|
||||
ip = ip_lib.IPWrapper()
|
||||
if prefix:
|
||||
tap_name = device_name.replace(prefix, 'tap')
|
||||
else:
|
||||
tap_name = device_name.replace(self.DEV_NAME_PREFIX, 'tap')
|
||||
|
||||
if not ip_lib.device_exists(device_name,
|
||||
namespace=namespace):
|
||||
# Create ns_veth in a namespace if one is configured.
|
||||
root_veth, ns_veth = ip.add_veth(tap_name, device_name,
|
||||
namespace2=namespace)
|
||||
ns_veth.link.set_address(mac_address)
|
||||
|
||||
else:
|
||||
ns_veth = ip.device(device_name)
|
||||
root_veth = ip.device(tap_name)
|
||||
LOG.warn(_("Device %s already exists"), device_name)
|
||||
|
||||
root_veth.link.set_up()
|
||||
ns_veth.link.set_up()
|
||||
|
||||
def unplug(self, device_name, bridge=None, namespace=None, prefix=None):
|
||||
"""Unplug the interface."""
|
||||
device = ip_lib.IPDevice(device_name, namespace)
|
||||
try:
|
||||
device.link.delete()
|
||||
LOG.debug(_("Unplugged interface '%s'"), device_name)
|
||||
except RuntimeError:
|
||||
LOG.error(_("Failed unplugging interface '%s'"),
|
||||
device_name)
|
|
@ -0,0 +1,422 @@
|
|||
# Copyright 2014 Mirantis Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 netaddr
|
||||
|
||||
from manila import utils
|
||||
|
||||
|
||||
LOOPBACK_DEVNAME = 'lo'
|
||||
|
||||
|
||||
class SubProcessBase(object):
|
||||
def __init__(self, namespace=None):
|
||||
self.namespace = namespace
|
||||
|
||||
def _run(self, options, command, args):
|
||||
if self.namespace:
|
||||
return self._as_root(options, command, args)
|
||||
else:
|
||||
return self._execute(options, command, args)
|
||||
|
||||
def _as_root(self, options, command, args, use_root_namespace=False):
|
||||
namespace = self.namespace if not use_root_namespace else None
|
||||
|
||||
return self._execute(options, command, args, namespace, as_root=True)
|
||||
|
||||
@classmethod
|
||||
def _execute(cls, options, command, args, namespace=None, as_root=False):
|
||||
opt_list = ['-%s' % o for o in options]
|
||||
if namespace:
|
||||
ip_cmd = ['ip', 'netns', 'exec', namespace, 'ip']
|
||||
else:
|
||||
ip_cmd = ['ip']
|
||||
total_cmd = ip_cmd + opt_list + [command] + list(args)
|
||||
return utils.execute(*total_cmd, run_as_root=as_root)[0]
|
||||
|
||||
|
||||
class IPWrapper(SubProcessBase):
|
||||
def __init__(self, namespace=None):
|
||||
super(IPWrapper, self).__init__(namespace=namespace)
|
||||
self.netns = IpNetnsCommand(self)
|
||||
|
||||
def device(self, name):
|
||||
return IPDevice(name, self.namespace)
|
||||
|
||||
def get_devices(self, exclude_loopback=False):
|
||||
retval = []
|
||||
output = self._execute('o', 'link', ('list',), self.namespace)
|
||||
for line in output.split('\n'):
|
||||
if '<' not in line:
|
||||
continue
|
||||
tokens = line.split(':', 2)
|
||||
if len(tokens) >= 3:
|
||||
name = tokens[1].split('@', 1)[0].strip()
|
||||
|
||||
if exclude_loopback and name == LOOPBACK_DEVNAME:
|
||||
continue
|
||||
|
||||
retval.append(IPDevice(name, self.namespace))
|
||||
return retval
|
||||
|
||||
def add_tuntap(self, name, mode='tap'):
|
||||
self._as_root('', 'tuntap', ('add', name, 'mode', mode))
|
||||
return IPDevice(name, self.namespace)
|
||||
|
||||
def add_veth(self, name1, name2, namespace2=None):
|
||||
args = ['add', name1, 'type', 'veth', 'peer', 'name', name2]
|
||||
|
||||
if namespace2 is None:
|
||||
namespace2 = self.namespace
|
||||
else:
|
||||
self.ensure_namespace(namespace2)
|
||||
args += ['netns', namespace2]
|
||||
|
||||
self._as_root('', 'link', tuple(args))
|
||||
|
||||
return (IPDevice(name1, self.namespace), IPDevice(name2, namespace2))
|
||||
|
||||
def ensure_namespace(self, name):
|
||||
if not self.netns.exists(name):
|
||||
ip = self.netns.add(name)
|
||||
lo = ip.device(LOOPBACK_DEVNAME)
|
||||
lo.link.set_up()
|
||||
else:
|
||||
ip = IPWrapper(name)
|
||||
return ip
|
||||
|
||||
def namespace_is_empty(self):
|
||||
return not self.get_devices(exclude_loopback=True)
|
||||
|
||||
def garbage_collect_namespace(self):
|
||||
"""Conditionally destroy the namespace if it is empty."""
|
||||
if self.namespace and self.netns.exists(self.namespace):
|
||||
if self.namespace_is_empty():
|
||||
self.netns.delete(self.namespace)
|
||||
return True
|
||||
return False
|
||||
|
||||
def add_device_to_namespace(self, device):
|
||||
if self.namespace:
|
||||
device.link.set_netns(self.namespace)
|
||||
|
||||
@classmethod
|
||||
def get_namespaces(cls):
|
||||
output = cls._execute('', 'netns', ('list',))
|
||||
return [l.strip() for l in output.split('\n')]
|
||||
|
||||
|
||||
class IPDevice(SubProcessBase):
|
||||
def __init__(self, name, namespace=None):
|
||||
super(IPDevice, self).__init__(namespace=namespace)
|
||||
self.name = name
|
||||
self.link = IpLinkCommand(self)
|
||||
self.addr = IpAddrCommand(self)
|
||||
self.route = IpRouteCommand(self)
|
||||
|
||||
def __eq__(self, other):
|
||||
return (other is not None and self.name == other.name
|
||||
and self.namespace == other.namespace)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class IpCommandBase(object):
|
||||
COMMAND = ''
|
||||
|
||||
def __init__(self, parent):
|
||||
self._parent = parent
|
||||
|
||||
def _run(self, *args, **kwargs):
|
||||
return self._parent._run(kwargs.get('options', []), self.COMMAND, args)
|
||||
|
||||
def _as_root(self, *args, **kwargs):
|
||||
return self._parent._as_root(kwargs.get('options', []),
|
||||
self.COMMAND,
|
||||
args,
|
||||
kwargs.get('use_root_namespace', False))
|
||||
|
||||
|
||||
class IpDeviceCommandBase(IpCommandBase):
|
||||
@property
|
||||
def name(self):
|
||||
return self._parent.name
|
||||
|
||||
|
||||
class IpLinkCommand(IpDeviceCommandBase):
|
||||
COMMAND = 'link'
|
||||
|
||||
def set_address(self, mac_address):
|
||||
self._as_root('set', self.name, 'address', mac_address)
|
||||
|
||||
def set_mtu(self, mtu_size):
|
||||
self._as_root('set', self.name, 'mtu', mtu_size)
|
||||
|
||||
def set_up(self):
|
||||
self._as_root('set', self.name, 'up')
|
||||
|
||||
def set_down(self):
|
||||
self._as_root('set', self.name, 'down')
|
||||
|
||||
def set_netns(self, namespace):
|
||||
self._as_root('set', self.name, 'netns', namespace)
|
||||
self._parent.namespace = namespace
|
||||
|
||||
def set_name(self, name):
|
||||
self._as_root('set', self.name, 'name', name)
|
||||
self._parent.name = name
|
||||
|
||||
def set_alias(self, alias_name):
|
||||
self._as_root('set', self.name, 'alias', alias_name)
|
||||
|
||||
def delete(self):
|
||||
self._as_root('delete', self.name)
|
||||
|
||||
@property
|
||||
def address(self):
|
||||
return self.attributes.get('link/ether')
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
return self.attributes.get('state')
|
||||
|
||||
@property
|
||||
def mtu(self):
|
||||
return self.attributes.get('mtu')
|
||||
|
||||
@property
|
||||
def qdisc(self):
|
||||
return self.attributes.get('qdisc')
|
||||
|
||||
@property
|
||||
def qlen(self):
|
||||
return self.attributes.get('qlen')
|
||||
|
||||
@property
|
||||
def alias(self):
|
||||
return self.attributes.get('alias')
|
||||
|
||||
@property
|
||||
def attributes(self):
|
||||
return self._parse_line(self._run('show', self.name, options='o'))
|
||||
|
||||
def _parse_line(self, value):
|
||||
if not value:
|
||||
return {}
|
||||
|
||||
device_name, settings = value.replace("\\", '').split('>', 1)
|
||||
tokens = settings.split()
|
||||
keys = tokens[::2]
|
||||
values = [int(v) if v.isdigit() else v for v in tokens[1::2]]
|
||||
|
||||
retval = dict(zip(keys, values))
|
||||
return retval
|
||||
|
||||
|
||||
class IpAddrCommand(IpDeviceCommandBase):
|
||||
COMMAND = 'addr'
|
||||
|
||||
def add(self, ip_version, cidr, broadcast, scope='global'):
|
||||
self._as_root('add',
|
||||
cidr,
|
||||
'brd',
|
||||
broadcast,
|
||||
'scope',
|
||||
scope,
|
||||
'dev',
|
||||
self.name,
|
||||
options=[ip_version])
|
||||
|
||||
def delete(self, ip_version, cidr):
|
||||
self._as_root('del',
|
||||
cidr,
|
||||
'dev',
|
||||
self.name,
|
||||
options=[ip_version])
|
||||
|
||||
def flush(self):
|
||||
self._as_root('flush', self.name)
|
||||
|
||||
def list(self, scope=None, to=None, filters=None):
|
||||
if filters is None:
|
||||
filters = []
|
||||
|
||||
retval = []
|
||||
|
||||
if scope:
|
||||
filters += ['scope', scope]
|
||||
if to:
|
||||
filters += ['to', to]
|
||||
|
||||
for line in self._run('show', self.name, *filters).split('\n'):
|
||||
line = line.strip()
|
||||
if not line.startswith('inet'):
|
||||
continue
|
||||
parts = line.split()
|
||||
if parts[0] == 'inet6':
|
||||
version = 6
|
||||
scope = parts[3]
|
||||
broadcast = '::'
|
||||
else:
|
||||
version = 4
|
||||
if parts[2] == 'brd':
|
||||
broadcast = parts[3]
|
||||
scope = parts[5]
|
||||
else:
|
||||
# sometimes output of 'ip a' might look like:
|
||||
# inet 192.168.100.100/24 scope global eth0
|
||||
# and broadcast needs to be calculated from CIDR
|
||||
broadcast = str(netaddr.IPNetwork(parts[1]).broadcast)
|
||||
scope = parts[3]
|
||||
|
||||
retval.append(dict(cidr=parts[1],
|
||||
broadcast=broadcast,
|
||||
scope=scope,
|
||||
ip_version=version,
|
||||
dynamic=('dynamic' == parts[-1])))
|
||||
return retval
|
||||
|
||||
|
||||
class IpRouteCommand(IpDeviceCommandBase):
|
||||
COMMAND = 'route'
|
||||
|
||||
def add_gateway(self, gateway, metric=None):
|
||||
args = ['replace', 'default', 'via', gateway]
|
||||
if metric:
|
||||
args += ['metric', metric]
|
||||
args += ['dev', self.name]
|
||||
self._as_root(*args)
|
||||
|
||||
def delete_gateway(self, gateway):
|
||||
self._as_root('del',
|
||||
'default',
|
||||
'via',
|
||||
gateway,
|
||||
'dev',
|
||||
self.name)
|
||||
|
||||
def get_gateway(self, scope=None, filters=None):
|
||||
if filters is None:
|
||||
filters = []
|
||||
|
||||
retval = None
|
||||
|
||||
if scope:
|
||||
filters += ['scope', scope]
|
||||
|
||||
route_list_lines = self._run('list', 'dev', self.name,
|
||||
*filters).split('\n')
|
||||
default_route_line = next((x.strip() for x in
|
||||
route_list_lines if
|
||||
x.strip().startswith('default')), None)
|
||||
if default_route_line:
|
||||
gateway_index = 2
|
||||
parts = default_route_line.split()
|
||||
retval = dict(gateway=parts[gateway_index])
|
||||
metric_index = 4
|
||||
parts_has_metric = (len(parts) > metric_index)
|
||||
if parts_has_metric:
|
||||
retval.update(metric=int(parts[metric_index]))
|
||||
|
||||
return retval
|
||||
|
||||
def pullup_route(self, interface_name):
|
||||
"""Ensures that the route entry for the interface is before all
|
||||
others on the same subnet.
|
||||
"""
|
||||
device_list = []
|
||||
device_route_list_lines = self._run('list', 'proto', 'kernel',
|
||||
'dev', interface_name).split('\n')
|
||||
for device_route_line in device_route_list_lines:
|
||||
try:
|
||||
subnet = device_route_line.split()[0]
|
||||
except Exception:
|
||||
continue
|
||||
subnet_route_list_lines = self._run('list', 'proto', 'kernel',
|
||||
'match', subnet).split('\n')
|
||||
for subnet_route_line in subnet_route_list_lines:
|
||||
i = iter(subnet_route_line.split())
|
||||
while(i.next() != 'dev'):
|
||||
pass
|
||||
device = i.next()
|
||||
try:
|
||||
while(i.next() != 'src'):
|
||||
pass
|
||||
src = i.next()
|
||||
except Exception:
|
||||
src = ''
|
||||
if device != interface_name:
|
||||
device_list.append((device, src))
|
||||
else:
|
||||
break
|
||||
|
||||
for (device, src) in device_list:
|
||||
self._as_root('del', subnet, 'dev', device)
|
||||
if (src != ''):
|
||||
self._as_root('append', subnet, 'proto', 'kernel',
|
||||
'src', src, 'dev', device)
|
||||
else:
|
||||
self._as_root('append', subnet, 'proto', 'kernel',
|
||||
'dev', device)
|
||||
|
||||
|
||||
class IpNetnsCommand(IpCommandBase):
|
||||
COMMAND = 'netns'
|
||||
|
||||
def add(self, name):
|
||||
self._as_root('add', name, use_root_namespace=True)
|
||||
return IPWrapper(name)
|
||||
|
||||
def delete(self, name):
|
||||
self._as_root('delete', name, use_root_namespace=True)
|
||||
|
||||
def execute(self, cmds, addl_env={}, check_exit_code=True):
|
||||
if not self._parent.namespace:
|
||||
raise Exception(_('No namespace defined for parent'))
|
||||
else:
|
||||
env_params = []
|
||||
if addl_env:
|
||||
env_params = (['env'] +
|
||||
['%s=%s' % pair for pair in addl_env.items()])
|
||||
total_cmd = ['ip', 'netns', 'exec', self._parent.namespace] + \
|
||||
env_params + list(cmds)
|
||||
return utils.execute(*total_cmd, run_as_root=True,
|
||||
check_exit_code=check_exit_code)
|
||||
|
||||
def exists(self, name):
|
||||
output = self._as_root('list', options='o', use_root_namespace=True)
|
||||
|
||||
for line in output.split('\n'):
|
||||
if name == line.strip():
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def device_exists(device_name, namespace=None):
|
||||
try:
|
||||
address = IPDevice(device_name, namespace).link.address
|
||||
except Exception as e:
|
||||
if 'does not exist' in str(e):
|
||||
return False
|
||||
raise
|
||||
return bool(address)
|
||||
|
||||
|
||||
def iproute_arg_supported(command, arg):
|
||||
command += ['help']
|
||||
stdout, stderr = utils.execute(command, check_exit_code=False,
|
||||
return_stderr=True)
|
||||
return any(arg in line for line in stderr.split('\n'))
|
|
@ -0,0 +1,56 @@
|
|||
# Copyright 2014 Mirantis Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 re
|
||||
|
||||
from manila.openstack.common import log as logging
|
||||
from manila import utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OVSBridge:
|
||||
def __init__(self, br_name):
|
||||
self.br_name = br_name
|
||||
self.re_id = self.re_compile_id()
|
||||
|
||||
def re_compile_id(self):
|
||||
external = 'external_ids\s*'
|
||||
mac = 'attached-mac="(?P<vif_mac>([a-fA-F\d]{2}:){5}([a-fA-F\d]{2}))"'
|
||||
iface = 'iface-id="(?P<vif_id>[^"]+)"'
|
||||
name = 'name\s*:\s"(?P<port_name>[^"]*)"'
|
||||
port = 'ofport\s*:\s(?P<ofport>-?\d+)'
|
||||
_re = ('%(external)s:\s{ ( %(mac)s,? | %(iface)s,? | . )* }'
|
||||
' \s+ %(name)s \s+ %(port)s' % {'external': external,
|
||||
'mac': mac,
|
||||
'iface': iface, 'name': name,
|
||||
'port': port})
|
||||
return re.compile(_re, re.M | re.X)
|
||||
|
||||
def run_vsctl(self, args):
|
||||
full_args = ["ovs-vsctl", "--timeout=2"] + args
|
||||
try:
|
||||
return utils.execute(*full_args, run_as_root=True)
|
||||
except Exception as e:
|
||||
LOG.error(_("Unable to execute %(cmd)s. Exception: %(exception)s"),
|
||||
{'cmd': full_args, 'exception': e})
|
||||
|
||||
def reset_bridge(self):
|
||||
self.run_vsctl(["--", "--if-exists", "del-br", self.br_name])
|
||||
self.run_vsctl(["add-br", self.br_name])
|
||||
|
||||
def delete_port(self, port_name):
|
||||
self.run_vsctl(["--", "--if-exists", "del-port", self.br_name,
|
||||
port_name])
|
|
@ -0,0 +1,224 @@
|
|||
# Copyright 2014 Mirantis Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 manila.network.linux import interface
|
||||
from manila.network.linux import ip_lib
|
||||
from manila import test
|
||||
from manila.tests import conf_fixture
|
||||
from manila import utils
|
||||
|
||||
|
||||
class BaseChild(interface.LinuxInterfaceDriver):
|
||||
def plug(*args):
|
||||
pass
|
||||
|
||||
def unplug(*args):
|
||||
pass
|
||||
|
||||
|
||||
FakeSubnet = {
|
||||
'cidr': '192.168.1.1/24',
|
||||
}
|
||||
|
||||
|
||||
FakeAllocation = {
|
||||
'subnet': FakeSubnet,
|
||||
'ip_address': '192.168.1.2',
|
||||
'ip_version': 4,
|
||||
}
|
||||
|
||||
|
||||
FakePort = {
|
||||
'id': 'abcdef01-1234-5678-90ab-ba0987654321',
|
||||
'fixed_ips': [FakeAllocation],
|
||||
'device_id': 'cccccccc-cccc-cccc-cccc-cccccccccccc',
|
||||
}
|
||||
|
||||
|
||||
class TestBase(test.TestCase):
|
||||
def setUp(self):
|
||||
super(TestBase, self).setUp()
|
||||
self.conf = conf_fixture.CONF
|
||||
self.conf.register_opts(interface.OPTS)
|
||||
self.ip_dev_p = mock.patch.object(ip_lib, 'IPDevice')
|
||||
self.ip_dev = self.ip_dev_p.start()
|
||||
self.ip_p = mock.patch.object(ip_lib, 'IPWrapper')
|
||||
self.ip = self.ip_p.start()
|
||||
self.device_exists_p = mock.patch.object(ip_lib, 'device_exists')
|
||||
self.device_exists = self.device_exists_p.start()
|
||||
|
||||
def tearDown(self):
|
||||
self.ip_dev_p.stop()
|
||||
self.ip_p.stop()
|
||||
self.device_exists_p.stop()
|
||||
super(TestBase, self).tearDown()
|
||||
|
||||
|
||||
class TestABCDriver(TestBase):
|
||||
def test_get_device_name(self):
|
||||
bc = BaseChild()
|
||||
device_name = bc.get_device_name(FakePort)
|
||||
self.assertEqual('tapabcdef01-12', device_name)
|
||||
|
||||
def test_l3_init(self):
|
||||
addresses = [dict(ip_version=4, scope='global',
|
||||
dynamic=False, cidr='172.16.77.240/24')]
|
||||
self.ip_dev().addr.list = mock.Mock(return_value=addresses)
|
||||
|
||||
bc = BaseChild()
|
||||
ns = '12345678-1234-5678-90ab-ba0987654321'
|
||||
bc.init_l3('tap0', ['192.168.1.2/24'], namespace=ns)
|
||||
self.ip_dev.assert_has_calls(
|
||||
[mock.call('tap0', namespace=ns),
|
||||
mock.call().addr.list(scope='global', filters=['permanent']),
|
||||
mock.call().addr.add(4, '192.168.1.2/24', '192.168.1.255'),
|
||||
mock.call().addr.delete(4, '172.16.77.240/24')])
|
||||
|
||||
|
||||
class TestOVSInterfaceDriver(TestBase):
|
||||
|
||||
def test_get_device_name(self):
|
||||
br = interface.OVSInterfaceDriver()
|
||||
device_name = br.get_device_name(FakePort)
|
||||
self.assertEqual('tapabcdef01-12', device_name)
|
||||
|
||||
def test_plug_no_ns(self):
|
||||
self._test_plug()
|
||||
|
||||
def test_plug_with_ns(self):
|
||||
self._test_plug(namespace='01234567-1234-1234-99')
|
||||
|
||||
def test_plug_alt_bridge(self):
|
||||
self._test_plug(bridge='br-foo')
|
||||
|
||||
def _test_plug(self, additional_expectation=[], bridge=None,
|
||||
namespace=None):
|
||||
|
||||
if not bridge:
|
||||
bridge = 'br-int'
|
||||
|
||||
def device_exists(dev, namespace=None):
|
||||
return dev == bridge
|
||||
|
||||
vsctl_cmd = ['ovs-vsctl', '--', '--may-exist', 'add-port',
|
||||
bridge, 'tap0', '--', 'set', 'Interface', 'tap0',
|
||||
'type=internal', '--', 'set', 'Interface', 'tap0',
|
||||
'external-ids:iface-id=port-1234', '--', 'set',
|
||||
'Interface', 'tap0',
|
||||
'external-ids:iface-status=active', '--', 'set',
|
||||
'Interface', 'tap0',
|
||||
'external-ids:attached-mac=aa:bb:cc:dd:ee:ff']
|
||||
|
||||
with mock.patch.object(utils, 'execute') as execute:
|
||||
ovs = interface.OVSInterfaceDriver()
|
||||
self.device_exists.side_effect = device_exists
|
||||
ovs.plug('port-1234',
|
||||
'tap0',
|
||||
'aa:bb:cc:dd:ee:ff',
|
||||
bridge=bridge,
|
||||
namespace=namespace)
|
||||
execute.assert_called_once_with(*vsctl_cmd, run_as_root=True)
|
||||
|
||||
expected = [mock.call(),
|
||||
mock.call().device('tap0'),
|
||||
mock.call().device().link.set_address('aa:bb:cc:dd:ee:ff')]
|
||||
expected.extend(additional_expectation)
|
||||
if namespace:
|
||||
expected.extend(
|
||||
[mock.call().ensure_namespace(namespace),
|
||||
mock.call().ensure_namespace().add_device_to_namespace(
|
||||
mock.ANY)])
|
||||
expected.extend([mock.call().device().link.set_up()])
|
||||
|
||||
self.ip.assert_has_calls(expected)
|
||||
|
||||
def test_unplug(self, bridge=None):
|
||||
if not bridge:
|
||||
bridge = 'br-int'
|
||||
with mock.patch('manila.network.linux.ovs_lib.OVSBridge') as ovs_br:
|
||||
ovs = interface.OVSInterfaceDriver()
|
||||
ovs.unplug('tap0')
|
||||
ovs_br.assert_has_calls([mock.call(bridge),
|
||||
mock.call().delete_port('tap0')])
|
||||
|
||||
|
||||
class TestBridgeInterfaceDriver(TestBase):
|
||||
def test_get_device_name(self):
|
||||
br = interface.BridgeInterfaceDriver()
|
||||
device_name = br.get_device_name(FakePort)
|
||||
self.assertEqual('ns-abcdef01-12', device_name)
|
||||
|
||||
def test_plug_no_ns(self):
|
||||
self._test_plug()
|
||||
|
||||
def test_plug_with_ns(self):
|
||||
self._test_plug(namespace='01234567-1234-1234-99')
|
||||
|
||||
def _test_plug(self, namespace=None, mtu=None):
|
||||
def device_exists(device, root_helper=None, namespace=None):
|
||||
return device.startswith('brq')
|
||||
|
||||
root_veth = mock.Mock()
|
||||
ns_veth = mock.Mock()
|
||||
|
||||
self.ip().add_veth = mock.Mock(return_value=(root_veth, ns_veth))
|
||||
|
||||
self.device_exists.side_effect = device_exists
|
||||
br = interface.BridgeInterfaceDriver()
|
||||
mac_address = 'aa:bb:cc:dd:ee:ff'
|
||||
br.plug('port-1234',
|
||||
'ns-0',
|
||||
mac_address,
|
||||
namespace=namespace)
|
||||
|
||||
ip_calls = [mock.call(),
|
||||
mock.call().add_veth('tap0', 'ns-0', namespace2=namespace)]
|
||||
ns_veth.assert_has_calls([mock.call.link.set_address(mac_address)])
|
||||
|
||||
self.ip.assert_has_calls(ip_calls)
|
||||
|
||||
root_veth.assert_has_calls([mock.call.link.set_up()])
|
||||
ns_veth.assert_has_calls([mock.call.link.set_up()])
|
||||
|
||||
def test_plug_dev_exists(self):
|
||||
self.device_exists.return_value = True
|
||||
with mock.patch('manila.network.linux.interface.LOG.warn') as log:
|
||||
br = interface.BridgeInterfaceDriver()
|
||||
br.plug('port-1234',
|
||||
'tap0',
|
||||
'aa:bb:cc:dd:ee:ff')
|
||||
self.ip_dev.assert_has_calls([])
|
||||
self.assertEqual(log.call_count, 1)
|
||||
|
||||
def test_unplug_no_device(self):
|
||||
self.device_exists.return_value = False
|
||||
self.ip_dev().link.delete.side_effect = RuntimeError
|
||||
with mock.patch('manila.network.linux.interface.LOG') as log:
|
||||
br = interface.BridgeInterfaceDriver()
|
||||
br.unplug('tap0')
|
||||
[mock.call(), mock.call('tap0'), mock.call().link.delete()]
|
||||
self.assertEqual(log.error.call_count, 1)
|
||||
|
||||
def test_unplug(self):
|
||||
self.device_exists.return_value = True
|
||||
with mock.patch('manila.network.linux.interface.LOG.debug') as log:
|
||||
br = interface.BridgeInterfaceDriver()
|
||||
br.unplug('tap0')
|
||||
log.assert_called_once()
|
||||
|
||||
self.ip_dev.assert_has_calls([mock.call('tap0', None),
|
||||
mock.call().link.delete()])
|
|
@ -0,0 +1,684 @@
|
|||
# Copyright 2014 Mirantis Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 manila.network.linux import ip_lib
|
||||
from manila import test
|
||||
|
||||
NETNS_SAMPLE = [
|
||||
'12345678-1234-5678-abcd-1234567890ab',
|
||||
'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb',
|
||||
'cccccccc-cccc-cccc-cccc-cccccccccccc']
|
||||
|
||||
LINK_SAMPLE = [
|
||||
'1: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc noqueue state UNKNOWN \\'
|
||||
'link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00',
|
||||
'2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP '
|
||||
'qlen 1000\ link/ether cc:dd:ee:ff:ab:cd brd ff:ff:ff:ff:ff:ff'
|
||||
'\ alias openvswitch',
|
||||
'3: br-int: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN '
|
||||
'\ link/ether aa:bb:cc:dd:ee:ff brd ff:ff:ff:ff:ff:ff',
|
||||
'4: gw-ddc717df-49: <BROADCAST,MULTICAST> mtu 1500 qdisc noop '
|
||||
'state DOWN \ link/ether fe:dc:ba:fe:dc:ba brd ff:ff:ff:ff:ff:ff',
|
||||
'5: eth0.50@eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc '
|
||||
' noqueue master brq0b24798c-07 state UP mode DEFAULT'
|
||||
'\ link/ether ab:04:49:b6:ab:a0 brd ff:ff:ff:ff:ff:ff']
|
||||
|
||||
ADDR_SAMPLE = ("""
|
||||
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP qlen 1000
|
||||
link/ether dd:cc:aa:b9:76:ce brd ff:ff:ff:ff:ff:ff
|
||||
inet 172.16.77.240/24 brd 172.16.77.255 scope global eth0
|
||||
inet6 2001:470:9:1224:5595:dd51:6ba2:e788/64 scope global temporary dynamic
|
||||
valid_lft 14187sec preferred_lft 3387sec
|
||||
inet6 2001:470:9:1224:fd91:272:581e:3a32/64 scope global temporary """
|
||||
"""deprecated dynamic
|
||||
valid_lft 14187sec preferred_lft 0sec
|
||||
inet6 2001:470:9:1224:4508:b885:5fb:740b/64 scope global temporary """
|
||||
"""deprecated dynamic
|
||||
valid_lft 14187sec preferred_lft 0sec
|
||||
inet6 2001:470:9:1224:dfcc:aaff:feb9:76ce/64 scope global dynamic
|
||||
valid_lft 14187sec preferred_lft 3387sec
|
||||
inet6 fe80::dfcc:aaff:feb9:76ce/64 scope link
|
||||
valid_lft forever preferred_lft forever
|
||||
""")
|
||||
|
||||
ADDR_SAMPLE2 = ("""
|
||||
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP qlen 1000
|
||||
link/ether dd:cc:aa:b9:76:ce brd ff:ff:ff:ff:ff:ff
|
||||
inet 172.16.77.240/24 scope global eth0
|
||||
inet6 2001:470:9:1224:5595:dd51:6ba2:e788/64 scope global temporary dynamic
|
||||
valid_lft 14187sec preferred_lft 3387sec
|
||||
inet6 2001:470:9:1224:fd91:272:581e:3a32/64 scope global temporary """
|
||||
"""deprecated dynamic
|
||||
valid_lft 14187sec preferred_lft 0sec
|
||||
inet6 2001:470:9:1224:4508:b885:5fb:740b/64 scope global temporary """
|
||||
"""deprecated dynamic
|
||||
valid_lft 14187sec preferred_lft 0sec
|
||||
inet6 2001:470:9:1224:dfcc:aaff:feb9:76ce/64 scope global dynamic
|
||||
valid_lft 14187sec preferred_lft 3387sec
|
||||
inet6 fe80::dfcc:aaff:feb9:76ce/64 scope link
|
||||
valid_lft forever preferred_lft forever
|
||||
""")
|
||||
|
||||
GATEWAY_SAMPLE1 = ("""
|
||||
default via 10.35.19.254 metric 100
|
||||
10.35.16.0/22 proto kernel scope link src 10.35.17.97
|
||||
""")
|
||||
|
||||
GATEWAY_SAMPLE2 = ("""
|
||||
default via 10.35.19.254 metric 100
|
||||
""")
|
||||
|
||||
GATEWAY_SAMPLE3 = ("""
|
||||
10.35.16.0/22 proto kernel scope link src 10.35.17.97
|
||||
""")
|
||||
|
||||
GATEWAY_SAMPLE4 = ("""
|
||||
default via 10.35.19.254
|
||||
""")
|
||||
|
||||
DEVICE_ROUTE_SAMPLE = ("10.0.0.0/24 scope link src 10.0.0.2")
|
||||
|
||||
SUBNET_SAMPLE1 = ("10.0.0.0/24 dev qr-23380d11-d2 scope link src 10.0.0.1\n"
|
||||
"10.0.0.0/24 dev tap1d7888a7-10 scope link src 10.0.0.2")
|
||||
SUBNET_SAMPLE2 = ("10.0.0.0/24 dev tap1d7888a7-10 scope link src 10.0.0.2\n"
|
||||
"10.0.0.0/24 dev qr-23380d11-d2 scope link src 10.0.0.1")
|
||||
|
||||
|
||||
class TestSubProcessBase(test.TestCase):
|
||||
def setUp(self):
|
||||
super(TestSubProcessBase, self).setUp()
|
||||
self.execute_p = mock.patch('manila.utils.execute')
|
||||
self.execute = self.execute_p.start()
|
||||
|
||||
def tearDown(self):
|
||||
self.execute_p.stop()
|
||||
super(TestSubProcessBase, self).tearDown()
|
||||
|
||||
def test_execute_wrapper(self):
|
||||
ip_lib.SubProcessBase._execute('o', 'link', ('list',))
|
||||
|
||||
self.execute.assert_called_once_with('ip', '-o', 'link', 'list',
|
||||
run_as_root=False)
|
||||
|
||||
def test_execute_wrapper_int_options(self):
|
||||
ip_lib.SubProcessBase._execute([4], 'link', ('list',))
|
||||
|
||||
self.execute.assert_called_once_with('ip', '-4', 'link', 'list',
|
||||
run_as_root=False)
|
||||
|
||||
def test_execute_wrapper_no_options(self):
|
||||
ip_lib.SubProcessBase._execute([], 'link', ('list',))
|
||||
|
||||
self.execute.assert_called_once_with('ip', 'link', 'list',
|
||||
run_as_root=False)
|
||||
|
||||
def test_run_no_namespace(self):
|
||||
base = ip_lib.SubProcessBase()
|
||||
base._run([], 'link', ('list',))
|
||||
self.execute.assert_called_once_with('ip', 'link', 'list',
|
||||
run_as_root=False)
|
||||
|
||||
def test_run_namespace(self):
|
||||
base = ip_lib.SubProcessBase('ns')
|
||||
base._run([], 'link', ('list',))
|
||||
self.execute.assert_called_once_with('ip', 'netns', 'exec', 'ns',
|
||||
'ip', 'link', 'list',
|
||||
run_as_root=True)
|
||||
|
||||
def test_as_root_namespace(self):
|
||||
base = ip_lib.SubProcessBase('ns')
|
||||
base._as_root([], 'link', ('list',))
|
||||
self.execute.assert_called_once_with('ip', 'netns', 'exec', 'ns',
|
||||
'ip', 'link', 'list',
|
||||
run_as_root=True)
|
||||
|
||||
|
||||
class TestIpWrapper(test.TestCase):
|
||||
def setUp(self):
|
||||
super(TestIpWrapper, self).setUp()
|
||||
self.execute_p = mock.patch.object(ip_lib.IPWrapper, '_execute')
|
||||
self.execute = self.execute_p.start()
|
||||
|
||||
def tearDown(self):
|
||||
self.execute_p.stop()
|
||||
super(TestIpWrapper, self).tearDown()
|
||||
|
||||
def test_get_devices(self):
|
||||
self.execute.return_value = '\n'.join(LINK_SAMPLE)
|
||||
retval = ip_lib.IPWrapper().get_devices()
|
||||
self.assertEqual(retval,
|
||||
[ip_lib.IPDevice('lo'),
|
||||
ip_lib.IPDevice('eth0'),
|
||||
ip_lib.IPDevice('br-int'),
|
||||
ip_lib.IPDevice('gw-ddc717df-49'),
|
||||
ip_lib.IPDevice('eth0.50')])
|
||||
|
||||
self.execute.assert_called_once_with('o', 'link', ('list',), None)
|
||||
|
||||
def test_get_devices_malformed_line(self):
|
||||
self.execute.return_value = '\n'.join(LINK_SAMPLE + ['gibberish'])
|
||||
retval = ip_lib.IPWrapper().get_devices()
|
||||
self.assertEqual(retval,
|
||||
[ip_lib.IPDevice('lo'),
|
||||
ip_lib.IPDevice('eth0'),
|
||||
ip_lib.IPDevice('br-int'),
|
||||
ip_lib.IPDevice('gw-ddc717df-49'),
|
||||
ip_lib.IPDevice('eth0.50')])
|
||||
|
||||
self.execute.assert_called_once_with('o', 'link', ('list',), None)
|
||||
|
||||
def test_get_namespaces(self):
|
||||
self.execute.return_value = '\n'.join(NETNS_SAMPLE)
|
||||
retval = ip_lib.IPWrapper.get_namespaces()
|
||||
self.assertEqual(retval,
|
||||
['12345678-1234-5678-abcd-1234567890ab',
|
||||
'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb',
|
||||
'cccccccc-cccc-cccc-cccc-cccccccccccc'])
|
||||
|
||||
self.execute.assert_called_once_with('', 'netns', ('list',))
|
||||
|
||||
def test_add_tuntap(self):
|
||||
ip_lib.IPWrapper().add_tuntap('tap0')
|
||||
self.execute.assert_called_once_with('', 'tuntap',
|
||||
('add', 'tap0', 'mode', 'tap'),
|
||||
None, as_root=True)
|
||||
|
||||
def test_add_veth(self):
|
||||
ip_lib.IPWrapper().add_veth('tap0', 'tap1')
|
||||
self.execute.assert_called_once_with('', 'link',
|
||||
('add', 'tap0', 'type', 'veth',
|
||||
'peer', 'name', 'tap1'),
|
||||
None, as_root=True)
|
||||
|
||||
def test_add_veth_with_namespaces(self):
|
||||
ns2 = 'ns2'
|
||||
with mock.patch.object(ip_lib.IPWrapper, 'ensure_namespace') as en:
|
||||
ip_lib.IPWrapper().add_veth('tap0', 'tap1', namespace2=ns2)
|
||||
en.assert_has_calls([mock.call(ns2)])
|
||||
self.execute.assert_called_once_with('', 'link',
|
||||
('add', 'tap0', 'type', 'veth',
|
||||
'peer', 'name', 'tap1',
|
||||
'netns', ns2),
|
||||
None, as_root=True)
|
||||
|
||||
def test_get_device(self):
|
||||
dev = ip_lib.IPWrapper('ns').device('eth0')
|
||||
self.assertEqual(dev.namespace, 'ns')
|
||||
self.assertEqual(dev.name, 'eth0')
|
||||
|
||||
def test_ensure_namespace(self):
|
||||
with mock.patch.object(ip_lib, 'IPDevice') as ip_dev:
|
||||
ip = ip_lib.IPWrapper()
|
||||
with mock.patch.object(ip.netns, 'exists') as ns_exists:
|
||||
ns_exists.return_value = False
|
||||
ip.ensure_namespace('ns')
|
||||
self.execute.assert_has_calls(
|
||||
[mock.call([], 'netns', ('add', 'ns'), None,
|
||||
as_root=True)])
|
||||
ip_dev.assert_has_calls([mock.call('lo', 'ns'),
|
||||
mock.call().link.set_up()])
|
||||
|
||||
def test_ensure_namespace_existing(self):
|
||||
with mock.patch.object(ip_lib, 'IpNetnsCommand') as ip_ns_cmd:
|
||||
ip_ns_cmd.exists.return_value = True
|
||||
ns = ip_lib.IPWrapper().ensure_namespace('ns')
|
||||
self.assertFalse(self.execute.called)
|
||||
self.assertEqual(ns.namespace, 'ns')
|
||||
|
||||
def test_namespace_is_empty_no_devices(self):
|
||||
ip = ip_lib.IPWrapper('ns')
|
||||
with mock.patch.object(ip, 'get_devices') as get_devices:
|
||||
get_devices.return_value = []
|
||||
|
||||
self.assertTrue(ip.namespace_is_empty())
|
||||
get_devices.assert_called_once_with(exclude_loopback=True)
|
||||
|
||||
def test_namespace_is_empty(self):
|
||||
ip = ip_lib.IPWrapper('ns')
|
||||
with mock.patch.object(ip, 'get_devices') as get_devices:
|
||||
get_devices.return_value = [mock.Mock()]
|
||||
|
||||
self.assertFalse(ip.namespace_is_empty())
|
||||
get_devices.assert_called_once_with(exclude_loopback=True)
|
||||
|
||||
def test_garbage_collect_namespace_does_not_exist(self):
|
||||
with mock.patch.object(ip_lib, 'IpNetnsCommand') as ip_ns_cmd_cls:
|
||||
ip_ns_cmd_cls.return_value.exists.return_value = False
|
||||
ip = ip_lib.IPWrapper('ns')
|
||||
with mock.patch.object(ip, 'namespace_is_empty') as mock_is_empty:
|
||||
self.assertFalse(ip.garbage_collect_namespace())
|
||||
ip_ns_cmd_cls.assert_has_calls([mock.call().exists('ns')])
|
||||
self.assertNotIn(mock.call().delete('ns'),
|
||||
ip_ns_cmd_cls.return_value.mock_calls)
|
||||
self.assertEqual(mock_is_empty.mock_calls, [])
|
||||
|
||||
def test_garbage_collect_namespace_existing_empty_ns(self):
|
||||
with mock.patch.object(ip_lib, 'IpNetnsCommand') as ip_ns_cmd_cls:
|
||||
ip_ns_cmd_cls.return_value.exists.return_value = True
|
||||
|
||||
ip = ip_lib.IPWrapper('ns')
|
||||
|
||||
with mock.patch.object(ip, 'namespace_is_empty') as mock_is_empty:
|
||||
mock_is_empty.return_value = True
|
||||
self.assertTrue(ip.garbage_collect_namespace())
|
||||
|
||||
mock_is_empty.assert_called_once_with()
|
||||
expected = [mock.call().exists('ns'),
|
||||
mock.call().delete('ns')]
|
||||
ip_ns_cmd_cls.assert_has_calls(expected)
|
||||
|
||||
def test_garbage_collect_namespace_existing_not_empty(self):
|
||||
lo_device = mock.Mock()
|
||||
lo_device.name = 'lo'
|
||||
tap_device = mock.Mock()
|
||||
tap_device.name = 'tap1'
|
||||
|
||||
with mock.patch.object(ip_lib, 'IpNetnsCommand') as ip_ns_cmd_cls:
|
||||
ip_ns_cmd_cls.return_value.exists.return_value = True
|
||||
|
||||
ip = ip_lib.IPWrapper('ns')
|
||||
|
||||
with mock.patch.object(ip, 'namespace_is_empty') as mock_is_empty:
|
||||
mock_is_empty.return_value = False
|
||||
|
||||
self.assertFalse(ip.garbage_collect_namespace())
|
||||
|
||||
mock_is_empty.assert_called_once_with()
|
||||
expected = [mock.call(ip),
|
||||
mock.call().exists('ns')]
|
||||
self.assertEqual(ip_ns_cmd_cls.mock_calls, expected)
|
||||
self.assertNotIn(mock.call().delete('ns'),
|
||||
ip_ns_cmd_cls.mock_calls)
|
||||
|
||||
def test_add_device_to_namespace(self):
|
||||
dev = mock.Mock()
|
||||
ip_lib.IPWrapper('ns').add_device_to_namespace(dev)
|
||||
dev.assert_has_calls([mock.call.link.set_netns('ns')])
|
||||
|
||||
def test_add_device_to_namespace_is_none(self):
|
||||
dev = mock.Mock()
|
||||
ip_lib.IPWrapper().add_device_to_namespace(dev)
|
||||
self.assertEqual(dev.mock_calls, [])
|
||||
|
||||
|
||||
class TestIPDevice(test.TestCase):
|
||||
def test_eq_same_name(self):
|
||||
dev1 = ip_lib.IPDevice('tap0')
|
||||
dev2 = ip_lib.IPDevice('tap0')
|
||||
self.assertEqual(dev1, dev2)
|
||||
|
||||
def test_eq_diff_name(self):
|
||||
dev1 = ip_lib.IPDevice('tap0')
|
||||
dev2 = ip_lib.IPDevice('tap1')
|
||||
self.assertNotEqual(dev1, dev2)
|
||||
|
||||
def test_eq_same_namespace(self):
|
||||
dev1 = ip_lib.IPDevice('tap0', 'ns1')
|
||||
dev2 = ip_lib.IPDevice('tap0', 'ns1')
|
||||
self.assertEqual(dev1, dev2)
|
||||
|
||||
def test_eq_diff_namespace(self):
|
||||
dev1 = ip_lib.IPDevice('tap0', 'ns1')
|
||||
dev2 = ip_lib.IPDevice('tap0', 'ns2')
|
||||
self.assertNotEqual(dev1, dev2)
|
||||
|
||||
def test_eq_other_is_none(self):
|
||||
dev1 = ip_lib.IPDevice('tap0', 'ns1')
|
||||
self.assertNotEqual(dev1, None)
|
||||
|
||||
def test_str(self):
|
||||
self.assertEqual(str(ip_lib.IPDevice('tap0')), 'tap0')
|
||||
|
||||
|
||||
class TestIPCommandBase(test.TestCase):
|
||||
def setUp(self):
|
||||
super(TestIPCommandBase, self).setUp()
|
||||
self.ip = mock.Mock()
|
||||
self.ip.namespace = 'namespace'
|
||||
self.ip_cmd = ip_lib.IpCommandBase(self.ip)
|
||||
self.ip_cmd.COMMAND = 'foo'
|
||||
|
||||
def test_run(self):
|
||||
self.ip_cmd._run('link', 'show')
|
||||
self.ip.assert_has_calls([mock.call._run([], 'foo', ('link', 'show'))])
|
||||
|
||||
def test_run_with_options(self):
|
||||
self.ip_cmd._run('link', options='o')
|
||||
self.ip.assert_has_calls([mock.call._run('o', 'foo', ('link', ))])
|
||||
|
||||
def test_as_root(self):
|
||||
self.ip_cmd._as_root('link')
|
||||
self.ip.assert_has_calls(
|
||||
[mock.call._as_root([], 'foo', ('link', ), False)])
|
||||
|
||||
def test_as_root_with_options(self):
|
||||
self.ip_cmd._as_root('link', options='o')
|
||||
self.ip.assert_has_calls(
|
||||
[mock.call._as_root('o', 'foo', ('link', ), False)])
|
||||
|
||||
|
||||
class TestIPDeviceCommandBase(test.TestCase):
|
||||
def setUp(self):
|
||||
super(TestIPDeviceCommandBase, self).setUp()
|
||||
self.ip_dev = mock.Mock()
|
||||
self.ip_dev.name = 'eth0'
|
||||
self.ip_dev._execute = mock.Mock(return_value='executed')
|
||||
self.ip_cmd = ip_lib.IpDeviceCommandBase(self.ip_dev)
|
||||
self.ip_cmd.COMMAND = 'foo'
|
||||
|
||||
def test_name_property(self):
|
||||
self.assertEqual(self.ip_cmd.name, 'eth0')
|
||||
|
||||
|
||||
class TestIPCmdBase(test.TestCase):
|
||||
def setUp(self):
|
||||
super(TestIPCmdBase, self).setUp()
|
||||
self.parent = mock.Mock()
|
||||
self.parent.name = 'eth0'
|
||||
|
||||
def _assert_call(self, options, args):
|
||||
self.parent.assert_has_calls([
|
||||
mock.call._run(options, self.command, args)])
|
||||
|
||||
def _assert_sudo(self, options, args, force_root_namespace=False):
|
||||
self.parent.assert_has_calls(
|
||||
[mock.call._as_root(options, self.command, args,
|
||||
force_root_namespace)])
|
||||
|
||||
|
||||
class TestIpLinkCommand(TestIPCmdBase):
|
||||
def setUp(self):
|
||||
super(TestIpLinkCommand, self).setUp()
|
||||
self.parent._run.return_value = LINK_SAMPLE[1]
|
||||
self.command = 'link'
|
||||
self.link_cmd = ip_lib.IpLinkCommand(self.parent)
|
||||
|
||||
def test_set_address(self):
|
||||
self.link_cmd.set_address('aa:bb:cc:dd:ee:ff')
|
||||
self._assert_sudo([], ('set', 'eth0', 'address', 'aa:bb:cc:dd:ee:ff'))
|
||||
|
||||
def test_set_mtu(self):
|
||||
self.link_cmd.set_mtu(1500)
|
||||
self._assert_sudo([], ('set', 'eth0', 'mtu', 1500))
|
||||
|
||||
def test_set_up(self):
|
||||
self.link_cmd.set_up()
|
||||
self._assert_sudo([], ('set', 'eth0', 'up'))
|
||||
|
||||
def test_set_down(self):
|
||||
self.link_cmd.set_down()
|
||||
self._assert_sudo([], ('set', 'eth0', 'down'))
|
||||
|
||||
def test_set_netns(self):
|
||||
self.link_cmd.set_netns('foo')
|
||||
self._assert_sudo([], ('set', 'eth0', 'netns', 'foo'))
|
||||
self.assertEqual(self.parent.namespace, 'foo')
|
||||
|
||||
def test_set_name(self):
|
||||
self.link_cmd.set_name('tap1')
|
||||
self._assert_sudo([], ('set', 'eth0', 'name', 'tap1'))
|
||||
self.assertEqual(self.parent.name, 'tap1')
|
||||
|
||||
def test_set_alias(self):
|
||||
self.link_cmd.set_alias('openvswitch')
|
||||
self._assert_sudo([], ('set', 'eth0', 'alias', 'openvswitch'))
|
||||
|
||||
def test_delete(self):
|
||||
self.link_cmd.delete()
|
||||
self._assert_sudo([], ('delete', 'eth0'))
|
||||
|
||||
def test_address_property(self):
|
||||
self.parent._execute = mock.Mock(return_value=LINK_SAMPLE[1])
|
||||
self.assertEqual(self.link_cmd.address, 'cc:dd:ee:ff:ab:cd')
|
||||
|
||||
def test_mtu_property(self):
|
||||
self.parent._execute = mock.Mock(return_value=LINK_SAMPLE[1])
|
||||
self.assertEqual(self.link_cmd.mtu, 1500)
|
||||
|
||||
def test_qdisc_property(self):
|
||||
self.parent._execute = mock.Mock(return_value=LINK_SAMPLE[1])
|
||||
self.assertEqual(self.link_cmd.qdisc, 'mq')
|
||||
|
||||
def test_qlen_property(self):
|
||||
self.parent._execute = mock.Mock(return_value=LINK_SAMPLE[1])
|
||||
self.assertEqual(self.link_cmd.qlen, 1000)
|
||||
|
||||
def test_alias_property(self):
|
||||
self.parent._execute = mock.Mock(return_value=LINK_SAMPLE[1])
|
||||
self.assertEqual(self.link_cmd.alias, 'openvswitch')
|
||||
|
||||
def test_state_property(self):
|
||||
self.parent._execute = mock.Mock(return_value=LINK_SAMPLE[1])
|
||||
self.assertEqual(self.link_cmd.state, 'UP')
|
||||
|
||||
def test_settings_property(self):
|
||||
expected = {'mtu': 1500,
|
||||
'qlen': 1000,
|
||||
'state': 'UP',
|
||||
'qdisc': 'mq',
|
||||
'brd': 'ff:ff:ff:ff:ff:ff',
|
||||
'link/ether': 'cc:dd:ee:ff:ab:cd',
|
||||
'alias': 'openvswitch'}
|
||||
self.parent._execute = mock.Mock(return_value=LINK_SAMPLE[1])
|
||||
self.assertEqual(self.link_cmd.attributes, expected)
|
||||
self._assert_call('o', ('show', 'eth0'))
|
||||
|
||||
|
||||
class TestIpAddrCommand(TestIPCmdBase):
|
||||
def setUp(self):
|
||||
super(TestIpAddrCommand, self).setUp()
|
||||
self.parent.name = 'tap0'
|
||||
self.command = 'addr'
|
||||
self.addr_cmd = ip_lib.IpAddrCommand(self.parent)
|
||||
|
||||
def test_add_address(self):
|
||||
self.addr_cmd.add(4, '192.168.45.100/24', '192.168.45.255')
|
||||
self._assert_sudo([4],
|
||||
('add', '192.168.45.100/24', 'brd', '192.168.45.255',
|
||||
'scope', 'global', 'dev', 'tap0'))
|
||||
|
||||
def test_add_address_scoped(self):
|
||||
self.addr_cmd.add(4, '192.168.45.100/24', '192.168.45.255',
|
||||
scope='link')
|
||||
self._assert_sudo([4],
|
||||
('add', '192.168.45.100/24', 'brd', '192.168.45.255',
|
||||
'scope', 'link', 'dev', 'tap0'))
|
||||
|
||||
def test_del_address(self):
|
||||
self.addr_cmd.delete(4, '192.168.45.100/24')
|
||||
self._assert_sudo([4],
|
||||
('del', '192.168.45.100/24', 'dev', 'tap0'))
|
||||
|
||||
def test_flush(self):
|
||||
self.addr_cmd.flush()
|
||||
self._assert_sudo([], ('flush', 'tap0'))
|
||||
|
||||
def test_list(self):
|
||||
expected = [
|
||||
dict(ip_version=4, scope='global',
|
||||
dynamic=False, cidr='172.16.77.240/24',
|
||||
broadcast='172.16.77.255'),
|
||||
dict(ip_version=6, scope='global',
|
||||
dynamic=True, cidr='2001:470:9:1224:5595:dd51:6ba2:e788/64',
|
||||
broadcast='::'),
|
||||
dict(ip_version=6, scope='global',
|
||||
dynamic=True, cidr='2001:470:9:1224:fd91:272:581e:3a32/64',
|
||||
broadcast='::'),
|
||||
dict(ip_version=6, scope='global',
|
||||
dynamic=True, cidr='2001:470:9:1224:4508:b885:5fb:740b/64',
|
||||
broadcast='::'),
|
||||
dict(ip_version=6, scope='global',
|
||||
dynamic=True, cidr='2001:470:9:1224:dfcc:aaff:feb9:76ce/64',
|
||||
broadcast='::'),
|
||||
dict(ip_version=6, scope='link',
|
||||
dynamic=False, cidr='fe80::dfcc:aaff:feb9:76ce/64',
|
||||
broadcast='::')]
|
||||
|
||||
test_cases = [ADDR_SAMPLE, ADDR_SAMPLE2]
|
||||
|
||||
for test_case in test_cases:
|
||||
self.parent._run = mock.Mock(return_value=test_case)
|
||||
self.assertEqual(self.addr_cmd.list(), expected)
|
||||
self._assert_call([], ('show', 'tap0'))
|
||||
|
||||
def test_list_filtered(self):
|
||||
expected = [
|
||||
dict(ip_version=4, scope='global',
|
||||
dynamic=False, cidr='172.16.77.240/24',
|
||||
broadcast='172.16.77.255')]
|
||||
|
||||
test_cases = [ADDR_SAMPLE, ADDR_SAMPLE2]
|
||||
|
||||
for test_case in test_cases:
|
||||
output = '\n'.join(test_case.split('\n')[0:4])
|
||||
self.parent._run.return_value = output
|
||||
self.assertEqual(self.addr_cmd.list('global',
|
||||
filters=['permanent']), expected)
|
||||
self._assert_call([], ('show', 'tap0', 'permanent', 'scope',
|
||||
'global'))
|
||||
|
||||
|
||||
class TestIpRouteCommand(TestIPCmdBase):
|
||||
def setUp(self):
|
||||
super(TestIpRouteCommand, self).setUp()
|
||||
self.parent.name = 'eth0'
|
||||
self.command = 'route'
|
||||
self.route_cmd = ip_lib.IpRouteCommand(self.parent)
|
||||
|
||||
def test_add_gateway(self):
|
||||
gateway = '192.168.45.100'
|
||||
metric = 100
|
||||
self.route_cmd.add_gateway(gateway, metric)
|
||||
self._assert_sudo([],
|
||||
('replace', 'default', 'via', gateway,
|
||||
'metric', metric,
|
||||
'dev', self.parent.name))
|
||||
|
||||
def test_del_gateway(self):
|
||||
gateway = '192.168.45.100'
|
||||
self.route_cmd.delete_gateway(gateway)
|
||||
self._assert_sudo([],
|
||||
('del', 'default', 'via', gateway,
|
||||
'dev', self.parent.name))
|
||||
|
||||
def test_get_gateway(self):
|
||||
test_cases = [{'sample': GATEWAY_SAMPLE1,
|
||||
'expected': {'gateway': '10.35.19.254',
|
||||
'metric': 100}},
|
||||
{'sample': GATEWAY_SAMPLE2,
|
||||
'expected': {'gateway': '10.35.19.254',
|
||||
'metric': 100}},
|
||||
{'sample': GATEWAY_SAMPLE3,
|
||||
'expected': None},
|
||||
{'sample': GATEWAY_SAMPLE4,
|
||||
'expected': {'gateway': '10.35.19.254'}}]
|
||||
for test_case in test_cases:
|
||||
self.parent._run = mock.Mock(return_value=test_case['sample'])
|
||||
self.assertEqual(self.route_cmd.get_gateway(),
|
||||
test_case['expected'])
|
||||
|
||||
def test_pullup_route(self):
|
||||
# interface is not the first in the list - requires
|
||||
# deleting and creating existing entries
|
||||
output = [DEVICE_ROUTE_SAMPLE, SUBNET_SAMPLE1]
|
||||
|
||||
def pullup_side_effect(self, *args):
|
||||
result = output.pop(0)
|
||||
return result
|
||||
|
||||
self.parent._run = mock.Mock(side_effect=pullup_side_effect)
|
||||
self.route_cmd.pullup_route('tap1d7888a7-10')
|
||||
self._assert_sudo([], ('del', '10.0.0.0/24', 'dev', 'qr-23380d11-d2'))
|
||||
self._assert_sudo([], ('append', '10.0.0.0/24', 'proto', 'kernel',
|
||||
'src', '10.0.0.1', 'dev', 'qr-23380d11-d2'))
|
||||
|
||||
def test_pullup_route_first(self):
|
||||
# interface is first in the list - no changes
|
||||
output = [DEVICE_ROUTE_SAMPLE, SUBNET_SAMPLE2]
|
||||
|
||||
def pullup_side_effect(self, *args):
|
||||
result = output.pop(0)
|
||||
return result
|
||||
|
||||
self.parent._run = mock.Mock(side_effect=pullup_side_effect)
|
||||
self.route_cmd.pullup_route('tap1d7888a7-10')
|
||||
# Check two calls - device get and subnet get
|
||||
self.assertEqual(len(self.parent._run.mock_calls), 2)
|
||||
|
||||
|
||||
class TestIpNetnsCommand(TestIPCmdBase):
|
||||
def setUp(self):
|
||||
super(TestIpNetnsCommand, self).setUp()
|
||||
self.command = 'netns'
|
||||
self.netns_cmd = ip_lib.IpNetnsCommand(self.parent)
|
||||
|
||||
def test_add_namespace(self):
|
||||
ns = self.netns_cmd.add('ns')
|
||||
self._assert_sudo([], ('add', 'ns'), force_root_namespace=True)
|
||||
self.assertEqual(ns.namespace, 'ns')
|
||||
|
||||
def test_delete_namespace(self):
|
||||
with mock.patch('manila.utils.execute'):
|
||||
self.netns_cmd.delete('ns')
|
||||
self._assert_sudo([], ('delete', 'ns'), force_root_namespace=True)
|
||||
|
||||
def test_namespace_exists(self):
|
||||
retval = '\n'.join(NETNS_SAMPLE)
|
||||
self.parent._as_root.return_value = retval
|
||||
self.assertTrue(
|
||||
self.netns_cmd.exists('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb'))
|
||||
self._assert_sudo('o', ('list',), force_root_namespace=True)
|
||||
|
||||
def test_namespace_doest_not_exist(self):
|
||||
retval = '\n'.join(NETNS_SAMPLE)
|
||||
self.parent._as_root.return_value = retval
|
||||
self.assertFalse(
|
||||
self.netns_cmd.exists('bbbbbbbb-1111-2222-3333-bbbbbbbbbbbb'))
|
||||
self._assert_sudo('o', ('list',), force_root_namespace=True)
|
||||
|
||||
def test_execute(self):
|
||||
self.parent.namespace = 'ns'
|
||||
with mock.patch('manila.utils.execute') as execute:
|
||||
self.netns_cmd.execute(['ip', 'link', 'list'])
|
||||
execute.assert_called_once_with('ip', 'netns', 'exec', 'ns', 'ip',
|
||||
'link', 'list',
|
||||
run_as_root=True,
|
||||
check_exit_code=True)
|
||||
|
||||
def test_execute_env_var_prepend(self):
|
||||
self.parent.namespace = 'ns'
|
||||
with mock.patch('manila.utils.execute') as execute:
|
||||
env = dict(FOO=1, BAR=2)
|
||||
self.netns_cmd.execute(['ip', 'link', 'list'], env)
|
||||
execute.assert_called_once_with(
|
||||
'ip', 'netns', 'exec', 'ns', 'env', 'FOO=1', 'BAR=2',
|
||||
'ip', 'link', 'list',
|
||||
run_as_root=True, check_exit_code=True)
|
||||
|
||||
|
||||
class TestDeviceExists(test.TestCase):
|
||||
def test_device_exists(self):
|
||||
with mock.patch.object(ip_lib.IPDevice, '_execute') as _execute:
|
||||
_execute.return_value = LINK_SAMPLE[1]
|
||||
self.assertTrue(ip_lib.device_exists('eth0'))
|
||||
_execute.assert_called_once_with('o', 'link', ('show', 'eth0'))
|
||||
|
||||
def test_device_does_not_exist(self):
|
||||
with mock.patch.object(ip_lib.IPDevice, '_execute') as _execute:
|
||||
_execute.return_value = ''
|
||||
_execute.side_effect = RuntimeError('Device does not exist.')
|
||||
self.assertFalse(ip_lib.device_exists('eth0'))
|
|
@ -0,0 +1,64 @@
|
|||
# Copyright 2014 Mirantis Inc.
|
||||
#
|
||||
# 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 manila.network.linux import ovs_lib
|
||||
from manila import test
|
||||
|
||||
|
||||
class OVS_Lib_Test(test.TestCase):
|
||||
"""A test suite to excercise the OVS libraries."""
|
||||
|
||||
def setUp(self):
|
||||
super(OVS_Lib_Test, self).setUp()
|
||||
self.BR_NAME = "br-int"
|
||||
self.TO = "--timeout=2"
|
||||
|
||||
self.br = ovs_lib.OVSBridge(self.BR_NAME)
|
||||
self.execute_p = mock.patch('manila.utils.execute')
|
||||
self.execute = self.execute_p.start()
|
||||
|
||||
def tearDown(self):
|
||||
self.execute_p.stop()
|
||||
super(OVS_Lib_Test, self).tearDown()
|
||||
|
||||
def test_reset_bridge(self):
|
||||
self.br.reset_bridge()
|
||||
self.execute.assert_has_calls([mock.call("ovs-vsctl", self.TO, "--",
|
||||
"--if-exists", "del-br", self.BR_NAME, run_as_root=True),
|
||||
mock.call("ovs-vsctl", self.TO, "add-br",
|
||||
self.BR_NAME, run_as_root=True)])
|
||||
|
||||
def test_delete_port(self):
|
||||
pname = "tap5"
|
||||
self.br.delete_port(pname)
|
||||
self.execute.assert_called_once_with("ovs-vsctl", self.TO, "--",
|
||||
"--if-exists", "del-port", self.BR_NAME, pname,
|
||||
run_as_root=True)
|
||||
|
||||
def test_port_id_regex(self):
|
||||
result = ('external_ids : {attached-mac="fa:16:3e:23:5b:f2",'
|
||||
' iface-id="5c1321a7-c73f-4a77-95e6-9f86402e5c8f",'
|
||||
' iface-status=active}\nname :'
|
||||
' "dhc5c1321a7-c7"\nofport : 2\n')
|
||||
match = self.br.re_id.search(result)
|
||||
vif_mac = match.group('vif_mac')
|
||||
vif_id = match.group('vif_id')
|
||||
port_name = match.group('port_name')
|
||||
ofport = int(match.group('ofport'))
|
||||
self.assertEqual(vif_mac, 'fa:16:3e:23:5b:f2')
|
||||
self.assertEqual(vif_id, '5c1321a7-c73f-4a77-95e6-9f86402e5c8f')
|
||||
self.assertEqual(port_name, 'dhc5c1321a7-c7')
|
||||
self.assertEqual(ofport, 2)
|
Loading…
Reference in New Issue