Refactor for meter ID Generator

Add a Singleton meter ID Generator for both bandwidth limit
and packet rate limit, because for one bridge the meter ID
is a sharing range.

Closes-Bug: #1964342
Change-Id: Ibb9762d57913ea701dcf2746a0e0db74c6a7ca01
This commit is contained in:
LIU Yulong 2022-09-02 10:13:20 +08:00
parent 9e2a0ac058
commit c3ebefa5f7
4 changed files with 158 additions and 42 deletions

View File

@ -28,3 +28,6 @@ NO_PBLOCKS_TYPES = [
constants.DEVICE_OWNER_ROUTER_HA_INTF,
constants.DEVICE_OWNER_FLOATINGIP,
]
METER_FLAG_BPS = "bps"
METER_FLAG_PPS = "pps"

View File

@ -22,6 +22,7 @@ from oslo_config import cfg
from oslo_log import log as logging
from neutron.agent.l2.extensions import qos_linux as qos
from neutron.plugins.ml2.common import constants as comm_consts
from neutron.services.qos.drivers.openvswitch import driver
@ -30,27 +31,71 @@ LOG = logging.getLogger(__name__)
MAX_RETIES = 1000
class MeterRuleManager(object):
class MeterIDGenerator(object):
# This cache will be:
# PORT_METER_ID = {"port_id_1_ingress": 1,
# "port_id_1_egress: 2,
# "port_id_2_ingress": 3,
# "port_id_2_egress: 4}
PORT_METER_ID = {}
# This will be:
# PORT_INFO_INGRESS = {"port_id_1": (of_port_name, mac_1, local_vlan_1),
# "port_id_2": (of_port_name, mac_2, local_vlan_2),
PORT_INFO_INGRESS = {}
# PORT_INFO_EGRESS = {"port_id_1": (of_port_name, mac_1, of_port_1),
# "port_id_2": (of_port_name, mac_2, of_port_2),
PORT_INFO_EGRESS = {}
def __init__(self, br_int):
def __new__(cls, *args, **kwargs):
# make it a singleton
if not hasattr(cls, '_instance'):
cls._instance = super(MeterIDGenerator, cls).__new__(cls)
cls.PORT_METER_ID = {}
return cls._instance
def __init__(self, max_meter):
self.max_meter = max_meter
def _generate_meter_id(self):
if self.max_meter <= 0:
return
used_meter_ids = self.PORT_METER_ID.values()
cid = None
times = 0
while not cid or cid in used_meter_ids:
cid = random.randint(1, self.max_meter)
times += 1
if times >= MAX_RETIES:
return
return cid
def allocate_meter_id(self, key):
meter_id = self._generate_meter_id()
if not meter_id:
return
self.set_meter_id(key, meter_id)
return meter_id
def remove_port_meter_id(self, key):
return self.PORT_METER_ID.pop(key, None)
def set_meter_id(self, key, meter_id):
self.PORT_METER_ID[key] = meter_id
class MeterRuleManager(object):
def __init__(self, br_int, type_=comm_consts.METER_FLAG_PPS):
self.br_int = br_int
self.max_meter = 0
self._init_max_meter_id()
self.rule_type = type_
self.generator = MeterIDGenerator(self.max_meter)
# This will be:
# PORT_INFO_INGRESS = {"port_id_1": (mac_1, 1),
# "port_id_2": (mac_2, 2),
# "port_id_3": (mac_3, 3),
# "port_id_4": (mac_4, 4)}
self.PORT_INFO_INGRESS = {}
# PORT_INFO_EGRESS = {"port_id_1": (mac_1, 1),
# "port_id_2": (mac_2, 1),
# "port_id_3": (mac_3, 1),
# "port_id_4": (mac_4, 1)}
self.PORT_INFO_EGRESS = {}
def _init_max_meter_id(self):
self.max_meter = 0
features = self.br_int.list_meter_features()
for f in features:
if f["max_meter"] > 0:
@ -58,20 +103,20 @@ class MeterRuleManager(object):
break
def get_data_key(self, port_id, direction):
return "%s_%s" % (port_id, direction)
return "%s_%s_%s" % (self.rule_type, port_id, direction)
def load_port_meter_id(self, port_name, port_id, direction):
key = self.get_data_key(port_id, direction)
try:
meter_id = self.br_int.get_value_from_other_config(
port_name, key, value_type=int)
if meter_id:
self.PORT_METER_ID[key] = meter_id
else:
LOG.warning("Failed to load port %(port)s meter id in "
self.generator.set_meter_id(key, meter_id)
return meter_id
except Exception:
LOG.warning("Failed to load port $(port)s meter id in "
"direction %(direction)s",
{"direction": direction,
"port": port_id})
return meter_id
def store_port_meter_id_to_ovsdb(self, port_name, port_id,
direction, meter_id):
@ -84,32 +129,13 @@ class MeterRuleManager(object):
self.br_int.remove_value_from_other_config(
port_name, key)
def generate_meter_id(self):
if self.max_meter <= 0:
return
used_meter_ids = self.PORT_METER_ID.values()
cid = None
times = 0
while not cid or cid in used_meter_ids:
cid = random.randint(1, self.max_meter)
times += 1
if times >= MAX_RETIES:
LOG.warning("Failed to allocate meter "
"id after %d retries", times)
return
return cid
def allocate_meter_id(self, port_id, direction):
meter_id = self.generate_meter_id()
if not meter_id:
return
key = self.get_data_key(port_id, direction)
self.PORT_METER_ID[key] = meter_id
return meter_id
return self.generator.allocate_meter_id(key)
def remove_port_meter_id(self, port_id, direction):
key = self.get_data_key(port_id, direction)
return self.PORT_METER_ID.pop(key, None)
return self.generator.remove_port_meter_id(key)
def set_port_info_ingress(self, port_id, port_name, mac, vlan):
self.PORT_INFO_INGRESS[port_id] = (port_name, mac, vlan)

View File

@ -21,6 +21,7 @@ from oslo_log import log as logging
from neutron.agent.common import async_process
from neutron.agent.linux import iptables_manager
from neutron.common import utils as common_utils
from neutron.plugins.ml2.common import constants as comm_consts
LOG = logging.getLogger(__name__)
@ -87,11 +88,12 @@ def wait_until_dscp_marking_rule_applied_ovs(bridge, port_vif, rule):
def wait_until_pkt_meter_rule_applied_ovs(bridge, port_vif, port_id,
direction, mac=None):
direction, mac=None,
type_=comm_consts.METER_FLAG_PPS):
def _pkt_rate_limit_rule_applied():
port_num = bridge.get_port_ofport(port_vif)
port_vlan = bridge.get_port_tag_by_name(port_vif)
key = "%s_%s" % (port_id, direction)
key = "%s_%s_%s" % (type_, port_id, direction)
meter_id = bridge.get_value_from_other_config(
port_vif, key, value_type=int)

View File

@ -20,6 +20,7 @@ from oslo_utils import uuidutils
from neutron.objects.qos import policy
from neutron.objects.qos import rule
from neutron.plugins.ml2.common import constants as comm_consts
from neutron.plugins.ml2.drivers.openvswitch.agent import (
ovs_agent_extension_api as ovs_ext_api)
from neutron.plugins.ml2.drivers.openvswitch.agent.extension_drivers import (
@ -243,6 +244,90 @@ class QosOVSAgentDriverTestCase(ovs_test_base.OVSAgentConfigTestBase):
self.delete_meter.assert_not_called()
self.remove_meter_from_port.assert_not_called()
def test_meter_manager_allocate_meter_id(self):
meter_cache_pps = qos_driver.MeterRuleManager(mock.Mock())
meter_cache_pps.generator.max_meter = 10000
meter_cache_bps = qos_driver.MeterRuleManager(
mock.Mock(), type_=comm_consts.METER_FLAG_BPS)
meter_cache_bps.generator.max_meter = 10000
meter_cache_pps.allocate_meter_id("1", "ingress")
meter_cache_pps.allocate_meter_id("1", "egress")
meter_cache_bps.allocate_meter_id("1", "ingress")
meter_cache_bps.allocate_meter_id("1", "egress")
meter_cache_pps.allocate_meter_id("2", "ingress")
meter_cache_pps.allocate_meter_id("2", "egress")
meter_cache_bps.allocate_meter_id("2", "ingress")
meter_cache_bps.allocate_meter_id("2", "egress")
self.assertEqual(
meter_cache_pps.generator.PORT_METER_ID,
meter_cache_bps.generator.PORT_METER_ID)
pps_keys = meter_cache_pps.generator.PORT_METER_ID.keys()
bps_keys = meter_cache_bps.generator.PORT_METER_ID.keys()
self.assertEqual(
2, len([k for k in pps_keys if k.startswith('pps_1')]))
self.assertEqual(
2, len([k for k in bps_keys if k.startswith('bps_1')]))
self.assertEqual(
2, len([k for k in pps_keys if k.startswith('pps_2')]))
self.assertEqual(
2, len([k for k in bps_keys if k.startswith('bps_2')]))
self.assertEqual(
meter_cache_pps.generator.PORT_METER_ID.keys(),
meter_cache_bps.generator.PORT_METER_ID.keys())
pps_values = list(meter_cache_pps.generator.PORT_METER_ID.values())
bps_values = list(meter_cache_bps.generator.PORT_METER_ID.values())
self.assertEqual(pps_values, bps_values)
except_keys = ["pps_1_ingress", "pps_1_egress",
"bps_1_ingress", "bps_1_egress",
"pps_2_ingress", "pps_2_egress",
"bps_2_ingress", "bps_2_egress"]
except_values = []
for key in except_keys:
value = meter_cache_bps.generator.PORT_METER_ID.get(key)
if value:
except_values.append(value)
self.assertEqual(8, len(set(except_values)))
def test_meter_manager_remove_port_meter_id(self):
meter_cache_pps = qos_driver.MeterRuleManager(mock.Mock())
meter_cache_pps.generator.max_meter = 10000
meter_cache_bps = qos_driver.MeterRuleManager(
mock.Mock(), type_=comm_consts.METER_FLAG_BPS)
meter_cache_bps.generator.max_meter = 10000
meter_cache_pps.allocate_meter_id("1", "ingress")
meter_cache_pps.allocate_meter_id("1", "egress")
meter_cache_bps.allocate_meter_id("1", "ingress")
meter_cache_bps.allocate_meter_id("1", "egress")
meter_cache_pps.allocate_meter_id("2", "ingress")
meter_cache_pps.allocate_meter_id("2", "egress")
meter_cache_bps.allocate_meter_id("2", "ingress")
meter_cache_bps.allocate_meter_id("2", "egress")
self.assertEqual(
meter_cache_pps.generator.PORT_METER_ID,
meter_cache_bps.generator.PORT_METER_ID)
meter_cache_bps.remove_port_meter_id("2", "ingress")
meter_cache_pps.remove_port_meter_id("1", "egress")
self.assertNotIn(
"pps_1_egress", meter_cache_pps.generator.PORT_METER_ID.keys())
self.assertNotIn(
"bps_2_ingress", meter_cache_pps.generator.PORT_METER_ID.keys())
pps_values = list(meter_cache_pps.generator.PORT_METER_ID.values())
bps_values = list(meter_cache_bps.generator.PORT_METER_ID.values())
self.assertEqual(pps_values, bps_values)
except_keys = ["pps_1_ingress",
"bps_1_ingress", "bps_1_egress",
"pps_2_ingress", "pps_2_egress",
"bps_2_egress"]
except_values = []
for key in except_keys:
value = meter_cache_bps.generator.PORT_METER_ID.get(key)
if value:
except_values.append(value)
self.assertEqual(6, len(set(except_values)))
def _assert_rules_create_updated(self):
self.create_egress.assert_called_once_with(
self.port_name, self.rules[0].max_kbps,