Skip to content

Commit b34ea94

Browse files
authored
Merge pull request #61 from stackhpc/upstream/yoga-2023-08-14
Synchronise yoga with upstream
2 parents ef4c4ec + 3944dff commit b34ea94

File tree

14 files changed

+132
-39
lines changed

14 files changed

+132
-39
lines changed

neutron/cmd/ovn/neutron_ovn_db_sync_util.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,12 @@ def post_fork_initialize(self, resource, event, trigger, **kwargs):
5757
def ovn_client(self):
5858
return self._ovn_client
5959

60-
def _set_hash_ring_nodes_offline(self):
61-
"""Don't set hash ring nodes as offline.
60+
def _remove_node_from_hash_ring(self):
61+
"""Don't remove the node from the Hash Ring.
6262
6363
If this method was not overridden, cleanup would be performed when
64-
calling the db sync and running neutron server would mark all the
65-
nodes from the ring as offline.
64+
calling the db sync and running neutron server would remove the
65+
nodes from the Hash Ring.
6666
"""
6767

6868
# Since we are not using the ovn mechanism driver while syncing,

neutron/common/utils.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,12 @@
3535
from eventlet.green import subprocess
3636
import netaddr
3737
from neutron_lib.api.definitions import availability_zone as az_def
38+
from neutron_lib.api.definitions import portbindings
39+
from neutron_lib.api.definitions import portbindings_extended
3840
from neutron_lib import constants as n_const
3941
from neutron_lib import context as n_context
4042
from neutron_lib.db import api as db_api
43+
from neutron_lib.plugins import utils as plugin_utils
4144
from neutron_lib.services.trunk import constants as trunk_constants
4245
from neutron_lib.utils import helpers
4346
from oslo_config import cfg
@@ -1046,3 +1049,16 @@ def is_session_active(session):
10461049
if not (session.dirty or session.deleted or session.new):
10471050
return False
10481051
return True
1052+
1053+
1054+
# TODO(slaweq): this should be moved to neutron_lib.plugins.utils module
1055+
def is_port_bound(port, log_message=True):
1056+
active_binding = plugin_utils.get_port_binding_by_status_and_host(
1057+
port.get('port_bindings', []), n_const.ACTIVE)
1058+
if not active_binding:
1059+
if log_message:
1060+
LOG.warning('Binding for port %s was not found.', port)
1061+
return False
1062+
return active_binding[portbindings_extended.VIF_TYPE] not in (
1063+
portbindings.VIF_TYPE_UNBOUND,
1064+
portbindings.VIF_TYPE_BINDING_FAILED)

neutron/db/l3_dvr_db.py

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
from neutron_lib.api.definitions import external_net as extnet_apidef
1818
from neutron_lib.api.definitions import l3 as l3_apidef
1919
from neutron_lib.api.definitions import portbindings
20-
from neutron_lib.api.definitions import portbindings_extended
2120
from neutron_lib.api.definitions import router_admin_state_down_before_update
2221
from neutron_lib.api import validators
2322
from neutron_lib.callbacks import events
@@ -70,18 +69,6 @@ def is_admin_state_down_necessary():
7069
return _IS_ADMIN_STATE_DOWN_NECESSARY
7170

7271

73-
# TODO(slaweq): this should be moved to neutron_lib.plugins.utils module
74-
def is_port_bound(port):
75-
active_binding = plugin_utils.get_port_binding_by_status_and_host(
76-
port.get("port_bindings", []), const.ACTIVE)
77-
if not active_binding:
78-
LOG.warning("Binding for port %s was not found.", port)
79-
return False
80-
return active_binding[portbindings_extended.VIF_TYPE] not in [
81-
portbindings.VIF_TYPE_UNBOUND,
82-
portbindings.VIF_TYPE_BINDING_FAILED]
83-
84-
8572
@registry.has_registry_receivers
8673
class DVRResourceOperationHandler(object):
8774
"""Contains callbacks for DVR operations.
@@ -1426,7 +1413,7 @@ def is_router_distributed(self, context, router_id):
14261413

14271414
def get_ports_under_dvr_connected_subnet(self, context, subnet_id):
14281415
query = dvr_mac_db.get_ports_query_by_subnet_and_ip(context, subnet_id)
1429-
ports = [p for p in query.all() if is_port_bound(p)]
1416+
ports = [p for p in query.all() if n_utils.is_port_bound(p)]
14301417
# TODO(slaweq): if there would be way to pass to neutron-lib only
14311418
# list of extensions which actually should be processed, than setting
14321419
# process_extensions=True below could avoid that second loop and

neutron/db/ovn_hash_ring_db.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,13 @@ def remove_nodes_from_host(context, group_name):
5050
CONF.host, group_name)
5151

5252

53+
def remove_node_by_uuid(context, node_uuid):
54+
with db_api.CONTEXT_WRITER.using(context):
55+
context.session.query(ovn_models.OVNHashRing).filter(
56+
ovn_models.OVNHashRing.node_uuid == node_uuid).delete()
57+
LOG.info('Node "%s" removed from the Hash Ring', node_uuid)
58+
59+
5360
def _touch(context, updated_at=None, **filter_args):
5461
if updated_at is None:
5562
updated_at = timeutils.utcnow()
@@ -96,7 +103,9 @@ def count_offline_nodes(context, interval, group_name):
96103
return query.count()
97104

98105

99-
def set_nodes_from_host_as_offline(context, group_name):
100-
timestamp = datetime.datetime(day=26, month=10, year=1985, hour=9)
101-
_touch(context, updated_at=timestamp, hostname=CONF.host,
102-
group_name=group_name)
106+
@db_api.CONTEXT_READER
107+
def count_nodes_from_host(context, group_name):
108+
query = context.session.query(ovn_models.OVNHashRing).filter(
109+
ovn_models.OVNHashRing.group_name == group_name,
110+
ovn_models.OVNHashRing.hostname == CONF.host)
111+
return query.count()

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

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -270,17 +270,17 @@ def subscribe(self):
270270
resources.SECURITY_GROUP_RULE,
271271
events.BEFORE_DELETE)
272272

273-
def _set_hash_ring_nodes_offline(self, *args, **kwargs):
273+
def _remove_node_from_hash_ring(self, *args, **kwargs):
274+
# The node_uuid attribute will be empty for worker types
275+
# that are not added to the Hash Ring and can be skipped
276+
if self.node_uuid is None:
277+
return
274278
admin_context = n_context.get_admin_context()
275-
ovn_hash_ring_db.set_nodes_from_host_as_offline(
276-
admin_context, self.hash_ring_group)
277-
LOG.info('Hash Ring nodes from host "%s" marked as offline',
278-
cfg.CONF.host)
279+
ovn_hash_ring_db.remove_node_by_uuid(
280+
admin_context, self.node_uuid)
279281

280282
def pre_fork_initialize(self, resource, event, trigger, payload=None):
281283
"""Pre-initialize the ML2/OVN driver."""
282-
atexit.register(self._set_hash_ring_nodes_offline)
283-
signal.signal(signal.SIGTERM, self._set_hash_ring_nodes_offline)
284284
ovn_utils.create_neutron_pg_drop()
285285

286286
@staticmethod
@@ -298,6 +298,10 @@ def _setup_hash_ring(self):
298298
thread for this host. Subsequently workers just need to register
299299
themselves to the hash ring.
300300
"""
301+
# Attempt to remove the node from the ring when the worker stops
302+
atexit.register(self._remove_node_from_hash_ring)
303+
signal.signal(signal.SIGTERM, self._remove_node_from_hash_ring)
304+
301305
admin_context = n_context.get_admin_context()
302306
if not self._hash_ring_probe_event.is_set():
303307
# Clear existing entries

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
from neutron.objects import router as router_obj
4545
from neutron.objects import ports as ports_obj
4646
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovn_db_sync
47+
from neutron import service
4748
from neutron.services.logapi.drivers.ovn import driver as log_driver
4849

4950

@@ -1123,3 +1124,20 @@ def touch_hash_ring_nodes(self):
11231124
# here because we want the maintenance tasks from each instance to
11241125
# execute this task.
11251126
hash_ring_db.touch_nodes_from_host(self.ctx, self._group)
1127+
1128+
# Check the number of the nodes in the ring and log a message in
1129+
# case they are out of sync. See LP #2024205 for more information
1130+
# on this issue.
1131+
api_workers = service._get_api_workers()
1132+
num_nodes = hash_ring_db.count_nodes_from_host(self.ctx, self._group)
1133+
1134+
if num_nodes > api_workers:
1135+
LOG.critical(
1136+
'The number of nodes in the Hash Ring (%d) is higher than '
1137+
'the number of API workers (%d) for host "%s". Something is '
1138+
'not right and OVSDB events could be missed because of this. '
1139+
'Please check the status of the Neutron processes, this can '
1140+
'happen when the API workers are killed and restarted. '
1141+
'Restarting the service should fix the issue, see LP '
1142+
'#2024205 for more information.',
1143+
num_nodes, api_workers, cfg.CONF.host)

neutron/services/trunk/drivers/ovn/trunk_driver.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@
2222
from oslo_log import log
2323

2424
from neutron.common.ovn import constants as ovn_const
25+
from neutron.common import utils as n_utils
2526
from neutron.db import db_base_plugin_common
2627
from neutron.db import ovn_revision_numbers_db as db_rev
2728
from neutron.objects import ports as port_obj
2829
from neutron.services.trunk.drivers import base as trunk_base
30+
from neutron.services.trunk import exceptions as trunk_exc
2931

3032

3133
SUPPORTED_INTERFACES = (
@@ -162,6 +164,10 @@ def _unset_binding_profile(self, context, subport, ovn_txn):
162164
LOG.debug("Done unsetting parent for subport %s", subport.port_id)
163165
return db_port
164166

167+
@staticmethod
168+
def _is_port_bound(port):
169+
return n_utils.is_port_bound(port, log_message=False)
170+
165171
def trunk_updated(self, trunk):
166172
# Check if parent port is handled by OVN.
167173
if not self.plugin_driver.nb_ovn.lookup('Logical_Switch_Port',
@@ -208,6 +214,16 @@ def trunk_event(self, resource, event, trunk_plugin, payload):
208214
self.trunk_updated(payload.states[0])
209215
elif event == events.AFTER_DELETE:
210216
self.trunk_deleted(payload.states[0])
217+
elif event == events.PRECOMMIT_CREATE:
218+
trunk = payload.desired_state
219+
parent_port = trunk.db_obj.port
220+
if self._is_port_bound(parent_port):
221+
raise trunk_exc.ParentPortInUse(port_id=parent_port.id)
222+
elif event == events.PRECOMMIT_DELETE:
223+
trunk = payload.states[0]
224+
parent_port = payload.states[1]
225+
if self._is_port_bound(parent_port):
226+
raise trunk_exc.TrunkInUse(trunk_id=trunk.id)
211227

212228
def subport_event(self, resource, event, trunk_plugin, payload):
213229
if event == events.AFTER_CREATE:
@@ -233,7 +249,8 @@ def register(self, resource, event, trigger, payload=None):
233249
resource, event, trigger, payload=payload)
234250
self._handler = OVNTrunkHandler(self.plugin_driver)
235251
for _event in (events.AFTER_CREATE, events.AFTER_UPDATE,
236-
events.AFTER_DELETE):
252+
events.AFTER_DELETE, events.PRECOMMIT_CREATE,
253+
events.PRECOMMIT_DELETE):
237254
registry.subscribe(self._handler.trunk_event,
238255
resources.TRUNK,
239256
_event)

neutron/services/trunk/plugin.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@ def delete_trunk(self, context, trunk_id):
294294
trunk = self._get_trunk(context, trunk_id)
295295
rules.trunk_can_be_managed(context, trunk)
296296
trunk_port_validator = rules.TrunkPortValidator(trunk.port_id)
297+
parent_port = trunk.db_obj.port
297298
if trunk_port_validator.can_be_trunked_or_untrunked(context):
298299
# NOTE(status_police): when a trunk is deleted, the logical
299300
# object disappears from the datastore, therefore there is no
@@ -307,7 +308,7 @@ def delete_trunk(self, context, trunk_id):
307308
'deleting trunk port %s: %s', trunk_id,
308309
str(e))
309310
payload = events.DBEventPayload(context, resource_id=trunk_id,
310-
states=(trunk,))
311+
states=(trunk, parent_port))
311312
registry.publish(resources.TRUNK, events.PRECOMMIT_DELETE,
312313
self, payload=payload)
313314
else:
@@ -317,7 +318,7 @@ def delete_trunk(self, context, trunk_id):
317318
registry.publish(resources.TRUNK, events.AFTER_DELETE, self,
318319
payload=events.DBEventPayload(
319320
context, resource_id=trunk_id,
320-
states=(trunk,)))
321+
states=(trunk, parent_port)))
321322

322323
@db_base_plugin_common.convert_result_to_dict
323324
def add_subports(self, context, trunk_id, subports):

neutron/tests/functional/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@ def trigger(self):
363363
# NOTE(ralonsoh): do not access to the DB at exit when the SQL
364364
# connection is already closed, to avoid useless exception messages.
365365
mock.patch.object(
366-
self.mech_driver, '_set_hash_ring_nodes_offline').start()
366+
self.mech_driver, '_remove_node_from_hash_ring').start()
367367
self.mech_driver.pre_fork_initialize(
368368
mock.ANY, mock.ANY, trigger_cls.trigger)
369369

neutron/tests/functional/services/trunk/drivers/ovn/test_trunk_driver.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import contextlib
1616

1717
from neutron_lib.api.definitions import portbindings
18+
from neutron_lib.callbacks import exceptions as n_exc
1819
from neutron_lib import constants as n_consts
1920
from neutron_lib.db import api as db_api
2021
from neutron_lib.plugins import utils
@@ -113,6 +114,25 @@ def test_trunk_create_with_subports(self):
113114
with self.trunk([subport]) as trunk:
114115
self._verify_trunk_info(trunk, has_items=True)
115116

117+
def test_trunk_create_parent_port_bound(self):
118+
with self.network() as network:
119+
with self.subnet(network=network) as subnet:
120+
with self.port(subnet=subnet) as parent_port:
121+
pb = port_obj.PortBinding.get_objects(
122+
self.context, port_id=parent_port['port']['id'])
123+
port_obj.PortBinding.update_object(
124+
self.context, {'vif_type': portbindings.VIF_TYPE_OVS},
125+
port_id=pb[0].port_id, host=pb[0].host)
126+
tenant_id = uuidutils.generate_uuid()
127+
trunk = {'trunk': {
128+
'port_id': parent_port['port']['id'],
129+
'tenant_id': tenant_id, 'project_id': tenant_id,
130+
'admin_state_up': True,
131+
'name': 'trunk', 'sub_ports': []}}
132+
self.assertRaises(n_exc.CallbackFailure,
133+
self.trunk_plugin.create_trunk,
134+
self.context, trunk)
135+
116136
def test_subport_add(self):
117137
with self.subport() as subport:
118138
with self.trunk() as trunk:
@@ -147,3 +167,14 @@ def test_trunk_delete(self):
147167
with self.trunk() as trunk:
148168
self.trunk_plugin.delete_trunk(self.context, trunk['id'])
149169
self._verify_trunk_info({}, has_items=False)
170+
171+
def test_trunk_delete_parent_port_bound(self):
172+
with self.trunk() as trunk:
173+
bp = port_obj.PortBinding.get_objects(
174+
self.context, port_id=trunk['port_id'])
175+
port_obj.PortBinding.update_object(
176+
self.context, {'vif_type': portbindings.VIF_TYPE_OVS},
177+
port_id=bp[0].port_id, host=bp[0].host)
178+
self.assertRaises(n_exc.CallbackFailure,
179+
self.trunk_plugin.delete_trunk,
180+
self.context, trunk['id'])

0 commit comments

Comments
 (0)