This patch enables to bind a QoS policy to the router gateway, then in L3 agent side SNAT traffic for the VMs without floating IPs can be limited under the policy bandwidth rules. This is suit for all kinds of L3 routers: DVR, DVR with SNAT HA, L3 HA and Legacy. API update router gateway json: { router": { "external_gateway_info": { ... "qos_policy_id": "policy-uuid" } } } Depends-On: https://review.openstack.org/#/c/567497/ Partially-Implements blueprint: router-gateway-ip-qos Closes-Bug: #1757044 Related-Bug: #1596611 Change-Id: I26e22bce7edd1f93b2ac0048b61b14f858938537changes/68/424468/37
parent
9ad2e05088
commit
00bf365025
@ -0,0 +1,129 @@
|
||||
# Copyright 2018 OpenStack Foundation
|
||||
# Copyright 2017 Letv Cloud Computing
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
from neutron_lib.api.definitions import l3 as l3_apidef
|
||||
from neutron_lib.api.definitions import qos_gateway_ip
|
||||
from neutron_lib.api import extensions
|
||||
from neutron_lib.services.qos import constants as qos_consts
|
||||
from oslo_log import log as logging
|
||||
|
||||
from neutron.db import _resource_extend as resource_extend
|
||||
from neutron.db import l3_db
|
||||
from neutron.db import l3_gwmode_db
|
||||
from neutron.objects.qos import policy as policy_object
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@resource_extend.has_resource_extenders
|
||||
class L3_gw_ip_qos_dbonly_mixin(l3_gwmode_db.L3_NAT_dbonly_mixin):
|
||||
"""Mixin class to add router gateway IP's QoS extra attributes."""
|
||||
|
||||
_gw_ip_qos = None
|
||||
|
||||
@staticmethod
|
||||
@resource_extend.extends([l3_apidef.ROUTERS])
|
||||
def _extend_router_dict_gw_qos(router_res, router_db):
|
||||
if router_db.gw_port_id and router_db.get('qos_policy_binding'):
|
||||
policy_id = router_db.qos_policy_binding.policy_id
|
||||
router_res[l3_apidef.EXTERNAL_GW_INFO].update(
|
||||
{qos_consts.QOS_POLICY_ID: policy_id})
|
||||
|
||||
@property
|
||||
def _is_gw_ip_qos_supported(self):
|
||||
if self._gw_ip_qos is None:
|
||||
# Check L3 service plugin
|
||||
self._gw_ip_qos = extensions.is_extension_supported(
|
||||
self, qos_gateway_ip.ALIAS)
|
||||
return self._gw_ip_qos
|
||||
|
||||
def _create_gw_ip_qos_db(self, context, router_id, policy_id):
|
||||
policy = policy_object.QosPolicy.get_policy_obj(context, policy_id)
|
||||
policy.attach_router(router_id)
|
||||
|
||||
def _delete_gw_ip_qos_db(self, context, router_id, policy_id):
|
||||
policy = policy_object.QosPolicy.get_policy_obj(context, policy_id)
|
||||
policy.detach_router(router_id)
|
||||
|
||||
def _update_router_gw_info(self, context, router_id, info, router=None):
|
||||
# Calls superclass, pass router db object for avoiding re-loading
|
||||
router = super(L3_gw_ip_qos_dbonly_mixin,
|
||||
self)._update_router_gw_info(
|
||||
context, router_id, info, router)
|
||||
|
||||
if self._is_gw_ip_qos_supported and router.gw_port:
|
||||
self._update_router_gw_qos_policy(context, router_id,
|
||||
info, router)
|
||||
|
||||
return router
|
||||
|
||||
def _get_router_gateway_policy_binding(self, context, router_id):
|
||||
router = self._get_router(context, router_id)
|
||||
return router.qos_policy_binding
|
||||
|
||||
def _update_router_gw_qos_policy(self, context, router_id, info, router):
|
||||
if not info or qos_consts.QOS_POLICY_ID not in info:
|
||||
# An explicit 'None' for `qos_polcy_id` indicates to clear
|
||||
# the router gateway IP policy. So if info does not have
|
||||
# the key `qos_polcy_id`, we can not decide what behavior
|
||||
# to be done, then directly return here.
|
||||
return
|
||||
|
||||
new_qos_policy_id = info[qos_consts.QOS_POLICY_ID]
|
||||
if router.qos_policy_binding:
|
||||
old_qos_policy_id = router.qos_policy_binding.policy_id
|
||||
|
||||
if old_qos_policy_id == new_qos_policy_id:
|
||||
return
|
||||
if old_qos_policy_id:
|
||||
self._delete_gw_ip_qos_db(context,
|
||||
router_id,
|
||||
old_qos_policy_id)
|
||||
|
||||
with context.session.begin(subtransactions=True):
|
||||
context.session.refresh(router)
|
||||
|
||||
if new_qos_policy_id:
|
||||
self._create_gw_ip_qos_db(
|
||||
context, router_id, new_qos_policy_id)
|
||||
|
||||
def _build_routers_list(self, context, routers, gw_ports):
|
||||
routers = super(L3_gw_ip_qos_dbonly_mixin,
|
||||
self)._build_routers_list(
|
||||
context, routers, gw_ports)
|
||||
|
||||
if not self._is_gw_ip_qos_supported:
|
||||
return routers
|
||||
|
||||
for rtr in routers:
|
||||
gw_port_id = rtr['gw_port_id']
|
||||
# Collect gw ports only if available
|
||||
if gw_port_id and gw_ports.get(gw_port_id):
|
||||
rtr['gw_port'] = gw_ports[gw_port_id]
|
||||
router_gateway_policy_binding = (
|
||||
self._get_router_gateway_policy_binding(
|
||||
context, rtr['id']))
|
||||
qos_policy_id = None
|
||||
if router_gateway_policy_binding:
|
||||
qos_policy_id = router_gateway_policy_binding.policy_id
|
||||
rtr['gw_port'][qos_consts.QOS_POLICY_ID] = qos_policy_id
|
||||
return routers
|
||||
|
||||
|
||||
class L3_gw_ip_qos_db_mixin(L3_gw_ip_qos_dbonly_mixin,
|
||||
l3_db.L3_NAT_db_mixin):
|
||||
pass
|
@ -1 +1 @@
|
||||
cada2437bf41
|
||||
195176fb410d
|
||||
|
@ -0,0 +1,45 @@
|
||||
# Copyright 2018 OpenStack Foundation
|
||||
# Copyright 2017 Letv Cloud Computing
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
"""router gateway IP QoS
|
||||
|
||||
Revision ID: 195176fb410d
|
||||
Revises: cada2437bf41
|
||||
Create Date: 2016-04-28 12:38:09.872706
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
from neutron_lib.db import constants as db_const
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '195176fb410d'
|
||||
down_revision = 'cada2437bf41'
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.create_table(
|
||||
'qos_router_gw_policy_bindings',
|
||||
sa.Column('policy_id',
|
||||
sa.String(length=db_const.UUID_FIELD_SIZE),
|
||||
sa.ForeignKey('qos_policies.id', ondelete='CASCADE'),
|
||||
nullable=False, primary_key=True),
|
||||
sa.Column('router_id',
|
||||
sa.String(length=db_const.UUID_FIELD_SIZE),
|
||||
sa.ForeignKey('routers.id', ondelete='CASCADE'),
|
||||
nullable=False, unique=True, primary_key=True))
|
@ -0,0 +1,24 @@
|
||||
# Copyright 2018 OpenStack Foundation
|
||||
# Copyright 2017 Letv Cloud Computing
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from neutron_lib.api.definitions import qos_gateway_ip as apidef
|
||||
from neutron_lib.api import extensions
|
||||
|
||||
|
||||
class Qos_gateway_ip(extensions.APIExtensionDescriptor):
|
||||
"""Extension class supporting gateway IP rate limit in all router."""
|
||||
|
||||
api_definition = apidef
|
@ -0,0 +1,233 @@
|
||||
# Copyright 2018 OpenStack Foundation
|
||||
# Copyright 2017 Letv Cloud Computing
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
from neutron_lib.api.definitions import l3 as l3_apidef
|
||||
from neutron_lib.api.definitions import qos_gateway_ip
|
||||
from neutron_lib import context
|
||||
from neutron_lib.services.qos import constants as qos_consts
|
||||
from oslo_config import cfg
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from neutron.conf.db import extraroute_db
|
||||
from neutron.db import l3_gateway_ip_qos
|
||||
from neutron.extensions import l3
|
||||
from neutron.objects.qos import policy
|
||||
from neutron.tests.unit.extensions import test_l3
|
||||
|
||||
|
||||
class GatewayIPQoSTestExtensionManager(object):
|
||||
|
||||
def get_resources(self):
|
||||
l3_apidef.RESOURCE_ATTRIBUTE_MAP['routers'].update(
|
||||
qos_gateway_ip.RESOURCE_ATTRIBUTE_MAP['routers'])
|
||||
return l3.L3.get_resources()
|
||||
|
||||
def get_actions(self):
|
||||
return []
|
||||
|
||||
def get_request_extensions(self):
|
||||
return []
|
||||
|
||||
|
||||
class TestGatewayIPQoSIntPlugin(
|
||||
test_l3.TestL3NatIntPlugin,
|
||||
l3_gateway_ip_qos.L3_gw_ip_qos_db_mixin):
|
||||
supported_extension_aliases = ["external-net",
|
||||
"router",
|
||||
"ext-gw-mode",
|
||||
qos_gateway_ip.ALIAS]
|
||||
|
||||
|
||||
class TestGatewayIPQoSL3NatServicePlugin(
|
||||
test_l3.TestL3NatServicePlugin,
|
||||
l3_gateway_ip_qos.L3_gw_ip_qos_db_mixin):
|
||||
supported_extension_aliases = ["router",
|
||||
"ext-gw-mode",
|
||||
qos_gateway_ip.ALIAS]
|
||||
|
||||
|
||||
class GatewayIPQoSDBTestCaseBase(object):
|
||||
|
||||
def test_create_router_gateway_with_qos_policy(self):
|
||||
ctx = context.get_admin_context()
|
||||
policy_obj = policy.QosPolicy(ctx,
|
||||
id=uuidutils.generate_uuid(),
|
||||
project_id='tenant', name='pol1',
|
||||
rules=[])
|
||||
policy_obj.create()
|
||||
with self.subnet(cidr='11.0.0.0/24') as public_sub,\
|
||||
self.router() as r:
|
||||
self._set_net_external(public_sub['subnet']['network_id'])
|
||||
res = self._add_external_gateway_to_router(
|
||||
r['router']['id'],
|
||||
public_sub['subnet']['network_id'],
|
||||
policy_id=policy_obj.id)
|
||||
self.assertEqual(
|
||||
policy_obj.id,
|
||||
res['router']['external_gateway_info'].get(
|
||||
qos_consts.QOS_POLICY_ID))
|
||||
|
||||
def test_update_router_gateway_with_qos_policy(self):
|
||||
ctx = context.get_admin_context()
|
||||
policy_obj = policy.QosPolicy(ctx,
|
||||
id=uuidutils.generate_uuid(),
|
||||
project_id='tenant', name='pol1',
|
||||
rules=[])
|
||||
policy_obj.create()
|
||||
with self.subnet(cidr='11.0.0.0/24') as public_sub,\
|
||||
self.router() as r:
|
||||
self._set_net_external(public_sub['subnet']['network_id'])
|
||||
res = self._add_external_gateway_to_router(
|
||||
r['router']['id'],
|
||||
public_sub['subnet']['network_id'])
|
||||
self.assertIsNone(
|
||||
res['router']['external_gateway_info'].get(
|
||||
qos_consts.QOS_POLICY_ID))
|
||||
|
||||
# update router gateway
|
||||
res = self._add_external_gateway_to_router(
|
||||
r['router']['id'],
|
||||
public_sub['subnet']['network_id'],
|
||||
policy_id=policy_obj.id)
|
||||
self.assertEqual(
|
||||
policy_obj.id,
|
||||
res['router']['external_gateway_info'].get(
|
||||
qos_consts.QOS_POLICY_ID))
|
||||
|
||||
def test_clear_router_gateway_and_create_with_old_qos_policy_implicitly(
|
||||
self):
|
||||
ctx = context.get_admin_context()
|
||||
policy_obj = policy.QosPolicy(ctx,
|
||||
id=uuidutils.generate_uuid(),
|
||||
project_id='tenant', name='pol1',
|
||||
rules=[])
|
||||
policy_obj.create()
|
||||
with self.subnet(cidr='11.0.0.0/24') as public_sub,\
|
||||
self.router() as r:
|
||||
self._set_net_external(public_sub['subnet']['network_id'])
|
||||
res = self._add_external_gateway_to_router(
|
||||
r['router']['id'],
|
||||
public_sub['subnet']['network_id'],
|
||||
policy_id=policy_obj.id)
|
||||
self.assertEqual(
|
||||
policy_obj.id,
|
||||
res['router']['external_gateway_info'].get(
|
||||
qos_consts.QOS_POLICY_ID))
|
||||
|
||||
# Clear router gateway
|
||||
self._remove_external_gateway_from_router(
|
||||
r['router']['id'],
|
||||
public_sub['subnet']['network_id'],
|
||||
external_gw_info={})
|
||||
|
||||
# Create router gateway again, then the qos policy binding will be
|
||||
# reused here.
|
||||
res = self._add_external_gateway_to_router(
|
||||
r['router']['id'],
|
||||
public_sub['subnet']['network_id'])
|
||||
self.assertEqual(
|
||||
policy_obj.id,
|
||||
res['router']['external_gateway_info'].get(
|
||||
qos_consts.QOS_POLICY_ID))
|
||||
|
||||
def test_clear_router_gateway_qos_policy(self):
|
||||
ctx = context.get_admin_context()
|
||||
policy_obj = policy.QosPolicy(ctx,
|
||||
id=uuidutils.generate_uuid(),
|
||||
project_id='tenant', name='pol1',
|
||||
rules=[])
|
||||
policy_obj.create()
|
||||
with self.subnet(cidr='11.0.0.0/24') as public_sub,\
|
||||
self.router() as r:
|
||||
self._set_net_external(public_sub['subnet']['network_id'])
|
||||
res = self._add_external_gateway_to_router(
|
||||
r['router']['id'],
|
||||
public_sub['subnet']['network_id'])
|
||||
self.assertIsNone(
|
||||
res['router']['external_gateway_info'].get(
|
||||
qos_consts.QOS_POLICY_ID))
|
||||
|
||||
# update router gateway
|
||||
res = self._add_external_gateway_to_router(
|
||||
r['router']['id'],
|
||||
public_sub['subnet']['network_id'],
|
||||
policy_id=policy_obj.id)
|
||||
self.assertEqual(
|
||||
policy_obj.id,
|
||||
res['router']['external_gateway_info'].get(
|
||||
qos_consts.QOS_POLICY_ID))
|
||||
|
||||
# Explicitly clear router gateway qos policy binding
|
||||
res = self._add_external_gateway_to_router(
|
||||
r['router']['id'],
|
||||
public_sub['subnet']['network_id'],
|
||||
policy_id=None,
|
||||
is_remove=True)
|
||||
self.assertIsNone(
|
||||
res['router']['external_gateway_info'].get(
|
||||
qos_consts.QOS_POLICY_ID))
|
||||
|
||||
|
||||
class GatewayIPQoSDBIntTestCase(test_l3.L3BaseForIntTests,
|
||||
test_l3.L3NatTestCaseMixin,
|
||||
GatewayIPQoSDBTestCaseBase):
|
||||
|
||||
def setUp(self, plugin=None):
|
||||
if not plugin:
|
||||
plugin = ('neutron.tests.unit.extensions.test_qos_gateway_ip.'
|
||||
'TestGatewayIPQoSIntPlugin')
|
||||
service_plugins = {'qos': 'neutron.services.qos.qos_plugin.QoSPlugin'}
|
||||
|
||||
extraroute_db.register_db_extraroute_opts()
|
||||
# for these tests we need to enable overlapping ips
|
||||
cfg.CONF.set_default('allow_overlapping_ips', True)
|
||||
cfg.CONF.set_default('max_routes', 3)
|
||||
|
||||
ext_mgr = GatewayIPQoSTestExtensionManager()
|
||||
super(test_l3.L3BaseForIntTests, self).setUp(
|
||||
plugin=plugin,
|
||||
ext_mgr=ext_mgr,
|
||||
service_plugins=service_plugins)
|
||||
|
||||
self.setup_notification_driver()
|
||||
|
||||
|
||||
class GatewayIPQoSDBSepTestCase(test_l3.L3BaseForSepTests,
|
||||
test_l3.L3NatTestCaseMixin,
|
||||
GatewayIPQoSDBTestCaseBase):
|
||||
|
||||
def setUp(self):
|
||||
# the plugin without L3 support
|
||||
plugin = 'neutron.tests.unit.extensions.test_l3.TestNoL3NatPlugin'
|
||||
# the L3 service plugin
|
||||
l3_plugin = ('neutron.tests.unit.extensions.test_qos_gateway_ip.'
|
||||
'TestGatewayIPQoSL3NatServicePlugin')
|
||||
service_plugins = {'l3_plugin_name': l3_plugin,
|
||||
'qos': 'neutron.services.qos.qos_plugin.QoSPlugin'}
|
||||
|
||||
extraroute_db.register_db_extraroute_opts()
|
||||
# for these tests we need to enable overlapping ips
|
||||
cfg.CONF.set_default('allow_overlapping_ips', True)
|
||||
cfg.CONF.set_default('max_routes', 3)
|
||||
|
||||
ext_mgr = GatewayIPQoSTestExtensionManager()
|
||||
super(test_l3.L3BaseForSepTests, self).setUp(
|
||||
plugin=plugin,
|
||||
ext_mgr=ext_mgr,
|
||||
service_plugins=service_plugins)
|
||||
|
||||
self.setup_notification_driver()
|
@ -0,0 +1,15 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
A new attribute ``qos_policy_id`` is added to the L3 router
|
||||
gateway.
|
||||
|
||||
* It enables users to associate QoS policies to L3 router gateways
|
||||
to control the rate of transmission of the associated SNAT traffic.
|
||||
* At the moment, only bandwidth limit rules are supported in the
|
||||
QoS polices.
|
||||
* To enable this feature, the ``qos`` service plugin has to be
|
||||
configured in the Neutron server and the ``gateway_ip_qos``
|
||||
extension has to be configured in the L3 agents. Please refer to
|
||||
the ``QoS`` section of the ``OpenStack Networking Guide`` for more
|
||||
specific details.
|
Loading…
Reference in new issue