get_device_by_ip: don't fail if device was deleted

The function gets a list of all the devices that exists on the machine,
and then iterates on them one at a time in order to find the correct
device which holds the ip specified. However, if one of the devices was
in the mean time deleted, the code will raise an Exception. In the ovs
agent's case, this will cause it to not run at all (requiring a
restart).

Also, changes to a few tests of LinuxBridge were made because
linuxbridge doesn't check that cfg.CONF.VXLAN.local_ip is not empty
before using it (a bug, surely). Since it's out of scope of this patch
to fix this, workarounds were implemented to make sure the tests ignore
the option instead.

Closes-Bug: #1506503
Change-Id: Iad285d7c763b0e8e8f877c6892aadb0043e9a186
This commit is contained in:
John Schwarz 2015-10-15 17:34:01 +03:00
parent 9da4dfae56
commit f3dece785e
5 changed files with 60 additions and 21 deletions

View File

@ -41,6 +41,7 @@ LOOPBACK_DEVNAME = 'lo'
SYS_NET_PATH = '/sys/class/net'
DEFAULT_GW_PATTERN = re.compile(r"via (\S+)")
METRIC_PATTERN = re.compile(r"metric (\S+)")
DEVICE_NAME_PATTERN = re.compile(r"(\d+?): (\S+?):.*")
class AddressNotReady(exceptions.NeutronException):
@ -129,10 +130,19 @@ class IPWrapper(SubProcessBase):
return retval
def get_device_by_ip(self, ip):
"""Get the IPDevice from system which has ip configured."""
for device in self.get_devices():
if device.addr.list(to=ip):
return device
"""Get the IPDevice from system which has ip configured.
@param ip: look for the device holding this ip. If this is None,
None is returned.
@type ip: str.
"""
if not ip:
return None
addr = IpAddrCommand(self)
devices = addr.get_devices_with_ip(to=ip)
if devices:
return IPDevice(devices[0]['name'], namespace=self.namespace)
def add_tuntap(self, name, mode='tap'):
self._as_root([], 'tuntap', ('add', name, 'mode', mode))
@ -513,24 +523,40 @@ class IpAddrCommand(IpDeviceCommandBase):
def flush(self, ip_version):
self._as_root([ip_version], ('flush', self.name))
def list(self, scope=None, to=None, filters=None, ip_version=None):
def get_devices_with_ip(self, name=None, scope=None, to=None,
filters=None, ip_version=None):
"""Get a list of all the devices with an IP attached in the namespace.
@param name: if it's not None, only a device with that matching name
will be returned.
"""
options = [ip_version] if ip_version else []
args = ['show', self.name]
args = ['show']
if name:
args += [name]
if filters:
args += filters
retval = []
if scope:
args += ['scope', scope]
if to:
args += ['to', to]
retval = []
for line in self._run(options, tuple(args)).split('\n'):
line = line.strip()
if not line.startswith('inet'):
match = DEVICE_NAME_PATTERN.search(line)
if match:
# Found a match for a device name, but its' addresses will
# only appear in following lines, so we may as well continue.
device_name = match.group(2)
continue
parts = line.split()
elif not line.startswith('inet'):
continue
parts = line.split(" ")
if parts[0] == 'inet6':
scope = parts[3]
else:
@ -539,13 +565,19 @@ class IpAddrCommand(IpDeviceCommandBase):
else:
scope = parts[3]
retval.append(dict(cidr=parts[1],
retval.append(dict(name=device_name,
cidr=parts[1],
scope=scope,
dynamic=('dynamic' == parts[-1]),
tentative=('tentative' in line),
dadfailed=('dadfailed' == parts[-1])))
return retval
def list(self, scope=None, to=None, filters=None, ip_version=None):
"""Get device details of a device named <self.name>."""
return self.get_devices_with_ip(
self.name, scope, to, filters, ip_version)
def wait_until_address_ready(self, address, wait_time=30):
"""Wait until an address is no longer marked 'tentative'

View File

@ -121,6 +121,10 @@ class IpLibTestCase(IpLibTestFramework):
device.link.delete()
self.assertFalse(ip_lib.vxlan_in_use(9999, namespace=attr.namespace))
def test_ipwrapper_get_device_by_ip_None(self):
ip_wrapper = ip_lib.IPWrapper(namespace=None)
self.assertIsNone(ip_wrapper.get_device_by_ip(ip=None))
def test_ipwrapper_get_device_by_ip(self):
attr = self.generate_device_details()
self.manage_device(attr)

View File

@ -13,6 +13,7 @@
# under the License.
import mock
from oslo_config import cfg
from oslo_log import log as logging
import testtools
@ -31,6 +32,7 @@ class LinuxBridgeAgentTests(test_ip_lib.IpLibTestFramework):
agent_rpc = ('neutron.agent.rpc.PluginApi')
mock.patch(agent_rpc).start()
mock.patch('neutron.agent.rpc.PluginReportStateAPI').start()
cfg.CONF.set_override('enable_vxlan', False, 'VXLAN')
def test_validate_interface_mappings(self):
mappings = {'physnet1': 'int1', 'physnet2': 'int2'}

View File

@ -35,6 +35,7 @@ class LinuxbridgeCleanupTest(base.BaseSudoTestCase):
prefix=lb_agent.BRIDGE_NAME_PREFIX))).fixture
config = callback(br_fixture)
config.update({'VXLAN': {'enable_vxlan': 'False'}})
temp_dir = self.useFixture(fixtures.TempDir()).path
conf = self.useFixture(config_fixtures.ConfigFileFixture(

View File

@ -778,21 +778,21 @@ class TestIpAddrCommand(TestIPCmdBase):
def test_list(self):
expected = [
dict(scope='global', dadfailed=False, tentative=False,
dict(name='eth0', scope='global', dadfailed=False, tentative=False,
dynamic=False, cidr='172.16.77.240/24'),
dict(scope='global', dadfailed=False, tentative=False,
dict(name='eth0', scope='global', dadfailed=False, tentative=False,
dynamic=True, cidr='2001:470:9:1224:5595:dd51:6ba2:e788/64'),
dict(scope='link', dadfailed=False, tentative=True,
dict(name='eth0', scope='link', dadfailed=False, tentative=True,
dynamic=False, cidr='fe80::3023:39ff:febc:22ae/64'),
dict(scope='link', dadfailed=True, tentative=True,
dict(name='eth0', scope='link', dadfailed=True, tentative=True,
dynamic=False, cidr='fe80::3023:39ff:febc:22af/64'),
dict(scope='global', dadfailed=False, tentative=False,
dict(name='eth0', scope='global', dadfailed=False, tentative=False,
dynamic=True, cidr='2001:470:9:1224:fd91:272:581e:3a32/64'),
dict(scope='global', dadfailed=False, tentative=False,
dict(name='eth0', scope='global', dadfailed=False, tentative=False,
dynamic=True, cidr='2001:470:9:1224:4508:b885:5fb:740b/64'),
dict(scope='global', dadfailed=False, tentative=False,
dict(name='eth0', scope='global', dadfailed=False, tentative=False,
dynamic=True, cidr='2001:470:9:1224:dfcc:aaff:feb9:76ce/64'),
dict(scope='link', dadfailed=False, tentative=False,
dict(name='eth0', scope='link', dadfailed=False, tentative=False,
dynamic=False, cidr='fe80::dfcc:aaff:feb9:76ce/64')]
test_cases = [ADDR_SAMPLE, ADDR_SAMPLE2]
@ -824,7 +824,7 @@ class TestIpAddrCommand(TestIPCmdBase):
def test_list_filtered(self):
expected = [
dict(scope='global', tentative=False, dadfailed=False,
dict(name='eth0', scope='global', tentative=False, dadfailed=False,
dynamic=False, cidr='172.16.77.240/24')]
test_cases = [ADDR_SAMPLE, ADDR_SAMPLE2]