Skip to content

Commit 61ad633

Browse files
committed
Catch non-existent entry failures better in ip_lib
The privileged/agent/linux/ip_lib.py code was not always catching "entry does not exist" type errors when deleting entries, and most of the callers were not catching it either, which could lead to random failures. Add code in the IP route, rule and bridge fdb code to catch these errors and not raise on them, other exceptions will still be raised. Also fixed delete_neigh_entry() to not raise when the given namespace does not exist to make it like all the other calls in the file. Added or modified functional tests for above cases. Conflicts: neutron/privileged/agent/linux/ip_lib.py neutron/tests/unit/privileged/agent/linux/test_ip_lib.py Change-Id: I083649ab1b9a9057ee276a7f3ba069eb667db870 Closes-bug: #2030804 (cherry picked from commit 16875b5)
1 parent 3ab7d6e commit 61ad633

File tree

4 files changed

+92
-29
lines changed

4 files changed

+92
-29
lines changed

neutron/privileged/agent/linux/ip_lib.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,10 @@ def delete_neigh_entry(ip_version, ip_address, mac_address, device, namespace,
473473
if e.code == errno.ENOENT:
474474
return
475475
raise
476+
except OSError as e:
477+
if e.errno == errno.ENOENT:
478+
raise NetworkNamespaceNotFound(netns_name=namespace)
479+
raise
476480

477481

478482
@tenacity.retry(
@@ -682,6 +686,11 @@ def delete_ip_rule(namespace, **kwargs):
682686
try:
683687
with get_iproute(namespace) as ip:
684688
ip.rule('del', **kwargs)
689+
except netlink_exceptions.NetlinkError as e:
690+
# trying to delete a non-existent entry shouldn't raise an error
691+
if e.code == errno.ENOENT:
692+
return
693+
raise
685694
except OSError as e:
686695
if e.errno == errno.ENOENT:
687696
raise NetworkNamespaceNotFound(netns_name=namespace)
@@ -787,6 +796,11 @@ def delete_ip_route(namespace, cidr, ip_version, device=None, via=None,
787796
try:
788797
with get_iproute(namespace) as ip:
789798
ip.route('del', **kwargs)
799+
except netlink_exceptions.NetlinkError as e:
800+
# trying to delete a non-existent entry shouldn't raise an error
801+
if e.code == errno.ESRCH:
802+
return
803+
raise
790804
except OSError as e:
791805
if e.errno == errno.ENOENT:
792806
raise NetworkNamespaceNotFound(netns_name=namespace)
@@ -821,6 +835,11 @@ def _command_bridge_fdb(command, mac, device, dst_ip=None, namespace=None,
821835
kwargs['dst'] = dst_ip
822836
with get_iproute(namespace) as ip:
823837
return make_serializable(ip.fdb(command, **kwargs))
838+
except netlink_exceptions.NetlinkError as e:
839+
# trying to delete a non-existent entry shouldn't raise an error
840+
if command == 'del' and e.code == errno.ENOENT:
841+
return
842+
raise
824843
except OSError as e:
825844
if e.errno == errno.ENOENT:
826845
raise NetworkNamespaceNotFound(netns_name=namespace)
@@ -836,20 +855,20 @@ def add_bridge_fdb(mac, device, dst_ip=None, namespace=None, **kwargs):
836855

837856
@privileged.default.entrypoint
838857
def append_bridge_fdb(mac, device, dst_ip=None, namespace=None, **kwargs):
839-
"""Add a FDB entry"""
858+
"""Append a FDB entry"""
840859
_command_bridge_fdb('append', mac, device, dst_ip=dst_ip,
841860
namespace=namespace, **kwargs)
842861

843862

844863
@privileged.default.entrypoint
845864
def replace_bridge_fdb(mac, device, dst_ip=None, namespace=None, **kwargs):
846-
"""Add a FDB entry"""
865+
"""Replace a FDB entry"""
847866
_command_bridge_fdb('replace', mac, device, dst_ip=dst_ip,
848867
namespace=namespace, **kwargs)
849868

850869

851870
@privileged.default.entrypoint
852871
def delete_bridge_fdb(mac, device, dst_ip=None, namespace=None, **kwargs):
853-
"""Add a FDB entry"""
872+
"""Delete a FDB entry"""
854873
_command_bridge_fdb('del', mac, device, dst_ip=dst_ip,
855874
namespace=namespace, **kwargs)

neutron/tests/functional/agent/linux/test_bridge_lib.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,13 @@ def test_add_delete(self):
211211
namespace=self.namespace)
212212
self._assert_mac(self.MAC1, self.device, present=False)
213213

214+
try:
215+
# This should not raise for a non-existent entry
216+
bridge_lib.FdbInterface.delete(self.MAC1, self.device,
217+
namespace=self.namespace)
218+
except Exception:
219+
self.fail('Delete FDB entry threw unexpected exception')
220+
214221
def test_add_delete_dst(self):
215222
self._assert_mac(self.MAC1, self.device_vxlan, present=False)
216223
bridge_lib.FdbInterface.add(

neutron/tests/functional/privileged/agent/linux/test_ip_lib.py

Lines changed: 52 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -239,19 +239,34 @@ def test_get_devices_info_veth_same_namespaces(self):
239239
self.assertEqual(veth1_2['index'], veth1_1_link)
240240

241241

242-
class ListIpRulesTestCase(functional_base.BaseSudoTestCase):
243-
244-
RULE_TABLES = {'default': 253, 'main': 254, 'local': 255}
242+
class BaseIpRuleTestCase(functional_base.BaseSudoTestCase):
245243

246244
def setUp(self):
247-
super(ListIpRulesTestCase, self).setUp()
245+
super().setUp()
248246
self.namespace = 'ns_test-' + uuidutils.generate_uuid()
249247
self.ns = priv_ip_lib.create_netns(self.namespace)
250248
self.addCleanup(self._remove_ns)
251249

252250
def _remove_ns(self):
253251
priv_ip_lib.remove_netns(self.namespace)
254252

253+
def _check_rules(self, rules, parameters, values, exception_string=None,
254+
raise_exception=True):
255+
for rule in rules:
256+
if all(rule.get(parameter) == value
257+
for parameter, value in zip(parameters, values)):
258+
return True
259+
else:
260+
if raise_exception:
261+
self.fail('Rule with %s was expected' % exception_string)
262+
else:
263+
return False
264+
265+
266+
class ListIpRulesTestCase(BaseIpRuleTestCase):
267+
268+
RULE_TABLES = {'default': 253, 'main': 254, 'local': 255}
269+
255270
def test_list_default_rules_ipv4(self):
256271
rules_ipv4 = priv_ip_lib.list_ip_rules(self.namespace, 4)
257272
self.assertEqual(3, len(rules_ipv4))
@@ -291,28 +306,7 @@ def test_list_rules_ipv6(self):
291306
self.fail('Rule added (2001:db8::1/64, table 20) not found')
292307

293308

294-
class RuleTestCase(functional_base.BaseSudoTestCase):
295-
296-
def setUp(self):
297-
super(RuleTestCase, self).setUp()
298-
self.namespace = 'ns_test-' + uuidutils.generate_uuid()
299-
self.ns = priv_ip_lib.create_netns(self.namespace)
300-
self.addCleanup(self._remove_ns)
301-
302-
def _remove_ns(self):
303-
priv_ip_lib.remove_netns(self.namespace)
304-
305-
def _check_rules(self, rules, parameters, values, exception_string=None,
306-
raise_exception=True):
307-
for rule in rules:
308-
if all(rule.get(parameter) == value
309-
for parameter, value in zip(parameters, values)):
310-
return True
311-
else:
312-
if raise_exception:
313-
self.fail('Rule with %s was expected' % exception_string)
314-
else:
315-
return False
309+
class AddIpRulesTestCase(BaseIpRuleTestCase):
316310

317311
def test_add_rule_ip(self):
318312
ip_addresses = ['192.168.200.250', '2001::250']
@@ -439,6 +433,23 @@ def test_add_rule_exists(self):
439433
self.assertEqual(4, len(rules))
440434

441435

436+
class DeleteIpRulesTestCase(BaseIpRuleTestCase):
437+
438+
def test_delete_rule_no_entry(self):
439+
iif = 'iif_device'
440+
priv_ip_lib.create_interface(iif, self.namespace, 'dummy')
441+
442+
try:
443+
# This should not raise for a non-existent entry
444+
priv_ip_lib.delete_ip_rule(self.namespace, iifname=iif)
445+
except Exception:
446+
self.fail('Delete IP rule threw unexpected exception')
447+
448+
rules = ip_lib.list_ip_rules(self.namespace, 4)
449+
# There are always 3 rules by default
450+
self.assertEqual(3, len(rules))
451+
452+
442453
class GetIpAddressesTestCase(functional_base.BaseSudoTestCase):
443454

444455
def _remove_ns(self, namespace):
@@ -665,6 +676,21 @@ def test_add_multipath_route(self):
665676
n_cons.IP_VERSION_4, via=multipath)
666677
self._check_routes(['192.168.0.0/24'], gateway=multipath)
667678

679+
def test_delete_route_no_entry(self):
680+
cidr = '192.168.0.0/24'
681+
self.device.addr.add('10.1.0.1/24')
682+
try:
683+
# This should not raise for a non-existent entry
684+
priv_ip_lib.delete_ip_route(self.namespace, cidr,
685+
n_cons.IP_VERSION_4,
686+
device=self.device_name)
687+
except Exception:
688+
self.fail('Delete IP route threw unexpected exception')
689+
690+
routes = ip_lib.list_ip_routes(self.namespace, n_cons.IP_VERSION_4)
691+
# There will be a single interface route since we added an IP
692+
self.assertEqual(1, len(routes))
693+
668694

669695
class GetLinkAttributesTestCase(functional_base.BaseSudoTestCase):
670696

neutron/tests/unit/privileged/agent/linux/test_ip_lib.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,17 @@ def test_run_iproute_neigh_namespace_not_exists(self):
157157
priv_lib._run_iproute_neigh,
158158
"test_cmd", "eth0", None, test_param="test_value")
159159

160+
def test_run_iproute_neigh_no_entry(self):
161+
with mock.patch.object(pyroute2, "IPRoute") as iproute_mock:
162+
iproute_mock.side_effect = netlink_exceptions.NetlinkError(
163+
code=errno.ENOENT)
164+
try:
165+
priv_lib._run_iproute_neigh(
166+
"test_cmd", "eth0", None, test_param="test_value")
167+
self.fail("NetlinkError exception not raised")
168+
except netlink_exceptions.NetlinkError as e:
169+
self.assertEqual(errno.ENOENT, e.code)
170+
160171
def test_run_iproute_neigh_error(self):
161172
with mock.patch.object(pyroute2, "IPRoute") as iproute_mock:
162173
iproute_mock.side_effect = OSError(

0 commit comments

Comments
 (0)