分析一下 neutron ovn (ovs) fip QoS 的实现方式
1. db l3_fip_qos.py
搜索 FloatingQoSDbMixin

2. 测试用例分析
neutron/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/test_qos.py
可以看到这个路径在 ovn/mech_driver/ovsdb/extensions/ 下,说明 ovn 确实支持该功能

meter 是支持 router gw ip 的 QoS 的

可以看到 ovn 启用了分布式公网 IP
2.1 子网绑定 qos policy
python
def _update_network(self, network_id, qos_policy_id):
data = {'network': {'qos_policy_id': qos_policy_id}}
return self._update('networks', network_id, data,
as_admin=True)['network']
def test_update_network(self):
"""Test update network (internal ports).
net1: [(1) from qos_policy0 to no QoS policy,
(2) from qos_policy0 to qos_policy1]
- port10: no QoS port policy
- port11: qos_policy0
- port12: qos_policy1
"""
policies_ports = [
(None, {self.ports[0].id}),
(self.qos_policies[1].id, {self.ports[0].id})]
self.ports[1].qos_policy_id = self.qos_policies[0].id
self.ports[1].update()
self.ports[2].qos_policy_id = self.qos_policies[1].id
self.ports[2].update()
for qos_policy_id, reference_ports in policies_ports:
self.networks[0] = self._update_network(self.networks[0]['id'],
qos_policy_id)
original_network = {'qos_policy_id': self.qos_policies[0],
pnet_api.NETWORK_TYPE: mock.ANY,
}
reviewed_port_ids, _, _ = self.qos_driver.update_network(
mock.ANY, self.networks[0], original_network)
self.assertEqual(reference_ports, reviewed_port_ids)
calls = [mock.call(mock.ANY, self.ports[0].id,
self.ports[0].network_id, self.tenant_type,
qos_policy_id, None)]
self.mock_rules.assert_has_calls(calls)
self.mock_rules.reset_mock()
## 外部 network 也支持绑定
def test_update_external_network(self):
"""Test update external network (floating IPs and GW IPs).
- fip0: qos_policy0
- fip1: no QoS FIP policy (inherits from external network QoS)
- router_fips: no QoS FIP policy (inherits from external network QoS)
"""
network_policies = [(self.qos_policies[1].id,
{self.fips[1].id},
{self.router_fips.id}),
(None,
{self.fips[1].id},
{self.router_fips.id})]
self.fips[0].qos_policy_id = self.qos_policies[0].id
self.fips[0].update()
for qos_policy_id, ref_fips, ref_routers in network_policies:
self.fips_network = self._update_network(self.fips_network['id'],
qos_policy_id)
original_network = {'qos_policy_id': self.qos_policies[0],
pnet_api.NETWORK_TYPE: mock.ANY,
}
_, reviewed_fips_ids, reviewed_router_ids = (
self.qos_driver.update_network(
mock.Mock(), self.fips_network, original_network))
self.assertEqual(ref_fips, reviewed_fips_ids)
self.assertEqual(ref_routers, reviewed_router_ids)
def test_update_network_external_ports(self):
"""Test update network with external ports.
- port10: no QoS port policy
- port11: no QoS port policy but external
- port12: qos_policy0
"""
policies_ports = [(self.qos_policies[0].id, {self.ports[0].id})]
self.ports[2].qos_policy_id = self.qos_policies[0].id
self.ports[2].update()
port_obj.PortBinding(self.ctx, port_id=self.ports[1].id, host='host',
profile={}, vif_type='',
vnic_type=portbindings_api.VNIC_DIRECT).create()
with mock.patch.object(self.qos_driver._driver._nb_idl,
'get_lswitch_port') as mock_lsp:
mock_lsp.side_effect = [
mock.Mock(type=ovn_const.LSP_TYPE_LOCALNET),
mock.Mock(type=ovn_const.LSP_TYPE_EXTERNAL)]
for qos_policy_id, reference_ports in policies_ports:
self.networks[0] = self._update_network(self.networks[0]['id'],
qos_policy_id)
original_network = {'qos_policy_id': self.qos_policies[0]}
reviewed_port_ids, _, _ = self.qos_driver.update_network(
mock.ANY, self.networks[0], original_network, reset=True)
self.assertEqual(reference_ports, reviewed_port_ids)
calls = [mock.call(
mock.ANY, self.ports[0].id, self.ports[0].network_id,
self.tenant_type, qos_policy_id, None)]
self.mock_rules.assert_has_calls(calls)
self.mock_rules.reset_mock()
2.2 router 绑定 qos

python
def _update_router_qos(self, context, router_id, qos_policy_id,
attach=True):
# NOTE(ralonsoh): router QoS policy is not yet implemented in Router
# OVO. Once we have this feature, this method can be removed.
qos = policy_obj.QosPolicy.get_policy_obj(context, qos_policy_id)
if attach:
qos.attach_router(router_id)
else:
qos.detach_router(router_id)
2.3 测试资源创建


2.4 QoS 规则的类型
python
def test__qos_rules(self, mock_get_rules, mock_warning):
rules = [
rule_obj.QosBandwidthLimitRule(
direction=constants.EGRESS_DIRECTION, **QOS_RULE_BW_1),
rule_obj.QosBandwidthLimitRule(
direction=constants.INGRESS_DIRECTION, **QOS_RULE_BW_2),
rule_obj.QosDscpMarkingRule(**QOS_RULE_DSCP_1),
rule_obj.QosMinimumBandwidthRule(
direction=constants.EGRESS_DIRECTION, **QOS_RULE_MINBW_1),
rule_obj.QosMinimumBandwidthRule(
direction=constants.INGRESS_DIRECTION, **QOS_RULE_MINBW_2),
]
mock_get_rules.return_value = rules
expected = {
constants.EGRESS_DIRECTION: {
qos_constants.RULE_TYPE_BANDWIDTH_LIMIT: QOS_RULE_BW_1,
qos_constants.RULE_TYPE_DSCP_MARKING: QOS_RULE_DSCP_1,
qos_constants.RULE_TYPE_MINIMUM_BANDWIDTH: QOS_RULE_MINBW_1},
constants.INGRESS_DIRECTION: {
qos_constants.RULE_TYPE_BANDWIDTH_LIMIT: QOS_RULE_BW_2}
}
self.assertEqual(expected, self.qos_driver._qos_rules(mock.ANY,
'policy_id1'))
mock_warning.assert_called_once_with(
'ML2/OVN QoS driver does not support minimum bandwidth rules '
'enforcement with ingress direction')
qos rule 在普通 port 和 fip 两种场景,用两种类型区分

2.5 QoS policy 支持绑定的对象类型一览
网络
路由器
FIP
(隧道 port)
python
def test_update_policy(self):
"""Test update QoS policy, networks and ports bound are updated.
QoS policy updated: qos_policy0
net1: no QoS policy
- port10: no port QoS policy
- port11: qos_policy0 --> handled during "update_port" and updated
- port12: qos_policy1
net2: qos_policy0
- port20: no port QoS policy --> handled during "update_network"
and updated
- port21: qos_policy0 --> handled during "update_network", not updated
handled during "update_port" and updated
- port22: qos_policy1 --> handled during "update_network", not updated
fip1: qos_policy0
fip2: qos_policy1
router1: qos_policy0
router2: qos_policy1
"""
self.ports[1].qos_policy_id = self.qos_policies[0].id
self.ports[1].update()
self.ports[2].qos_policy_id = self.qos_policies[1].id
self.ports[2].update()
self.ports[4].qos_policy_id = self.qos_policies[0].id
self.ports[4].update()
self.ports[5].qos_policy_id = self.qos_policies[1].id
self.ports[5].update()
self.networks[1] = self._update_network(
self.networks[1]['id'], self.qos_policies[0].id)
self.fips[0].qos_policy_id = self.qos_policies[0].id
self.fips[0].update()
self.fips[1].qos_policy_id = self.qos_policies[1].id
self.fips[1].update()
self._update_router_qos(self.ctx, self.routers[0].id,
self.qos_policies[0].id)
self._update_router_qos(self.ctx, self.routers[1].id,
self.qos_policies[1].id)
mock_qos_rules = mock.Mock()
with mock.patch.object(self.qos_driver, '_qos_rules',
return_value=mock_qos_rules), \
mock.patch.object(self.qos_driver, 'update_floatingip') as \
mock_update_fip, \
mock.patch.object(self.qos_driver, 'update_router') as \
mock_update_router:
self.qos_driver.update_policy(self.ctx, self.qos_policies[0])
# Ports updated from "update_port": self.ports[1], self.ports[4]
updated_ports = [self.ports[1], self.ports[4]]
calls = [mock.call(self.txn, port.id, port.network_id,
self.tenant_type, self.qos_policies[0].id,
mock_qos_rules, lsp=None)
for port in updated_ports]
# Port updated from "update_network": self.ports[3]
calls.append(mock.call(self.txn, self.ports[3].id,
self.ports[3].network_id, self.tenant_type,
self.qos_policies[0].id, mock_qos_rules))
# We can't ensure the call order because we are not enforcing any order
# when retrieving the port and the network list.
self.mock_rules.assert_has_calls(calls, any_order=True)
with db_api.CONTEXT_READER.using(self.ctx):
fip = self.qos_driver._plugin_l3.get_floatingip(self.ctx,
self.fips[0].id)
mock_update_fip.assert_called_once_with(self.txn, fip)
with db_api.CONTEXT_READER.using(self.ctx):
router = self.qos_driver._plugin_l3.get_router(self.ctx,
self.routers[0].id)
mock_update_router.assert_called_once_with(self.txn, router)
2.6 fip 绑定 QoS: qos_driver.update_floatingip
python
def test_update_floatingip(self):
# NOTE(ralonsoh): this rule will always apply:
# - If the FIP is being deleted, "qos_del_ext_ids" is called;
# "qos_add" and "qos_del" won't.
# - If the FIP is added or updated, "qos_del_ext_ids" won't be called
# and "qos_add" or "qos_del" will, depending on the rule directions.
nb_idl = self.qos_driver._driver._nb_idl
fip = self.fips[0]
original_fip = self.fips[1]
txn = mock.Mock()
# Update FIP, no QoS policy nor port/router
self.qos_driver.update_floatingip(txn, fip)
nb_idl.qos_del_ext_ids.assert_called_once()
nb_idl.qos_add.assert_not_called()
nb_idl.qos_del.assert_not_called()
nb_idl.reset_mock()
# Attach a port and a router, not QoS policy
fip.router_id = self.router_fips.id
fip.fixed_port_id = self.fips_ports[0].id
fip.update()
self.qos_driver.update_floatingip(txn, fip)
nb_idl.qos_del_ext_ids.assert_called_once()
nb_idl.qos_add.assert_not_called()
nb_idl.qos_del.assert_not_called()
nb_idl.reset_mock()
# Add a QoS policy
fip.qos_policy_id = self.qos_policies[0].id
fip.update()
self.qos_driver.update_floatingip(txn, fip)
nb_idl.qos_del_ext_ids.assert_not_called()
# QoS DSCP rule has only egress direction, ingress one is deleted.
# Check "OVNClientQosExtension.update_floatingip" and how the OVN QoS
# rules are added (if there is a rule in this direction) or deleted.
nb_idl.qos_add.assert_called_once()
nb_idl.qos_del.assert_called_once()
nb_idl.reset_mock()
# Remove QoS
fip.qos_policy_id = None
fip.update()
original_fip.qos_policy_id = self.qos_policies[0].id
original_fip.update()
self.qos_driver.update_floatingip(txn, fip)
nb_idl.qos_del_ext_ids.assert_called_once()
nb_idl.qos_add.assert_not_called()
nb_idl.qos_del.assert_not_called()
nb_idl.reset_mock()
# Add network QoS policy
fip.qos_network_policy_id = self.qos_policies[0].id
fip.update()
self.qos_driver.update_floatingip(txn, fip)
nb_idl.qos_del_ext_ids.assert_not_called()
nb_idl.qos_add.assert_called_once()
nb_idl.qos_del.assert_called_once()
nb_idl.reset_mock()
# Add again another QoS policy
fip.qos_policy_id = self.qos_policies[1].id
fip.update()
original_fip.qos_policy_id = None
original_fip.update()
self.qos_driver.update_floatingip(txn, fip)
nb_idl.qos_del_ext_ids.assert_not_called()
nb_idl.qos_add.assert_called_once()
nb_idl.qos_del.assert_called_once()
nb_idl.reset_mock()
# Detach the port and the router
fip.router_id = None
fip.fixed_port_id = None
fip.update()
original_fip.router_id = self.router_fips.id
original_fip.fixed_port_id = self.fips_ports[0].id
original_fip.qos_policy_id = self.qos_policies[1].id
original_fip.update()
self.qos_driver.update_floatingip(txn, fip)
nb_idl.qos_del_ext_ids.assert_called_once()
nb_idl.qos_add.assert_not_called()
nb_idl.qos_del.assert_not_called()
nb_idl.reset_mock()
# Force reset (delete any QoS)
fip_dict = {'floating_network_id': fip.floating_network_id,
'id': fip.id}
self.qos_driver.update_floatingip(txn, fip_dict)
nb_idl.qos_del_ext_ids.assert_called_once()
nb_idl.qos_add.assert_not_called()
nb_idl.qos_del.assert_not_called()
2.7 router 以及 network 绑定 qos
self.qos_driver.update_router self._update_network
python
def test_update_router(self):
nb_idl = self.qos_driver._driver._nb_idl
txn = mock.Mock()
# Update router, no QoS policy set.
router = self._get_router(self.routers[0].id)
self.qos_driver.update_router(txn, router)
nb_idl.qos_add.assert_not_called()
self.assertEqual(2, nb_idl.qos_del.call_count)
nb_idl.reset_mock()
# Add QoS policy.
self._update_router_qos(self.ctx, router['id'],
self.qos_policies[0].id)
router = self._get_router(self.routers[0].id)
self.qos_driver.update_router(txn, router)
nb_idl.qos_add.assert_called_once()
nb_idl.qos_del.assert_called_once()
nb_idl.reset_mock()
# Remove QoS
self._update_router_qos(self.ctx, router['id'],
self.qos_policies[0].id, attach=False)
router = self._get_router(self.routers[0].id)
self.qos_driver.update_router(txn, router)
nb_idl.qos_add.assert_not_called()
self.assertEqual(2, nb_idl.qos_del.call_count)
nb_idl.reset_mock()
# Add network QoS policy
ext_net = self.router_networks[0]
self.networks[1] = self._update_network(ext_net['id'],
self.qos_policies[1].id)
router = self._get_router(self.routers[0].id)
self.qos_driver.update_router(txn, router)
nb_idl.qos_add.assert_called_once()
nb_idl.qos_del.assert_called_once()
nb_idl.reset_mock()
2. router l3 qos 插件
python
class L3RouterPlugin(service_base.ServicePluginBase,
extraroute_db.ExtraRoute_db_mixin,
l3_hamode_db.L3_HA_NAT_db_mixin,
l3_gateway_ip_qos.L3_gw_ip_qos_db_mixin,
l3_dvr_ha_scheduler_db.L3_DVR_HA_scheduler_db_mixin,
dns_db.DNSDbMixin,
l3_fip_qos.FloatingQoSDbMixin,
l3_fip_port_details.Fip_port_details_db_mixin,
l3_fip_pools_db.FloatingIPPoolsMixin):
def supported_extension_aliases(self):
if not hasattr(self, '_aliases'):
aliases = self._supported_extension_aliases[:]
disable_dvr_extension_by_config(aliases)
disable_l3_qos_extension_by_plugins('qos-fip', aliases)
disable_l3_qos_extension_by_plugins('qos-gateway-ip', aliases)
self._aliases = aliases
return self._aliases
总结: 3. neutron (CMS) 操作 ovn nb 的位置
neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/extensions/qos.py

bash
def _apply_ovn_rule_qos(self, txn, rules, ovn_rule_qos):
"""Add or remove the OVN QoS rules (for max-bw and DSCP rules only).
:param txn: the ovsdbapp transaction object.
:param rules: Neutron QoS rules (per direction).
:param ovn_rule_qos: dictionary with the Neutron QoS rules with the
parameters needed to call ``qos_add`` or
``qos_del`` commands.
"""
if rules and not (ovn_rule_qos.get('rate') is None and
ovn_rule_qos.get('dscp') is None):
# NOTE(ralonsoh): with "may_exist=True", the "qos_add" will
# create the QoS OVN rule or update the existing one.
# NOTE(ralonsoh): if the Neutron QoS rules don't have at least
# a max-bw rule or a DSCP rule, skip this command.
txn.add(self.nb_idl.qos_add(**ovn_rule_qos, may_exist=True))
else:
# Delete, if exists, the QoS rule in this direction.
txn.add(self.nb_idl.qos_del(**ovn_rule_qos, if_exists=True))