summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2012-12-12 22:43:02 +0000
committerGerrit Code Review <review@openstack.org>2012-12-12 22:43:02 +0000
commitd5b91dd39bd89eed98742cd02ea604a842a45447 (patch)
tree977ef001c7bd8df53e65f66d6ac1dea4333d379b
parent7b5ce8f8cc7cffb4b3be2fefb4878a6a6d23b838 (diff)
parent40fcbcb8852cad03eec345106772292fa08b25dc (diff)
downloadnova-d5b91dd39bd89eed98742cd02ea604a842a45447.tar.gz
nova-d5b91dd39bd89eed98742cd02ea604a842a45447.tar.xz
nova-d5b91dd39bd89eed98742cd02ea604a842a45447.zip
Merge "Remove unused bridge interfaces"
-rw-r--r--nova/db/api.py4
-rw-r--r--nova/db/sqlalchemy/api.py5
-rw-r--r--nova/network/linux_net.py117
-rw-r--r--nova/network/manager.py29
-rw-r--r--nova/tests/network/test_linux_net.py27
-rw-r--r--nova/tests/test_db_api.py17
6 files changed, 195 insertions, 4 deletions
diff --git a/nova/db/api.py b/nova/db/api.py
index cfa6a6487..67d8e7618 100644
--- a/nova/db/api.py
+++ b/nova/db/api.py
@@ -809,6 +809,10 @@ def network_get_all_by_uuids(context, network_uuids,
# pylint: disable=C0103
+def network_in_use_on_host(context, network_id, host=None):
+ """Indicates if a network is currently in use on host."""
+ return IMPL.network_in_use_on_host(context, network_id, host)
+
def network_get_associated_fixed_ips(context, network_id, host=None):
"""Get all network's ips that have been associated."""
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index 216fb6e4d..29c40bb69 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -2241,6 +2241,11 @@ def network_get_associated_fixed_ips(context, network_id, host=None):
return data
+def network_in_use_on_host(context, network_id, host):
+ fixed_ips = network_get_associated_fixed_ips(context, network_id, host)
+ return len(fixed_ips) > 0
+
+
@require_admin_context
def _network_get_query(context, session=None):
return model_query(context, models.Network, session=session,
diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py
index 7929c235c..7059ecddb 100644
--- a/nova/network/linux_net.py
+++ b/nova/network/linux_net.py
@@ -750,6 +750,18 @@ def _add_dnsmasq_accept_rules(dev):
iptables_manager.apply()
+def _remove_dnsmasq_accept_rules(dev):
+ """Remove DHCP and DNS traffic allowed through to dnsmasq."""
+ table = iptables_manager.ipv4['filter']
+ for port in [67, 53]:
+ for proto in ['udp', 'tcp']:
+ args = {'dev': dev, 'port': port, 'proto': proto}
+ table.remove_rule('INPUT',
+ '-i %(dev)s -p %(proto)s -m %(proto)s '
+ '--dport %(port)s -j ACCEPT' % args)
+ iptables_manager.apply()
+
+
def get_dhcp_opts(context, network_ref):
"""Get network's hosts config in dhcp-opts format."""
hosts = []
@@ -811,6 +823,7 @@ def kill_dhcp(dev):
_execute('kill', '-9', pid, run_as_root=True)
else:
LOG.debug(_('Pid %d is stale, skip killing dnsmasq'), pid)
+ _remove_dnsmasq_accept_rules(dev)
# NOTE(ja): Sending a HUP only reloads the hostfile, so any
@@ -1138,7 +1151,21 @@ class LinuxBridgeInterfaceDriver(LinuxNetInterfaceDriver):
iptables_manager.apply()
return network['bridge']
- def unplug(self, network):
+ def unplug(self, network, gateway=True):
+ vlan = network.get('vlan')
+ if vlan is not None:
+ iface = 'vlan%s' % vlan
+ LinuxBridgeInterfaceDriver.remove_vlan_bridge(vlan,
+ network['bridge'])
+ else:
+ iface = CONF.flat_interface or network['bridge_interface']
+ LinuxBridgeInterfaceDriver.remove_bridge(network['bridge'],
+ gateway)
+
+ if CONF.share_dhcp_address:
+ remove_isolate_dhcp_address(iface, network['dhcp_server'])
+
+ iptables_manager.apply()
return self.get_dev(network)
def get_dev(self, network):
@@ -1154,7 +1181,13 @@ class LinuxBridgeInterfaceDriver(LinuxNetInterfaceDriver):
return interface
@classmethod
- @lockutils.synchronized('ensure_vlan', 'nova-', external=True)
+ def remove_vlan_bridge(cls, vlan_num, bridge):
+ """Delete a bridge and vlan."""
+ LinuxBridgeInterfaceDriver.remove_bridge(bridge)
+ LinuxBridgeInterfaceDriver.remove_vlan(vlan_num)
+
+ @classmethod
+ @lockutils.synchronized('lock_vlan', 'nova-', external=True)
def ensure_vlan(_self, vlan_num, bridge_interface, mac_address=None):
"""Create a vlan unless it already exists."""
interface = 'vlan%s' % vlan_num
@@ -1179,7 +1212,24 @@ class LinuxBridgeInterfaceDriver(LinuxNetInterfaceDriver):
return interface
@classmethod
- @lockutils.synchronized('ensure_bridge', 'nova-', external=True)
+ @lockutils.synchronized('lock_vlan', 'nova-', external=True)
+ def remove_vlan(cls, vlan_num):
+ """Delete a vlan"""
+ vlan_interface = 'vlan%s' % vlan_num
+ if not device_exists(vlan_interface):
+ return
+ else:
+ try:
+ utils.execute('ip', 'link', 'delete', vlan_interface,
+ run_as_root=True, check_exit_code=[0, 2, 254])
+ except exception.ProcessExecutionError:
+ LOG.error(_("Failed unplugging VLAN interface '%s'"),
+ vlan_interface)
+ raise
+ LOG.debug(_("Unplugged VLAN interface '%s'"), vlan_interface)
+
+ @classmethod
+ @lockutils.synchronized('lock_bridge', 'nova-', external=True)
def ensure_bridge(_self, bridge, interface, net_attrs=None, gateway=True,
filtering=True):
"""Create a bridge unless it already exists.
@@ -1260,6 +1310,34 @@ class LinuxBridgeInterfaceDriver(LinuxNetInterfaceDriver):
ipv4_filter.add_rule('FORWARD',
'--out-interface %s -j DROP' % bridge)
+ @classmethod
+ @lockutils.synchronized('lock_bridge', 'nova-', external=True)
+ def remove_bridge(cls, bridge, gateway=True, filtering=True):
+ """Delete a bridge."""
+ if not device_exists(bridge):
+ return
+ else:
+ if filtering:
+ ipv4_filter = iptables_manager.ipv4['filter']
+ if gateway:
+ ipv4_filter.remove_rule('FORWARD',
+ '--in-interface %s -j ACCEPT' % bridge)
+ ipv4_filter.remove_rule('FORWARD',
+ '--out-interface %s -j ACCEPT' % bridge)
+ else:
+ ipv4_filter.remove_rule('FORWARD',
+ '--in-interface %s -j DROP' % bridge)
+ ipv4_filter.remove_rule('FORWARD',
+ '--out-interface %s -j DROP' % bridge)
+ try:
+ utils.execute('ip', 'link', 'delete', bridge, run_as_root=True,
+ check_exit_code=[0, 2, 254])
+ except exception.ProcessExecutionError:
+ LOG.error(_("Failed unplugging bridge interface '%s'"), bridge)
+ raise
+
+ LOG.debug(_("Unplugged bridge interface '%s'"), bridge)
+
@lockutils.synchronized('ebtables', 'nova-', external=True)
def ensure_ebtables_rules(rules):
@@ -1270,6 +1348,13 @@ def ensure_ebtables_rules(rules):
_execute(*cmd, run_as_root=True)
+@lockutils.synchronized('ebtables', 'nova-', external=True)
+def remove_ebtables_rules(rules):
+ for rule in rules:
+ cmd = ['ebtables', '-D'] + rule.split()
+ _execute(*cmd, check_exit_code=False, run_as_root=True)
+
+
def isolate_dhcp_address(interface, address):
# block arp traffic to address accross the interface
rules = []
@@ -1296,6 +1381,32 @@ def isolate_dhcp_address(interface, address):
% (interface, address), top=True)
+def remove_isolate_dhcp_address(interface, address):
+ # block arp traffic to address accross the interface
+ rules = []
+ rules.append('INPUT -p ARP -i %s --arp-ip-dst %s -j DROP'
+ % (interface, address))
+ rules.append('OUTPUT -p ARP -o %s --arp-ip-src %s -j DROP'
+ % (interface, address))
+ remove_ebtables_rules(rules)
+ # NOTE(vish): the above is not possible with iptables/arptables
+ # block dhcp broadcast traffic across the interface
+ ipv4_filter = iptables_manager.ipv4['filter']
+ ipv4_filter.remove_rule('FORWARD',
+ '-m physdev --physdev-in %s -d 255.255.255.255 '
+ '-p udp --dport 67 -j DROP' % interface, top=True)
+ ipv4_filter.remove_rule('FORWARD',
+ '-m physdev --physdev-out %s -d 255.255.255.255 '
+ '-p udp --dport 67 -j DROP' % interface, top=True)
+ # block ip traffic to address accross the interface
+ ipv4_filter.remove_rule('FORWARD',
+ '-m physdev --physdev-in %s -d %s -j DROP'
+ % (interface, address), top=True)
+ ipv4_filter.remove_rule('FORWARD',
+ '-m physdev --physdev-out %s -s %s -j DROP'
+ % (interface, address), top=True)
+
+
# plugs interfaces using Open vSwitch
class LinuxOVSInterfaceDriver(LinuxNetInterfaceDriver):
diff --git a/nova/network/manager.py b/nova/network/manager.py
index faeaff87c..d916d3fb7 100644
--- a/nova/network/manager.py
+++ b/nova/network/manager.py
@@ -152,6 +152,11 @@ network_opts = [
cfg.BoolOpt('fake_call',
default=False,
help='If True, skip using the queue and make local calls'),
+ cfg.BoolOpt('teardown_unused_network_gateway',
+ default=False,
+ help='If True, unused gateway devices (VLAN and bridge) are '
+ 'deleted in VLAN network mode with multi hosted '
+ 'networks'),
cfg.BoolOpt('force_dhcp_release',
default=False,
help='If True, send a dhcp release on instance termination'),
@@ -1457,7 +1462,6 @@ class NetworkManager(manager.SchedulerDependentManager):
if teardown:
network = self._get_network_by_id(context,
fixed_ip_ref['network_id'])
- self._teardown_network_on_host(context, network)
if CONF.force_dhcp_release:
dev = self.driver.get_dev(network)
@@ -1480,6 +1484,8 @@ class NetworkManager(manager.SchedulerDependentManager):
# callback will get called by nova-dhcpbridge.
self.driver.release_dhcp(dev, address, vif['address'])
+ self._teardown_network_on_host(context, network)
+
def lease_fixed_ip(self, context, address):
"""Called by dhcp-bridge when ip is leased."""
LOG.debug(_('Leased IP |%(address)s|'), locals(), context=context)
@@ -2250,6 +2256,7 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager):
return NetworkManager.create_networks(
self, context, vpn=True, **kwargs)
+ @lockutils.synchronized('setup_network', 'nova-', external=True)
def _setup_network_on_host(self, context, network):
"""Sets up network on this host."""
if not network['vpn_public_address']:
@@ -2281,6 +2288,7 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager):
self.db.network_update(context, network['id'],
{'gateway_v6': gateway})
+ @lockutils.synchronized('setup_network', 'nova-', external=True)
def _teardown_network_on_host(self, context, network):
if not CONF.fake_network:
network['dhcp_server'] = self._get_dhcp_ip(context, network)
@@ -2289,6 +2297,25 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager):
elevated = context.elevated()
self.driver.update_dhcp(elevated, dev, network)
+ # NOTE(ethuleau): For multi hosted networks, if the network is no
+ # more used on this host and if VPN forwarding rule aren't handed
+ # by the host, we delete the network gateway.
+ vpn_address = network['vpn_public_address']
+ if (CONF.teardown_unused_network_gateway and
+ network['multi_host'] and vpn_address != CONF.vpn_ip and
+ not self.db.network_in_use_on_host(context, network['id'],
+ self.host)):
+ LOG.debug("Remove unused gateway %s", network['bridge'])
+ self.driver.kill_dhcp(dev)
+ self.l3driver.remove_gateway(network)
+ if not CONF.share_dhcp_address:
+ values = {'allocated': False,
+ 'host': None}
+ self.db.fixed_ip_update(context, network['dhcp_server'],
+ values)
+ else:
+ self.driver.update_dhcp(context, dev, network)
+
def _get_network_dict(self, network):
"""Returns the dict representing necessary and meta network fields"""
diff --git a/nova/tests/network/test_linux_net.py b/nova/tests/network/test_linux_net.py
index 55a9c7777..68aaa6251 100644
--- a/nova/tests/network/test_linux_net.py
+++ b/nova/tests/network/test_linux_net.py
@@ -494,6 +494,33 @@ class LinuxNetworkTestCase(test.TestCase):
for inp in expected_inputs:
self.assertTrue(inp in inputs[0])
+ executes = []
+ inputs = []
+
+ @classmethod
+ def fake_remove(_self, bridge, gateway):
+ return
+
+ self.stubs.Set(linux_net.LinuxBridgeInterfaceDriver,
+ 'remove_bridge', fake_remove)
+
+ driver.unplug(network)
+ expected = [
+ ('ebtables', '-D', 'INPUT', '-p', 'ARP', '-i', iface,
+ '--arp-ip-dst', dhcp, '-j', 'DROP'),
+ ('ebtables', '-D', 'OUTPUT', '-p', 'ARP', '-o', iface,
+ '--arp-ip-src', dhcp, '-j', 'DROP'),
+ ('iptables-save', '-c', '-t', 'filter'),
+ ('iptables-restore', '-c'),
+ ('iptables-save', '-c', '-t', 'nat'),
+ ('iptables-restore', '-c'),
+ ('ip6tables-save', '-c', '-t', 'filter'),
+ ('ip6tables-restore', '-c'),
+ ]
+ self.assertEqual(executes, expected)
+ for inp in expected_inputs:
+ self.assertFalse(inp in inputs[0])
+
def _test_initialize_gateway(self, existing, expected, routes=''):
self.flags(fake_network=False)
executes = []
diff --git a/nova/tests/test_db_api.py b/nova/tests/test_db_api.py
index f2124c021..29bce8bf5 100644
--- a/nova/tests/test_db_api.py
+++ b/nova/tests/test_db_api.py
@@ -582,6 +582,23 @@ class DbApiTestCase(test.TestCase):
data = db.network_get_all_by_host(ctxt, 'foo')
self.assertEqual(len(data), 3)
+ def test_network_in_use_on_host(self):
+ ctxt = context.get_admin_context()
+
+ values = {'host': 'foo', 'hostname': 'myname'}
+ instance = db.instance_create(ctxt, values)
+ values = {'address': 'bar', 'instance_uuid': instance['uuid']}
+ vif = db.virtual_interface_create(ctxt, values)
+ values = {'address': 'baz',
+ 'network_id': 1,
+ 'allocated': True,
+ 'instance_uuid': instance['uuid'],
+ 'virtual_interface_id': vif['id']}
+ db.fixed_ip_create(ctxt, values)
+
+ self.assertEqual(db.network_in_use_on_host(ctxt, 1, 'foo'), True)
+ self.assertEqual(db.network_in_use_on_host(ctxt, 1, 'bar'), False)
+
def _timeout_test(self, ctxt, timeout, multi_host):
values = {'host': 'foo'}
instance = db.instance_create(ctxt, values)