Skip to content

Commit e82afb1

Browse files
committed
[OVN] Fix rate and burst for stateless security groups
Right now, as per kernel limitation, the burst limit is not correctly enforcing the rate and burst when using the ovn "log-related" option and stateless security groups. We log exactly double the burst. Creating a new meter that limits the rate and burst to half of the expected ones is a workaround that solves the issue. Closes-bug: #2032929 Conflicts: - neutron/services/logapi/drivers/ovn/driver.py - neutron/tests/functional/services/logapi/drivers/ovn/test_driver.py Signed-off-by: Elvira García <[email protected]> Change-Id: Ib0047d38c58bcebb23c8887e7934987ff8c8a432 (cherry picked from commit a3a113a)
1 parent cf09634 commit e82afb1

File tree

6 files changed

+102
-26
lines changed

6 files changed

+102
-26
lines changed

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

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2730,7 +2730,8 @@ def add_txns_to_remove_port_dns_records(self, txn, port):
27302730
txn.add(self._nb_idl.dns_remove_record(
27312731
ls_dns_record.uuid, ptr_record, if_exists=True))
27322732

2733-
def create_ovn_fair_meter(self, meter_name, from_reload=False, txn=None):
2733+
def _create_ovn_fair_meter(self, meter_name, from_reload=False, txn=None,
2734+
stateless=False):
27342735
"""Create row in Meter table with fair attribute set to True.
27352736
27362737
Create a row in OVN's NB Meter table based on well-known name. This
@@ -2745,21 +2746,35 @@ def create_ovn_fair_meter(self, meter_name, from_reload=False, txn=None):
27452746
"""
27462747
meter = self._nb_idl.db_find_rows(
27472748
"Meter", ("name", "=", meter_name)).execute(check_error=True)
2748-
# The meter is created when a log object is created, not by default.
2749-
# This condition avoids creating the meter if it wasn't there already
2749+
# The meters are created when a log object is created, not by default.
2750+
# This condition avoids creating the meter if it wasn't there already.
27502751
commands = []
27512752
if from_reload and not meter:
27522753
return
2754+
2755+
burst_limit = cfg.CONF.network_log.burst_limit
2756+
rate_limit = cfg.CONF.network_log.rate_limit
2757+
if stateless:
2758+
meter_name = meter_name + "_stateless"
2759+
burst_limit = int(burst_limit / 2)
2760+
rate_limit = int(rate_limit / 2)
2761+
# The stateless meter is only created once the stateful meter was
2762+
# successfully created.
2763+
# The treatment of limits is not equal for stateful and stateless
2764+
# traffic at a kernel level according to:
2765+
# https://bugzilla.redhat.com/show_bug.cgi?id=2212952
2766+
# The stateless meter is created to adjust this issue.
2767+
meter = self._nb_idl.db_find_rows(
2768+
"Meter", ("name", "=", meter_name)).execute(check_error=True)
27532769
if meter:
27542770
meter = meter[0]
27552771
meter_band = self._nb_idl.lookup("Meter_Band",
27562772
meter.bands[0].uuid, default=None)
27572773
if meter_band:
27582774
if all((meter.unit == "pktps",
27592775
meter.fair[0],
2760-
meter_band.rate == cfg.CONF.network_log.rate_limit,
2761-
meter_band.burst_size ==
2762-
cfg.CONF.network_log.burst_limit)):
2776+
meter_band.rate == rate_limit,
2777+
meter_band.burst_size == burst_limit)):
27632778
# Meter (and its meter-band) unchanged: noop.
27642779
return
27652780
# Re-create meter (and its meter-band) with the new attributes.
@@ -2773,10 +2788,15 @@ def create_ovn_fair_meter(self, meter_name, from_reload=False, txn=None):
27732788
commands.append(self._nb_idl.meter_add(
27742789
name=meter_name,
27752790
unit="pktps",
2776-
rate=cfg.CONF.network_log.rate_limit,
2791+
rate=rate_limit,
27772792
fair=True,
2778-
burst_size=cfg.CONF.network_log.burst_limit,
2793+
burst_size=burst_limit,
27792794
may_exist=False,
27802795
external_ids={ovn_const.OVN_DEVICE_OWNER_EXT_ID_KEY:
27812796
log_const.LOGGING_PLUGIN}))
27822797
self._transaction(commands, txn=txn)
2798+
2799+
def create_ovn_fair_meter(self, meter_name, from_reload=False, txn=None):
2800+
self._create_ovn_fair_meter(meter_name, from_reload, txn)
2801+
self._create_ovn_fair_meter(meter_name, from_reload, txn,
2802+
stateless=True)

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

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from neutron.common.ovn import constants as ovn_const
2929
from neutron.common.ovn import utils
3030
from neutron.conf.services import logging as log_cfg
31+
from neutron.objects import securitygroup as sg_obj
3132
from neutron.services.logapi.common import db_api
3233
from neutron.services.logapi.common import sg_callback
3334
from neutron.services.logapi.drivers import base
@@ -152,9 +153,17 @@ def _remove_acls_log(self, pgs, ovn_txn, log_name=None):
152153
msg += " for network log {}".format(log_name)
153154
LOG.info(msg, acl_changes, acl_absents, acl_visits)
154155

155-
def _set_acls_log(self, pgs, ovn_txn, actions_enabled, log_name):
156+
def _set_acls_log(self, pgs, context, ovn_txn, actions_enabled, log_name):
156157
acl_changes, acl_visits = 0, 0
157158
for pg in pgs:
159+
meter_name = self.meter_name
160+
if ovn_const.OVN_DROP_PORT_GROUP_NAME not in pg["name"]:
161+
stateful = (sg_obj.SecurityGroup
162+
.get_sg_by_id(context, pg["name"]
163+
.replace('pg_', '', 1)
164+
.replace('_', '-')).stateful)
165+
if not stateful:
166+
meter_name = meter_name + ("_stateless")
158167
for acl_uuid in pg["acls"]:
159168
acl_visits += 1
160169
acl = self.ovn_nb.lookup("ACL", acl_uuid)
@@ -163,7 +172,7 @@ def _set_acls_log(self, pgs, ovn_txn, actions_enabled, log_name):
163172
continue
164173
columns = {
165174
'log': acl.action in actions_enabled,
166-
'meter': self.meter_name,
175+
'meter': meter_name,
167176
'name': log_name,
168177
'severity': "info"
169178
}
@@ -183,7 +192,7 @@ def _update_log_objs(self, context, ovn_txn, log_objs):
183192
for log_obj in log_objs:
184193
pgs = self._pgs_from_log_obj(context, log_obj)
185194
actions_enabled = self._acl_actions_enabled(log_obj)
186-
self._set_acls_log(pgs, ovn_txn, actions_enabled,
195+
self._set_acls_log(pgs, context, ovn_txn, actions_enabled,
187196
utils.ovn_name(log_obj.id))
188197

189198
def _pgs_all(self):
@@ -266,7 +275,7 @@ def create_log(self, context, log_obj):
266275
with self.ovn_nb.transaction(check_error=True) as ovn_txn:
267276
self._ovn_client.create_ovn_fair_meter(self.meter_name,
268277
txn=ovn_txn)
269-
self._set_acls_log(pgs, ovn_txn, actions_enabled,
278+
self._set_acls_log(pgs, context, ovn_txn, actions_enabled,
270279
utils.ovn_name(log_obj.id))
271280

272281
def create_log_precommit(self, context, log_obj):
@@ -334,7 +343,7 @@ def update_log(self, context, log_obj):
334343
if not self._unset_disabled_acls(context, log_obj, ovn_txn):
335344
pgs = self._pgs_from_log_obj(context, log_obj)
336345
actions_enabled = self._acl_actions_enabled(log_obj)
337-
self._set_acls_log(pgs, ovn_txn, actions_enabled,
346+
self._set_acls_log(pgs, context, ovn_txn, actions_enabled,
338347
utils.ovn_name(log_obj.id))
339348

340349
def delete_log(self, context, log_obj):
@@ -356,6 +365,8 @@ def delete_log(self, context, log_obj):
356365
self._remove_acls_log(pgs, ovn_txn)
357366
ovn_txn.add(self.ovn_nb.meter_del(self.meter_name,
358367
if_exists=True))
368+
ovn_txn.add(self.ovn_nb.meter_del(
369+
self.meter_name + "_stateless", if_exists=True))
359370
LOG.info("All ACL logs cleared after deletion of log_obj %s",
360371
log_obj.id)
361372
return

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

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1125,10 +1125,9 @@ def test_check_for_logging_conf_change(self):
11251125
# Check a meter and fair meter exist
11261126
self.assertTrue(self.nb_api._tables['Meter'].rows)
11271127
self.assertTrue(self.nb_api._tables['Meter_Band'].rows)
1128-
self.assertEqual(cfg.CONF.network_log.burst_limit,
1129-
[*self.nb_api._tables['Meter_Band'].rows.values()][0].burst_size)
1130-
self.assertEqual(cfg.CONF.network_log.rate_limit,
1131-
[*self.nb_api._tables['Meter_Band'].rows.values()][0].rate)
1128+
self.assertEqual(len([*self.nb_api._tables['Meter'].rows.values()]),
1129+
len([*self.nb_api._tables['Meter_Band'].rows.values()]))
1130+
self._check_meters_consistency()
11321131
# Update burst and rate limit values on the configuration
11331132
ovn_config.cfg.CONF.set_override('burst_limit', CFG_NEW_BURST,
11341133
group='network_log')
@@ -1138,7 +1137,16 @@ def test_check_for_logging_conf_change(self):
11381137
self.assertRaises(periodics.NeverAgain,
11391138
self.maint.check_fair_meter_consistency)
11401139
# Check meter band was effectively changed after the maintenance call
1141-
self.assertEqual(CFG_NEW_BURST,
1142-
[*self.nb_api._tables['Meter_Band'].rows.values()][0].burst_size)
1143-
self.assertEqual(CFG_NEW_RATE,
1144-
[*self.nb_api._tables['Meter_Band'].rows.values()][0].rate)
1140+
self._check_meters_consistency(CFG_NEW_BURST, CFG_NEW_RATE)
1141+
1142+
def _check_meters_consistency(self, new_burst=None, new_rate=None):
1143+
burst, rate = (new_burst, new_rate) if new_burst else (
1144+
cfg.CONF.network_log.burst_limit, cfg.CONF.network_log.rate_limit)
1145+
for meter in [*self.nb_api._tables['Meter'].rows.values()]:
1146+
meter_band = self.nb_api.lookup('Meter_Band', meter.bands[0].uuid)
1147+
if "_stateless" in meter.name:
1148+
self.assertEqual(int(burst / 2), meter_band.burst_size)
1149+
self.assertEqual(int(rate / 2), meter_band.rate)
1150+
else:
1151+
self.assertEqual(burst, meter_band.burst_size)
1152+
self.assertEqual(rate, meter_band.rate)

neutron/tests/functional/services/logapi/drivers/ovn/test_driver.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ def setUp(self):
3030
self._check_is_supported()
3131
self.ctxt = context.Context('admin', 'fake_tenant')
3232

33+
# Since these tests use the _create_network() from the unit test suite
34+
# but _create_security_group() is from the functional tests, two
35+
# different tenant_ids will be used unless we specify the following
36+
# line in the code:
37+
self._tenant_id = self.ctxt.project_id
38+
3339
def _check_is_supported(self):
3440
if not self.log_driver.network_logging_supported(self.nb_api):
3541
self.skipTest("The current OVN version does not offer support "

neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_ovn_client.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,16 @@ def test_create_ovn_fair_meter(self):
247247
self.ovn_client.create_ovn_fair_meter(self._log_driver.meter_name)
248248
self.assertFalse(self.nb_idl.meter_del.called)
249249
self.assertTrue(self.nb_idl.meter_add.called)
250-
self.nb_idl.meter_add.assert_called_once_with(
250+
self.nb_idl.meter_add.assert_any_call(
251+
name=self._log_driver.meter_name + "_stateless",
252+
unit="pktps",
253+
rate=int(self.fake_cfg_network_log.rate_limit / 2),
254+
fair=True,
255+
burst_size=int(self.fake_cfg_network_log.burst_limit / 2),
256+
may_exist=False,
257+
external_ids={constants.OVN_DEVICE_OWNER_EXT_ID_KEY:
258+
log_const.LOGGING_PLUGIN})
259+
self.nb_idl.meter_add.assert_any_call(
251260
name=self._log_driver.meter_name,
252261
unit="pktps",
253262
rate=self.fake_cfg_network_log.rate_limit,
@@ -259,10 +268,17 @@ def test_create_ovn_fair_meter(self):
259268

260269
def test_create_ovn_fair_meter_unchanged(self):
261270
mock_find_rows = mock.Mock()
262-
mock_find_rows.execute.return_value = [self._fake_meter()]
271+
fake_meter1 = [self._fake_meter()]
272+
fake_meter2 = [self._fake_meter(
273+
name=self._log_driver.meter_name + "_stateless",
274+
bands=[mock.Mock(uuid='tb_stateless')])]
275+
mock_find_rows.execute.side_effect = [fake_meter1, fake_meter1,
276+
fake_meter2, fake_meter2]
263277
self.nb_idl.db_find_rows.return_value = mock_find_rows
264278
self.nb_idl.lookup.side_effect = lambda table, key, default: (
265-
self._fake_meter_band() if key == "test_band" else default)
279+
self._fake_meter_band() if key == "test_band" else
280+
self._fake_meter_band_stateless() if key == "tb_stateless" else
281+
default)
266282
self.ovn_client.create_ovn_fair_meter(self._log_driver.meter_name)
267283
self.assertFalse(self.nb_idl.meter_del.called)
268284
self.assertFalse(self.nb_idl.meter_add.called)

neutron/tests/unit/services/logapi/drivers/ovn/test_driver.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
from neutron.common.ovn import constants as ovn_const
2323
from neutron.common.ovn import utils as ovn_utils
24+
from neutron.objects import securitygroup as sg_obj
2425
from neutron.services.logapi.drivers.ovn import driver as ovn_driver
2526
from neutron.tests import base
2627
from neutron.tests.unit import fake_resources
@@ -92,6 +93,15 @@ def _fake_meter_band(self, **kwargs):
9293
meter_band_obj_dict = {**meter_band_defaults_dict, **kwargs}
9394
return mock.Mock(**meter_band_obj_dict)
9495

96+
def _fake_meter_band_stateless(self, **kwargs):
97+
meter_band_defaults_dict = {
98+
'uuid': 'tb_stateless',
99+
'rate': int(self.fake_cfg_network_log.rate_limit / 2),
100+
'burst_size': int(self.fake_cfg_network_log.burst_limit / 2),
101+
}
102+
meter_band_obj_dict = {**meter_band_defaults_dict, **kwargs}
103+
return mock.Mock(**meter_band_obj_dict)
104+
95105

96106
class TestOVNDriver(TestOVNDriverBase):
97107
def test_create(self):
@@ -287,7 +297,8 @@ def _mock_lookup(_pg_table, acl_uuid, default):
287297
self._nb_ovn.db_set.call_count)
288298

289299
@mock.patch.object(ovn_driver.LOG, 'info')
290-
def test__set_acls_log(self, m_info):
300+
@mock.patch.object(sg_obj.SecurityGroup, 'get_sg_by_id')
301+
def test__set_acls_log(self, get_sg, m_info):
291302
pg_dict = self._fake_pg_dict(acls=['acl1', 'acl2', 'acl3', 'acl4'])
292303
log_name = 'test_obj_name'
293304
used_name = 'test_used_name'
@@ -297,10 +308,14 @@ def _mock_lookup(_pg_table, acl_uuid):
297308
return self._fake_acl()
298309
return self._fake_acl(name=used_name)
299310

311+
sg = fake_resources.FakeSecurityGroup.create_one_security_group(
312+
attrs={'stateful': True})
313+
get_sg.return_value = sg
300314
self._nb_ovn.lookup.side_effect = _mock_lookup
301315
actions_enabled = self._log_driver._acl_actions_enabled(
302316
self._fake_log_obj(event=log_const.ALL_EVENT))
303-
self._log_driver._set_acls_log([pg_dict], self._nb_ovn.transaction,
317+
self._log_driver._set_acls_log([pg_dict], self.context,
318+
self._nb_ovn.transaction,
304319
actions_enabled, log_name)
305320
info_args, _info_kwargs = m_info.call_args_list[0]
306321
self.assertIn('Set %d (out of %d visited) ACLs for network log %s',

0 commit comments

Comments
 (0)