Merge "Remove unused bridge interfaces"
This commit is contained in:
commit
d5b91dd39b
nova
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user