Skip to content

Commit 90f1917

Browse files
authored
Merge pull request #189 from stackhpc/upstream/2023.1-2024-11-18
Synchronise 2023.1 with upstream
2 parents 46ed9fe + e6dce50 commit 90f1917

File tree

19 files changed

+189
-64
lines changed

19 files changed

+189
-64
lines changed

neutron/db/ovn_revision_numbers_db.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,12 @@ class UnknownResourceType(n_exc.NeutronException):
8888
message = 'Uknown resource type: %(resource_type)s'
8989

9090

91+
# NOTE(ralonsoh): to be moved to neutron-lib
92+
class RevisionNumberNotDefined(n_exc.NeutronException):
93+
message = ('Unique revision number not found for %(resource_uuid)s, '
94+
'the resource type is required in query')
95+
96+
9197
def _get_standard_attr_id(context, resource_uuid, resource_type):
9298
try:
9399
row = context.session.query(STD_ATTR_MAP[resource_type]).filter_by(
@@ -151,14 +157,26 @@ def _ensure_revision_row_exist(context, resource, resource_type, std_attr_id):
151157

152158

153159
@db_api.retry_if_session_inactive()
154-
def get_revision_row(context, resource_uuid):
160+
@db_api.CONTEXT_READER
161+
def get_revision_row(context, resource_uuid, resource_type=None):
162+
"""Retrieve the resource revision number
163+
164+
Only the Neutron ports can have two revision number registers, one for the
165+
Logical_Switch_Port and another for the Logical_Router_Port, if this port
166+
is a router interface. It is not strictly needed to filter by resource_type
167+
if the resource is not a port.
168+
"""
155169
try:
156-
with db_api.CONTEXT_READER.using(context):
157-
return context.session.query(
158-
ovn_models.OVNRevisionNumbers).filter_by(
159-
resource_uuid=resource_uuid).one()
170+
filters = {'resource_uuid': resource_uuid}
171+
if resource_type:
172+
filters['resource_type'] = resource_type
173+
return context.session.query(
174+
ovn_models.OVNRevisionNumbers).filter_by(
175+
**filters).one()
160176
except exc.NoResultFound:
161177
pass
178+
except exc.MultipleResultsFound:
179+
raise RevisionNumberNotDefined(resource_uuid=resource_uuid)
162180

163181

164182
@db_api.retry_if_session_inactive()

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,11 @@ def _create_security_group_precommit(self, resource, event, trigger,
403403
context, security_group['id'],
404404
ovn_const.TYPE_SECURITY_GROUPS,
405405
std_attr_id=security_group['standard_attr_id'])
406+
for sg_rule in security_group['security_group_rules']:
407+
ovn_revision_numbers_db.create_initial_revision(
408+
context, sg_rule['id'],
409+
ovn_const.TYPE_SECURITY_GROUP_RULES,
410+
std_attr_id=sg_rule['standard_attr_id'])
406411

407412
def _create_security_group(self, resource, event, trigger, payload):
408413
context = payload.context

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

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from neutron_lib.plugins import utils as p_utils
3333
from neutron_lib.services.logapi import constants as log_const
3434
from neutron_lib.services.qos import constants as qos_consts
35+
from neutron_lib.services.trunk import constants as trunk_const
3536
from neutron_lib.utils import helpers
3637
from neutron_lib.utils import net as n_net
3738
from oslo_config import cfg
@@ -289,7 +290,18 @@ def update_lsp_host_info(self, context, db_port, up=True):
289290
Defaults to True.
290291
"""
291292
cmd = []
293+
if db_port.device_owner == trunk_const.TRUNK_SUBPORT_OWNER:
294+
# NOTE(ralonsoh): OVN subports don't have host ID information.
295+
return
296+
297+
port_up = self._nb_idl.lsp_get_up(db_port.id).execute(
298+
check_error=True)
292299
if up:
300+
if not port_up:
301+
LOG.warning('Logical_Switch_Port %s host information not '
302+
'updated, the port state is down')
303+
return
304+
293305
if not db_port.port_bindings:
294306
return
295307

@@ -311,6 +323,11 @@ def update_lsp_host_info(self, context, db_port, up=True):
311323
self._nb_idl.db_set(
312324
'Logical_Switch_Port', db_port.id, ext_ids))
313325
else:
326+
if port_up:
327+
LOG.warning('Logical_Switch_Port %s host information not '
328+
'removed, the port state is up')
329+
return
330+
314331
cmd.append(
315332
self._nb_idl.db_remove(
316333
'Logical_Switch_Port', db_port.id, 'external_ids',
@@ -818,7 +835,8 @@ def delete_port(self, context, port_id, port_object=None):
818835
# to allow at least one maintenance cycle before we delete the
819836
# revision number so that the port doesn't stale and eventually
820837
# gets deleted by the maintenance task.
821-
rev_row = db_rev.get_revision_row(context, port_id)
838+
rev_row = db_rev.get_revision_row(
839+
context, port_id, resource_type=ovn_const.TYPE_PORTS)
822840
time_ = (timeutils.utcnow() - datetime.timedelta(
823841
seconds=ovn_const.DB_CONSISTENCY_CHECK_INTERVAL + 30))
824842
if rev_row and rev_row.created_at >= time_:
@@ -2425,6 +2443,9 @@ def create_security_group(self, context, security_group):
24252443
self.is_allow_stateless_supported())
24262444
db_rev.bump_revision(
24272445
context, security_group, ovn_const.TYPE_SECURITY_GROUPS)
2446+
for sg_rule in security_group['security_group_rules']:
2447+
db_rev.bump_revision(
2448+
context, sg_rule, ovn_const.TYPE_SECURITY_GROUP_RULES)
24282449

24292450
def _add_port_to_drop_port_group(self, port, txn):
24302451
txn.add(self._nb_idl.pg_add_ports(ovn_const.OVN_DROP_PORT_GROUP_NAME,

neutron/services/ovn_l3/plugin.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,18 @@ def update_router_gateway_port_bindings(self, router, host):
366366
port = self._plugin.update_port(
367367
context, port['id'],
368368
{'port': {portbindings.HOST_ID: host}})
369-
369+
# Updates OVN NB database with hostname for lsp router
370+
# gateway port
371+
with self._nb_ovn.transaction(check_error=True) as txn:
372+
ext_ids = (
373+
"external_ids",
374+
{ovn_const.OVN_HOST_ID_EXT_ID_KEY: host},
375+
)
376+
txn.add(
377+
self._nb_ovn.db_set(
378+
"Logical_Switch_Port", port["id"], ext_ids
379+
)
380+
)
370381
if port['status'] != status:
371382
self._plugin.update_port_status(context, port['id'], status)
372383

neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,8 @@ def test_port(self):
371371
# Assert the revision number no longer exists
372372
self.assertIsNone(db_rev.get_revision_row(
373373
self.context,
374-
neutron_obj['id']))
374+
neutron_obj['id'],
375+
resource_type=ovn_const.TYPE_PORTS))
375376

376377
def test_subnet_global_dhcp4_opts(self):
377378
obj_name = 'globaltestsubnet'

neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovn_db_resources.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from neutron.common.ovn import utils
2727
from neutron.common import utils as n_utils
2828
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf as ovn_config
29+
from neutron.db import ovn_revision_numbers_db as rev_db
2930
from neutron.tests.functional import base
3031

3132

@@ -922,6 +923,25 @@ def test_port_security_port_group(self):
922923
self._verify_port_acls(port_id, expected_acls_with_sg_ps_enabled)
923924

924925

926+
class TestSecurityGroups(base.TestOVNFunctionalBase):
927+
928+
def test_security_group_creation_and_deletion(self):
929+
sg = self._make_security_group(self.fmt)['security_group']
930+
rev_num = rev_db.get_revision_row(self.context, sg['id'])
931+
self.assertEqual(1, rev_num.revision_number)
932+
for sg_rule in sg['security_group_rules']:
933+
rev_num = rev_db.get_revision_row(self.context, sg_rule['id'])
934+
self.assertEqual(0, rev_num.revision_number)
935+
936+
self._delete('security-groups', sg['id'])
937+
self.assertIsNone(rev_db.get_revision_row(self.context, sg['id']))
938+
# NOTE(ralonsoh): the deletion of the revision numbers of the SG rules
939+
# will be fixed in a follow-up patch.
940+
# for sg_rule in sg['security_group_rules']:
941+
# self.assertIsNone(rev_db.get_revision_row(self.context,
942+
# sg_rule['id']))
943+
944+
925945
class TestDNSRecords(base.TestOVNFunctionalBase):
926946
_extension_drivers = ['port_security', 'dns']
927947

neutron/tests/functional/services/ovn_l3/test_plugin.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,12 @@ def fake_select(*args, **kwargs):
443443
self.assertGreaterEqual(client_select.call_count, 3)
444444
self.assertGreaterEqual(plugin_select.call_count, 3)
445445

446+
def _find_port_binding(self, port_id):
447+
cmd = self.sb_api.db_find_rows('Port_Binding',
448+
('logical_port', '=', port_id))
449+
rows = cmd.execute(check_error=True)
450+
return rows[0] if rows else None
451+
446452
def test_router_gateway_port_binding_host_id(self):
447453
# Test setting chassis on chassisredirect port in Port_Binding table,
448454
# will update host_id of corresponding router gateway port
@@ -465,14 +471,30 @@ def test_router_gateway_port_binding_host_id(self):
465471
may_exist=True).execute(check_error=True)
466472

467473
def check_port_binding_host_id(port_id):
474+
# Get port from Neutron DB
468475
port = core_plugin.get_ports(
469476
self.context, filters={'id': [port_id]})[0]
470-
return port[portbindings.HOST_ID] == host_id
477+
# Get port from OVN DB
478+
bp = self._find_port_binding(port_id)
479+
ovn_host_id = bp.external_ids.get(ovn_const.OVN_HOST_ID_EXT_ID_KEY)
480+
return port[portbindings.HOST_ID] == host_id == ovn_host_id
471481

472482
# Test if router gateway port updated with this chassis
473483
n_utils.wait_until_true(lambda: check_port_binding_host_id(
474484
gw_port_id))
475485

486+
# Simulate failover to another chassis and check host_id in Neutron DB
487+
# and external_ids:neutron:host_id in OVN DB are updated
488+
chassis = idlutils.row_by_value(
489+
self.sb_api.idl, "Chassis", "name", self.chassis2
490+
)
491+
host_id = chassis.hostname
492+
self.sb_api.lsp_unbind(logical_port).execute(check_error=True)
493+
self.sb_api.lsp_bind(logical_port, self.chassis2).execute(
494+
check_error=True
495+
)
496+
n_utils.wait_until_true(lambda: check_port_binding_host_id(gw_port_id))
497+
476498
def _validate_router_ipv6_ra_configs(self, lrp_name, expected_ra_confs):
477499
lrp = idlutils.row_by_value(self.nb_api.idl,
478500
'Logical_Router_Port', 'name', lrp_name)

neutron/tests/unit/db/test_db_base_plugin_v2.py

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,14 @@ def _req(self, method, resource, data=None, fmt=None, id=None, params=None,
246246
query_string=params, context=context,
247247
headers=headers)
248248

249+
def _check_http_response(self, res):
250+
# Things can go wrong - raise HTTP exc with res code only
251+
# so it can be caught by unit tests
252+
if res.status_int >= webob.exc.HTTPClientError.code:
253+
res.charset = 'utf8'
254+
raise webob.exc.HTTPClientError(explanation=str(res),
255+
code=res.status_int)
256+
249257
def new_create_request(self, resource, data, fmt=None, id=None,
250258
subresource=None, context=None):
251259
return self._req('POST', resource, data, fmt, id=id,
@@ -523,10 +531,7 @@ def _make_network(self, fmt, name, admin_state_up, **kwargs):
523531
res = self._create_network(fmt, name, admin_state_up, **kwargs)
524532
# TODO(salvatore-orlando): do exception handling in this test module
525533
# in a uniform way (we do it differently for ports, subnets, and nets
526-
# Things can go wrong - raise HTTP exc with res code only
527-
# so it can be caught by unit tests
528-
if res.status_int >= webob.exc.HTTPClientError.code:
529-
raise webob.exc.HTTPClientError(code=res.status_int)
534+
self._check_http_response(res)
530535
return self.deserialize(fmt, res)
531536

532537
def _make_subnet(self, fmt, network, gateway, cidr, subnetpool_id=None,
@@ -551,10 +556,7 @@ def _make_subnet(self, fmt, network, gateway, cidr, subnetpool_id=None,
551556
ipv6_ra_mode=ipv6_ra_mode,
552557
ipv6_address_mode=ipv6_address_mode,
553558
set_context=set_context)
554-
# Things can go wrong - raise HTTP exc with res code only
555-
# so it can be caught by unit tests
556-
if res.status_int >= webob.exc.HTTPClientError.code:
557-
raise webob.exc.HTTPClientError(code=res.status_int)
559+
self._check_http_response(res)
558560
return self.deserialize(fmt, res)
559561

560562
def _make_v6_subnet(self, network, ra_addr_mode, ipv6_pd=False):
@@ -580,18 +582,30 @@ def _make_subnetpool(self, fmt, prefixes, admin=False, **kwargs):
580582
**kwargs)
581583
# Things can go wrong - raise HTTP exc with res code only
582584
# so it can be caught by unit tests
583-
if res.status_int >= webob.exc.HTTPClientError.code:
584-
raise webob.exc.HTTPClientError(code=res.status_int)
585+
self._check_http_response(res)
585586
return self.deserialize(fmt, res)
586587

587588
def _make_port(self, fmt, net_id, expected_res_status=None, **kwargs):
588589
res = self._create_port(fmt, net_id, expected_res_status, **kwargs)
589590
# Things can go wrong - raise HTTP exc with res code only
590591
# so it can be caught by unit tests
591-
if res.status_int >= webob.exc.HTTPClientError.code:
592-
raise webob.exc.HTTPClientError(code=res.status_int)
592+
self._check_http_response(res)
593593
return self.deserialize(fmt, res)
594594

595+
def _make_security_group(self, fmt, name=None, expected_res_status=None,
596+
project_id=None, is_admin=False):
597+
name = name or 'sg-{}'.format(uuidutils.generate_uuid())
598+
project_id = project_id or self._tenant_id
599+
data = {'security_group': {'name': name,
600+
'description': name,
601+
'project_id': project_id}}
602+
sg_req = self.new_create_request('security-groups', data, fmt)
603+
sg_res = sg_req.get_response(self.api)
604+
if expected_res_status:
605+
self.assertEqual(expected_res_status, sg_res.status_int)
606+
self._check_http_response(sg_res)
607+
return self.deserialize(fmt, sg_res)
608+
595609
def _create_qos_rule(self, fmt, qos_policy_id, rule_type, max_kbps=None,
596610
max_burst_kbps=None, dscp_mark=None, min_kbps=None,
597611
direction=constants.EGRESS_DIRECTION,

neutron/tests/unit/db/test_l3_db.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
from neutron_lib.plugins import utils as plugin_utils
3333
from oslo_utils import uuidutils
3434
import testtools
35-
import webob.exc
3635

3736
from neutron.db import extraroute_db
3837
from neutron.db import l3_db
@@ -951,8 +950,7 @@ def _create_external_network(self, name=None, **kwargs):
951950
res = self._create_network(
952951
self.fmt, name, True,
953952
arg_list=(extnet_apidef.EXTERNAL,), **kwargs)
954-
if res.status_int >= webob.exc.HTTPClientError.code:
955-
raise webob.exc.HTTPClientError(code=res.status_int)
953+
self._check_http_response(res)
956954
return self.deserialize(self.fmt, res)
957955

958956
def test_update_router_gw_notify(self):

neutron/tests/unit/db/test_ovn_revision_numbers_db.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
from neutron.api import extensions
2626
from neutron.common import config
27+
from neutron.common.ovn import constants as ovn_const
2728
from neutron.db.models import ovn as ovn_models
2829
from neutron.db import ovn_revision_numbers_db as ovn_rn_db
2930
import neutron.extensions
@@ -32,7 +33,6 @@
3233
from neutron.tests.unit.extensions import test_l3
3334
from neutron.tests.unit.extensions import test_securitygroup
3435

35-
3636
EXTENSIONS_PATH = ':'.join(neutron.extensions.__path__)
3737
PLUGIN_CLASS = (
3838
'neutron.tests.unit.db.test_ovn_revision_numbers_db.TestMaintenancePlugin')
@@ -123,6 +123,24 @@ def test_create_initial_revision_may_exist_duplicated_entry(self):
123123
self.fail("create_initial_revision shouldn't raise "
124124
"DBDuplicateEntry when may_exist is True")
125125

126+
def test_get_revision_row_ports(self):
127+
res = self._create_port(self.fmt, self.net['id'])
128+
port = self.deserialize(self.fmt, res)['port']
129+
with db_api.CONTEXT_WRITER.using(self.ctx):
130+
for resource_type in (ovn_const.TYPE_PORTS,
131+
ovn_const.TYPE_ROUTER_PORTS):
132+
self._create_initial_revision(port['id'], resource_type)
133+
134+
for resource_type in (ovn_const.TYPE_PORTS,
135+
ovn_const.TYPE_ROUTER_PORTS):
136+
row = ovn_rn_db.get_revision_row(
137+
self.ctx, port['id'], resource_type=resource_type)
138+
self.assertEqual(resource_type, row.resource_type)
139+
self.assertEqual(port['id'], row.resource_uuid)
140+
141+
self.assertRaises(ovn_rn_db.RevisionNumberNotDefined,
142+
ovn_rn_db.get_revision_row, self.ctx, port['id'])
143+
126144

127145
class TestMaintenancePlugin(test_securitygroup.SecurityGroupTestPlugin,
128146
test_l3.TestL3NatBasePlugin):

0 commit comments

Comments
 (0)