Skip to content

Commit 1c8723d

Browse files
authored
Merge pull request #113 from stackhpc/upstream/2023.1-2024-01-22
Synchronise 2023.1 with upstream
2 parents d294a8f + 5033488 commit 1c8723d

File tree

12 files changed

+156
-61
lines changed

12 files changed

+156
-61
lines changed

neutron/agent/linux/interface.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ def _add_device_to_namespace(self, ip_wrapper, device, namespace):
365365
namespace_obj = ip_wrapper.ensure_namespace(namespace)
366366
for i in range(9):
367367
try:
368-
namespace_obj.add_device_to_namespace(device)
368+
namespace_obj.add_device_to_namespace(device, is_ovs_port=True)
369369
break
370370
except ip_lib.NetworkInterfaceNotFound:
371371
# NOTE(slaweq): if the exception was NetworkInterfaceNotFound

neutron/agent/linux/ip_lib.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,22 @@ def ensure_namespace(self, name):
259259
return ip
260260

261261
def namespace_is_empty(self):
262-
return not self.get_devices()
262+
try:
263+
return not self.get_devices()
264+
except OSError as e:
265+
# This can happen if we previously got terminated in the middle of
266+
# removing this namespace. In this case the bind mount of the
267+
# namespace under /var/run/netns will be removed, but the namespace
268+
# file is still there. As the bind mount is gone we can no longer
269+
# access the namespace to validate that it is empty. But since it
270+
# should have already been removed we are sure that the check has
271+
# passed the last time and since the namespace is unuseable that
272+
# can not have changed.
273+
# Future calls to pyroute2 to remove that namespace will clean up
274+
# the leftover file.
275+
if e.errno == errno.EINVAL:
276+
return True
277+
raise e
263278

264279
def garbage_collect_namespace(self):
265280
"""Conditionally destroy the namespace if it is empty."""
@@ -269,9 +284,9 @@ def garbage_collect_namespace(self):
269284
return True
270285
return False
271286

272-
def add_device_to_namespace(self, device):
287+
def add_device_to_namespace(self, device, is_ovs_port=False):
273288
if self.namespace:
274-
device.link.set_netns(self.namespace)
289+
device.link.set_netns(self.namespace, is_ovs_port=is_ovs_port)
275290

276291
def add_vlan(self, name, physical_interface, vlan_id):
277292
privileged.create_interface(name,
@@ -461,10 +476,15 @@ def set_down(self):
461476
privileged.set_link_attribute(
462477
self.name, self._parent.namespace, state='down')
463478

464-
def set_netns(self, namespace):
479+
def set_netns(self, namespace, is_ovs_port=False):
465480
privileged.set_link_attribute(
466481
self.name, self._parent.namespace, net_ns_fd=namespace)
467482
self._parent.namespace = namespace
483+
if is_ovs_port:
484+
# NOTE(slaweq): because of the "shy port" which may dissapear for
485+
# short time after it's moved to the namespace we need to wait
486+
# a bit before checking if port really exists in the namespace
487+
time.sleep(1)
468488
common_utils.wait_until_true(lambda: self.exists, timeout=5,
469489
sleep=0.5)
470490

neutron/agent/ovn/metadata/agent.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,10 @@ def sync(self, provision=True):
430430
ns.startswith(NS_PREFIX) and
431431
ns not in metadata_namespaces]
432432
for ns in unused_namespaces:
433-
self.teardown_datapath(self._get_datapath_name(ns))
433+
try:
434+
self.teardown_datapath(self._get_datapath_name(ns))
435+
except Exception:
436+
LOG.exception('Error unable to destroy namespace: %s', ns)
434437

435438
# resync all network namespaces based on the associated datapaths,
436439
# even those that are already running. This is to make sure

neutron/db/db_base_plugin_v2.py

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -643,32 +643,39 @@ def _validate_subnet(self, context, s, cur_subnet=None):
643643
"supported if enable_dhcp is True.")
644644
raise exc.InvalidInput(error_message=error_message)
645645

646-
if validators.is_attr_set(s.get('gateway_ip')):
647-
self._validate_ip_version(ip_ver, s['gateway_ip'], 'gateway_ip')
648-
if has_cidr:
649-
is_gateway_not_valid = (
650-
ipam.utils.check_gateway_invalid_in_subnet(
651-
s['cidr'], s['gateway_ip']))
652-
if is_gateway_not_valid:
653-
error_message = _("Gateway is not valid on subnet")
654-
raise exc.InvalidInput(error_message=error_message)
655-
# Ensure the gateway IP is not assigned to any port
656-
# skip this check in case of create (s parameter won't have id)
646+
gateway_ip = s.get('gateway_ip', constants.ATTR_NOT_SPECIFIED)
647+
if validators.is_attr_set(gateway_ip) or gateway_ip is None:
648+
# Validate the gateway IP, if defined in the request.
649+
if s['gateway_ip']:
650+
self._validate_ip_version(ip_ver, gateway_ip, 'gateway_ip')
651+
if has_cidr:
652+
is_gateway_not_valid = (
653+
ipam.utils.check_gateway_invalid_in_subnet(
654+
s['cidr'], gateway_ip))
655+
if is_gateway_not_valid:
656+
error_message = _("Gateway is not valid on subnet")
657+
raise exc.InvalidInput(error_message=error_message)
658+
659+
# Ensure the current subnet gateway IP is not assigned to any port.
660+
# The subnet gateway IP cannot be modified or removed if in use
661+
# (assigned to a router interface).
662+
# Skip this check in case of create (s parameter won't have id).
657663
# NOTE(salv-orlando): There is slight chance of a race, when
658664
# a subnet-update and a router-interface-add operation are
659665
# executed concurrently
660-
s_gateway_ip = netaddr.IPAddress(s['gateway_ip'])
666+
s_gateway_ip = (netaddr.IPAddress(gateway_ip) if gateway_ip else
667+
None)
661668
if (cur_subnet and
662669
s_gateway_ip != cur_subnet['gateway_ip'] and
663670
not ipv6_utils.is_ipv6_pd_enabled(s)):
664-
gateway_ip = str(cur_subnet['gateway_ip'])
671+
current_gateway_ip = str(cur_subnet['gateway_ip'])
665672
alloc = port_obj.IPAllocation.get_alloc_routerports(
666-
context, cur_subnet['id'], gateway_ip=gateway_ip,
673+
context, cur_subnet['id'], gateway_ip=current_gateway_ip,
667674
first=True)
668675

669676
if alloc and alloc.port_id:
670677
raise exc.GatewayIpInUse(
671-
ip_address=gateway_ip,
678+
ip_address=current_gateway_ip,
672679
port_id=alloc.port_id)
673680

674681
if validators.is_attr_set(s.get('dns_nameservers')):

neutron/objects/router.py

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010
# License for the specific language governing permissions and limitations
1111
# under the License.
1212

13-
import itertools
14-
1513
import netaddr
1614

1715
from neutron_lib.api.definitions import availability_zone as az_def
@@ -409,20 +407,11 @@ def get_scoped_floating_ips(cls, context, router_ids):
409407

410408
# Filter out on router_ids
411409
query = query.filter(l3.FloatingIP.router_id.in_(router_ids))
412-
return cls._unique_floatingip_iterator(context, query)
413410

414-
@classmethod
415-
def _unique_floatingip_iterator(cls, context, query):
416-
"""Iterates over only one row per floating ip. Ignores others."""
417-
# Group rows by fip id. They must be sorted by same.
418-
q = query.order_by(l3.FloatingIP.id)
419-
keyfunc = lambda row: row[0]['id']
420-
group_iterator = itertools.groupby(q, keyfunc)
421-
422-
# Just hit the first row of each group
423-
for key, value in group_iterator:
424-
# pylint: disable=stop-iteration-return
425-
row = list(next(value))
411+
# Remove duplicate rows based on FIP IDs
412+
query = query.group_by(l3.FloatingIP.id)
413+
414+
for row in query:
426415
yield (cls._load_object(context, row[0]), row[1])
427416

428417
@classmethod

neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovsdb_monitor.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,8 @@ def notify(self, event, row, updates=None):
670670
class BaseOvnSbIdl(Ml2OvnIdlBase):
671671
@classmethod
672672
def from_server(cls, connection_string, helper):
673+
if 'Chassis_Private' in helper.schema_json['tables']:
674+
helper.register_table('Chassis_Private')
673675
helper.register_table('Chassis')
674676
helper.register_table('Encap')
675677
helper.register_table('Port_Binding')

neutron/tests/unit/agent/linux/test_interface.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -470,11 +470,11 @@ def device_exists(dev, namespace=None):
470470
expected.extend(
471471
[mock.call().ensure_namespace(namespace),
472472
mock.call().ensure_namespace().add_device_to_namespace(
473-
mock.ANY),
473+
mock.ANY, is_ovs_port=True),
474474
mock.call().ensure_namespace().add_device_to_namespace(
475-
mock.ANY),
475+
mock.ANY, is_ovs_port=True),
476476
mock.call().ensure_namespace().add_device_to_namespace(
477-
mock.ANY)])
477+
mock.ANY, is_ovs_port=True)])
478478
expected.extend([
479479
mock.call(namespace=namespace),
480480
mock.call().device('tap0'),

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,21 @@ def test_garbage_collect_namespace_existing_not_empty(self):
357357
self.assertNotIn(mock.call().delete('ns'),
358358
ip_ns_cmd_cls.mock_calls)
359359

360+
def test_garbage_collect_namespace_existing_broken(self):
361+
with mock.patch.object(ip_lib, 'IpNetnsCommand') as ip_ns_cmd_cls:
362+
ip_ns_cmd_cls.return_value.exists.return_value = True
363+
364+
ip = ip_lib.IPWrapper(namespace='ns')
365+
366+
with mock.patch.object(ip, 'get_devices',
367+
side_effect=OSError(errno.EINVAL, None)
368+
) as mock_get_devices:
369+
self.assertTrue(ip.garbage_collect_namespace())
370+
371+
mock_get_devices.assert_called_once_with()
372+
expected = [mock.call().delete('ns')]
373+
ip_ns_cmd_cls.assert_has_calls(expected)
374+
360375
@mock.patch.object(priv_lib, 'create_interface')
361376
def test_add_vlan(self, create):
362377
retval = ip_lib.IPWrapper().add_vlan('eth0.1', 'eth0', '1')
@@ -507,7 +522,8 @@ def fake_create_interface(ifname, namespace, kind, **kwargs):
507522
def test_add_device_to_namespace(self):
508523
dev = mock.Mock()
509524
ip_lib.IPWrapper(namespace='ns').add_device_to_namespace(dev)
510-
dev.assert_has_calls([mock.call.link.set_netns('ns')])
525+
dev.assert_has_calls(
526+
[mock.call.link.set_netns('ns', is_ovs_port=False)])
511527

512528
def test_add_device_to_namespace_is_none(self):
513529
dev = mock.Mock()

neutron/tests/unit/agent/ovn/metadata/test_agent.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,31 @@ def test_sync_teardown_namespace(self):
134134
lnn.assert_called_once_with()
135135
tdp.assert_called_once_with('3')
136136

137+
def test_sync_teardown_namespace_does_not_crash_on_error(self):
138+
"""Test that sync tears down unneeded metadata namespaces.
139+
Even if that fails it continues to provision other datapaths
140+
"""
141+
with mock.patch.object(
142+
self.agent, 'provision_datapath') as pdp,\
143+
mock.patch.object(
144+
ip_lib, 'list_network_namespaces',
145+
return_value=['ovnmeta-1', 'ovnmeta-2', 'ovnmeta-3',
146+
'ns1', 'ns2']) as lnn,\
147+
mock.patch.object(
148+
self.agent, 'teardown_datapath',
149+
side_effect=Exception()) as tdp:
150+
self.agent.sync()
151+
152+
pdp.assert_has_calls(
153+
[
154+
mock.call(p.datapath)
155+
for p in self.ports
156+
],
157+
any_order=True
158+
)
159+
lnn.assert_called_once_with()
160+
tdp.assert_called_once_with('3')
161+
137162
def test_get_networks_datapaths(self):
138163
"""Test get_networks_datapaths returns only datapath objects for the
139164
networks containing vif ports of type ''(blank) and 'external'.

neutron/tests/unit/db/test_l3_db.py

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -252,27 +252,6 @@ def test__make_floatingip_dict_with_scope(self, make_fip_dict):
252252
'fixed_ip_address_scope': mock.sentinel.address_scope_id,
253253
'id': mock.sentinel.fip_ip}, result)
254254

255-
def test__unique_floatingip_iterator(self):
256-
context = mock.MagicMock()
257-
query = mock.MagicMock()
258-
query.order_by().__iter__.return_value = [
259-
({'id': 'id1'}, 'scope1'),
260-
({'id': 'id1'}, 'scope1'),
261-
({'id': 'id2'}, 'scope2'),
262-
({'id': 'id2'}, 'scope2'),
263-
({'id': 'id2'}, 'scope2'),
264-
({'id': 'id3'}, 'scope3')]
265-
query.reset_mock()
266-
with mock.patch.object(
267-
l3_obj.FloatingIP, '_load_object',
268-
side_effect=({'id': 'id1'}, {'id': 'id2'}, {'id': 'id3'})):
269-
result = list(
270-
l3_obj.FloatingIP._unique_floatingip_iterator(context, query))
271-
query.order_by.assert_called_once_with(l3_models.FloatingIP.id)
272-
self.assertEqual([({'id': 'id1'}, 'scope1'),
273-
({'id': 'id2'}, 'scope2'),
274-
({'id': 'id3'}, 'scope3')], result)
275-
276255
@mock.patch.object(directory, 'get_plugin')
277256
def test_prevent_l3_port_deletion_port_not_found(self, gp):
278257
# port not found doesn't prevent

0 commit comments

Comments
 (0)