Skip to content

Commit 87f7b9a

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. Change-Id: I083649ab1b9a9057ee276a7f3ba069eb667db870 Closes-bug: #2030804 (cherry picked from commit 16875b5)
1 parent d25fcad commit 87f7b9a

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
@@ -525,6 +525,10 @@ def delete_neigh_entry(ip_version, ip_address, mac_address, device, namespace,
525525
if e.code == errno.ENOENT:
526526
return
527527
raise
528+
except OSError as e:
529+
if e.errno == errno.ENOENT:
530+
raise NetworkNamespaceNotFound(netns_name=namespace)
531+
raise
528532

529533

530534
@tenacity.retry(
@@ -712,6 +716,11 @@ def delete_ip_rule(namespace, **kwargs):
712716
try:
713717
with get_iproute(namespace) as ip:
714718
ip.rule('del', **kwargs)
719+
except netlink_exceptions.NetlinkError as e:
720+
# trying to delete a non-existent entry shouldn't raise an error
721+
if e.code == errno.ENOENT:
722+
return
723+
raise
715724
except OSError as e:
716725
if e.errno == errno.ENOENT:
717726
raise NetworkNamespaceNotFound(netns_name=namespace)
@@ -817,6 +826,11 @@ def delete_ip_route(namespace, cidr, ip_version, device=None, via=None,
817826
try:
818827
with get_iproute(namespace) as ip:
819828
ip.route('del', **kwargs)
829+
except netlink_exceptions.NetlinkError as e:
830+
# trying to delete a non-existent entry shouldn't raise an error
831+
if e.code == errno.ESRCH:
832+
return
833+
raise
820834
except OSError as e:
821835
if e.errno == errno.ENOENT:
822836
raise NetworkNamespaceNotFound(netns_name=namespace)
@@ -851,6 +865,11 @@ def _command_bridge_fdb(command, mac, device, dst_ip=None, namespace=None,
851865
kwargs['dst'] = dst_ip
852866
with get_iproute(namespace) as ip:
853867
return priv_linux.make_serializable(ip.fdb(command, **kwargs))
868+
except netlink_exceptions.NetlinkError as e:
869+
# trying to delete a non-existent entry shouldn't raise an error
870+
if command == 'del' and e.code == errno.ENOENT:
871+
return
872+
raise
854873
except OSError as e:
855874
if e.errno == errno.ENOENT:
856875
raise NetworkNamespaceNotFound(netns_name=namespace)
@@ -866,20 +885,20 @@ def add_bridge_fdb(mac, device, dst_ip=None, namespace=None, **kwargs):
866885

867886
@privileged.default.entrypoint
868887
def append_bridge_fdb(mac, device, dst_ip=None, namespace=None, **kwargs):
869-
"""Add a FDB entry"""
888+
"""Append a FDB entry"""
870889
_command_bridge_fdb('append', mac, device, dst_ip=dst_ip,
871890
namespace=namespace, **kwargs)
872891

873892

874893
@privileged.default.entrypoint
875894
def replace_bridge_fdb(mac, device, dst_ip=None, namespace=None, **kwargs):
876-
"""Add a FDB entry"""
895+
"""Replace a FDB entry"""
877896
_command_bridge_fdb('replace', mac, device, dst_ip=dst_ip,
878897
namespace=namespace, **kwargs)
879898

880899

881900
@privileged.default.entrypoint
882901
def delete_bridge_fdb(mac, device, dst_ip=None, namespace=None, **kwargs):
883-
"""Add a FDB entry"""
902+
"""Delete a FDB entry"""
884903
_command_bridge_fdb('del', mac, device, dst_ip=dst_ip,
885904
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
@@ -260,19 +260,34 @@ def test_get_link_devices_using_index(self):
260260
index=10000)
261261

262262

263-
class ListIpRulesTestCase(functional_base.BaseSudoTestCase):
264-
265-
RULE_TABLES = {'default': 253, 'main': 254, 'local': 255}
263+
class BaseIpRuleTestCase(functional_base.BaseSudoTestCase):
266264

267265
def setUp(self):
268-
super(ListIpRulesTestCase, self).setUp()
266+
super().setUp()
269267
self.namespace = 'ns_test-' + uuidutils.generate_uuid()
270268
self.ns = priv_ip_lib.create_netns(self.namespace)
271269
self.addCleanup(self._remove_ns)
272270

273271
def _remove_ns(self):
274272
priv_ip_lib.remove_netns(self.namespace)
275273

274+
def _check_rules(self, rules, parameters, values, exception_string=None,
275+
raise_exception=True):
276+
for rule in rules:
277+
if all(rule.get(parameter) == value
278+
for parameter, value in zip(parameters, values)):
279+
return True
280+
else:
281+
if raise_exception:
282+
self.fail('Rule with %s was expected' % exception_string)
283+
else:
284+
return False
285+
286+
287+
class ListIpRulesTestCase(BaseIpRuleTestCase):
288+
289+
RULE_TABLES = {'default': 253, 'main': 254, 'local': 255}
290+
276291
def test_list_default_rules_ipv4(self):
277292
rules_ipv4 = priv_ip_lib.list_ip_rules(self.namespace, 4)
278293
self.assertEqual(3, len(rules_ipv4))
@@ -312,28 +327,7 @@ def test_list_rules_ipv6(self):
312327
self.fail('Rule added (2001:db8::1/64, table 20) not found')
313328

314329

315-
class RuleTestCase(functional_base.BaseSudoTestCase):
316-
317-
def setUp(self):
318-
super(RuleTestCase, self).setUp()
319-
self.namespace = 'ns_test-' + uuidutils.generate_uuid()
320-
self.ns = priv_ip_lib.create_netns(self.namespace)
321-
self.addCleanup(self._remove_ns)
322-
323-
def _remove_ns(self):
324-
priv_ip_lib.remove_netns(self.namespace)
325-
326-
def _check_rules(self, rules, parameters, values, exception_string=None,
327-
raise_exception=True):
328-
for rule in rules:
329-
if all(rule.get(parameter) == value
330-
for parameter, value in zip(parameters, values)):
331-
return True
332-
else:
333-
if raise_exception:
334-
self.fail('Rule with %s was expected' % exception_string)
335-
else:
336-
return False
330+
class AddIpRulesTestCase(BaseIpRuleTestCase):
337331

338332
def test_add_rule_ip(self):
339333
ip_addresses = ['192.168.200.250', '2001::250']
@@ -460,6 +454,23 @@ def test_add_rule_exists(self):
460454
self.assertEqual(4, len(rules))
461455

462456

457+
class DeleteIpRulesTestCase(BaseIpRuleTestCase):
458+
459+
def test_delete_rule_no_entry(self):
460+
iif = 'iif_device'
461+
priv_ip_lib.create_interface(iif, self.namespace, 'dummy')
462+
463+
try:
464+
# This should not raise for a non-existent entry
465+
priv_ip_lib.delete_ip_rule(self.namespace, iifname=iif)
466+
except Exception:
467+
self.fail('Delete IP rule threw unexpected exception')
468+
469+
rules = ip_lib.list_ip_rules(self.namespace, 4)
470+
# There are always 3 rules by default
471+
self.assertEqual(3, len(rules))
472+
473+
463474
class GetIpAddressesTestCase(functional_base.BaseSudoTestCase):
464475

465476
def _remove_ns(self, namespace):
@@ -687,6 +698,21 @@ def test_add_multipath_route(self):
687698
n_cons.IP_VERSION_4, via=multipath)
688699
self._check_routes(['192.168.0.0/24'], gateway=multipath)
689700

701+
def test_delete_route_no_entry(self):
702+
cidr = '192.168.0.0/24'
703+
self.device.addr.add('10.1.0.1/24')
704+
try:
705+
# This should not raise for a non-existent entry
706+
priv_ip_lib.delete_ip_route(self.namespace, cidr,
707+
n_cons.IP_VERSION_4,
708+
device=self.device_name)
709+
except Exception:
710+
self.fail('Delete IP route threw unexpected exception')
711+
712+
routes = ip_lib.list_ip_routes(self.namespace, n_cons.IP_VERSION_4)
713+
# There will be a single interface route since we added an IP
714+
self.assertEqual(1, len(routes))
715+
690716

691717
class GetLinkAttributesTestCase(functional_base.BaseSudoTestCase):
692718

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

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

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

0 commit comments

Comments
 (0)