Skip to content

Commit cc76d7c

Browse files
Privsep the ebtables modification code.
The same pattern as previous changes, although with some slightly bigger unit test fallout. Change-Id: Iaa59f346030680f7dc38cf17c907b31d2292a837 Co-Authored-By: Stephen Finucane <[email protected]>
1 parent 3685de1 commit cc76d7c

File tree

5 files changed

+176
-135
lines changed

5 files changed

+176
-135
lines changed

nova/network/linux_net.py

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1491,30 +1491,27 @@ def _exec_ebtables(table, rule, insert_rule=True, check_exit_code=True):
14911491
# other error (like a rule doesn't exist) so we have to
14921492
# to parse stderr.
14931493
try:
1494-
cmd = ['ebtables', '--concurrent', '-t', table]
1495-
if insert_rule:
1496-
cmd.append('-I')
1497-
else:
1498-
cmd.append('-D')
1499-
cmd.extend(rule)
1500-
1501-
_execute(*cmd, check_exit_code=[0], run_as_root=True)
1494+
nova.privsep.linux_net.modify_ebtables(table, rule,
1495+
insert_rule=insert_rule)
15021496
except processutils.ProcessExecutionError as exc:
15031497
# See if we can retry the error.
15041498
if any(error in exc.stderr for error in retry_strings):
15051499
if count > attempts and check_exit_code:
1506-
LOG.warning('%s failed. Not Retrying.', ' '.join(cmd))
1500+
LOG.warning('Rule edit for %s failed. Not Retrying.',
1501+
table)
15071502
raise
15081503
else:
15091504
# We need to sleep a bit before retrying
1510-
LOG.warning("%(cmd)s failed. Sleeping %(time)s "
1511-
"seconds before retry.",
1512-
{'cmd': ' '.join(cmd), 'time': sleep})
1505+
LOG.warning('Rule edit for %(table)s failed. '
1506+
'Sleeping %(time)s seconds before retry.',
1507+
{'table': table, 'time': sleep})
15131508
time.sleep(sleep)
15141509
else:
15151510
# Not eligible for retry
15161511
if check_exit_code:
1517-
LOG.warning('%s failed. Not Retrying.', ' '.join(cmd))
1512+
LOG.warning('Rule edit for %s failed and not eligible '
1513+
'for retry.',
1514+
table)
15181515
raise
15191516
else:
15201517
return

nova/privsep/linux_net.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,3 +247,15 @@ def ipv4_forwarding_check():
247247
@nova.privsep.sys_admin_pctxt.entrypoint
248248
def _enable_ipv4_forwarding_inner():
249249
processutils.execute('sysctl', '-w', 'net.ipv4.ip_forward=1')
250+
251+
252+
@nova.privsep.sys_admin_pctxt.entrypoint
253+
def modify_ebtables(table, rule, insert_rule=True):
254+
cmd = ['ebtables', '--concurrent', '-t', table]
255+
if insert_rule:
256+
cmd.append('-I')
257+
else:
258+
cmd.append('-D')
259+
cmd.extend(rule)
260+
261+
processutils.execute(*cmd, check_exit_code=[0])

nova/tests/unit/network/test_linux_net.py

Lines changed: 114 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import datetime
1818
import os
1919
import re
20-
import time
2120

2221
import mock
2322
import netifaces
@@ -793,7 +792,8 @@ def test_dnsmasq_execute_use_network_dns_servers(self):
793792
]
794793
self._test_dnsmasq_execute(extra_expected=expected)
795794

796-
def test_isolated_host(self):
795+
@mock.patch('nova.privsep.linux_net.modify_ebtables')
796+
def test_isolated_host(self, mock_modify_ebtables):
797797
self.flags(fake_network=False,
798798
share_dhcp_address=True)
799799
executes = []
@@ -824,34 +824,47 @@ def fake_ensure(bridge, interface, network, gateway):
824824
driver.plug(network, 'fakemac')
825825

826826
expected = [
827-
('ebtables', '--concurrent', '-t', 'filter', '-D', 'INPUT', '-p',
828-
'ARP', '-i', iface, '--arp-ip-dst', dhcp, '-j', 'DROP'),
829-
('ebtables', '--concurrent', '-t', 'filter', '-I', 'INPUT', '-p',
830-
'ARP', '-i', iface, '--arp-ip-dst', dhcp, '-j', 'DROP'),
831-
('ebtables', '--concurrent', '-t', 'filter', '-D', 'OUTPUT', '-p',
832-
'ARP', '-o', iface, '--arp-ip-src', dhcp, '-j', 'DROP'),
833-
('ebtables', '--concurrent', '-t', 'filter', '-I', 'OUTPUT', '-p',
834-
'ARP', '-o', iface, '--arp-ip-src', dhcp, '-j', 'DROP'),
835-
('ebtables', '--concurrent', '-t', 'filter', '-D', 'FORWARD',
836-
'-p', 'IPv4', '-i', iface, '--ip-protocol', 'udp',
837-
'--ip-destination-port', '67:68', '-j', 'DROP'),
838-
('ebtables', '--concurrent', '-t', 'filter', '-I', 'FORWARD',
839-
'-p', 'IPv4', '-i', iface, '--ip-protocol', 'udp',
840-
'--ip-destination-port', '67:68', '-j', 'DROP'),
841-
('ebtables', '--concurrent', '-t', 'filter', '-D', 'FORWARD',
842-
'-p', 'IPv4', '-o', iface, '--ip-protocol', 'udp',
843-
'--ip-destination-port', '67:68', '-j', 'DROP'),
844-
('ebtables', '--concurrent', '-t', 'filter', '-I', 'FORWARD',
845-
'-p', 'IPv4', '-o', iface, '--ip-protocol', 'udp',
846-
'--ip-destination-port', '67:68', '-j', 'DROP'),
847827
('iptables-save', '-c'),
848828
('iptables-restore', '-c'),
849829
('ip6tables-save', '-c'),
850830
('ip6tables-restore', '-c'),
851831
]
852832
self.assertEqual(expected, executes)
853-
854-
executes = []
833+
mock_modify_ebtables.assert_has_calls([
834+
mock.call('filter',
835+
['INPUT', '-p', 'ARP', '-i', iface, '--arp-ip-dst',
836+
dhcp, '-j', 'DROP'],
837+
insert_rule=False),
838+
mock.call('filter',
839+
['INPUT', '-p', 'ARP', '-i', iface, '--arp-ip-dst',
840+
dhcp, '-j', 'DROP'],
841+
insert_rule=True),
842+
mock.call('filter',
843+
['OUTPUT', '-p', 'ARP', '-o', iface, '--arp-ip-src',
844+
dhcp, '-j', 'DROP'],
845+
insert_rule=False),
846+
mock.call('filter',
847+
['OUTPUT', '-p', 'ARP', '-o', iface, '--arp-ip-src',
848+
dhcp, '-j', 'DROP'],
849+
insert_rule=True),
850+
mock.call('filter',
851+
['FORWARD', '-p', 'IPv4', '-i', iface, '--ip-protocol',
852+
'udp', '--ip-destination-port', '67:68', '-j', 'DROP'],
853+
insert_rule=False),
854+
mock.call('filter',
855+
['FORWARD', '-p', 'IPv4', '-i', iface, '--ip-protocol',
856+
'udp', '--ip-destination-port', '67:68', '-j', 'DROP'],
857+
insert_rule=True),
858+
mock.call('filter',
859+
['FORWARD', '-p', 'IPv4', '-o', iface, '--ip-protocol',
860+
'udp', '--ip-destination-port', '67:68', '-j', 'DROP'],
861+
insert_rule=False),
862+
mock.call('filter',
863+
['FORWARD', '-p', 'IPv4', '-o', iface, '--ip-protocol',
864+
'udp', '--ip-destination-port', '67:68', '-j', 'DROP'],
865+
insert_rule=True)])
866+
867+
mock_modify_ebtables.reset_mock()
855868

856869
def fake_remove(bridge, gateway):
857870
return
@@ -861,19 +874,23 @@ def fake_remove(bridge, gateway):
861874
fake_remove)
862875

863876
driver.unplug(network)
864-
expected = [
865-
('ebtables', '--concurrent', '-t', 'filter', '-D', 'INPUT', '-p',
866-
'ARP', '-i', iface, '--arp-ip-dst', dhcp, '-j', 'DROP'),
867-
('ebtables', '--concurrent', '-t', 'filter', '-D', 'OUTPUT', '-p',
868-
'ARP', '-o', iface, '--arp-ip-src', dhcp, '-j', 'DROP'),
869-
('ebtables', '--concurrent', '-t', 'filter', '-D', 'FORWARD',
870-
'-p', 'IPv4', '-i', iface, '--ip-protocol', 'udp',
871-
'--ip-destination-port', '67:68', '-j', 'DROP'),
872-
('ebtables', '--concurrent', '-t', 'filter', '-D', 'FORWARD',
873-
'-p', 'IPv4', '-o', iface, '--ip-protocol', 'udp',
874-
'--ip-destination-port', '67:68', '-j', 'DROP'),
875-
]
876-
self.assertEqual(expected, executes)
877+
mock_modify_ebtables.assert_has_calls([
878+
mock.call('filter',
879+
['INPUT', '-p', 'ARP', '-i', iface, '--arp-ip-dst',
880+
dhcp, '-j', 'DROP'],
881+
insert_rule=False),
882+
mock.call('filter',
883+
['OUTPUT', '-p', 'ARP', '-o', iface, '--arp-ip-src',
884+
dhcp, '-j', 'DROP'],
885+
insert_rule=False),
886+
mock.call('filter',
887+
['FORWARD', '-p', 'IPv4', '-i', iface, '--ip-protocol',
888+
'udp', '--ip-destination-port', '67:68', '-j', 'DROP'],
889+
insert_rule=False),
890+
mock.call('filter',
891+
['FORWARD', '-p', 'IPv4', '-o', iface, '--ip-protocol',
892+
'udp', '--ip-destination-port', '67:68', '-j', 'DROP'],
893+
insert_rule=False)])
877894

878895
@mock.patch('nova.privsep.linux_net.routes_show')
879896
@mock.patch('nova.privsep.linux_net.route_delete')
@@ -1211,85 +1228,66 @@ def fake_execute(*cmd, **kwargs):
12111228
driver.ensure_bridge('brq1234567-89', '')
12121229
device_exists.assert_called_once_with('brq1234567-89')
12131230

1214-
def test_exec_ebtables_success(self):
1215-
executes = []
1216-
1217-
def fake_execute(*args, **kwargs):
1218-
executes.append(args)
1219-
return "", ""
1220-
1221-
with mock.patch.object(self.driver, '_execute',
1222-
side_effect=fake_execute):
1223-
self.driver._exec_ebtables('fake', ['fake'])
1224-
self.assertEqual(1, len(executes))
1225-
1226-
def _ebtables_race_stderr(self):
1227-
return (u"Unable to update the kernel. Two possible causes:\n"
1228-
"1. Multiple ebtables programs were executing simultaneously."
1229-
" The ebtables\n userspace tool doesn't by default support "
1230-
"multiple ebtables programs running\n concurrently. The "
1231-
"ebtables option --concurrent or a tool like flock can be\n "
1232-
"used to support concurrent scripts that update the ebtables "
1233-
"kernel tables.\n2. The kernel doesn't support a certain "
1234-
"ebtables extension, consider\n recompiling your kernel or "
1235-
"insmod the extension.\n.\n")
1236-
1237-
def test_exec_ebtables_fail_all(self):
1238-
executes = []
1239-
1240-
def fake_sleep(interval):
1241-
pass
1242-
1243-
def fake_execute(*args, **kwargs):
1244-
executes.append(args)
1245-
raise processutils.ProcessExecutionError('error',
1246-
stderr=self._ebtables_race_stderr())
1247-
1248-
with mock.patch.object(time, 'sleep', side_effect=fake_sleep), \
1249-
mock.patch.object(self.driver, '_execute',
1250-
side_effect=fake_execute):
1251-
self.assertRaises(processutils.ProcessExecutionError,
1252-
self.driver._exec_ebtables, 'fake', ['fake'])
1253-
max_calls = CONF.ebtables_exec_attempts
1254-
self.assertEqual(max_calls, len(executes))
1255-
1256-
def test_exec_ebtables_fail_no_retry(self):
1257-
executes = []
1258-
1259-
def fake_sleep(interval):
1260-
pass
1261-
1262-
def fake_execute(*args, **kwargs):
1263-
executes.append(args)
1264-
raise processutils.ProcessExecutionError('error',
1265-
stderr="Sorry, rule does not exist")
1266-
1267-
with mock.patch.object(time, 'sleep', side_effect=fake_sleep), \
1268-
mock.patch.object(self.driver, '_execute',
1269-
side_effect=fake_execute):
1270-
self.assertRaises(processutils.ProcessExecutionError,
1271-
self.driver._exec_ebtables, 'fake', 'fake')
1272-
self.assertEqual(1, len(executes))
1273-
1274-
def test_exec_ebtables_fail_once(self):
1275-
executes = []
1276-
1277-
def fake_sleep(interval):
1278-
pass
1279-
1280-
def fake_execute(*args, **kwargs):
1281-
executes.append(args)
1282-
if len(executes) == 1:
1283-
raise processutils.ProcessExecutionError('error',
1284-
stderr=self._ebtables_race_stderr())
1285-
else:
1286-
return "", ""
1287-
1288-
with mock.patch.object(time, 'sleep', side_effect=fake_sleep), \
1289-
mock.patch.object(self.driver, '_execute',
1290-
side_effect=fake_execute):
1291-
self.driver._exec_ebtables('fake', 'fake')
1292-
self.assertEqual(2, len(executes))
1231+
@mock.patch('nova.privsep.linux_net.modify_ebtables',
1232+
return_value=('', ''))
1233+
def test_exec_ebtables_success(self, mock_modify_ebtables):
1234+
self.driver._exec_ebtables('fake', 'fake')
1235+
mock_modify_ebtables.assert_called()
1236+
1237+
@mock.patch('nova.privsep.linux_net.modify_ebtables',
1238+
side_effect=processutils.ProcessExecutionError(
1239+
'error',
1240+
stderr=(u'Unable to update the kernel. Two possible '
1241+
'causes:\n1. Multiple ebtables programs were '
1242+
'executing simultaneously. The ebtables\n '
1243+
'userspace tool doesn\'t by default support '
1244+
'multiple ebtables programs running\n '
1245+
'concurrently. The ebtables option --concurrent '
1246+
'or a tool like flock can be\n used to support '
1247+
'concurrent scripts that update the ebtables '
1248+
'kernel tables.\n2. The kernel doesn\'t support '
1249+
'a certain ebtables extension, consider\n '
1250+
'recompiling your kernel or insmod the '
1251+
'extension.\n.\n')))
1252+
@mock.patch('time.sleep')
1253+
def test_exec_ebtables_fail_all(self, mock_sleep, mock_modify_ebtables):
1254+
self.flags(ebtables_exec_attempts=5)
1255+
self.assertRaises(processutils.ProcessExecutionError,
1256+
self.driver._exec_ebtables, 'fake', 'fake')
1257+
self.assertEqual(5, mock_modify_ebtables.call_count)
1258+
1259+
@mock.patch('nova.privsep.linux_net.modify_ebtables',
1260+
side_effect=processutils.ProcessExecutionError(
1261+
'error',
1262+
stderr=(u'Sorry, rule does not exist')))
1263+
@mock.patch('time.sleep')
1264+
def test_exec_ebtables_fail_no_retry(self, mock_sleep,
1265+
mock_modify_ebtables):
1266+
self.assertRaises(processutils.ProcessExecutionError,
1267+
self.driver._exec_ebtables, 'fake', 'fake')
1268+
mock_modify_ebtables.assert_called()
1269+
1270+
@mock.patch('nova.privsep.linux_net.modify_ebtables',
1271+
side_effect=[
1272+
processutils.ProcessExecutionError(
1273+
'error',
1274+
stderr=(u'Unable to update the kernel. Two possible '
1275+
'causes:\n1. Multiple ebtables programs were '
1276+
'executing simultaneously. The ebtables\n '
1277+
'userspace tool doesn\'t by default support '
1278+
'multiple ebtables programs running\n '
1279+
'concurrently. The ebtables option '
1280+
'--concurrent or a tool like flock can be\n '
1281+
'used to support concurrent scripts that '
1282+
'update the ebtables kernel tables.\n2. The '
1283+
'kernel doesn\'t support a certain ebtables '
1284+
'extension, consider\n recompiling your '
1285+
'kernel or insmod the extension.\n.\n')),
1286+
('', '')])
1287+
@mock.patch('time.sleep')
1288+
def test_exec_ebtables_fail_once(self, mock_sleep, mock_modify_ebtables):
1289+
self.driver._exec_ebtables('fake', 'fake')
1290+
self.assertEqual(2, mock_modify_ebtables.call_count)
12931291

12941292
@mock.patch('os.path.exists', return_value=True)
12951293
@mock.patch('nova.privsep.linux_net.set_device_disabled')

nova/tests/unit/network/test_manager.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -884,7 +884,9 @@ def setUp(self):
884884
@mock.patch('nova.objects.floating_ip.FloatingIPList.get_by_host')
885885
@mock.patch('nova.network.linux_net.iptables_manager._apply')
886886
@mock.patch('nova.privsep.linux_net.bind_ip')
887-
def test_init_host_iptables_defer_apply(self, bind_ip, iptable_apply,
887+
@mock.patch('nova.privsep.linux_net.modify_ebtables')
888+
def test_init_host_iptables_defer_apply(self, modify_ebtables,
889+
bind_ip, iptable_apply,
888890
floating_get_by_host,
889891
fixed_get_by_id):
890892
def get_by_id(context, fixed_ip_id, **kwargs):
@@ -1770,8 +1772,10 @@ def test_add_fixed_ip_instance_without_vpn_requested_networks(
17701772
@mock.patch('nova.privsep.linux_net.bind_ip')
17711773
@mock.patch('nova.privsep.linux_net.unbind_ip')
17721774
@mock.patch('nova.privsep.linux_net.clean_conntrack')
1775+
@mock.patch('nova.privsep.linux_net.modify_ebtables')
17731776
def test_ip_association_and_allocation_of_other_project(
1774-
self, clean_conntrack, unbind_ip, bind_ip, net_get, fixed_get):
1777+
self, modify_ebtables, clean_conntrack, unbind_ip, bind_ip,
1778+
net_get, fixed_get):
17751779
"""Makes sure that we cannot deallocaate or disassociate
17761780
a public IP of other project.
17771781
"""
@@ -2089,7 +2093,9 @@ def test_get_networks_by_uuids_ordering(self):
20892093
@mock.patch('nova.objects.floating_ip.FloatingIPList.get_by_host')
20902094
@mock.patch('nova.network.linux_net.iptables_manager._apply')
20912095
@mock.patch('nova.privsep.linux_net.bind_ip')
2092-
def test_init_host_iptables_defer_apply(self, bind_ip, iptable_apply,
2096+
@mock.patch('nova.privsep.linux_net.modify_ebtables')
2097+
def test_init_host_iptables_defer_apply(self, modify_ebtables, bind_ip,
2098+
iptable_apply,
20932099
floating_get_by_host,
20942100
fixed_get_by_id):
20952101
def get_by_id(context, fixed_ip_id, **kwargs):
@@ -2887,7 +2893,9 @@ def setUp(self):
28872893
@mock.patch('nova.privsep.linux_net.ipv4_forwarding_check',
28882894
return_value=False)
28892895
@mock.patch('nova.privsep.linux_net._enable_ipv4_forwarding_inner')
2890-
def test_allocate_for_instance(self, mock_forwarding_enable,
2896+
@mock.patch('nova.privsep.linux_net.modify_ebtables')
2897+
def test_allocate_for_instance(self, mock_modify_ebtables,
2898+
mock_forwarding_enable,
28912899
mock_forwarding_check,
28922900
mock_clean_conntrack,
28932901
mock_address_command,
@@ -3133,7 +3141,10 @@ def test_double_deallocation(self):
31333141

31343142
@mock.patch('nova.privsep.linux_net.unbind_ip')
31353143
@mock.patch('nova.privsep.linux_net.clean_conntrack')
3136-
def test_deallocation_deleted_instance(self, mock_clean_conntrack,
3144+
@mock.patch('nova.privsep.linux_net.modify_ebtables')
3145+
def test_deallocation_deleted_instance(self,
3146+
mock_modify_ebtables,
3147+
mock_clean_conntrack,
31373148
mock_unbind_ip):
31383149
self.stubs.Set(self.network, '_teardown_network_on_host',
31393150
lambda *args, **kwargs: None)
@@ -3156,7 +3167,10 @@ def test_deallocation_deleted_instance(self, mock_clean_conntrack,
31563167

31573168
@mock.patch('nova.privsep.linux_net.unbind_ip')
31583169
@mock.patch('nova.privsep.linux_net.clean_conntrack')
3159-
def test_deallocation_duplicate_floating_ip(self, mock_clean_conntrack,
3170+
@mock.patch('nova.privsep.linux_net.modify_ebtables')
3171+
def test_deallocation_duplicate_floating_ip(self,
3172+
mock_modify_ebtables,
3173+
mock_clean_conntrack,
31603174
mock_unbind_ip):
31613175
self.stubs.Set(self.network, '_teardown_network_on_host',
31623176
lambda *args, **kwargs: None)

0 commit comments

Comments
 (0)