Merge "Ensure network connectivity for linuxbridge flat network."
This commit is contained in:
@@ -110,6 +110,7 @@ class IPDevice(SubProcessBase):
|
||||
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
|
||||
@@ -227,7 +228,10 @@ class IpAddrCommand(IpDeviceCommandBase):
|
||||
def flush(self):
|
||||
self._as_root('flush', self.name)
|
||||
|
||||
def list(self, scope=None, to=None, filters=[]):
|
||||
def list(self, scope=None, to=None, filters=None):
|
||||
if filters is None:
|
||||
filters = []
|
||||
|
||||
retval = []
|
||||
|
||||
if scope:
|
||||
@@ -243,17 +247,64 @@ class IpAddrCommand(IpDeviceCommandBase):
|
||||
if parts[0] == 'inet6':
|
||||
version = 6
|
||||
scope = parts[3]
|
||||
broadcast = '::'
|
||||
else:
|
||||
version = 4
|
||||
broadcast = parts[3]
|
||||
scope = parts[5]
|
||||
|
||||
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 = ['add', '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
|
||||
|
||||
|
||||
class IpNetnsCommand(IpCommandBase):
|
||||
COMMAND = 'netns'
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ import eventlet
|
||||
import pyudev
|
||||
from sqlalchemy.ext.sqlsoup import SqlSoup
|
||||
|
||||
from quantum.agent.linux import ip_lib
|
||||
from quantum.agent.linux import utils
|
||||
from quantum.agent import rpc as agent_rpc
|
||||
from quantum.common import config as logging_config
|
||||
@@ -65,6 +66,7 @@ class LinuxBridge:
|
||||
def __init__(self, interface_mappings, root_helper):
|
||||
self.interface_mappings = interface_mappings
|
||||
self.root_helper = root_helper
|
||||
self.ip = ip_lib.IPWrapper(self.root_helper)
|
||||
|
||||
def device_exists(self, device):
|
||||
"""Check if ethernet device exists."""
|
||||
@@ -175,10 +177,19 @@ class LinuxBridge:
|
||||
self.ensure_bridge(bridge_name, interface)
|
||||
return interface
|
||||
|
||||
def get_interface_details(self, interface):
|
||||
device = self.ip.device(interface)
|
||||
ips = device.addr.list(scope='global')
|
||||
|
||||
# Update default gateway if necessary
|
||||
gateway = device.route.get_gateway(scope='global')
|
||||
return ips, gateway
|
||||
|
||||
def ensure_flat_bridge(self, network_id, physical_interface):
|
||||
"""Create a non-vlan bridge unless it already exists."""
|
||||
bridge_name = self.get_bridge_name(network_id)
|
||||
self.ensure_bridge(bridge_name, physical_interface)
|
||||
ips, gateway = self.get_interface_details(physical_interface)
|
||||
self.ensure_bridge(bridge_name, physical_interface, ips, gateway)
|
||||
return physical_interface
|
||||
|
||||
def ensure_vlan(self, physical_interface, vlan_id):
|
||||
@@ -198,7 +209,33 @@ class LinuxBridge:
|
||||
LOG.debug("Done creating subinterface %s" % interface)
|
||||
return interface
|
||||
|
||||
def ensure_bridge(self, bridge_name, interface):
|
||||
def update_interface_ip_details(self, destination, source, ips,
|
||||
gateway):
|
||||
if ips or gateway:
|
||||
dst_device = self.ip.device(destination)
|
||||
src_device = self.ip.device(source)
|
||||
|
||||
# Append IP's to bridge if necessary
|
||||
for ip in ips:
|
||||
dst_device.addr.add(ip_version=ip['ip_version'],
|
||||
cidr=ip['cidr'],
|
||||
broadcast=ip['broadcast'])
|
||||
|
||||
if gateway:
|
||||
# Ensure that the gateway can be updated by changing the metric
|
||||
metric = 100
|
||||
if 'metric' in gateway:
|
||||
metric = gateway['metric'] - 1
|
||||
dst_device.route.add_gateway(gateway=gateway['gateway'],
|
||||
metric=metric)
|
||||
src_device.route.delete_gateway(gateway=gateway['gateway'])
|
||||
|
||||
# Remove IP's from interface
|
||||
for ip in ips:
|
||||
src_device.addr.delete(ip_version=ip['ip_version'],
|
||||
cidr=ip['cidr'])
|
||||
|
||||
def ensure_bridge(self, bridge_name, interface, ips=None, gateway=None):
|
||||
"""
|
||||
Create a bridge unless it already exists.
|
||||
"""
|
||||
@@ -220,6 +257,9 @@ class LinuxBridge:
|
||||
LOG.debug("Done starting bridge %s for subinterface %s" %
|
||||
(bridge_name, interface))
|
||||
|
||||
# Update IP info if necessary
|
||||
self.update_interface_ip_details(bridge_name, interface, ips, gateway)
|
||||
|
||||
# Check if the interface is part of the bridge
|
||||
if not self.interface_exists_on_bridge(bridge_name, interface):
|
||||
try:
|
||||
@@ -296,8 +336,16 @@ class LinuxBridge:
|
||||
for interface in interfaces_on_bridge:
|
||||
self.remove_interface(bridge_name, interface)
|
||||
for physical_interface in self.interface_mappings.itervalues():
|
||||
if interface.startswith(physical_interface):
|
||||
self.delete_vlan(interface)
|
||||
if physical_interface == interface:
|
||||
# This is a flat network => return IP's from bridge to
|
||||
# interface
|
||||
ips, gateway = self.get_interface_details(bridge_name)
|
||||
self.update_interface_ip_details(interface,
|
||||
bridge_name,
|
||||
ips, gateway)
|
||||
else:
|
||||
if interface.startswith(physical_interface):
|
||||
self.delete_vlan(interface)
|
||||
|
||||
LOG.debug("Deleting bridge %s" % bridge_name)
|
||||
if utils.execute(['ip', 'link', 'set', bridge_name, 'down'],
|
||||
@@ -353,8 +401,8 @@ class LinuxBridgeRpcCallbacks():
|
||||
LOG.debug("network_delete received")
|
||||
network_id = kwargs.get('network_id')
|
||||
bridge_name = self.linux_br.get_bridge_name(network_id)
|
||||
# (TODO) garyk delete the bridge interface
|
||||
LOG.debug("Delete %s", bridge_name)
|
||||
self.linux_br.delete_vlan_bridge(bridge_name)
|
||||
|
||||
def port_update(self, context, **kwargs):
|
||||
LOG.debug("port_update received")
|
||||
|
||||
@@ -55,6 +55,23 @@ ADDR_SAMPLE = ("""
|
||||
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
|
||||
""")
|
||||
|
||||
|
||||
class TestSubProcessBase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
@@ -383,17 +400,23 @@ class TestIpAddrCommand(TestIPCmdBase):
|
||||
def test_list(self):
|
||||
expected = [
|
||||
dict(ip_version=4, scope='global',
|
||||
dynamic=False, cidr='172.16.77.240/24'),
|
||||
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'),
|
||||
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'),
|
||||
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'),
|
||||
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'),
|
||||
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')]
|
||||
dynamic=False, cidr='fe80::dfcc:aaff:feb9:76ce/64',
|
||||
broadcast='::')]
|
||||
|
||||
self.parent._run = mock.Mock(return_value=ADDR_SAMPLE)
|
||||
self.assertEquals(self.addr_cmd.list(), expected)
|
||||
@@ -402,7 +425,8 @@ class TestIpAddrCommand(TestIPCmdBase):
|
||||
def test_list_filtered(self):
|
||||
expected = [
|
||||
dict(ip_version=4, scope='global',
|
||||
dynamic=False, cidr='172.16.77.240/24')]
|
||||
dynamic=False, cidr='172.16.77.240/24',
|
||||
broadcast='172.16.77.255')]
|
||||
|
||||
output = '\n'.join(ADDR_SAMPLE.split('\n')[0:4])
|
||||
self.parent._run.return_value = output
|
||||
@@ -411,6 +435,46 @@ class TestIpAddrCommand(TestIPCmdBase):
|
||||
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([],
|
||||
('add', '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.assertEquals(self.route_cmd.get_gateway(),
|
||||
test_case['expected'])
|
||||
|
||||
|
||||
class TestIpNetnsCommand(TestIPCmdBase):
|
||||
def setUp(self):
|
||||
super(TestIpNetnsCommand, self).setUp()
|
||||
|
||||
Reference in New Issue
Block a user