Skip to content

Commit fa93909

Browse files
committed
Consider logging options when using OVNdbsync
Previously, OVN db sync would erase an ACL if any unexpected property appeared on it and not recreate it again. This happened because of the order of deletion and creation of the ACLS: the new ACL was first created and then deleted just the moment after that. This meant that even crucial ACLs like the ones bounded to the pg_drop port group, which are used to reject all the traffic by default on ML2/OVN environments, would dissapear. The order of the ACL deletion and creation has been inverted to avoid this. Furthermore, security group logging was not supported on the ovn_db_sync script, which would also cause the logging parameters to dissapear. Now, the logging options are considered when doing a sync. Conflicts: neutron/common/ovn/acl.py Closes-Bug: #2107925 Change-Id: I00fa8332fdebc958ddb8f28c638670c75a70e0c5 Signed-off-by: Elvira Garcia <[email protected]> (cherry picked from commit 1cf5b6d)
1 parent ecdb6ed commit fa93909

File tree

9 files changed

+295
-32
lines changed

9 files changed

+295
-32
lines changed

neutron/common/ovn/acl.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,8 @@ def add_acls_for_drop_port_group(pg_name):
148148
"name": [],
149149
"severity": [],
150150
"direction": direction,
151-
"match": '{} == @{} && ip'.format(p, pg_name)}
151+
"match": '{} == @{} && ip'.format(p, pg_name),
152+
"meter": []}
152153
acl_list.append(acl)
153154
return acl_list
154155

@@ -167,6 +168,7 @@ def drop_all_ip_traffic_for_port(port):
167168
"severity": [],
168169
"direction": direction,
169170
"match": '{} == "{}" && ip'.format(p, port['id']),
171+
"meter": [],
170172
"external_ids": {'neutron:lport': port['id']}}
171173
acl_list.append(acl)
172174
return acl_list
@@ -187,6 +189,7 @@ def add_sg_rule_acl_for_port_group(port_group, r, stateful, match):
187189
"severity": [],
188190
"direction": dir_map[r['direction']],
189191
"match": match,
192+
"meter": [],
190193
ovn_const.OVN_SG_RULE_EXT_ID_KEY: r['id']}
191194
return acl
192195

neutron/common/ovn/constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@
259259

260260
ACL_EXPECTED_COLUMNS_NBDB = (
261261
'external_ids', 'direction', 'log', 'priority',
262-
'name', 'action', 'severity', 'match')
262+
'name', 'action', 'severity', 'match', 'meter')
263263

264264
# Resource types
265265
TYPE_NETWORKS = 'networks'

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

Lines changed: 67 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb.extensions import qos \
3636
as ovn_qos
3737
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovn_client
38+
from neutron.services.logapi.drivers.ovn import driver as log_driver
3839
from neutron.services.segments import db as segments_db
3940

4041

@@ -82,6 +83,18 @@ def __init__(self, core_plugin, ovn_api, sb_ovn, mode, ovn_driver):
8283
self.segments_plugin = (
8384
manager.NeutronManager.load_class_for_provider(
8485
'neutron.service_plugins', 'segments')())
86+
self.log_plugin = directory.get_plugin(plugin_constants.LOG_API)
87+
if not self.log_plugin:
88+
self.log_plugin = (
89+
manager.NeutronManager.load_class_for_provider(
90+
'neutron.service_plugins', 'log')())
91+
directory.add_plugin(plugin_constants.LOG_API, self.log_plugin)
92+
for driver in self.log_plugin.driver_manager.drivers:
93+
if driver.name == "ovn":
94+
self.ovn_log_driver = driver
95+
if not hasattr(self, 'ovn_log_driver'):
96+
self.ovn_log_driver = log_driver.OVNDriver()
97+
self.log_plugin.driver_manager.register_driver(self.ovn_log_driver)
8598

8699
def stop(self):
87100
if utils.is_ovn_l3(self.l3_plugin):
@@ -233,6 +246,9 @@ def sync_port_groups(self, ctx):
233246

234247
def _get_acls_from_port_groups(self):
235248
ovn_acls = []
249+
# Options and label columns are only present for OVN >= 22.03.
250+
# Furthermore label is a randint so it cannot be compared with any
251+
# expected neutron value. They are added later on the ACL addition.
236252
acl_columns = (self.ovn_api._tables['ACL'].columns.keys() &
237253
set(ovn_const.ACL_EXPECTED_COLUMNS_NBDB))
238254
acl_columns.discard('external_ids')
@@ -244,7 +260,16 @@ def _get_acls_from_port_groups(self):
244260
acl_string['port_group'] = pg.name
245261
if id_key in acl.external_ids:
246262
acl_string[id_key] = acl.external_ids[id_key]
263+
# This properties are present as lists of one item,
264+
# converting them to string.
265+
if acl_string['name']:
266+
acl_string['name'] = acl_string['name'][0]
267+
if acl_string['meter']:
268+
acl_string['meter'] = acl_string['meter'][0]
269+
if acl_string['severity']:
270+
acl_string['severity'] = acl_string['severity'][0]
247271
ovn_acls.append(acl_string)
272+
248273
return ovn_acls
249274

250275
def sync_acls(self, ctx):
@@ -274,6 +299,10 @@ def sync_acls(self, ctx):
274299
neutron_default_acls = acl_utils.add_acls_for_drop_port_group(
275300
ovn_const.OVN_DROP_PORT_GROUP_NAME)
276301

302+
# Add logging options
303+
self.ovn_log_driver.add_logging_options_to_acls(neutron_acls, ctx)
304+
self.ovn_log_driver.add_logging_options_to_acls(neutron_default_acls,
305+
ctx)
277306
ovn_acls = self._get_acls_from_port_groups()
278307
# Sort the acls in the ovn database according to the security
279308
# group rule id for easy comparison in the future.
@@ -304,14 +333,24 @@ def sync_acls(self, ctx):
304333
o_index += 1
305334
elif n_id == o_id:
306335
if any(item not in na.items() for item in oa.items()):
336+
for item in oa.items():
337+
if item not in na.items():
338+
LOG.warning('Property %(item)s from OVN ACL not '
339+
'found in Neutron ACL: %(n_acl)s',
340+
{'item': item,
341+
'n_acl': na})
307342
add_acls.append(na)
308343
remove_acls.append(oa)
309344
n_index += 1
310345
o_index += 1
311346
elif n_id > o_id:
347+
LOG.warning('ACL should not be present in OVN, removing'
348+
'%(acl)s', {'acl': oa})
312349
remove_acls.append(oa)
313350
o_index += 1
314351
else:
352+
LOG.warning('ACL should be present in OVN but is not, adding:'
353+
'%(acl)s', {'acl': na})
315354
add_acls.append(na)
316355
n_index += 1
317356

@@ -351,15 +390,41 @@ def get_num_acls(ovn_acls):
351390
if (self.mode == ovn_const.OVN_DB_SYNC_MODE_REPAIR and
352391
(num_acls_to_add or num_acls_to_remove)):
353392
one_time_pg_resync = True
393+
with self.ovn_api.transaction(check_error=True) as txn:
394+
for aclr in ovn_acls:
395+
LOG.warning('ACLs found in OVN NB DB but not in '
396+
'Neutron for port group %s',
397+
aclr['port_group'])
398+
txn.add(self.ovn_api.pg_acl_del(aclr['port_group'],
399+
aclr['direction'],
400+
aclr['priority'],
401+
aclr['match']))
402+
for aclr in ovn_acls_from_ls:
403+
# Remove all the ACLs from any Logical Switch if they have
404+
# any. Elements are (lswitch_name, list_of_acls).
405+
if len(aclr[1]) > 0:
406+
LOG.warning('Removing ACLs from OVN from Logical '
407+
'Switch %s', aclr[0])
408+
txn.add(self.ovn_api.acl_del(aclr[0]))
354409
while True:
355410
try:
356411
with self.ovn_api.transaction(check_error=True) as txn:
357412
for acla in neutron_acls:
358413
LOG.warning('ACL found in Neutron but not in '
359414
'OVN NB DB for port group %s',
360415
acla['port_group'])
361-
txn.add(self.ovn_api.pg_acl_add(
362-
**acla, may_exist=True))
416+
acl = txn.add(self.ovn_api.pg_acl_add(**acla,
417+
may_exist=True))
418+
# We need to do this now since label should be
419+
# random and not 0. We can use options as a way
420+
# to see if label is supported or not.
421+
if acla.get('log'):
422+
self.ovn_log_driver.add_label_related(acla,
423+
ctx)
424+
txn.add(self.ovn_api.db_set('ACL', acl,
425+
label=acla['label'],
426+
options=acla['options']))
427+
363428
except idlutils.RowNotFound as row_err:
364429
if row_err.msg.startswith("Cannot find Port_Group"):
365430
if one_time_pg_resync:
@@ -377,23 +442,6 @@ def get_num_acls(ovn_acls):
377442
raise
378443
break
379444

380-
with self.ovn_api.transaction(check_error=True) as txn:
381-
for aclr in ovn_acls:
382-
LOG.warning('ACLs found in OVN NB DB but not in '
383-
'Neutron for port group %s',
384-
aclr['port_group'])
385-
txn.add(self.ovn_api.pg_acl_del(aclr['port_group'],
386-
aclr['direction'],
387-
aclr['priority'],
388-
aclr['match']))
389-
for aclr in ovn_acls_from_ls:
390-
# Remove all the ACLs from any Logical Switch if they have
391-
# any. Elements are (lswitch_name, list_of_acls).
392-
if len(aclr[1]) > 0:
393-
LOG.warning('Removing ACLs from OVN from Logical '
394-
'Switch %s', aclr[0])
395-
txn.add(self.ovn_api.acl_del(aclr[0]))
396-
397445
LOG.debug('OVN-NB Sync ACLs completed @ %s', str(datetime.now()))
398446

399447
def _calculate_routes_differences(self, ovn_routes, db_routes):

neutron/services/logapi/drivers/ovn/driver.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,80 @@ def resource_update(self, context, log_objs):
409409
with self.ovn_nb.transaction(check_error=True) as ovn_txn:
410410
self._update_log_objs(context, ovn_txn, log_objs)
411411

412+
def add_logging_options_to_acls(self, neutron_acls, context):
413+
log_objs = self._get_logs(context)
414+
for log_obj in log_objs:
415+
pgs = self._pgs_from_log_obj(context, log_obj)
416+
actions_enabled = self._acl_actions_enabled(log_obj)
417+
self._set_neutron_acls_log(pgs, context, actions_enabled,
418+
utils.ovn_name(log_obj.id),
419+
neutron_acls)
420+
421+
# This function is a version of set_acls_log meant to change neutron
422+
# defined acls, mostly thought for ovndbsync consistency check.
423+
def _set_neutron_acls_log(self, pgs, context, actions_enabled, log_name,
424+
neutron_acls):
425+
acl_changes, acl_visits = 0, 0
426+
for pg in pgs:
427+
meter_name = self.meter_name
428+
if pg['name'] != ovn_const.OVN_DROP_PORT_GROUP_NAME:
429+
sg = sg_obj.SecurityGroup.get_sg_by_id(context,
430+
pg['external_ids'][ovn_const.OVN_SG_EXT_ID_KEY])
431+
if not sg:
432+
LOG.warning("Port Group %s is missing a corresponding "
433+
"security group, skipping its network log "
434+
"setting...", pg["name"])
435+
continue
436+
if not sg.stateful:
437+
meter_name = meter_name + ("_stateless")
438+
# We need to get the OVN ACL because UUID is not listed as a
439+
# property on neutron defined ACLs (and it shouldn't), so we need
440+
# to check which ACL is that UUID referring to, using match as
441+
# differentiating value.
442+
for acl in neutron_acls:
443+
acl_visits += 1
444+
# skip acls used by a different network log
445+
n_acl_name = acl['name']
446+
if n_acl_name and n_acl_name != log_name:
447+
continue
448+
action = acl['action'] in actions_enabled
449+
acl['log'] = action
450+
acl['meter'] = meter_name
451+
acl['name'] = log_name
452+
acl['severity'] = "info"
453+
if acl.get('options'):
454+
acl["options"] = {'log-related': "true"}
455+
# label is not set because the actual number should not
456+
# be compared or taken into account, we only need it to be
457+
# different from 0.
458+
acl_changes += 1
459+
LOG.info("Set %d (out of %d visited) Neutron ACLs for network log %s",
460+
acl_changes, acl_visits, log_name)
461+
462+
def _get_all_log_pgs(self, ctx):
463+
"""Get all Port Group names associated to a Log Object.
464+
465+
:param log_plugin: Currently loaded log_plugging.
466+
:param ctx: current running context information
467+
"""
468+
log_objs = self._get_logs(ctx)
469+
log_pgs = []
470+
for log_obj in log_objs:
471+
log_pgs.extend(self._pgs_from_log_obj(ctx, log_obj))
472+
return log_pgs
473+
474+
def add_label_related(self, n_acl, ctx):
475+
# Get acls to be able to check if label is present in OVN ACLs and
476+
# also check old label value for ACL if it was already present.
477+
acls = [acl for pg in self._get_all_log_pgs(ctx) for acl in pg["acls"]]
478+
if not acls:
479+
return
480+
acl = self.ovn_nb.lookup("ACL", acls[0], default=None)
481+
if not hasattr(acl, 'label'):
482+
return
483+
n_acl["label"] = secrets.SystemRandom().randrange(1, MAX_INT_LABEL)
484+
n_acl["options"] = {'log-related': 'true'}
485+
412486

413487
def register(plugin_driver):
414488
"""Register the driver."""

neutron/tests/functional/base.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import worker
5151
from neutron.plugins.ml2.drivers import type_geneve # noqa
5252
from neutron import service # noqa
53+
from neutron.services.logapi.drivers.ovn import driver as log_driver
5354
from neutron.tests import base
5455
from neutron.tests.common import base as common_base
5556
from neutron.tests.common import helpers
@@ -175,6 +176,7 @@ def setUp(self, maintenance_worker=False, service_plugins=None):
175176
self.addCleanup(exts.PluginAwareExtensionManager.clear_instance)
176177
self.ovsdb_server_mgr = None
177178
self._service_plugins = service_plugins
179+
log_driver.DRIVER = None
178180
super().setUp()
179181
self.test_log_dir = os.path.join(DEFAULT_LOG_DIR, self.id())
180182
base.setup_test_logging(
@@ -197,6 +199,11 @@ def setUp(self, maintenance_worker=False, service_plugins=None):
197199
self.mech_driver.log_driver)
198200
self.mech_driver.log_driver.plugin_driver = self.mech_driver
199201
self.mech_driver.log_driver._log_plugin_property = None
202+
for driver in self.log_plugin.driver_manager.drivers:
203+
if driver.name == "ovn":
204+
self.ovn_log_driver = driver
205+
if not hasattr(self, 'ovn_log_driver'):
206+
self.ovn_log_driver = log_driver.OVNDriver()
200207
self.ovn_northd_mgr = None
201208
self.maintenance_worker = maintenance_worker
202209
mock.patch(

0 commit comments

Comments
 (0)