Skip to content

Commit 66ddd0b

Browse files
authored
Merge pull request #51 from stackhpc/upstream/yoga-2023-07-03
Synchronise yoga with upstream
2 parents 9bd2441 + 73ba302 commit 66ddd0b

File tree

10 files changed

+164
-37
lines changed

10 files changed

+164
-37
lines changed

neutron/common/ovn/exceptions.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class StandardAttributeIDNotFound(n_exc.NeutronException):
3333

3434

3535
class HashRingIsEmpty(n_exc.NeutronException):
36-
message = _('Hash Ring returned empty when hashing "%(key)s". '
37-
'This should never happen in a normal situation, please '
38-
'check the status of your cluster')
36+
message = _('Hash Ring returned empty when hashing "%(key)s". All '
37+
'%(node_count)d nodes were found offline. This should never '
38+
'happen in a normal situation, please check the status '
39+
'of your cluster')

neutron/common/ovn/hash_ring_manager.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ def __init__(self, group_name):
3838
# Flag to rate limit the caching log
3939
self._prev_num_nodes = -1
4040
self.admin_ctx = context.get_admin_context()
41+
self._offline_node_count = 0
4142

4243
@property
4344
def _wait_startup_before_caching(self):
@@ -92,6 +93,11 @@ def _load_hash_ring(self, refresh=False):
9293
self._hash_ring = hashring.HashRing({node.node_uuid
9394
for node in nodes})
9495
self._last_time_loaded = timeutils.utcnow()
96+
self._offline_node_count = db_hash_ring.count_offline_nodes(
97+
self.admin_ctx, constants.HASH_RING_NODES_TIMEOUT,
98+
self._group)
99+
LOG.debug("Hash Ring loaded. %d active nodes. %d offline nodes",
100+
len(nodes), self._offline_node_count)
95101

96102
def refresh(self):
97103
self._load_hash_ring(refresh=True)
@@ -108,4 +114,5 @@ def get_node(self, key):
108114
# KeyError is raised
109115
return self._hash_ring[key].pop()
110116
except KeyError:
111-
raise exceptions.HashRingIsEmpty(key=key)
117+
raise exceptions.HashRingIsEmpty(
118+
key=key, node_count=self._offline_node_count)

neutron/db/ovn_hash_ring_db.py

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@
1717

1818
from neutron_lib.db import api as db_api
1919
from oslo_config import cfg
20+
from oslo_log import log
2021
from oslo_utils import timeutils
2122
from oslo_utils import uuidutils
2223

2324
from neutron.db.models import ovn as ovn_models
2425

2526
CONF = cfg.CONF
27+
LOG = log.getLogger(__name__)
2628

2729

2830
# NOTE(ralonsoh): this was migrated from networking-ovn to neutron and should
@@ -34,6 +36,8 @@ def add_node(context, group_name, node_uuid=None):
3436
with db_api.CONTEXT_WRITER.using(context):
3537
context.session.add(ovn_models.OVNHashRing(
3638
node_uuid=node_uuid, hostname=CONF.host, group_name=group_name))
39+
LOG.info('Node %s from host "%s" and group "%s" added to the Hash Ring',
40+
node_uuid, CONF.host, group_name)
3741
return node_uuid
3842

3943

@@ -42,6 +46,8 @@ def remove_nodes_from_host(context, group_name):
4246
context.session.query(ovn_models.OVNHashRing).filter(
4347
ovn_models.OVNHashRing.hostname == CONF.host,
4448
ovn_models.OVNHashRing.group_name == group_name).delete()
49+
LOG.info('Nodes from host "%s" and group "%s" removed from the Hash Ring',
50+
CONF.host, group_name)
4551

4652

4753
def _touch(context, **filter_args):
@@ -58,12 +64,31 @@ def touch_node(context, node_uuid):
5864
_touch(context, node_uuid=node_uuid)
5965

6066

61-
def get_active_nodes(context, interval, group_name, from_host=False):
67+
def _get_nodes_query(context, interval, group_name, offline=False,
68+
from_host=False):
6269
limit = timeutils.utcnow() - datetime.timedelta(seconds=interval)
63-
with db_api.CONTEXT_READER.using(context):
64-
query = context.session.query(ovn_models.OVNHashRing).filter(
65-
ovn_models.OVNHashRing.updated_at >= limit,
66-
ovn_models.OVNHashRing.group_name == group_name)
67-
if from_host:
68-
query = query.filter_by(hostname=CONF.host)
69-
return query.all()
70+
query = context.session.query(ovn_models.OVNHashRing).filter(
71+
ovn_models.OVNHashRing.group_name == group_name)
72+
73+
if offline:
74+
query = query.filter(ovn_models.OVNHashRing.updated_at < limit)
75+
else:
76+
query = query.filter(ovn_models.OVNHashRing.updated_at >= limit)
77+
78+
if from_host:
79+
query = query.filter_by(hostname=CONF.host)
80+
81+
return query
82+
83+
84+
@db_api.CONTEXT_READER
85+
def get_active_nodes(context, interval, group_name, from_host=False):
86+
query = _get_nodes_query(context, interval, group_name,
87+
from_host=from_host)
88+
return query.all()
89+
90+
91+
@db_api.CONTEXT_READER
92+
def count_offline_nodes(context, interval, group_name):
93+
query = _get_nodes_query(context, interval, group_name, offline=True)
94+
return query.count()

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
from oslo_db import exception as os_db_exc
4141
from oslo_log import log
4242
from oslo_utils import timeutils
43+
from ovsdbapp.backend.ovs_idl import idlutils
4344

4445
from neutron._i18n import _
4546
from neutron.common.ovn import acl as ovn_acl
@@ -1322,6 +1323,14 @@ def delete_agent(self, context, id, _driver=None):
13221323
chassis_name = agent['configurations']['chassis_name']
13231324
_driver.sb_ovn.chassis_del(chassis_name, if_exists=True).execute(
13241325
check_error=True)
1326+
if _driver.sb_ovn.is_table_present('Chassis_Private'):
1327+
# TODO(ralonsoh): implement the corresponding chassis_private
1328+
# commands in ovsdbapp.
1329+
try:
1330+
_driver.sb_ovn.db_destroy('Chassis_Private', chassis_name).execute(
1331+
check_error=True)
1332+
except idlutils.RowNotFound:
1333+
pass
13251334
# Send a specific event that all API workers can get to delete the agent
13261335
# from their caches. Ideally we could send a single transaction that both
13271336
# created and deleted the key, but alas python-ovs is too "smart"

neutron/services/qos/qos_plugin.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -651,9 +651,9 @@ def _validate_create_network_callback(self, resource, event, trigger,
651651
network_id = payload.resource_id
652652
network = network_object.Network.get_object(context, id=network_id)
653653

654-
policy_id = network.qos_policy_id
655-
if policy_id is None:
654+
if not network or not getattr(network, 'qos_policy_id', None):
656655
return
656+
policy_id = network.qos_policy_id
657657

658658
policy = policy_object.QosPolicy.get_object(
659659
context.elevated(), id=policy_id)

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

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,13 @@ def _set_sub_ports(self, parent_port, subports):
4949
context = n_context.get_admin_context()
5050
db_parent_port = port_obj.Port.get_object(context, id=parent_port)
5151
parent_port_status = db_parent_port.status
52+
parent_port_bindings = db_parent_port.bindings[0]
5253
for subport in subports:
5354
with db_api.CONTEXT_WRITER.using(context), (
5455
txn(check_error=True)) as ovn_txn:
5556
port = self._set_binding_profile(context, subport, parent_port,
56-
parent_port_status, ovn_txn)
57+
parent_port_status,
58+
parent_port_bindings, ovn_txn)
5759
db_rev.bump_revision(context, port, ovn_const.TYPE_PORTS)
5860

5961
def _unset_sub_ports(self, subports):
@@ -67,7 +69,8 @@ def _unset_sub_ports(self, subports):
6769

6870
@db_base_plugin_common.convert_result_to_dict
6971
def _set_binding_profile(self, context, subport, parent_port,
70-
parent_port_status, ovn_txn):
72+
parent_port_status,
73+
parent_port_bindings, ovn_txn):
7174
LOG.debug("Setting parent %s for subport %s",
7275
parent_port, subport.port_id)
7376
db_port = port_obj.Port.get_object(context, id=subport.port_id)
@@ -79,6 +82,9 @@ def _set_binding_profile(self, context, subport, parent_port,
7982
check_rev_cmd = self.plugin_driver.nb_ovn.check_revision_number(
8083
db_port.id, db_port, ovn_const.TYPE_PORTS)
8184
ovn_txn.add(check_rev_cmd)
85+
parent_binding_host = ''
86+
if parent_port_bindings.host:
87+
parent_binding_host = parent_port_bindings.host
8288
try:
8389
# NOTE(flaviof): We expect binding's host to be set. Otherwise,
8490
# sub-port will not transition from DOWN to ACTIVE.
@@ -94,6 +100,7 @@ def _set_binding_profile(self, context, subport, parent_port,
94100
port_obj.PortBinding.update_object(
95101
context,
96102
{'profile': binding.profile,
103+
'host': parent_binding_host,
97104
'vif_type': portbindings.VIF_TYPE_OVS},
98105
port_id=subport.port_id,
99106
host=binding.host)
@@ -155,6 +162,14 @@ def _unset_binding_profile(self, context, subport, ovn_txn):
155162
LOG.debug("Done unsetting parent for subport %s", subport.port_id)
156163
return db_port
157164

165+
def trunk_updated(self, trunk):
166+
# Check if parent port is handled by OVN.
167+
if not self.plugin_driver.nb_ovn.lookup('Logical_Switch_Port',
168+
trunk.port_id, default=None):
169+
return
170+
if trunk.sub_ports:
171+
self._set_sub_ports(trunk.port_id, trunk.sub_ports)
172+
158173
def trunk_created(self, trunk):
159174
# Check if parent port is handled by OVN.
160175
if not self.plugin_driver.nb_ovn.lookup('Logical_Switch_Port',
@@ -189,6 +204,8 @@ def subports_deleted(self, trunk, subports):
189204
def trunk_event(self, resource, event, trunk_plugin, payload):
190205
if event == events.AFTER_CREATE:
191206
self.trunk_created(payload.states[0])
207+
elif event == events.AFTER_UPDATE:
208+
self.trunk_updated(payload.states[0])
192209
elif event == events.AFTER_DELETE:
193210
self.trunk_deleted(payload.states[0])
194211

@@ -215,13 +232,16 @@ def register(self, resource, event, trigger, payload=None):
215232
super(OVNTrunkDriver, self).register(
216233
resource, event, trigger, payload=payload)
217234
self._handler = OVNTrunkHandler(self.plugin_driver)
218-
for trunk_event in (events.AFTER_CREATE, events.AFTER_DELETE):
235+
for _event in (events.AFTER_CREATE, events.AFTER_UPDATE,
236+
events.AFTER_DELETE):
219237
registry.subscribe(self._handler.trunk_event,
220238
resources.TRUNK,
221-
trunk_event)
239+
_event)
240+
241+
for _event in (events.AFTER_CREATE, events.AFTER_DELETE):
222242
registry.subscribe(self._handler.subport_event,
223243
resources.SUBPORTS,
224-
trunk_event)
244+
_event)
225245

226246
@classmethod
227247
def create(cls, plugin_driver):

neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1169,6 +1169,19 @@ def _create_test_agent(self):
11691169
_, status = self.plugin.create_or_update_agent(self.context, agent)
11701170
return status['id']
11711171

1172+
def _check_chassis_registers(self, present=True):
1173+
chassis = self.sb_api.lookup('Chassis', self.chassis, default=None)
1174+
chassis_name = chassis.name if chassis else None
1175+
if self.sb_api.is_table_present('Chassis_Private'):
1176+
ch_private = self.sb_api.lookup(
1177+
'Chassis_Private', self.chassis, default=None)
1178+
ch_private_name = ch_private.name if ch_private else None
1179+
self.assertEqual(chassis_name, ch_private_name)
1180+
if present:
1181+
self.assertEqual(self.chassis, chassis_name)
1182+
else:
1183+
self.assertIsNone(chassis)
1184+
11721185
def test_agent_show(self):
11731186
for agent_id in self.agent_types.values():
11741187
self.assertTrue(self.plugin.get_agent(self.context, agent_id))
@@ -1217,12 +1230,15 @@ def test_agent_delete(self):
12171230
self.assertRaises(agent_exc.AgentNotFound, self.plugin.get_agent,
12181231
self.context, agent_id)
12191232

1220-
# OVN controller agent deletion, that triggers the "Chassis" register
1221-
# deletion. The "Chassis" register deletion triggers the host OVN
1222-
# agents deletion, both controller and metadata if present.
1233+
# OVN controller agent deletion, that triggers the "Chassis" and
1234+
# "Chassis_Private" registers deletion. The registers deletion triggers
1235+
# the host OVN agents deletion, both controller and metadata if
1236+
# present.
12231237
controller_id = self.agent_types[ovn_const.OVN_CONTROLLER_AGENT]
12241238
metadata_id = self.agent_types[ovn_const.OVN_METADATA_AGENT]
1239+
self._check_chassis_registers()
12251240
self.plugin.delete_agent(self.context, controller_id)
1241+
self._check_chassis_registers(present=False)
12261242
self.assertRaises(agent_exc.AgentNotFound, self.plugin.get_agent,
12271243
self.context, controller_id)
12281244
self.assertEqual(

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

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,23 @@
1414

1515
import contextlib
1616

17-
from neutron.services.trunk import plugin as trunk_plugin
18-
from neutron.tests.functional import base
17+
from neutron_lib.api.definitions import portbindings
1918
from neutron_lib import constants as n_consts
20-
from neutron_lib.objects import registry as obj_reg
19+
from neutron_lib.db import api as db_api
2120
from neutron_lib.plugins import utils
2221
from neutron_lib.services.trunk import constants as trunk_consts
2322
from oslo_utils import uuidutils
2423

2524
from neutron.common.ovn import constants as ovn_const
25+
from neutron.objects import ports as port_obj
26+
from neutron.services.trunk import plugin as trunk_plugin
27+
from neutron.tests.functional import base
2628

2729

2830
class TestOVNTrunkDriver(base.TestOVNFunctionalBase):
2931

30-
def setUp(self):
31-
super(TestOVNTrunkDriver, self).setUp()
32+
def setUp(self, **kwargs):
33+
super().setUp(**kwargs)
3234
self.trunk_plugin = trunk_plugin.TrunkPlugin()
3335
self.trunk_plugin.add_segmentation_type(
3436
trunk_consts.SEGMENTATION_TYPE_VLAN,
@@ -39,7 +41,8 @@ def trunk(self, sub_ports=None):
3941
sub_ports = sub_ports or []
4042
with self.network() as network:
4143
with self.subnet(network=network) as subnet:
42-
with self.port(subnet=subnet) as parent_port:
44+
with self.port(subnet=subnet,
45+
device_owner='compute:nova') as parent_port:
4346
tenant_id = uuidutils.generate_uuid()
4447
trunk = {'trunk': {
4548
'port_id': parent_port['port']['id'],
@@ -64,17 +67,14 @@ def _get_ovn_trunk_info(self):
6467
if row.parent_name and row.tag:
6568
device_owner = row.external_ids[
6669
ovn_const.OVN_DEVICE_OWNER_EXT_ID_KEY]
67-
revision_number = row.external_ids[
68-
ovn_const.OVN_REV_NUM_EXT_ID_KEY]
6970
ovn_trunk_info.append({'port_id': row.name,
7071
'parent_port_id': row.parent_name,
7172
'tag': row.tag,
7273
'device_owner': device_owner,
73-
'revision_number': revision_number,
7474
})
7575
return ovn_trunk_info
7676

77-
def _verify_trunk_info(self, trunk, has_items):
77+
def _verify_trunk_info(self, trunk, has_items, host=''):
7878
ovn_subports_info = self._get_ovn_trunk_info()
7979
neutron_subports_info = []
8080
for subport in trunk.get('sub_ports', []):
@@ -83,19 +83,27 @@ def _verify_trunk_info(self, trunk, has_items):
8383
'parent_port_id': [trunk['port_id']],
8484
'tag': [subport['segmentation_id']],
8585
'device_owner': trunk_consts.TRUNK_SUBPORT_OWNER,
86-
'revision_number': '2',
8786
})
88-
# Check that the subport has the binding is active.
89-
binding = obj_reg.load_class('PortBinding').get_object(
90-
self.context, port_id=subport['port_id'], host='')
91-
self.assertEqual(n_consts.PORT_STATUS_ACTIVE, binding['status'])
87+
# Check the subport binding.
88+
pb = port_obj.PortBinding.get_object(
89+
self.context, port_id=subport['port_id'], host=host)
90+
self.assertEqual(n_consts.PORT_STATUS_ACTIVE, pb.status)
91+
self.assertEqual(host, pb.host)
9292

9393
self.assertCountEqual(ovn_subports_info, neutron_subports_info)
9494
self.assertEqual(has_items, len(neutron_subports_info) != 0)
9595

9696
if trunk.get('status'):
9797
self.assertEqual(trunk_consts.TRUNK_ACTIVE_STATUS, trunk['status'])
9898

99+
def _bind_port(self, port_id, host):
100+
with db_api.CONTEXT_WRITER.using(self.context):
101+
pb = port_obj.PortBinding.get_object(self.context,
102+
port_id=port_id, host='')
103+
pb.delete()
104+
port_obj.PortBinding(self.context, port_id=port_id, host=host,
105+
vif_type=portbindings.VIF_TYPE_OVS).create()
106+
99107
def test_trunk_create(self):
100108
with self.trunk() as trunk:
101109
self._verify_trunk_info(trunk, has_items=False)
@@ -113,10 +121,22 @@ def test_subport_add(self):
113121
new_trunk = self.trunk_plugin.get_trunk(self.context,
114122
trunk['id'])
115123
self._verify_trunk_info(new_trunk, has_items=True)
124+
# Bind parent port. That will trigger the binding of the
125+
# trunk subports too, using the same host ID.
126+
self._bind_port(trunk['port_id'], 'host1')
127+
self.mech_driver.set_port_status_up(trunk['port_id'])
128+
self._verify_trunk_info(new_trunk, has_items=True,
129+
host='host1')
116130

117131
def test_subport_delete(self):
118132
with self.subport() as subport:
119133
with self.trunk([subport]) as trunk:
134+
# Bind parent port.
135+
self._bind_port(trunk['port_id'], 'host1')
136+
self.mech_driver.set_port_status_up(trunk['port_id'])
137+
self._verify_trunk_info(trunk, has_items=True,
138+
host='host1')
139+
120140
self.trunk_plugin.remove_subports(self.context, trunk['id'],
121141
{'sub_ports': [subport]})
122142
new_trunk = self.trunk_plugin.get_trunk(self.context,

0 commit comments

Comments
 (0)