]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Ensure network connectivity for linuxbridge flat network.
authorGary Kotton <gkotton@redhat.com>
Thu, 23 Aug 2012 08:57:26 +0000 (04:57 -0400)
committerGary Kotton <gkotton@redhat.com>
Mon, 27 Aug 2012 16:50:04 +0000 (12:50 -0400)
Fixes bug 1040447

In the event that an IP address and gateway are defined on an interface that is
used in a flat network, then the interface details will be copied to the
bridge when it is created.
If the network is deleted then the IP addresses are returned to the interface.

Change-Id: Ic860d7635c137f49e5aaaa25dc9727ac60227f1a

quantum/agent/linux/ip_lib.py
quantum/plugins/linuxbridge/agent/linuxbridge_quantum_agent.py
quantum/tests/unit/test_linux_ip_lib.py

index 9d548b9d9537a2ed5bced3820b4577f7e82c0b25..c89faf4c9cb2742ee1144ae04c56e7b41fe512fa 100644 (file)
@@ -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'
 
index 78f09e44941286c11aee6fccdfa0e4d03f30d3b2..06778cce23b8031c723590bc7f00fa2bd80643b3 100755 (executable)
@@ -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")
index cb432e7f0f0836febb9201bd9926ad4320c3b34a..51a9d13cac12480b25197bfccbc5d3e62f386af7 100644 (file)
@@ -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()