Merge "Remove unused bridge interfaces"

This commit is contained in:
Jenkins 2012-12-12 22:43:02 +00:00 committed by Gerrit Code Review
commit d5b91dd39b
6 changed files with 195 additions and 4 deletions

@ -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."""

@ -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,

@ -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):

@ -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"""

@ -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 = []

@ -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)