Skip to content

Commit 2f7ecb9

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 Change-Id: I083649ab1b9a9057ee276a7f3ba069eb667db870 Closes-bug: #2030804 (cherry picked from commit 16875b5)
1 parent bef1f7a commit 2f7ecb9

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
@@ -474,6 +474,10 @@ def delete_neigh_entry(ip_version, ip_address, mac_address, device, namespace,
474474
if e.code == errno.ENOENT:
475475
return
476476
raise
477+
except OSError as e:
478+
if e.errno == errno.ENOENT:
479+
raise NetworkNamespaceNotFound(netns_name=namespace)
480+
raise
477481

478482

479483
@tenacity.retry(
@@ -683,6 +687,11 @@ def delete_ip_rule(namespace, **kwargs):
683687
try:
684688
with get_iproute(namespace) as ip:
685689
ip.rule('del', **kwargs)
690+
except netlink_exceptions.NetlinkError as e:
691+
# trying to delete a non-existent entry shouldn't raise an error
692+
if e.code == errno.ENOENT:
693+
return
694+
raise
686695
except OSError as e:
687696
if e.errno == errno.ENOENT:
688697
raise NetworkNamespaceNotFound(netns_name=namespace)
@@ -785,6 +794,11 @@ def delete_ip_route(namespace, cidr, ip_version, device=None, via=None,
785794
try:
786795
with get_iproute(namespace) as ip:
787796
ip.route('del', **kwargs)
797+
except netlink_exceptions.NetlinkError as e:
798+
# trying to delete a non-existent entry shouldn't raise an error
799+
if e.code == errno.ESRCH:
800+
return
801+
raise
788802
except OSError as e:
789803
if e.errno == errno.ENOENT:
790804
raise NetworkNamespaceNotFound(netns_name=namespace)
@@ -819,6 +833,11 @@ def _command_bridge_fdb(command, mac, device, dst_ip=None, namespace=None,
819833
kwargs['dst'] = dst_ip
820834
with get_iproute(namespace) as ip:
821835
return make_serializable(ip.fdb(command, **kwargs))
836+
except netlink_exceptions.NetlinkError as e:
837+
# trying to delete a non-existent entry shouldn't raise an error
838+
if command == 'del' and e.code == errno.ENOENT:
839+
return
840+
raise
822841
except OSError as e:
823842
if e.errno == errno.ENOENT:
824843
raise NetworkNamespaceNotFound(netns_name=namespace)
@@ -834,20 +853,20 @@ def add_bridge_fdb(mac, device, dst_ip=None, namespace=None, **kwargs):
834853

835854
@privileged.default.entrypoint
836855
def append_bridge_fdb(mac, device, dst_ip=None, namespace=None, **kwargs):
837-
"""Add a FDB entry"""
856+
"""Append a FDB entry"""
838857
return _command_bridge_fdb('append', mac, device, dst_ip=dst_ip,
839858
namespace=namespace, **kwargs)
840859

841860

842861
@privileged.default.entrypoint
843862
def replace_bridge_fdb(mac, device, dst_ip=None, namespace=None, **kwargs):
844-
"""Add a FDB entry"""
863+
"""Replace a FDB entry"""
845864
return _command_bridge_fdb('replace', mac, device, dst_ip=dst_ip,
846865
namespace=namespace, **kwargs)
847866

848867

849868
@privileged.default.entrypoint
850869
def delete_bridge_fdb(mac, device, dst_ip=None, namespace=None, **kwargs):
851-
"""Add a FDB entry"""
870+
"""Delete a FDB entry"""
852871
return _command_bridge_fdb('del', mac, device, dst_ip=dst_ip,
853872
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']
@@ -433,6 +427,23 @@ def test_add_rule_exists(self):
433427
self.assertEqual(4, len(rules))
434428

435429

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

438449
def _remove_ns(self, namespace):
@@ -659,6 +670,21 @@ def test_add_multipath_route(self):
659670
n_cons.IP_VERSION_4, via=multipath)
660671
self._check_routes(['192.168.0.0/24'], gateway=multipath)
661672

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

663689
class GetLinkAttributesTestCase(functional_base.BaseSudoTestCase):
664690

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)