Skip to content

Commit 0d0e6cd

Browse files
committed
ovn: Use ovsdb-client to create neutron_pg_drop
Previously we used short living OVN database connection to create neutron_pg_drop Port Group before workers were spawned. The pre_fork_initialize actually happens after the api workers are spawned anyways and it blocks spawning of other workers, such as maintenance, rpc or periodic. If the OVN database was large it may take several minutes to connect to the database at scale and this blocks spawning of other workers. That means connecting to OVN in pre_fork is not a good idea. This patch replaces the mechanism by using ovsdb-client to send a transaction without connecting to the database and downloading the whole content. The command does following, everything is on the server side: 1) With timeout 0 it waits for neutron_pg_drop Port Group. If the PG is present, the transaction finishes and nothing happens. 2) If the PG is not present, it times out immediately and commits new entries that effectivelly creates neutron_pg_drop Port Group with implicit ACLs to block ingress and egress traffic. Conflicts: neutron/plugins/ml2/drivers/ovn/mech_driver/mech_driver.py neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py Closes-Bug: #1991579 Co-Authored-By: Terry Wilson <[email protected]> Change-Id: I27af495f96a3ea88dd31345dbfb55f1be8faabd6 (cherry picked from commit 50eee19) (cherry picked from commit 5deea00)
1 parent 44ec1f1 commit 0d0e6cd

File tree

9 files changed

+315
-291
lines changed

9 files changed

+315
-291
lines changed

neutron/common/ovn/utils.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from neutron_lib import exceptions as n_exc
2828
from neutron_lib.plugins import directory
2929
from neutron_lib.utils import net as n_utils
30+
from oslo_concurrency import processutils
3031
from oslo_config import cfg
3132
from oslo_log import log
3233
from oslo_serialization import jsonutils
@@ -56,6 +57,70 @@
5657
'PortExtraDHCPValidation', ['failed', 'invalid_ipv4', 'invalid_ipv6'])
5758

5859

60+
class OvsdbClientCommand(object):
61+
_CONNECTION = 0
62+
_PRIVATE_KEY = 1
63+
_CERTIFICATE = 2
64+
_CA_AUTHORITY = 3
65+
66+
OVN_Northbound = "OVN_Northbound"
67+
OVN_Southbound = "OVN_Southbound"
68+
69+
_db_settings = {
70+
OVN_Northbound: {
71+
_CONNECTION: ovn_conf.get_ovn_nb_connection,
72+
_PRIVATE_KEY: ovn_conf.get_ovn_nb_private_key,
73+
_CERTIFICATE: ovn_conf.get_ovn_nb_certificate,
74+
_CA_AUTHORITY: ovn_conf.get_ovn_nb_ca_cert,
75+
},
76+
OVN_Southbound: {
77+
_CONNECTION: ovn_conf.get_ovn_sb_connection,
78+
_PRIVATE_KEY: ovn_conf.get_ovn_sb_private_key,
79+
_CERTIFICATE: ovn_conf.get_ovn_sb_certificate,
80+
_CA_AUTHORITY: ovn_conf.get_ovn_sb_ca_cert,
81+
},
82+
}
83+
84+
@classmethod
85+
def run(cls, command):
86+
"""Run custom ovsdb protocol command.
87+
88+
:param command: JSON object of ovsdb protocol command
89+
"""
90+
try:
91+
db = command[0]
92+
except IndexError:
93+
raise KeyError(
94+
_("%s or %s schema must be specified in the command %s" % (
95+
cls.OVN_Northbound, cls.OVN_Southbound, command)))
96+
97+
if db not in (cls.OVN_Northbound, cls.OVN_Southbound):
98+
raise KeyError(
99+
_("%s or %s schema must be specified in the command %s" % (
100+
cls.OVN_Northbound, cls.OVN_Southbound, command)))
101+
102+
cmd = ['ovsdb-client',
103+
cls.COMMAND,
104+
cls._db_settings[db][cls._CONNECTION](),
105+
'--timeout',
106+
str(ovn_conf.get_ovn_ovsdb_timeout())]
107+
108+
if cls._db_settings[db][cls._PRIVATE_KEY]():
109+
cmd += ['-p', cls._db_settings[db][cls._PRIVATE_KEY](),
110+
'-c', cls._db_settings[db][cls._CERTIFICATE](),
111+
'-C', cls._db_settings[db][cls._CA_AUTHORITY]()]
112+
113+
cmd.append(jsonutils.dumps(command))
114+
115+
return processutils.execute(
116+
*cmd,
117+
log_errors=processutils.LOG_FINAL_ERROR)
118+
119+
120+
class OvsdbClientTransactCommand(OvsdbClientCommand):
121+
COMMAND = 'transact'
122+
123+
59124
def ovn_name(id):
60125
# The name of the OVN entry will be neutron-<UUID>
61126
# This is due to the fact that the OVN application checks if the name
@@ -697,3 +762,56 @@ def wrapper(*args, **kwargs):
697762
reraise=True)(func)(*args, **kwargs)
698763
return wrapper
699764
return inner
765+
766+
767+
def create_neutron_pg_drop():
768+
"""Create neutron_pg_drop Port Group.
769+
770+
It uses ovsdb-client to send to server transact command using ovsdb
771+
protocol that checks if the neutron_pg_drop row exists. If it exists
772+
it times out immediatelly. If it doesn't exist then it creates the
773+
Port_Group and default ACLs to drop all ingress and egress traffic.
774+
"""
775+
command = [
776+
"OVN_Northbound", {
777+
"op": "wait",
778+
"timeout": 0,
779+
"table": "Port_Group",
780+
"where": [
781+
["name", "==", constants.OVN_DROP_PORT_GROUP_NAME]
782+
],
783+
"until": "==",
784+
"rows": []
785+
}, {
786+
"op": "insert",
787+
"table": "ACL",
788+
"row": {
789+
"action": "drop",
790+
"direction": "to-lport",
791+
"match": "outport == @neutron_pg_drop && ip",
792+
"priority": 1001
793+
},
794+
"uuid-name": "droptoport"
795+
}, {
796+
"op": "insert",
797+
"table": "ACL",
798+
"row": {
799+
"action": "drop",
800+
"direction": "from-lport",
801+
"match": "inport == @neutron_pg_drop && ip",
802+
"priority": 1001
803+
},
804+
"uuid-name": "dropfromport"
805+
}, {
806+
"op": "insert",
807+
"table": "Port_Group",
808+
"row": {
809+
"name": constants.OVN_DROP_PORT_GROUP_NAME,
810+
"acls": ["set", [
811+
["named-uuid", "droptoport"],
812+
["named-uuid", "dropfromport"]
813+
]]
814+
}
815+
}]
816+
817+
OvsdbClientTransactCommand.run(command)

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

Lines changed: 11 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
from neutron_lib.plugins.ml2 import api
3737
from neutron_lib.utils import helpers
3838
from oslo_concurrency import lockutils
39-
from oslo_concurrency import processutils
4039
from oslo_config import cfg
4140
from oslo_db import exception as os_db_exc
4241
from oslo_log import log
@@ -59,7 +58,6 @@
5958
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import maintenance
6059
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovn_client
6160
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovn_db_sync
62-
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovsdb_monitor
6361
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import worker
6462
from neutron import service
6563
from neutron.services.logapi.drivers.ovn import driver as log_driver
@@ -279,47 +277,7 @@ def pre_fork_initialize(self, resource, event, trigger, payload=None):
279277
"""Pre-initialize the ML2/OVN driver."""
280278
atexit.register(self._clean_hash_ring)
281279
signal.signal(signal.SIGTERM, self._clean_hash_ring)
282-
self._create_neutron_pg_drop()
283-
284-
def _create_neutron_pg_drop(self):
285-
"""Create neutron_pg_drop Port Group.
286-
287-
The method creates a short living connection to the Northbound
288-
database. Because of multiple controllers can attempt to create the
289-
Port Group at the same time the transaction can fail and raise
290-
RuntimeError. In such case, we make sure the Port Group was created,
291-
otherwise the error is something else and it's raised to the caller.
292-
"""
293-
idl = ovsdb_monitor.OvnInitPGNbIdl.from_server(
294-
ovn_conf.get_ovn_nb_connection(), self.nb_schema_helper, self)
295-
# Only one server should try to create the port group
296-
idl.set_lock('pg_drop_creation')
297-
with ovsdb_monitor.short_living_ovsdb_api(
298-
impl_idl_ovn.OvsdbNbOvnIdl, idl) as pre_ovn_nb_api:
299-
try:
300-
create_default_drop_port_group(pre_ovn_nb_api)
301-
except KeyError:
302-
# Due to a bug in python-ovs, we can send transactions before
303-
# the initial OVSDB is populated in memory. This can break
304-
# the AddCommand post_commit method which tries to return a
305-
# row looked up by the newly commited row's uuid. Since we
306-
# don't care about the return value from the PgAddCommand, we
307-
# can just catch the KeyError and continue. This can be
308-
# removed when the python-ovs bug is resolved.
309-
pass
310-
except RuntimeError as re:
311-
# If we don't get the lock, and the port group didn't exist
312-
# when we tried to create it, it might still have been
313-
# created by another server and we just haven't gotten the
314-
# update yet.
315-
LOG.info("Waiting for Port Group %(pg)s to be created",
316-
{'pg': ovn_const.OVN_DROP_PORT_GROUP_NAME})
317-
if not idl.neutron_pg_drop_event.wait():
318-
LOG.error("Port Group %(pg)s was not created in time",
319-
{'pg': ovn_const.OVN_DROP_PORT_GROUP_NAME})
320-
raise re
321-
LOG.info("Porg Group %(pg)s was created by another server",
322-
{'pg': ovn_const.OVN_DROP_PORT_GROUP_NAME})
280+
ovn_utils.create_neutron_pg_drop()
323281

324282
@staticmethod
325283
def should_post_fork_initialize(worker_class):
@@ -1191,19 +1149,17 @@ def set_port_status_down(self, port_id):
11911149

11921150
def delete_mac_binding_entries(self, external_ip):
11931151
"""Delete all MAC_Binding entries associated to this IP address"""
1194-
cmd = ['ovsdb-client', 'transact', ovn_conf.get_ovn_sb_connection(),
1195-
'--timeout', str(ovn_conf.get_ovn_ovsdb_timeout())]
1196-
1197-
if ovn_conf.get_ovn_sb_private_key():
1198-
cmd += ['-p', ovn_conf.get_ovn_sb_private_key(), '-c',
1199-
ovn_conf.get_ovn_sb_certificate(), '-C',
1200-
ovn_conf.get_ovn_sb_ca_cert()]
1201-
1202-
cmd += ['["OVN_Southbound", {"op": "delete", "table": "MAC_Binding", '
1203-
'"where": [["ip", "==", "%s"]]}]' % external_ip]
1152+
cmd = [
1153+
"OVN_Southbound", {
1154+
"op": "delete",
1155+
"table": "MAC_Binding",
1156+
"where": [
1157+
["ip", "==", external_ip]
1158+
]
1159+
}
1160+
]
12041161

1205-
return processutils.execute(*cmd,
1206-
log_errors=processutils.LOG_FINAL_ERROR)
1162+
return ovn_utils.OvsdbClientTransactCommand.run(cmd)
12071163

12081164
def update_segment_host_mapping(self, host, phy_nets):
12091165
"""Update SegmentHostMapping in DB"""
@@ -1375,28 +1331,6 @@ def delete_agent(self, context, id, _driver=None):
13751331
if_exists=True).execute(check_error=True)
13761332

13771333

1378-
def create_default_drop_port_group(nb_idl):
1379-
pg_name = ovn_const.OVN_DROP_PORT_GROUP_NAME
1380-
if nb_idl.get_port_group(pg_name):
1381-
LOG.debug("Port Group %s already exists", pg_name)
1382-
return
1383-
with nb_idl.transaction(check_error=True) as txn:
1384-
# If drop Port Group doesn't exist yet, create it.
1385-
txn.add(nb_idl.pg_add(pg_name, acls=[], may_exist=True))
1386-
# Add ACLs to this Port Group so that all traffic is dropped.
1387-
acls = ovn_acl.add_acls_for_drop_port_group(pg_name)
1388-
for acl in acls:
1389-
txn.add(nb_idl.pg_acl_add(may_exist=True, **acl))
1390-
1391-
ports_with_pg = set()
1392-
for pg in nb_idl.get_sg_port_groups().values():
1393-
ports_with_pg.update(pg['ports'])
1394-
1395-
if ports_with_pg:
1396-
# Add the ports to the default Port Group
1397-
txn.add(nb_idl.pg_add_ports(pg_name, list(ports_with_pg)))
1398-
1399-
14001334
def get_availability_zones(cls, context, _driver, filters=None, fields=None,
14011335
sorts=None, limit=None, marker=None,
14021336
page_reverse=False):

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

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
from neutron_lib.plugins import utils as p_utils
3434
from neutron_lib.utils import helpers
3535
from neutron_lib.utils import net as n_net
36-
from oslo_concurrency import processutils
3736
from oslo_config import cfg
3837
from oslo_log import log
3938
from oslo_utils import excutils
@@ -1676,16 +1675,16 @@ def delete_mac_binding_entries_by_mac(self, mac):
16761675
is refer to patch:
16771676
https://review.opendev.org/c/openstack/neutron/+/812805
16781677
"""
1679-
cmd = ['ovsdb-client', 'transact', ovn_conf.get_ovn_sb_connection(),
1680-
'--timeout', str(ovn_conf.get_ovn_ovsdb_timeout())]
1681-
if ovn_conf.get_ovn_sb_private_key():
1682-
cmd += ['-p', ovn_conf.get_ovn_sb_private_key(), '-c',
1683-
ovn_conf.get_ovn_sb_certificate(), '-C',
1684-
ovn_conf.get_ovn_sb_ca_cert()]
1685-
cmd += ['["OVN_Southbound", {"op": "delete", "table": "MAC_Binding", '
1686-
'"where": [["mac", "==", "%s"]]}]' % mac]
1687-
return processutils.execute(*cmd,
1688-
log_errors=processutils.LOG_FINAL_ERROR)
1678+
cmd = [
1679+
"OVN_Southbound", {
1680+
"op": "delete",
1681+
"table": "MAC_Binding",
1682+
"where": [
1683+
["mac", "==", mac]
1684+
]
1685+
}
1686+
]
1687+
return utils.OvsdbClientTransactCommand.run(cmd)
16891688

16901689
def _delete_lrouter_port(self, context, port_id, router_id=None, txn=None):
16911690
"""Delete a logical router port."""

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

Lines changed: 0 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
# under the License.
1414

1515
import abc
16-
import contextlib
1716
import datetime
1817

1918
from neutron_lib import context as neutron_context
@@ -581,17 +580,6 @@ def run(self, event, row, old):
581580
self.driver.delete_mac_binding_entries(row.external_ip)
582581

583582

584-
class NeutronPgDropPortGroupCreated(row_event.WaitEvent):
585-
"""WaitEvent for neutron_pg_drop Create event."""
586-
def __init__(self, timeout=None):
587-
table = 'Port_Group'
588-
events = (self.ROW_CREATE,)
589-
conditions = (('name', '=', ovn_const.OVN_DROP_PORT_GROUP_NAME),)
590-
super(NeutronPgDropPortGroupCreated, self).__init__(
591-
events, table, conditions, timeout=timeout)
592-
self.event_name = 'PortGroupCreated'
593-
594-
595583
class OvnDbNotifyHandler(row_event.RowEventHandler):
596584
def __init__(self, driver):
597585
self.driver = driver
@@ -837,56 +825,6 @@ def post_connect(self):
837825
PortBindingChassisUpdateEvent(self.driver)])
838826

839827

840-
class OvnInitPGNbIdl(OvnIdl):
841-
"""Very limited OVN NB IDL.
842-
843-
This IDL is intended to be used only in initialization phase with short
844-
living DB connections.
845-
"""
846-
847-
tables = ['Port_Group', 'Logical_Switch_Port', 'ACL']
848-
849-
def __init__(self, driver, remote, schema):
850-
super(OvnInitPGNbIdl, self).__init__(driver, remote, schema)
851-
self.set_table_condition(
852-
'Port_Group', [['name', '==', ovn_const.OVN_DROP_PORT_GROUP_NAME]])
853-
self.neutron_pg_drop_event = NeutronPgDropPortGroupCreated(
854-
timeout=ovn_conf.get_ovn_ovsdb_timeout())
855-
self.notify_handler.watch_event(self.neutron_pg_drop_event)
856-
857-
def notify(self, event, row, updates=None):
858-
# Go ahead and process events even if the lock is contended so we can
859-
# know that some other server has created the drop group
860-
self.notify_handler.notify(event, row, updates)
861-
862-
@classmethod
863-
def from_server(cls, connection_string, helper, driver, pg_only=False):
864-
if pg_only:
865-
helper.register_table('Port_Group')
866-
else:
867-
for table in cls.tables:
868-
helper.register_table(table)
869-
870-
return cls(driver, connection_string, helper)
871-
872-
873-
@contextlib.contextmanager
874-
def short_living_ovsdb_api(api_class, idl):
875-
"""Context manager for short living connections to the database.
876-
877-
:param api_class: Class implementing the database calls
878-
(e.g. from the impl_idl module)
879-
:param idl: An instance of IDL class (e.g. instance of OvnNbIdl)
880-
"""
881-
conn = connection.Connection(
882-
idl, timeout=ovn_conf.get_ovn_ovsdb_timeout())
883-
api = api_class(conn)
884-
try:
885-
yield api
886-
finally:
887-
api.ovsdb_connection.stop()
888-
889-
890828
def _check_and_set_ssl_files(schema_name):
891829
if schema_name == 'OVN_Southbound':
892830
priv_key_file = ovn_conf.get_ovn_sb_private_key()

0 commit comments

Comments
 (0)