From db679ef7cb145fbede3dd514959baea81aad23a1 Mon Sep 17 00:00:00 2001 From: zhangxiaohan Date: Mon, 6 Nov 2017 11:09:16 +0800 Subject: [PATCH] QoS implementation(Part1: Qos Plugin) 1. What is the problem? Tricircle now don't support QoS service, we should add QoS servicesupporting. 2. What is the solution to the problem? We implement Tricircle QoS service by inherit neutron QoS plugin. For QoS automation deployment in local, we should implement QoS xjob jobs. Change-Id: Ifbf453b57f7e18919318e1dae2ca2849e149a29b Signed-off-by: xiaohan zhang --- devstack/plugin.sh | 16 + devstack/settings | 1 + .../add-qos-policy-rule-f8f1529d7ad5d888.yaml | 5 + setup.cfg | 2 + tricircle/common/client.py | 10 +- tricircle/common/constants.py | 31 +- tricircle/common/resource_handle.py | 32 +- tricircle/common/xrpcapi.py | 23 + tricircle/network/central_plugin.py | 236 +++-- tricircle/network/central_qos_plugin.py | 83 ++ tricircle/network/local_plugin.py | 10 + tricircle/network/qos_driver.py | 144 +++ tricircle/tempestplugin/gate_hook.sh | 3 + .../tempestplugin/qos_policy_rule_test.yaml | 861 ++++++++++++++++++ tricircle/tempestplugin/smoke_test.sh | 5 + tricircle/tempestplugin/task_runner.py | 6 +- .../tests/unit/network/test_central_plugin.py | 277 +++++- tricircle/tests/unit/network/test_qos.py | 249 +++++ tricircle/tests/unit/utils.py | 16 +- tricircle/xjob/xmanager.py | 299 +++++- 20 files changed, 2228 insertions(+), 81 deletions(-) create mode 100644 releasenotes/notes/add-qos-policy-rule-f8f1529d7ad5d888.yaml create mode 100644 tricircle/network/central_qos_plugin.py create mode 100644 tricircle/network/qos_driver.py create mode 100644 tricircle/tempestplugin/qos_policy_rule_test.yaml create mode 100644 tricircle/tests/unit/network/test_qos.py diff --git a/devstack/plugin.sh b/devstack/plugin.sh index 26b1c463..3e5e6415 100644 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -291,6 +291,11 @@ function start_central_neutron_server { iniset $NEUTRON_CONF.$server_index sfc drivers tricircle_sfc iniset $NEUTRON_CONF.$server_index flowclassifier drivers tricircle_fc fi + + if [ "$TRICIRCLE_ENABLE_QOS" == "True" ]; then + service_plugins+=",tricircle.network.central_qos_plugin.TricircleQosPlugin" + fi + if [ -n service_plugins ]; then service_plugins=$(echo $service_plugins| sed 's/^,//') iniset $NEUTRON_CONF.$server_index DEFAULT service_plugins "$service_plugins" @@ -324,6 +329,17 @@ function start_central_neutron_server { iniset $NEUTRON_CONF.$server_index tricircle enable_api_gateway False # default value of bridge_network_type is vxlan + if [ "$TRICIRCLE_ENABLE_QOS" == "True" ]; then + local p_exist=$(grep "^extension_drivers" /$Q_PLUGIN_CONF_FILE) + if [[ $p_exist != "" ]];then + if ! [[ $(echo $p_exist | grep "qos") ]];then + sed -i "s/$p_exist/$p_exist,qos/g" /$Q_PLUGIN_CONF_FILE + fi + else + sed -i "s/^\[ml2\]/\[ml2\]\nextension_drivers = qos/g" /$Q_PLUGIN_CONF_FILE + fi + fi + recreate_database $Q_DB_NAME$server_index $NEUTRON_BIN_DIR/neutron-db-manage --config-file $NEUTRON_CONF.$server_index --config-file /$Q_PLUGIN_CONF_FILE upgrade head diff --git a/devstack/settings b/devstack/settings index cc2adba3..07ab974e 100644 --- a/devstack/settings +++ b/devstack/settings @@ -13,6 +13,7 @@ TRICIRCLE_DEPLOY_WITH_CELL=${TRICIRCLE_DEPLOY_WITH_CELL:-False} # extensions working with tricircle TRICIRCLE_ENABLE_TRUNK=${TRICIRCLE_ENABLE_TRUNK:-False} TRICIRCLE_ENABLE_SFC=${TRICIRCLE_ENABLE_SFC:-False} +TRICIRCLE_ENABLE_QOS=${TRICIRCLE_ENABLE_QOS:-False} # these default settings are used for devstack based gate/check jobs TRICIRCLE_DEFAULT_VLAN_BRIDGE=${TRICIRCLE_DEFAULT_VLAN_BRIDGE:-br-vlan} diff --git a/releasenotes/notes/add-qos-policy-rule-f8f1529d7ad5d888.yaml b/releasenotes/notes/add-qos-policy-rule-f8f1529d7ad5d888.yaml new file mode 100644 index 00000000..e734f74f --- /dev/null +++ b/releasenotes/notes/add-qos-policy-rule-f8f1529d7ad5d888.yaml @@ -0,0 +1,5 @@ +--- +features: + - Provide central Neutron QoS plugin and implement QoS driver. Support QoS + policy creation, update and delete, QoS policy binding with network or + port. diff --git a/setup.cfg b/setup.cfg index f59343c4..df769948 100644 --- a/setup.cfg +++ b/setup.cfg @@ -64,6 +64,8 @@ tricircle.network.type_drivers = vlan = tricircle.network.drivers.type_vlan:VLANTypeDriver vxlan = tricircle.network.drivers.type_vxlan:VxLANTypeDriver flat = tricircle.network.drivers.type_flat:FlatTypeDriver +tricircle.network.extension_drivers = + qos = neutron.plugins.ml2.extensions.qos:QosExtensionDriver networking_sfc.flowclassifier.drivers = tricircle_fc = tricircle.network.central_fc_driver:TricircleFcDriver networking_sfc.sfc.drivers = diff --git a/tricircle/common/client.py b/tricircle/common/client.py index a704ab85..8eb0f867 100644 --- a/tricircle/common/client.py +++ b/tricircle/common/client.py @@ -209,8 +209,14 @@ class Client(object): if (handle_obj.support_resource[resource] & index) == 0: continue self.operation_resources_map[operation].add(resource) - setattr(self, '%s_%ss' % (operation, resource), - functools.partial( + if resource == 'qos_policy': + setattr(self, '%s_qos_policies' % operation, + functools.partial( + getattr(self, '%s_resources' % operation), + resource)) + else: + setattr(self, '%s_%ss' % (operation, resource), + functools.partial( getattr(self, '%s_resources' % operation), resource)) diff --git a/tricircle/common/constants.py b/tricircle/common/constants.py index 50334c46..82d249c4 100644 --- a/tricircle/common/constants.py +++ b/tricircle/common/constants.py @@ -36,6 +36,7 @@ RT_ROUTER = 'router' RT_NS_ROUTER = 'ns_router' RT_SG = 'security_group' RT_FIP = 'floatingip' +RT_QOS = 'qos_policy' REAL_SHADOW_TYPE_MAP = { RT_NETWORK: RT_SD_NETWORK, @@ -48,7 +49,7 @@ REAL_SHADOW_TYPE_MAP = { def is_valid_resource_type(resource_type): resource_type_table = [RT_NETWORK, RT_SUBNET, RT_PORT, RT_ROUTER, RT_SG, RT_TRUNK, RT_PORT_PAIR, RT_PORT_PAIR_GROUP, - RT_FLOW_CLASSIFIER, RT_PORT_CHAIN] + RT_FLOW_CLASSIFIER, RT_PORT_CHAIN, RT_QOS] return resource_type in resource_type_table @@ -113,6 +114,10 @@ JT_SHADOW_PORT_SETUP = 'shadow_port_setup' JT_TRUNK_SYNC = 'trunk_sync' JT_SFC_SYNC = 'sfc_sync' JT_RESOURCE_RECYCLE = 'resource_recycle' +JT_QOS_CREATE = 'qos_create' +JT_QOS_UPDATE = 'qos_update' +JT_QOS_DELETE = 'qos_delete' +JT_SYNC_QOS_RULE = 'sync_qos_rule' # network type NT_LOCAL = 'local' @@ -148,7 +153,17 @@ job_resource_map = { JT_SFC_SYNC: [(None, "pod_id"), (RT_PORT_CHAIN, "portchain_id"), (RT_NETWORK, "network_id")], - JT_RESOURCE_RECYCLE: [(None, "project_id")] + JT_RESOURCE_RECYCLE: [(None, "project_id")], + JT_QOS_CREATE: [(None, "pod_id"), + (RT_QOS, "policy_id"), + (None, "res_type"), + (None, "res_id")], + JT_QOS_UPDATE: [(None, "pod_id"), + (RT_QOS, "policy_id")], + JT_QOS_DELETE: [(None, "pod_id"), + (RT_QOS, "policy_id")], + JT_SYNC_QOS_RULE: [(None, "rule_id"), + (RT_QOS, "policy_id")] } # map raw job status to more human readable job status @@ -173,7 +188,11 @@ job_handles = { JT_TRUNK_SYNC: "sync_trunk", JT_SHADOW_PORT_SETUP: "setup_shadow_ports", JT_SFC_SYNC: "sync_service_function_chain", - JT_RESOURCE_RECYCLE: "recycle_resources" + JT_RESOURCE_RECYCLE: "recycle_resources", + JT_QOS_CREATE: "create_qos_policy", + JT_QOS_UPDATE: "update_qos_policy", + JT_QOS_DELETE: "delete_qos_policy", + JT_SYNC_QOS_RULE: "sync_qos_policy_rules" } # map job type to its primary resource and then we only validate the project_id @@ -189,7 +208,11 @@ job_primary_resource_map = { JT_TRUNK_SYNC: (RT_TRUNK, "trunk_id"), JT_SHADOW_PORT_SETUP: (RT_NETWORK, "network_id"), JT_SFC_SYNC: (RT_PORT_CHAIN, "portchain_id"), - JT_RESOURCE_RECYCLE: (None, "project_id") + JT_RESOURCE_RECYCLE: (None, "project_id"), + JT_QOS_CREATE: (RT_QOS, "policy_id"), + JT_QOS_UPDATE: (RT_QOS, "policy_id"), + JT_QOS_DELETE: (RT_QOS, "policy_id"), + JT_SYNC_QOS_RULE: (RT_QOS, "policy_id") } # admin API request path diff --git a/tricircle/common/resource_handle.py b/tricircle/common/resource_handle.py index 36bc7595..2e3faf24 100644 --- a/tricircle/common/resource_handle.py +++ b/tricircle/common/resource_handle.py @@ -35,6 +35,9 @@ LIST, CREATE, DELETE, GET, ACTION, UPDATE = 1, 2, 4, 8, 16, 32 operation_index_map = {'list': LIST, 'create': CREATE, 'delete': DELETE, 'get': GET, 'action': ACTION, 'update': UPDATE} +policy_rules = ('bandwidth_limit_rule', 'dscp_marking_rule', + 'minimum_bandwidth_rule') + LOG = logging.getLogger(__name__) @@ -98,7 +101,11 @@ class NeutronResourceHandle(ResourceHandle): 'port_chain': LIST | CREATE | DELETE | GET | UPDATE, 'port_pair_group': LIST | CREATE | DELETE | GET | UPDATE, 'port_pair': LIST | CREATE | DELETE | GET | UPDATE, - 'flow_classifier': LIST | CREATE | DELETE | GET | UPDATE} + 'flow_classifier': LIST | CREATE | DELETE | GET | UPDATE, + 'qos_policy': LIST | CREATE | DELETE | GET | UPDATE, + 'bandwidth_limit_rule': LIST | CREATE | DELETE | GET | UPDATE, + 'dscp_marking_rule': LIST | CREATE | DELETE | GET | UPDATE, + 'minimum_bandwidth_rule': LIST | CREATE | DELETE | GET | UPDATE} def _get_client(self, cxt): token = cxt.auth_token @@ -113,7 +120,10 @@ class NeutronResourceHandle(ResourceHandle): def handle_list(self, cxt, resource, filters): try: client = self._get_client(cxt) - collection = '%ss' % resource + if resource == 'qos_policy': + collection = 'qos_policies' + else: + collection = '%ss' % resource search_opts = _transform_filters(filters) return [res for res in getattr( client, 'list_%s' % collection)(**search_opts)[collection]] @@ -126,6 +136,10 @@ class NeutronResourceHandle(ResourceHandle): client = self._get_client(cxt) ret = getattr(client, 'create_%s' % resource)( *args, **kwargs) + + if resource == 'qos_policy': + return ret['policy'] + if resource in ret: return ret[resource] else: @@ -137,6 +151,9 @@ class NeutronResourceHandle(ResourceHandle): def handle_update(self, cxt, resource, *args, **kwargs): try: client = self._get_client(cxt) + if resource == 'qos_policy': + return getattr(client, 'update_%s' % resource)( + *args, **kwargs)['policy'] return getattr(client, 'update_%s' % resource)( *args, **kwargs)[resource] except q_exceptions.ConnectionFailed: @@ -146,6 +163,13 @@ class NeutronResourceHandle(ResourceHandle): def handle_get(self, cxt, resource, resource_id): try: client = self._get_client(cxt) + if resource == 'qos_policy': + return getattr(client, 'show_%s' % resource)( + resource_id)['policy'] + if resource in policy_rules: + (rule_id, policy_id) = resource_id.split('#') + return getattr(client, 'show_%s' % resource)( + rule_id, policy_id)[resource] return getattr(client, 'show_%s' % resource)(resource_id)[resource] except q_exceptions.ConnectionFailed: raise exceptions.EndpointNotAvailable( @@ -157,6 +181,10 @@ class NeutronResourceHandle(ResourceHandle): def handle_delete(self, cxt, resource, resource_id): try: client = self._get_client(cxt) + if resource in policy_rules: + (rule_id, policy_id) = resource_id.split('#') + return getattr(client, 'delete_%s' % resource)( + rule_id, policy_id) return getattr(client, 'delete_%s' % resource)(resource_id) except q_exceptions.ConnectionFailed: raise exceptions.EndpointNotAvailable( diff --git a/tricircle/common/xrpcapi.py b/tricircle/common/xrpcapi.py index 83252787..8a8b8805 100644 --- a/tricircle/common/xrpcapi.py +++ b/tricircle/common/xrpcapi.py @@ -140,3 +140,26 @@ class XJobAPI(object): ctxt, project_id, constants.job_handles[constants.JT_RESOURCE_RECYCLE], constants.JT_RESOURCE_RECYCLE, project_id) + + def create_qos_policy(self, ctxt, project_id, policy_id, pod_id, + res_type, res_id=None): + self.invoke_method( + ctxt, project_id, constants.job_handles[constants.JT_QOS_CREATE], + constants.JT_QOS_CREATE, '%s#%s#%s#%s' % (pod_id, policy_id, + res_type, res_id)) + + def update_qos_policy(self, ctxt, project_id, policy_id, pod_id): + self.invoke_method( + ctxt, project_id, constants.job_handles[constants.JT_QOS_UPDATE], + constants.JT_QOS_UPDATE, '%s#%s' % (pod_id, policy_id)) + + def delete_qos_policy(self, ctxt, project_id, policy_id, pod_id): + self.invoke_method( + ctxt, project_id, constants.job_handles[constants.JT_QOS_DELETE], + constants.JT_QOS_DELETE, '%s#%s' % (pod_id, policy_id)) + + def sync_qos_policy_rules(self, ctxt, project_id, policy_id): + self.invoke_method( + ctxt, project_id, + constants.job_handles[constants.JT_SYNC_QOS_RULE], + constants.JT_SYNC_QOS_RULE, policy_id) diff --git a/tricircle/network/central_plugin.py b/tricircle/network/central_plugin.py index 11600559..eb929a32 100644 --- a/tricircle/network/central_plugin.py +++ b/tricircle/network/central_plugin.py @@ -28,7 +28,9 @@ from neutron.callbacks import exceptions as callbacks_exc from neutron.callbacks import registry from neutron.callbacks import resources import neutron.common.exceptions as ml2_exceptions +from neutron.conf.plugins.ml2 import config # noqa from neutron.db import _resource_extend as resource_extend +from neutron.db import agents_db from neutron.db import api as q_db_api from neutron.db.availability_zone import router as router_az from neutron.db import db_base_plugin_v2 @@ -44,6 +46,8 @@ from neutron.db import l3_hamode_db # noqa from neutron.db import models_v2 from neutron.db import portbindings_db from neutron.extensions import providernet as provider +from neutron.objects.qos import policy as policy_object +from neutron.plugins.ml2 import managers as n_managers from neutron_lib.api.definitions import availability_zone as az_def from neutron_lib.api.definitions import external_net from neutron_lib.api.definitions import l3 as l3_apidef @@ -72,6 +76,7 @@ from tricircle.db import models import tricircle.network.exceptions as t_network_exc from tricircle.network import helper from tricircle.network import managers +from tricircle.network import qos_driver from tricircle.network import security_groups @@ -80,6 +85,11 @@ tricircle_opts = [ default=['vxlan,local'], help=_('List of network type driver entry points to be loaded ' 'from the tricircle.network.type_drivers namespace.')), + cfg.ListOpt('extension_drivers', + default=[], + help=_('List of network extension driver entry points to be ' + 'loaded from the neutron.ml2.extension_drivers ' + 'namespace.')), cfg.ListOpt('tenant_network_types', default=['vxlan,local'], help=_('Ordered list of network_types to allocate as tenant ' @@ -128,6 +138,7 @@ NON_VM_PORT_TYPES = [constants.DEVICE_OWNER_ROUTER_INTF, class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, + agents_db.AgentDbMixin, security_groups.TricircleSecurityGroupMixin, external_net_db.External_net_db_mixin, portbindings_db.PortBindingMixin, @@ -165,12 +176,15 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, def __init__(self): super(TricirclePlugin, self).__init__() LOG.info("Starting Tricircle Neutron Plugin") - self.clients = {} + self.clients = {'top': t_client.Client()} self.xjob_handler = xrpcapi.XJobAPI() self._setup_rpc() self.type_manager = managers.TricircleTypeManager() + self.extension_manager = n_managers.ExtensionManager() + self.extension_manager.initialize() self.type_manager.initialize() self.helper = helper.NetworkHelper(self) + qos_driver.register() def _setup_rpc(self): self.endpoints = [] @@ -303,6 +317,8 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, net_db = self.create_network_db(context, network) res = self._make_network_dict(net_db, process_extensions=False, context=context) + self.extension_manager.process_create_network(context, net_data, + res) self._process_l3_create(context, res, net_data) net_data['id'] = res['id'] self.type_manager.create_network_segments(context, net_data, @@ -392,19 +408,55 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, self._raise_if_updates_external_attribute(net_data) with context.session.begin(): - net = super(TricirclePlugin, self).update_network(context, - network_id, - network) + original_network = super(TricirclePlugin, self).get_network( + context, network_id) + policy = policy_object.QosPolicy.get_network_policy( + context, network_id) + if policy: + original_network['qos_policy_id'] = policy['id'] + else: + original_network['qos_policy_id'] = None + + updated_network = super( + TricirclePlugin, self).update_network( + context, network_id, network) + + self.extension_manager.process_update_network( + context, net_data, updated_network) + + self.type_manager.extend_network_dict_provider(context, + updated_network) + + updated_network = self.get_network(context, network_id) + + if net_data.get('qos_policy_id', None): + updated_network['qos_policy_id'] = net_data['qos_policy_id'] + + if not updated_network.get('qos_policy_id', None): + updated_network['qos_policy_id'] = None + + need_network_update_notify = ( + 'qos_policy_id' in net_data and + original_network['qos_policy_id'] != + updated_network['qos_policy_id']) + t_ctx = t_context.get_context_from_neutron_context(context) mappings = db_api.get_bottom_mappings_by_top_id( t_ctx, network_id, t_constants.RT_NETWORK) if mappings: self.xjob_handler.update_network( - t_ctx, net['tenant_id'], network_id, + t_ctx, updated_network['tenant_id'], network_id, t_constants.POD_NOT_SPECIFIED) - self.type_manager.extend_network_dict_provider(context, net) - return net + if need_network_update_notify and \ + updated_network['qos_policy_id'] and mappings: + t_policy_id = updated_network['qos_policy_id'] + self.xjob_handler.create_qos_policy( + t_ctx, updated_network['tenant_id'], t_policy_id, + t_constants.POD_NOT_SPECIFIED, t_constants.RT_NETWORK, + updated_network['id']) + + return updated_network def _convert_az2region_for_nets(self, context, nets): for net in nets: @@ -419,6 +471,11 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, def _convert_az2region(self, t_ctx, az_hints): return self.helper.convert_az2region(t_ctx, az_hints) + def _get_network_qos_info(self, context, net_id): + policy = policy_object.QosPolicy.get_network_policy( + context.elevated(), net_id) + return policy['id'] if policy else None + def get_network(self, context, network_id, fields=None): net = super(TricirclePlugin, self).get_network(context, network_id, fields) @@ -427,6 +484,9 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, self._convert_az2region_for_net(context, net) + net['qos_policy_id'] = \ + self._get_network_qos_info(context.elevated(), net['id']) + return net def get_networks(self, context, filters=None, fields=None, @@ -438,6 +498,11 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, self.type_manager.extend_networks_dict_provider(context, nets) self._convert_az2region_for_nets(context, nets) + + for net in nets: + net['qos_policy_id'] = \ + self._get_network_qos_info(context.elevated(), net['id']) + return nets def create_subnet(self, context, subnet): @@ -561,6 +626,7 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, self._ensure_default_security_group_on_port(context, port) sgids = self._get_security_groups_on_port(context, port) result = self._make_port_dict(db_port) + self.extension_manager.process_create_port(context, port_body, result) self._process_port_create_security_group(context, result, sgids) return result @@ -671,7 +737,7 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, def update_port(self, context, port_id, port): t_ctx = t_context.get_context_from_neutron_context(context) top_port = super(TricirclePlugin, self).get_port(context, port_id) - + updated_port = None # be careful that l3_db will call update_port to update device_id of # router interface, we cannot directly update bottom port in this case, # otherwise we will fail when attaching bottom port to bottom router @@ -679,15 +745,17 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, if t_constants.PROFILE_REGION in port['port'].get( 'binding:profile', {}): # this update request comes from local Neutron - res = super(TricirclePlugin, self).update_port(context, port_id, - port) + updated_port = super(TricirclePlugin, self).update_port(context, + port_id, + port) + profile_dict = port['port']['binding:profile'] region_name = profile_dict[t_constants.PROFILE_REGION] device_name = profile_dict[t_constants.PROFILE_DEVICE] t_ctx = t_context.get_context_from_neutron_context(context) pod = db_api.get_pod_by_name(t_ctx, region_name) - net = self.get_network(context, res['network_id']) + net = self.get_network(context, updated_port['network_id']) is_vxlan_network = ( net[provider_net.NETWORK_TYPE] == t_constants.NT_VxLAN) if is_vxlan_network: @@ -700,20 +768,41 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, # gateway port, but we only need to create resource routing # entries, trigger xjob and configure security group rules for # instance port - self._create_mapping_for_vm_port(t_ctx, res, pod) + self._create_mapping_for_vm_port(t_ctx, updated_port, pod) self._process_trunk_port(context, t_ctx, - res, pod, profile_dict) + updated_port, pod, profile_dict) # only trigger setup_bottom_router job - self._trigger_router_xjob_for_vm_port(context, res, pod) + self._trigger_router_xjob_for_vm_port(context, updated_port, + pod) self.xjob_handler.configure_security_group_rules( - t_ctx, res['tenant_id']) + t_ctx, updated_port['tenant_id']) if is_vxlan_network and ( cfg.CONF.client.cross_pod_vxlan_mode in ( t_constants.NM_P2P, t_constants.NM_L2GW)): - self.xjob_handler.setup_shadow_ports(t_ctx, res['tenant_id'], - pod['pod_id'], - res['network_id']) + self.xjob_handler.setup_shadow_ports( + t_ctx, updated_port['tenant_id'], pod['pod_id'], + updated_port['network_id']) + + network_binding_policy = \ + policy_object.QosPolicy.get_network_policy( + context, updated_port['network_id']) + + port_binding_policy = policy_object.QosPolicy.get_port_policy( + context, port_id) + + if network_binding_policy: + t_policy_id = network_binding_policy['id'] + self.xjob_handler.create_qos_policy( + t_ctx, t_ctx.project_id, t_policy_id, pod['pod_id'], + t_constants.RT_NETWORK, updated_port['network_id']) + + if port_binding_policy: + t_policy_id = port_binding_policy['id'] + self.xjob_handler.create_qos_policy( + t_ctx, t_ctx.project_id, t_policy_id, pod['pod_id'], + t_constants.RT_PORT, port_id) + # for vm port or port with empty device_owner, update top port and # bottom port elif top_port.get('device_owner') not in NON_VM_PORT_TYPES: @@ -722,6 +811,9 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, request_body = port[attributes.PORT] if mappings: with context.session.begin(): + original_qos_policy_id = \ + self._get_port_qos_info(context, port_id) + b_pod, b_port_id = mappings[0] b_region_name = b_pod['region_name'] b_client = self._get_client(region_name=b_region_name) @@ -733,36 +825,58 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, self._handle_bottom_security_group( t_ctx, request_body['security_groups'], b_pod) - res = super(TricirclePlugin, self).update_port( + updated_port = super(TricirclePlugin, self).update_port( context, port_id, port) + self.extension_manager.process_update_port( + context, request_body, updated_port) + updated_port = \ + super(TricirclePlugin, self).get_port(context, port_id) # name is not allowed to be updated, because it is used by # lock_handle to retrieve bottom/local resources that have # been created but not registered in the resource routing # table request_body.pop('name', None) - try: - b_client.update_ports(t_ctx, b_port_id, port) - except q_cli_exceptions.NotFound: - LOG.error( - ('port: %(port_id)s not found, ' - 'region name: %(name)s'), - {'port_id': b_port_id, 'name': b_region_name}) + request_body_policy_id = \ + request_body.get('qos_policy_id', None) + if request_body_policy_id: + request_body.pop('qos_policy_id') + + if request_body: + try: + b_client.update_ports(t_ctx, b_port_id, port) + except q_cli_exceptions.NotFound: + LOG.error( + ('port: %(port_id)s not found, ' + 'region name: %(name)s'), + {'port_id': b_port_id, 'name': b_region_name}) if request_body.get('security_groups', None): self.xjob_handler.configure_security_group_rules( - t_ctx, res['tenant_id']) + t_ctx, updated_port['tenant_id']) + + updated_port['qos_policy_id'] = request_body_policy_id + if request_body_policy_id and \ + original_qos_policy_id != \ + request_body_policy_id: + t_policy_id = updated_port['qos_policy_id'] + self.xjob_handler.create_qos_policy( + t_ctx, t_ctx.project_id, + t_policy_id, b_pod['pod_id'], + t_constants.RT_PORT, b_port_id) else: self._filter_unsupported_attrs(request_body) - res = super(TricirclePlugin, self).update_port( + updated_port = super(TricirclePlugin, self).update_port( context, port_id, port) + self.extension_manager.process_update_port( + context, request_body, updated_port) else: # for router interface, router gw, dhcp port, not directly # update bottom port - res = super(TricirclePlugin, self).update_port( + updated_port = super(TricirclePlugin, self).update_port( context, port_id, port) self._log_update_port_sensitive_attrs(port_id, port) - return res + return updated_port def _pre_delete_port(self, context, port_id, port_check): """Do some preliminary operations before deleting the port.""" @@ -816,6 +930,10 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, 'value': port_id}]) super(TricirclePlugin, self).delete_port(context, port_id) + def _get_port_qos_info(self, context, port_id): + policy = policy_object.QosPolicy.get_port_policy(context, port_id) + return policy['id'] if policy else None + def get_port(self, context, port_id, fields=None): t_ctx = t_context.get_context_from_neutron_context(context) mappings = db_api.get_bottom_mappings_by_top_id( @@ -830,28 +948,30 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, if fields: port = dict( [(k, v) for k, v in six.iteritems(port) if k in fields]) - if 'network_id' not in port and 'fixed_ips' not in port: - return port - - bottom_top_map = {} - with t_ctx.session.begin(): - for resource in (t_constants.RT_SUBNET, t_constants.RT_NETWORK, - t_constants.RT_ROUTER): - route_filters = [{'key': 'resource_type', - 'comparator': 'eq', - 'value': resource}] - routes = core.query_resource( - t_ctx, models.ResourceRouting, route_filters, []) - for route in routes: - if route['bottom_id']: - bottom_top_map[ - route['bottom_id']] = route['top_id'] - self._map_port_from_bottom_to_top(port, bottom_top_map) - return port + if 'network_id' in port or 'fixed_ips' in port: + bottom_top_map = {} + with t_ctx.session.begin(): + for resource in (t_constants.RT_SUBNET, + t_constants.RT_NETWORK, + t_constants.RT_ROUTER): + route_filters = [{'key': 'resource_type', + 'comparator': 'eq', + 'value': resource}] + routes = core.query_resource( + t_ctx, models.ResourceRouting, route_filters, []) + for route in routes: + if route['bottom_id']: + bottom_top_map[ + route['bottom_id']] = route['top_id'] + self._map_port_from_bottom_to_top(port, bottom_top_map) else: - return super(TricirclePlugin, self).get_port(context, + port = super(TricirclePlugin, self).get_port(context, port_id, fields) + port['qos_policy_id'] = \ + self._get_port_qos_info(context, port_id) + return port + @staticmethod def _apply_ports_filters(query, model, filters): if not filters: @@ -1051,8 +1171,13 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, if not filters or 'id' not in filters: # if filter is empty or "id" is not in the filter, no special # handle is required - return self._get_ports(context, filters, fields, sorts, limit, - marker, page_reverse) + ports = self._get_ports(context, filters, fields, sorts, limit, + marker, page_reverse) + for port in ports: + port['qos_policy_id'] = \ + self._get_port_qos_info(context, port['id']) + + return ports if len(filters) == 1: # only "id" is in the filter, we use get_port to get all the ports ports = [] @@ -1068,9 +1193,14 @@ class TricirclePlugin(db_base_plugin_v2.NeutronDbPluginV2, id_filters = filters.pop('id') ports = self._get_ports(context, filters, None, sorts, limit, marker, page_reverse) - return [super(TricirclePlugin, - self)._fields( + ports = [super(TricirclePlugin, + self)._fields( p, fields) for p in ports if p['id'] in id_filters] + for port in ports: + port['qos_policy_id'] = \ + self._get_port_qos_info(context, port['id']) + + return ports def _get_ports(self, context, filters=None, fields=None, sorts=None, limit=None, marker=None, page_reverse=False): diff --git a/tricircle/network/central_qos_plugin.py b/tricircle/network/central_qos_plugin.py new file mode 100644 index 00000000..23a2bec0 --- /dev/null +++ b/tricircle/network/central_qos_plugin.py @@ -0,0 +1,83 @@ +# Copyright 2017 Hunan University. +# 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.objects import ports as ports_object +from neutron.services.qos import qos_plugin +from neutron_lib.api.definitions import portbindings +from oslo_log import log + +import tricircle.common.client as t_client +import tricircle.common.constants as t_constants +import tricircle.common.context as t_context +import tricircle.db.api as db_api + +LOG = log.getLogger(__name__) + + +class TricircleQosPlugin(qos_plugin.QoSPlugin): + + def __init__(self): + super(TricircleQosPlugin, self).__init__() + self.clients = {'top': t_client.Client()} + + def _get_client(self, region_name): + if region_name not in self.clients: + self.clients[region_name] = t_client.Client(region_name) + return self.clients[region_name] + + def _get_ports_with_policy(self, context, policy): + networks_ids = policy.get_bound_networks() + + ports_with_net_policy = ports_object.Port.get_objects( + context, network_id=networks_ids) + + # Filter only these ports which don't have overwritten policy + ports_with_net_policy = [ + port for port in ports_with_net_policy if + port.qos_policy_id is None + ] + + ports_ids = policy.get_bound_ports() + ports_with_policy = ports_object.Port.get_objects( + context, id=ports_ids) + t_ports = list(set(ports_with_policy + ports_with_net_policy)) + + t_ctx = t_context.get_context_from_neutron_context(context) + for t_port in t_ports: + mappings = db_api.get_bottom_mappings_by_top_id( + t_ctx, t_port.id, t_constants.RT_PORT) + if mappings: + b_pod, b_port_id = mappings[0] + b_region_name = b_pod['region_name'] + b_client = self._get_client(region_name=b_region_name) + b_port = b_client.get_ports(t_ctx, b_port_id) + new_binding = ports_object.PortBinding( + port_id=t_port.id, + vif_type=b_port.get('binding:vif_type', + portbindings.VIF_TYPE_UNBOUND), + vnic_type=b_port.get('binding:vnic_type', + portbindings.VNIC_NORMAL) + ) + t_port.binding = new_binding + else: + new_binding = ports_object.PortBinding( + port_id=t_port.id, + vif_type=portbindings.VIF_TYPE_UNBOUND, + vnic_type=portbindings.VNIC_NORMAL + ) + t_port.binding = new_binding + + return t_ports diff --git a/tricircle/network/local_plugin.py b/tricircle/network/local_plugin.py index 7d5a7990..3fc48214 100644 --- a/tricircle/network/local_plugin.py +++ b/tricircle/network/local_plugin.py @@ -245,6 +245,9 @@ class TricirclePlugin(plugin.Ml2Plugin): net_body['name']) if net_id: net_body['id'] = net_id + + net_body.pop('qos_policy_id', None) + b_network = self.core_plugin.create_network(context, {'network': net_body}) return b_network @@ -349,6 +352,8 @@ class TricirclePlugin(plugin.Ml2Plugin): continue self._adapt_network_body(network) + + network.pop('qos_policy_id', None) b_network = self.core_plugin.create_network( context, {'network': network}) subnet_ids = self._ensure_subnet(context, network) @@ -391,6 +396,7 @@ class TricirclePlugin(plugin.Ml2Plugin): if not t_network: raise q_exceptions.NetworkNotFound(net_id=_id) self._adapt_network_body(t_network) + t_network.pop('qos_policy_id', None) b_network = self.core_plugin.create_network(context, {'network': t_network}) return t_network, b_network @@ -594,6 +600,8 @@ class TricirclePlugin(plugin.Ml2Plugin): self._handle_security_group(t_ctx, context, t_port) self._create_shadow_agent(context, port_body) + + t_port.pop('qos_policy_id', None) b_port = self.core_plugin.create_port(context, {'port': t_port}) return b_port @@ -779,6 +787,7 @@ class TricirclePlugin(plugin.Ml2Plugin): self._ensure_network_subnet(context, t_port) self._adapt_port_body_for_call(t_port) self._handle_security_group(t_ctx, context, t_port) + t_port.pop('qos_policy_id', None) b_port = self.core_plugin.create_port(context, {'port': t_port}) self._ensure_trunk(context, t_ctx, _id) @@ -822,6 +831,7 @@ class TricirclePlugin(plugin.Ml2Plugin): self._ensure_network_subnet(context, port) self._adapt_port_body_for_call(port) self._handle_security_group(t_ctx, context, port) + port.pop('qos_policy_id', None) b_port = self.core_plugin.create_port(context, {'port': port}) b_ports.append(self._fields(b_port, fields)) diff --git a/tricircle/network/qos_driver.py b/tricircle/network/qos_driver.py new file mode 100644 index 00000000..06cbeda5 --- /dev/null +++ b/tricircle/network/qos_driver.py @@ -0,0 +1,144 @@ +# Copyright 2017 Hunan University Co., Ltd. +# +# 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 portbindings +from neutron_lib import constants +from neutron_lib.db import constants as db_constants +from neutron_lib.services.qos import base +from neutron_lib.services.qos import constants as qos_consts + +from oslo_log import log as logging + +from tricircle.common import constants as t_constants +from tricircle.common import context +from tricircle.common import xrpcapi +from tricircle.db import api as db_api + +LOG = logging.getLogger(__name__) + +DRIVER = None + +SUPPORTED_RULES = { + qos_consts.RULE_TYPE_BANDWIDTH_LIMIT: { + qos_consts.MAX_KBPS: { + 'type:range': [0, db_constants.DB_INTEGER_MAX_VALUE]}, + qos_consts.MAX_BURST: { + 'type:range': [0, db_constants.DB_INTEGER_MAX_VALUE]}, + qos_consts.DIRECTION: { + 'type:values': constants.VALID_DIRECTIONS} + }, + qos_consts.RULE_TYPE_DSCP_MARKING: { + qos_consts.DSCP_MARK: {'type:values': constants.VALID_DSCP_MARKS} + }, + qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH: { + qos_consts.MIN_KBPS: { + 'type:range': [0, db_constants.DB_INTEGER_MAX_VALUE]}, + qos_consts.DIRECTION: {'type:values': [constants.EGRESS_DIRECTION]} + } +} + +VIF_TYPES = [portbindings.VIF_TYPE_OVS, + portbindings.VIF_TYPE_VHOST_USER, + portbindings.VIF_TYPE_UNBOUND] + + +class TricircleQoSDriver(base.DriverBase): + def __init__(self, name, vif_types, vnic_types, + supported_rules, + requires_rpc_notifications): + super(TricircleQoSDriver, self).__init__(name, vif_types, vnic_types, + supported_rules, + requires_rpc_notifications) + self.xjob_handler = xrpcapi.XJobAPI() + + @staticmethod + def create(): + return TricircleQoSDriver( + name='tricircle', + vif_types=VIF_TYPES, + vnic_types=portbindings.VNIC_TYPES, + supported_rules=SUPPORTED_RULES, + requires_rpc_notifications=False) + + def create_policy(self, q_context, policy): + """Create policy invocation. + + :param q_context: current running context information + :param policy: a QoSPolicy object being created, which will have no + rules. + """ + pass + + def create_policy_precommit(self, q_context, policy): + """Create policy precommit. + + :param q_context: current running context information + :param policy: a QoSPolicy object being created, which will have no + rules. + """ + pass + + def update_policy(self, q_context, policy): + """Update policy invocation. + + :param q_context: current running context information + :param policy: a QoSPolicy object being updated. + """ + pass + + def update_policy_precommit(self, q_context, policy): + """Update policy precommit. + + :param q_context: current running context information + :param policy: a QoSPolicy object being updated. + """ + t_context = context.get_context_from_neutron_context(q_context) + policy_id = policy['id'] + mappings = db_api.get_bottom_mappings_by_top_id( + t_context, policy_id, t_constants.RT_QOS) + + if mappings: + self.xjob_handler.update_qos_policy( + t_context, t_context.project_id, policy_id, + t_constants.POD_NOT_SPECIFIED) + self.xjob_handler.sync_qos_policy_rules( + t_context, t_context.project_id, policy_id) + + def delete_policy(self, q_context, policy): + """Delete policy invocation. + + :param q_context: current running context information + :param policy: a QoSPolicy object being deleted + """ + + def delete_policy_precommit(self, q_context, policy): + """Delete policy precommit. + + :param q_context: current running context information + :param policy: a QoSPolicy object being deleted + """ + t_context = context.get_context_from_neutron_context(q_context) + policy_id = policy['id'] + self.xjob_handler.delete_qos_policy( + t_context, t_context.project_id, policy_id, + t_constants.POD_NOT_SPECIFIED) + + +def register(): + """Register the driver.""" + global DRIVER + if not DRIVER: + DRIVER = TricircleQoSDriver.create() + LOG.debug('Tricircle QoS driver registered') diff --git a/tricircle/tempestplugin/gate_hook.sh b/tricircle/tempestplugin/gate_hook.sh index e7baa129..198b0ed2 100755 --- a/tricircle/tempestplugin/gate_hook.sh +++ b/tricircle/tempestplugin/gate_hook.sh @@ -48,12 +48,15 @@ function _setup_tricircle_multinode { export DEVSTACK_LOCAL_CONFIG+=$'\n'"TRICIRCLE_START_SERVICES=True" export DEVSTACK_LOCAL_CONFIG+=$'\n'"TRICIRCLE_ENABLE_TRUNK=True" export DEVSTACK_LOCAL_CONFIG+=$'\n'"TRICIRCLE_ENABLE_SFC=True" + export DEVSTACK_LOCAL_CONFIG+=$'\n'"TRICIRCLE_ENABLE_QOS=True" export DEVSTACK_LOCAL_CONFIG+=$'\n'"REGION_NAME=RegionOne" export DEVSTACK_LOCAL_CONFIG+=$'\n'"HOST_IP=$PRIMARY_NODE_IP" ML2_CONFIG=$'\n'"ML2_L3_PLUGIN=tricircle.network.local_l3_plugin.TricircleL3Plugin" + ML2_CONFIG+=$'\n'"ML2_L3_PLUGIN+=,neutron.services.qos.qos_plugin.QoSPlugin" ML2_CONFIG+=$'\n'"[[post-config|/"'$Q_PLUGIN_CONF_FILE]]' ML2_CONFIG+=$'\n'"[ml2]" + ML2_CONFIG+=$'\n'"extension_drivers = port_security,qos" ML2_CONFIG+=$'\n'"mechanism_drivers = openvswitch,linuxbridge,l2population" ML2_CONFIG+=$'\n'"[agent]" ML2_CONFIG+=$'\n'"extensions=sfc" diff --git a/tricircle/tempestplugin/qos_policy_rule_test.yaml b/tricircle/tempestplugin/qos_policy_rule_test.yaml new file mode 100644 index 00000000..8040dd0f --- /dev/null +++ b/tricircle/tempestplugin/qos_policy_rule_test.yaml @@ -0,0 +1,861 @@ +- task_set_id: preparation + tasks: + - task_id: policy1 + type: qos_policy + region: central + params: + name: policy1 + - task_id: bandwidth_limit_rule1 + region: central + type: qos_bandwidth_limit_rule + depend: [policy1] + params: + max_kbps: 3000 + max_burst_kbps: 300 + qos_policy: policy1@id + - task_id: policy2 + type: qos_policy + region: central + params: + name: policy2 + - task_id: bandwidth_limit_rule2 + region: central + type: qos_bandwidth_limit_rule + depend: [policy2] + params: + max_kbps: 3000 + max_burst_kbps: 300 + qos_policy: policy2@id + - task_id: policy3 + type: qos_policy + region: central + params: + name: policy3 + - task_id: policy4 + type: qos_policy + region: central + params: + name: policy4 + - task_id: policy5 + type: qos_policy + region: central + params: + name: policy5 + - task_id: bandwidth_limit_rule5 + region: central + type: qos_bandwidth_limit_rule + depend: [policy5] + params: + max_kbps: 3000 + max_burst_kbps: 300 + qos_policy: policy5@id + - task_id: dscp_marking_rule1 + region: central + type: qos_dscp_marking_rule + depend: [policy1] + params: + dscp_mark: 30 + qos_policy: policy1@id + - task_id: net1 + region: central + type: network + params: + name: net1 + - task_id: subnet1 + region: central + type: subnet + depend: [net1] + params: + name: subnet1 + ip_version: 4 + cidr: 10.0.1.0/24 + network_id: net1@id + - task_id: port1 + region: central + type: port + depend: + - net1 + - subnet1 + params: + name: port1 + network_id: net1@id + - task_id: net2 + region: central + type: network + params: + name: net2 + - task_id: subnet2 + region: central + type: subnet + depend: [net2] + params: + name: subnet2 + ip_version: 4 + cidr: 10.0.2.0/24 + network_id: net2@id + - task_id: port2 + region: central + type: port + depend: + - net2 + - subnet2 + params: + name: port2 + network_id: net2@id + - task_id: net3 + region: central + type: network + params: + name: net3 + - task_id: subnet3 + region: central + type: subnet + depend: [net3] + params: + name: subnet3 + ip_version: 4 + cidr: 10.0.3.0/24 + network_id: net3@id + - task_id: port3 + region: central + type: port + depend: + - net3 + - subnet3 + params: + name: port3 + network_id: net3@id + - task_id: net4 + region: central + type: network + params: + name: net4 + - task_id: subnet4 + region: central + type: subnet + depend: [net4] + params: + name: subnet4 + ip_version: 4 + cidr: 10.0.4.0/24 + network_id: net4@id + - task_id: port4 + region: central + type: port + depend: + - net4 + - subnet4 + params: + name: port4 + network_id: net4@id + - task_id: net5 + region: central + type: network + params: + name: net5 + - task_id: image1 + region: region1 + type: image + query: + get_one: true +- task_set_id: check_qos_create + depend: [preparation] + tasks: + - task_id: check_policy1_central + region: central + type: qos_policy + validate: + predicate: any + condition: + - name: policy1 + - task_id: check_bandwidth_limit_rule1 + region: central + type: qos_bandwidth_limit_rule + params: + qos_policy: preparation@policy1@id + validate: + predicate: any + condition: + - id: preparation@bandwidth_limit_rule1@id + - task_id: check_dscp_marking_rule1 + region: central + type: qos_dscp_marking_rule + params: + qos_policy: preparation@policy1@id + validate: + predicate: any + condition: + - id: preparation@dscp_marking_rule1@id + - task_id: check_policy1_region + region: region1 + type: qos_policy + validate: + predicate: all + condition: + - name: invalid-name +- task_set_id: policy_update_only_central + depend: [preparation] + tasks: + - task_id: policy1_update_only_central + region: central + type: qos_policy + action: + target: preparation@policy1@id + method: update + params: + name: policy1_update_only_central + - task_id: bandwidth_limit_rule1_update_only_central + region: central + type: qos_bandwidth_limit_rule + action: + target: preparation@bandwidth_limit_rule1@id + method: update + params: + qos_policy: preparation@policy1@id + max_kbps: 4000 + - task_id: dscp_marking_rule1_update_only_central + region: central + type: qos_dscp_marking_rule + action: + target: preparation@dscp_marking_rule1@id + method: update + params: + qos_policy: preparation@policy1@id + dscp_mark: 40 +- task_set_id: check_qos_update_only_central + depend: [preparation] + tasks: + - task_id: check_policy1_update_only_central + region: central + type: qos_policy + validate: + predicate: any + condition: + - name: policy1_update_only_central + - task_id: check_limit_rule1_update_only_central + region: central + type: qos_bandwidth_limit_rule + params: + qos_policy: preparation@policy1@id + validate: + predicate: any + condition: + - id: preparation@bandwidth_limit_rule1@id + max_kbps: 4000 + - task_id: check_dscp_rule1_update_only_central + region: central + type: qos_dscp_marking_rule + params: + qos_policy: preparation@policy1@id + validate: + predicate: any + condition: + - id: preparation@dscp_marking_rule1@id + dscp_mark: 40 +- task_set_id: central_bound_policy + depend: [preparation] + tasks: + - task_id: net1_policy + region: central + type: network + action: + target: preparation@net1@id + method: update + params: + qos_policy_id: preparation@policy1@id + - task_id: net5_policy + region: central + type: network + action: + target: preparation@net5@id + method: update + params: + qos_policy_id: preparation@policy5@id + - task_id: port3_policy + region: central + type: port + action: + target: preparation@port3@id + method: update + params: + qos_policy_id: preparation@policy3@id +- task_set_id: create_vm + depend: [preparation] + tasks: + - task_id: vm1 + region: region1 + type: server + params: + flavor_id: 1 + image_id: preparation@image1@id + name: vm1 + networks: + - uuid: preparation@net1@id + port: preparation@port1@id + - task_id: vm2 + region: region1 + type: server + params: + flavor_id: 1 + image_id: preparation@image1@id + name: vm2 + networks: + - uuid: preparation@net2@id + - task_id: vm3 + region: region1 + type: server + params: + flavor_id: 1 + image_id: preparation@image1@id + name: vm3 + networks: + - uuid: preparation@net3@id + port: preparation@port3@id + - task_id: vm4 + region: region1 + type: server + params: + flavor_id: 1 + image_id: preparation@image1@id + name: vm4 + networks: + - uuid: preparation@net4@id + port: preparation@port4@id +- task_set_id: check_vm + depend: [preparation] + tasks: + - task_id: check_vm1 + region: region1 + type: server + validate: + predicate: any + retries: 10 + condition: + - status: ACTIVE + name: vm1 + - task_id: check_vm2 + region: region1 + type: server + validate: + predicate: any + retries: 10 + condition: + - status: ACTIVE + name: vm2 + - task_id: check_vm3 + region: region1 + type: server + validate: + predicate: any + retries: 10 + condition: + - status: ACTIVE + name: vm3 + - task_id: check_vm4 + region: region1 + type: server + validate: + predicate: any + retries: 10 + condition: + - status: ACTIVE + name: vm4 +- task_set_id: wait_for_vm + tasks: + - task_id: check_job_vm + region: central + type: job + validate: + predicate: all + retries: 10 + condition: + - status: SUCCESS +- task_set_id: local_bound_policy + depend: [preparation] + tasks: + - task_id: net2_policy + region: central + type: network + action: + target: preparation@net2@id + method: update + params: + qos_policy_id: preparation@policy2@id + - task_id: port4_policy + region: central + type: port + action: + target: preparation@port4@id + method: update + params: + qos_policy_id: preparation@policy4@id +- task_set_id: wait_for_bound + tasks: + - task_id: check_job_bound + region: central + type: job + validate: + predicate: all + retries: 10 + condition: + - status: SUCCESS +- task_set_id: check_bound_policy + depend: [preparation] + tasks: + - task_id: check_net1_policy_central + region: central + type: network + validate: + predicate: any + condition: + - qos_policy_id: preparation@policy1@id + - task_id: check_net2_policy_central + region: central + type: network + validate: + predicate: any + condition: + - qos_policy_id: preparation@policy2@id + - task_id: check_net5_policy_central + region: central + type: network + validate: + predicate: any + condition: + - qos_policy_id: preparation@policy5@id + - task_id: check_port3_policy_central + region: central + type: port + validate: + predicate: any + condition: + - qos_policy_id: preparation@policy3@id + - task_id: check_port4_policy_central + region: central + type: port + validate: + predicate: any + condition: + - qos_policy_id: preparation@policy4@id + - task_id: check_policy1_region + region: region1 + type: qos_policy + validate: + predicate: any + condition: + - name: policy1_update_only_central + - task_id: check_policy2_region + region: region1 + type: qos_policy + validate: + predicate: any + condition: + - name: policy2 + - task_id: check_policy3_region + region: region1 + type: qos_policy + validate: + predicate: any + condition: + - name: policy3 + - task_id: check_policy4_region + region: region1 + type: qos_policy + validate: + predicate: any + condition: + - name: policy4 +- task_set_id: policy_update_with_local + depend: [preparation] + tasks: + - task_id: policy4_update_with_local + region: central + type: qos_policy + action: + target: preparation@policy4@id + method: update + params: + name: policy4_update_with_local + - task_id: bandwidth_limit_rule2_update_with_local + region: central + type: qos_bandwidth_limit_rule + action: + target: preparation@bandwidth_limit_rule2@id + method: update + params: + qos_policy: preparation@policy2@id + max_kbps: 5000 +- task_set_id: wait_for_job_update + tasks: + - task_id: check_job_update + region: central + type: job + validate: + predicate: all + retries: 10 + condition: + - status: SUCCESS +- task_set_id: check_qos_update_with_local + depend: [preparation] + tasks: + - task_id: check_policy4_update_with_local + region: central + type: qos_policy + validate: + predicate: any + condition: + - name: policy4_update_with_local + - task_id: check_policy4_update_region + region: region1 + type: qos_policy + validate: + predicate: any + condition: + - name: policy4_update_with_local + - task_id: check_limit_rule2_update_with_local + region: central + type: qos_bandwidth_limit_rule + params: + qos_policy: preparation@policy2@id + validate: + predicate: any + condition: + - id: preparation@bandwidth_limit_rule2@id + max_kbps: 5000 +- task_set_id: unbound_policy + depend: [preparation] + tasks: + - task_id: net1_no_policy + region: central + type: network + action: + target: preparation@net1@id + method: update + params: + qos_policy_id: + - task_id: net2_no_policy + region: central + type: network + action: + target: preparation@net2@id + method: update + params: + qos_policy_id: + - task_id: port3_no_policy + region: central + type: port + action: + target: preparation@port3@id + method: update + params: + qos_policy_id: + - task_id: port4_no_policy + region: central + type: port + action: + target: preparation@port4@id + method: update + params: + qos_policy_id: + - task_id: net5_no_policy + region: central + type: network + action: + target: preparation@net5@id + method: update + params: + qos_policy_id: +- task_set_id: wait_for_qos_unbound + tasks: + - task_id: check_job_qos_unbound + region: central + type: job + validate: + predicate: all + retries: 10 + condition: + - status: SUCCESS +- task_set_id: qos-rule-delete + depend: [preparation] + tasks: + - task_id: bandwidth_limit_rule1_delete + region: central + type: qos_bandwidth_limit_rule + action: + target: preparation@bandwidth_limit_rule1@id + method: delete + params: + qos_policy: preparation@policy1@id + - task_id: bandwidth_limit_rule2_delete + region: central + type: qos_bandwidth_limit_rule + action: + target: preparation@bandwidth_limit_rule2@id + method: delete + params: + qos_policy: preparation@policy2@id + - task_id: dscp_marking_rule1_delete + region: central + type: qos_dscp_marking_rule + action: + target: preparation@dscp_marking_rule1@id + method: delete + params: + qos_policy: preparation@policy1@id + - task_id: bandwidth_limit_rule5_delete + region: central + type: qos_bandwidth_limit_rule + action: + target: preparation@bandwidth_limit_rule5@id + method: delete + params: + qos_policy: preparation@policy5@id +- task_set_id: wait_for_rule_delete + depend: [preparation] + tasks: + - task_id: check_job_rule_delete + region: central + type: job + validate: + predicate: all + retries: 10 + condition: + - status: SUCCESS + - task_id: check_for_bandwidth_limit1_delete_central + region: central + type: qos_bandwidth_limit_rule + params: + qos_policy: preparation@policy1@id + validate: + predicate: all + retries: 10 + condition: + id: invalid-id + - task_id: check_for_bandwidth_limit2_delete_central + region: central + type: qos_bandwidth_limit_rule + params: + qos_policy: preparation@policy2@id + validate: + predicate: all + retries: 10 + condition: + id: invalid-id + - task_id: check_for_bandwidth_limit5_delete_central + region: central + type: qos_bandwidth_limit_rule + params: + qos_policy: preparation@policy5@id + validate: + predicate: all + retries: 10 + condition: + id: invalid-id + - task_id: check_for_dscp_marking1_delete_central + region: central + type: qos_dscp_marking_rule + params: + qos_policy: preparation@policy1@id + validate: + predicate: all + retries: 10 + condition: + id: invalid-id +- task_set_id: qos-policy-delete + depend: [preparation] + tasks: + - task_id: policy1_delete + region: central + type: qos_policy + action: + target: preparation@policy1@id + method: delete + - task_id: policy2_delete + region: central + type: qos_policy + action: + target: preparation@policy2@id + method: delete + - task_id: policy3_delete + region: central + type: qos_policy + action: + target: preparation@policy3@id + method: delete + - task_id: policy4_delete + region: central + type: qos_policy + action: + target: preparation@policy4@id + method: delete + - task_id: policy5_delete + region: central + type: qos_policy + action: + target: preparation@policy5@id + method: delete +- task_set_id: wait_for_policy_delete + tasks: + - task_id: check_for_policy_delete_central + region: central + type: qos_policy + validate: + predicate: all + retries: 10 + condition: + name: invalid-name + - task_id: check_for_policy_delete_region + region: region1 + type: qos_policy + validate: + predicate: all + retries: 10 + condition: + name: invalid-name +- task_set_id: delete_vm + depend: [create_vm] + tasks: + - task_id: delete_vm1 + region: region1 + type: server + action: + target: create_vm@vm1@id + method: delete + - task_id: delete_vm2 + region: region1 + type: server + action: + target: create_vm@vm2@id + method: delete + - task_id: delete_vm3 + region: region1 + type: server + action: + target: create_vm@vm3@id + method: delete + - task_id: delete_vm4 + region: region1 + type: server + action: + target: create_vm@vm4@id + method: delete +- task_set_id: wait_for_vm_delete + tasks: + - task_id: check_for_vm_delete + region: region1 + type: server + validate: + predicate: all + retries: 10 + condition: + name: invalid-name +- task_set_id: delete_net + depend: [preparation] + tasks: + - task_id: delete_port1 + region: central + type: port + action: + target: preparation@port1@id + method: delete + - task_id: delete_port2 + region: central + type: port + action: + target: preparation@port2@id + method: delete + - task_id: delete_port3 + region: central + type: port + action: + target: preparation@port3@id + method: delete + - task_id: delete_port4 + region: central + type: port + action: + target: preparation@port4@id + method: delete + - task_id: delete_subnet1 + region: central + type: subnet + depend: [delete_port1] + action: + target: preparation@subnet1@id + method: delete + retries: 3 + - task_id: delete_subnet2 + region: central + type: subnet + depend: [delete_port2] + action: + target: preparation@subnet2@id + method: delete + retries: 3 + - task_id: delete_subnet3 + region: central + type: subnet + depend: [delete_port3] + action: + target: preparation@subnet3@id + method: delete + retries: 3 + - task_id: delete_subnet4 + region: central + type: subnet + depend: [delete_port4] + action: + target: preparation@subnet4@id + method: delete + retries: 3 + - task_id: delete_net1 + region: central + type: network + depend: [delete_subnet1] + action: + target: preparation@net1@id + method: delete + - task_id: delete_net2 + region: central + type: network + depend: [delete_subnet2] + action: + target: preparation@net2@id + method: delete + - task_id: delete_net3 + region: central + type: network + depend: [delete_subnet3] + action: + target: preparation@net3@id + method: delete + - task_id: delete_net4 + region: central + type: network + depend: [delete_subnet4] + action: + target: preparation@net4@id + method: delete + - task_id: delete_net5 + region: central + type: network + action: + target: preparation@net5@id + method: delete +- task_set_id: check_net_delete + tasks: + - task_id: check_net_delete_job + region: region1 + type: network + validate: + predicate: all + condition: + - name: invalid-name + - task_id: check-jobs + region: central + type: job + validate: + predicate: all + retries: 10 + condition: + - status: SUCCESS diff --git a/tricircle/tempestplugin/smoke_test.sh b/tricircle/tempestplugin/smoke_test.sh index 0d09db24..52e98b64 100644 --- a/tricircle/tempestplugin/smoke_test.sh +++ b/tricircle/tempestplugin/smoke_test.sh @@ -25,3 +25,8 @@ fi #if [ $? != 0 ]; then # die $LINENO "Smoke test fails, error in service function chain test" #fi +echo "Start to run qos policy function test" +python run_yaml_test.py qos_policy_rule_test.yaml "$OS_AUTH_URL" "$OS_TENANT_NAME" "$OS_USERNAME" "$OS_PASSWORD" +if [ $? != 0 ]; then + die $LINENO "Smoke test fails, error in service function chain test" +fi diff --git a/tricircle/tempestplugin/task_runner.py b/tricircle/tempestplugin/task_runner.py index 082067da..a40a0abb 100644 --- a/tricircle/tempestplugin/task_runner.py +++ b/tricircle/tempestplugin/task_runner.py @@ -77,12 +77,15 @@ class SDKRunner(object): serv_reslist_map = { 'network_sdk': ['network', 'subnet', 'port', 'router', 'fip', 'trunk', 'flow_classifier', 'port_pair', 'port_pair_group', - 'port_chain'], + 'port_chain', 'qos_policy', 'qos_bandwidth_limit_rule', + 'qos_dscp_marking_rule', 'qos_minimum_bandwidth_rule'], 'compute': ['server'], 'image': ['image'], 'tricircle_sdk': ['job']} res_alias_map = { 'fip': 'ip'} + type_plural_map = { + 'qos_policy': 'qos_policie'} def __init__(self, auth_url, project, username, password): self.res_serv_map = {} @@ -133,6 +136,7 @@ class SDKRunner(object): serv = self.res_serv_map[_type] _type = self.res_alias_map.get(_type, _type) proxy = getattr(conn, serv) + _type = self.type_plural_map.get(_type, _type) _list = list(getattr(proxy, '%ss' % _type)(**params)) if get_one: return _list[0] diff --git a/tricircle/tests/unit/network/test_central_plugin.py b/tricircle/tests/unit/network/test_central_plugin.py index 11c63841..a72dcbdd 100644 --- a/tricircle/tests/unit/network/test_central_plugin.py +++ b/tricircle/tests/unit/network/test_central_plugin.py @@ -38,6 +38,9 @@ from neutron.db import ipam_pluggable_backend from neutron.db import l3_db from neutron.db import models_v2 from neutron.db import rbac_db_models as rbac_db +from neutron.services.qos.drivers import manager as q_manager + +from neutron.plugins.ml2 import managers as n_managers from neutron.ipam import driver from neutron.ipam import exceptions as ipam_exc @@ -60,12 +63,15 @@ import tricircle.db.api as db_api from tricircle.db import core from tricircle.db import models import tricircle.network.central_plugin as plugin +from tricircle.network import central_qos_plugin from tricircle.network.drivers import type_flat from tricircle.network.drivers import type_local from tricircle.network.drivers import type_vlan from tricircle.network.drivers import type_vxlan from tricircle.network import helper from tricircle.network import managers +from tricircle.network import qos_driver +from tricircle.tests.unit.network import test_qos from tricircle.tests.unit.network import test_security_groups import tricircle.tests.unit.utils as test_utils from tricircle.xjob import xmanager @@ -84,18 +90,24 @@ TOP_SEGMENTS = _resource_store.TOP_NETWORKSEGMENTS TOP_FLOATINGIPS = _resource_store.TOP_FLOATINGIPS TOP_SGS = _resource_store.TOP_SECURITYGROUPS TOP_SG_RULES = _resource_store.TOP_SECURITYGROUPRULES +TOP_POLICIES = _resource_store.TOP_QOS_POLICIES +TOP_POLICY_RULES = _resource_store.TOP_QOS_BANDWIDTH_LIMIT_RULES BOTTOM1_NETS = _resource_store.BOTTOM1_NETWORKS BOTTOM1_SUBNETS = _resource_store.BOTTOM1_SUBNETS BOTTOM1_PORTS = _resource_store.BOTTOM1_PORTS BOTTOM1_SGS = _resource_store.BOTTOM1_SECURITYGROUPS BOTTOM1_FIPS = _resource_store.BOTTOM1_FLOATINGIPS BOTTOM1_ROUTERS = _resource_store.BOTTOM1_ROUTERS +BOTTOM1_POLICIES = _resource_store.BOTTOM1_QOS_POLICIES +BOTTOM1_POLICY_RULES = _resource_store.BOTTOM1_QOS_BANDWIDTH_LIMIT_RULES BOTTOM2_NETS = _resource_store.BOTTOM2_NETWORKS BOTTOM2_SUBNETS = _resource_store.BOTTOM2_SUBNETS BOTTOM2_PORTS = _resource_store.BOTTOM2_PORTS BOTTOM2_SGS = _resource_store.BOTTOM2_SECURITYGROUPS BOTTOM2_FIPS = _resource_store.BOTTOM2_FLOATINGIPS BOTTOM2_ROUTERS = _resource_store.BOTTOM2_ROUTERS +BOTTOM2_POLICIES = _resource_store.BOTTOM2_QOS_POLICIES +BOTTOM2_POLICY_RULES = _resource_store.BOTTOM2_QOS_BANDWIDTH_LIMIT_RULES TEST_TENANT_ID = test_utils.TEST_TENANT_ID FakeNeutronContext = test_utils.FakeNeutronContext @@ -271,7 +283,9 @@ class FakeClient(test_utils.FakeClient): if 'gateway_ip' not in body[_type]: cidr = body[_type]['cidr'] body[_type]['gateway_ip'] = cidr[:cidr.rindex('.')] + '.1' - if 'id' not in body[_type]: + if _type == 'qos_policy': + body['policy']['id'] = uuidutils.generate_uuid() + elif 'id' not in body[_type]: body[_type]['id'] = uuidutils.generate_uuid() return super(FakeClient, self).create_resources(_type, ctx, body) @@ -419,7 +433,7 @@ class FakeClient(test_utils.FakeClient): elif action == 'remove_interface': return self.remove_interface_routers(ctx, *args, **kwargs) - def _is_bridge_network_attached(): + def _is_bridge_network_attached(self): pass def create_floatingips(self, ctx, body): @@ -484,7 +498,86 @@ class FakeClient(test_utils.FakeClient): # group return copy.deepcopy(sg) - def get_security_group(self, context, _id, fields=None, tenant_id=None): + def get_security_group(self, ctx, _id, fields=None, tenant_id=None): + pass + + def get_qos_policies(self, ctx, policy_id): + rules = {'rules': []} + rule_list = \ + self._res_map[self.region_name]['qos_bandwidth_limit_rules'] + for rule in rule_list: + if rule['qos_policy_id'] == policy_id: + rules['rules'].append(rule) + + res_list = self._res_map[self.region_name]['qos_policy'] + for policy in res_list: + if policy['id'] == policy_id: + policy['rules'] = rules['rules'] + return policy + + def update_qos_policies(self, ctx, policy_id, body): + self.update_resources('policy', ctx, policy_id, body) + + def delete_qos_policies(self, ctx, policy_id): + self.delete_resources('policy', ctx, policy_id) + + def list_bandwidth_limit_rules(self, ctx, filters): + policy_id = filters[0].get("value") + if self.region_name == 'top': + res_list = \ + self._res_map[self.region_name]['qos_bandwidth_limit_rules'] + else: + res_list = self._res_map[self.region_name]['qos_policy'] + for policy in res_list: + if policy['id'] == policy_id: + res_list = policy.get('rules', []) + + ret_rules = [] + for rule in res_list: + if rule['qos_policy_id'] == policy_id: + ret_rules.append(rule) + + return ret_rules + + def list_dscp_marking_rules(self, ctx, filters): + return [] + + def list_minimum_bandwidth_rules(self, ctx, filters): + return [] + + def create_bandwidth_limit_rules(self, ctx, policy_id, body): + res_list = self._res_map[self.region_name]['qos_policy'] + for policy in res_list: + if policy['id'] == policy_id: + rule_id = uuidutils.generate_uuid() + body['bandwidth_limit_rule']['id'] = rule_id + body['bandwidth_limit_rule']['qos_policy_id'] = policy_id + policy['rules'].append(body['bandwidth_limit_rule']) + return body + + raise q_exceptions.Conflict() + + def create_dscp_marking_rules(self, ctx, policy_id, body): + pass + + def create_minimum_bandwidth_rules(self, ctx, policy_id, body): + pass + + def delete_bandwidth_limit_rules(self, ctx, combined_id): + (rule_id, policy_id) = combined_id.split('#') + res_list = self._res_map[self.region_name]['qos_policy'] + for policy in res_list: + if policy['id'] == policy_id: + for rule in policy['rules']: + if rule['id'] == rule_id: + policy['rules'].remove(rule) + return + raise q_exceptions.Conflict() + + def delete_dscp_marking_rules(self, ctx, combined_id): + pass + + def delete_minimum_bandwidth_rules(self, ctx, combined_id): pass def list_security_groups(self, ctx, sg_filters): @@ -585,6 +678,26 @@ class FakeBaseRPCAPI(object): def setup_shadow_ports(self, ctxt, project_id, pod_id, net_id): pass + def create_qos_policy(self, ctxt, project_id, policy_id, pod_id, + res_type, res_id=None): + combine_id = '%s#%s#%s#%s' % (pod_id, policy_id, res_type, res_id) + self.xmanager.create_qos_policy( + ctxt, payload={constants.JT_QOS_CREATE: combine_id}) + + def update_qos_policy(self, ctxt, project_id, policy_id, pod_id): + combine_id = '%s#%s' % (pod_id, policy_id) + self.xmanager.update_qos_policy( + ctxt, payload={constants.JT_QOS_UPDATE: combine_id}) + + def delete_qos_policy(self, ctxt, project_id, policy_id, pod_id): + combine_id = '%s#%s' % (pod_id, policy_id) + self.xmanager.delete_qos_policy( + ctxt, payload={constants.JT_QOS_DELETE: combine_id}) + + def sync_qos_policy_rules(self, ctxt, project_id, policy_id): + self.xmanager.sync_qos_policy_rules( + ctxt, payload={constants.JT_SYNC_QOS_RULE: policy_id}) + class FakeRPCAPI(FakeBaseRPCAPI): def __init__(self, fake_plugin): @@ -659,12 +772,46 @@ class FakeTypeManager(managers.TricircleTypeManager): break -class FakePlugin(plugin.TricirclePlugin): +class FakeExtensionManager(n_managers.ExtensionManager): + def __init__(self): + super(FakeExtensionManager, self).__init__() + + +class FakeTricircleQoSDriver(qos_driver.TricircleQoSDriver): + def __init__(self, name, vif_types, vnic_types, + supported_rules, + requires_rpc_notifications): + super(FakeTricircleQoSDriver, self).__init__( + name, vif_types, vnic_types, supported_rules, + requires_rpc_notifications) + self.xjob_handler = FakeRPCAPI(self) + + @staticmethod + def create(): + return FakeTricircleQoSDriver( + name='tricircle', + vif_types=qos_driver.VIF_TYPES, + vnic_types=portbindings.VNIC_TYPES, + supported_rules=qos_driver.SUPPORTED_RULES, + requires_rpc_notifications=False) + + +class FakeQosServiceDriverManager(q_manager.QosServiceDriverManager): + def __init__(self): + self._drivers = [FakeTricircleQoSDriver.create()] + self.rpc_notifications_required = False + + +class FakePlugin(plugin.TricirclePlugin, + central_qos_plugin.TricircleQosPlugin): def __init__(self): self.set_ipam_backend() self.helper = FakeHelper(self) self.xjob_handler = FakeRPCAPI(self) self.type_manager = FakeTypeManager() + self.extension_manager = FakeExtensionManager() + self.extension_manager.initialize() + self.driver_manager = FakeQosServiceDriverManager() def _get_client(self, region_name): return FakeClient(region_name) @@ -809,7 +956,8 @@ class FakeTrunkPlugin(object): class PluginTest(unittest.TestCase, - test_security_groups.TricircleSecurityGroupTestMixin): + test_security_groups.TricircleSecurityGroupTestMixin, + test_qos.TricircleQosTestMixin): def setUp(self): core.initialize() core.ModelBase.metadata.create_all(core.get_engine()) @@ -909,10 +1057,14 @@ class PluginTest(unittest.TestCase, port2 = fake_plugin.get_port(neutron_context, 'top_id_2') fake_plugin.get_port(neutron_context, 'top_id_3') - self.assertEqual({'id': 'top_id_1', 'name': 'bottom'}, port1) - self.assertEqual({'id': 'top_id_2', 'name': 'bottom'}, port2) + self.assertEqual({'id': 'top_id_1', 'name': 'bottom', + 'qos_policy_id': None}, port1) + self.assertEqual({'id': 'top_id_2', 'name': 'bottom', + 'qos_policy_id': None}, port2) calls = [mock.call(neutron_context, 'top_id_0', None), - mock.call(neutron_context, 'top_id_3', None)] + mock.call().__setitem__('qos_policy_id', None), + mock.call(neutron_context, 'top_id_3', None), + mock.call().__setitem__('qos_policy_id', None)] mock_plugin_method.assert_has_calls(calls) @patch.object(context, 'get_context_from_neutron_context', @@ -934,11 +1086,15 @@ class PluginTest(unittest.TestCase, marker=ports3[-1]['id']) ports = [] expected_ports = [{'id': 'top_id_0', 'name': 'top', + 'qos_policy_id': None, 'fixed_ips': [{'subnet_id': 'top_subnet_id', 'ip_address': '10.0.0.1'}]}, - {'id': 'top_id_1', 'name': 'bottom'}, - {'id': 'top_id_2', 'name': 'bottom'}, - {'id': 'top_id_3', 'name': 'top'}] + {'id': 'top_id_1', 'name': 'bottom', + 'qos_policy_id': None}, + {'id': 'top_id_2', 'name': 'bottom', + 'qos_policy_id': None}, + {'id': 'top_id_3', 'name': 'top', + 'qos_policy_id': None}] for _ports in (ports1, ports2, ports3, ports4): ports.extend(_ports) six.assertCountEqual(self, expected_ports, ports) @@ -963,9 +1119,11 @@ class PluginTest(unittest.TestCase, ports3 = fake_plugin.get_ports(neutron_context, filters={'id': ['top_id_4']}) self.assertEqual([{'id': 'top_id_0', 'name': 'top', + 'qos_policy_id': None, 'fixed_ips': [{'subnet_id': 'top_subnet_id', 'ip_address': '10.0.0.1'}]}], ports1) - self.assertEqual([{'id': 'top_id_1', 'name': 'bottom'}], ports2) + self.assertEqual([{'id': 'top_id_1', 'name': 'bottom', + 'qos_policy_id': None}], ports2) self.assertEqual([], ports3) TOP_ROUTERS.append({'id': 'router_id'}) @@ -990,9 +1148,9 @@ class PluginTest(unittest.TestCase, ports = fake_plugin.get_ports(neutron_context, filters={'device_id': ['router_id']}) expected = [{'id': 'top_id_1', 'name': 'bottom', - 'device_id': 'router_id'}, + 'qos_policy_id': None, 'device_id': 'router_id'}, {'id': 'top_id_2', 'name': 'bottom', - 'device_id': 'router_id'}] + 'qos_policy_id': None, 'device_id': 'router_id'}] six.assertCountEqual(self, expected, ports) @patch.object(context, 'get_context_from_neutron_context') @@ -3372,6 +3530,97 @@ class PluginTest(unittest.TestCase, 'pod_id_1', TOP_SGS, TOP_SG_RULES, BOTTOM1_SGS) + @patch.object(context, 'get_context_from_neutron_context') + def test_create_policy(self, mock_context): + fake_plugin = FakePlugin() + q_ctx = FakeNeutronContext() + t_ctx = context.get_db_context() + mock_context.return_value = t_ctx + + self._test_create_policy(fake_plugin, q_ctx, t_ctx) + + @patch.object(context, 'get_context_from_neutron_context') + def test_update_policy(self, mock_context): + self._basic_pod_route_setup() + fake_plugin = FakePlugin() + q_ctx = FakeNeutronContext() + t_ctx = context.get_db_context() + mock_context.return_value = t_ctx + + self._test_update_policy(fake_plugin, q_ctx, t_ctx, 'pod_id_1', + BOTTOM1_POLICIES) + + @patch.object(context, 'get_context_from_neutron_context') + def test_delete_policy(self, mock_context): + self._basic_pod_route_setup() + fake_plugin = FakePlugin() + q_ctx = FakeNeutronContext() + t_ctx = context.get_db_context() + mock_context.return_value = t_ctx + + self._test_delete_policy(fake_plugin, q_ctx, t_ctx, 'pod_id_1', + BOTTOM1_POLICIES) + + @patch.object(context, 'get_context_from_neutron_context') + def test_create_policy_rule(self, mock_context): + self._basic_pod_route_setup() + fake_plugin = FakePlugin() + q_ctx = FakeNeutronContext() + t_ctx = context.get_db_context() + mock_context.return_value = t_ctx + + self._test_create_policy_rule(fake_plugin, q_ctx, t_ctx, 'pod_id_1', + BOTTOM1_POLICIES) + + @patch.object(context, 'get_context_from_neutron_context') + def test_delete_policy_rule(self, mock_context): + self._basic_pod_route_setup() + fake_plugin = FakePlugin() + q_ctx = FakeNeutronContext() + t_ctx = context.get_db_context() + mock_context.return_value = t_ctx + + self._test_delete_policy_rule(fake_plugin, q_ctx, t_ctx, 'pod_id_1', + BOTTOM1_POLICIES) + + @patch.object(context, 'get_context_from_neutron_context') + def test_update_network_with_qos_policy(self, mock_context): + self._basic_pod_route_setup() + t_ctx = context.get_db_context() + fake_plugin = FakePlugin() + fake_client = FakeClient('pod_1') + q_ctx = FakeNeutronContext() + mock_context.return_value = t_ctx + + tenant_id = TEST_TENANT_ID + net_id, _, _, _ = \ + self._prepare_network_subnet(tenant_id, t_ctx, 'pod_1', 1) + + self._test_update_network_with_qos_policy(fake_plugin, fake_client, + q_ctx, t_ctx, 'pod_id_1', + net_id, BOTTOM1_POLICIES) + + @patch.object(context, 'get_context_from_neutron_context') + def test_update_port_with_qos_policy(self, mock_context): + project_id = TEST_TENANT_ID + self._basic_pod_route_setup() + q_ctx = FakeNeutronContext() + fake_client = FakeClient('pod_1') + t_ctx = context.get_db_context() + fake_plugin = FakePlugin() + mock_context.return_value = t_ctx + (t_net_id, t_subnet_id, + b_net_id, b_subnet_id) = self._prepare_network_subnet( + project_id, t_ctx, 'pod_1', 1) + t_port_id, b_port_id = self._prepare_port_test( + project_id, t_ctx, 'pod_1', 1, t_net_id, b_net_id, + t_subnet_id, b_subnet_id) + + self._test_update_port_with_qos_policy(fake_plugin, fake_client, + q_ctx, t_ctx, + 'pod_id_1', t_port_id, + b_port_id, BOTTOM1_POLICIES) + @patch.object(FakeBaseRPCAPI, 'setup_shadow_ports') @patch.object(FakeClient, 'update_ports') @patch.object(context, 'get_context_from_neutron_context') diff --git a/tricircle/tests/unit/network/test_qos.py b/tricircle/tests/unit/network/test_qos.py new file mode 100644 index 00000000..97214c20 --- /dev/null +++ b/tricircle/tests/unit/network/test_qos.py @@ -0,0 +1,249 @@ +# Copyright 2015 Huawei Technologies Co., Ltd. +# 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.objects.qos import rule +from oslo_utils import uuidutils + +from tricircle.common import constants +from tricircle.db import api as db_api + + +class TricircleQosTestMixin(object): + def _test_create_policy(self, plugin, q_ctx, t_ctx): + project_id = 'test_prject_id' + t_policy = { + 'policy': { + 'name': 'test_qos', + 'description': 'This policy limits the ports to 10Mbit max.', + 'project_id': project_id + } + } + + res = plugin.create_policy(q_ctx, t_policy) + res1 = plugin.get_policy(q_ctx, res['id']) + + self.assertEqual('test_qos', res['name']) + self.assertEqual(res1['id'], res['id']) + self.assertEqual(res1['name'], res['name']) + self.assertEqual(res['description'], res['description']) + + def _test_update_policy(self, plugin, q_ctx, t_ctx, + pod_id, bottom_policy): + project_id = 'test_prject_id' + t_policy = { + 'policy': { + 'name': 'test_qos', + 'description': 'This policy limits the ports to 10Mbit max.', + 'project_id': project_id + } + } + + res = plugin.create_policy(q_ctx, t_policy) + + updated_qos = { + 'policy': { + 'name': 'test_updated_qos' + } + } + + updated_res = plugin.update_policy(q_ctx, res['id'], updated_qos) + self.assertEqual(res['id'], updated_res['id']) + self.assertEqual('test_updated_qos', updated_res['name']) + + b_policy_id = uuidutils.generate_uuid() + b_policy = { + 'id': b_policy_id, 'name': b_policy_id, 'description': '', + 'tenant_id': project_id + } + bottom_policy.append(b_policy) + db_api.create_resource_mapping(t_ctx, res['id'], b_policy_id, + pod_id, project_id, constants.RT_QOS) + + updated_qos = { + 'policy': { + 'name': 'test_policy' + } + } + + updated_res = plugin.update_policy(q_ctx, res['id'], updated_qos) + self.assertEqual('test_policy', updated_res['name']) + self.assertEqual('test_policy', bottom_policy[0]['name']) + + def _test_delete_policy(self, plugin, q_ctx, + t_ctx, pod_id, bottom_policy): + project_id = 'test_prject_id' + t_policy = { + 'policy': { + 'name': 'test_qos', + 'description': 'This policy limits the ports to 10Mbit max.', + 'project_id': project_id + } + } + + res = plugin.create_policy(q_ctx, t_policy) + b_policy_id = uuidutils.generate_uuid() + b_policy = { + 'id': b_policy_id, 'name': b_policy_id, 'description': '', + 'tenant_id': project_id + } + bottom_policy.append(b_policy) + db_api.create_resource_mapping(t_ctx, res['id'], b_policy_id, + pod_id, project_id, constants.RT_QOS) + + self.assertEqual(1, len(bottom_policy)) + plugin.delete_policy(q_ctx, res['id']) + self.assertEqual(0, len(bottom_policy)) + + def _test_create_policy_rule(self, plugin, q_ctx, + t_ctx, pod_id, bottom_policy): + project_id = 'test_prject_id' + t_policy = { + 'policy': { + 'name': 'test_qos', + 'description': 'This policy limits the ports to 10Mbit max.', + 'project_id': project_id + } + } + + res = plugin.create_policy(q_ctx, t_policy) + + rule_data = { + "bandwidth_limit_rule": { + "max_kbps": "10000" + } + } + + t_rule = plugin.create_policy_rule( + q_ctx, rule.QosBandwidthLimitRule, res['id'], rule_data) + res1 = plugin.get_policy(q_ctx, res['id']) + + self.assertEqual(1, len(res1['rules'])) + self.assertEqual(t_rule['id'], res1['rules'][0]['id']) + + b_policy_id = uuidutils.generate_uuid() + b_policy = {'id': b_policy_id, 'name': b_policy_id, 'description': '', + 'tenant_id': project_id, 'rules': []} + bottom_policy.append(b_policy) + db_api.create_resource_mapping(t_ctx, res['id'], b_policy_id, + pod_id, project_id, constants.RT_QOS) + + rule_data = { + "bandwidth_limit_rule": { + "max_kbps": "10000", + "max_burst_kbps": "20000" + } + } + + t_rule = plugin.create_policy_rule( + q_ctx, rule.QosBandwidthLimitRule, res['id'], rule_data) + + self.assertEqual(2, len(bottom_policy[0]['rules'])) + b_rule = bottom_policy[0]['rules'][0] + self.assertEqual(b_policy_id, b_rule['qos_policy_id']) + b_rule = bottom_policy[0]['rules'][1] + self.assertEqual(b_policy_id, b_rule['qos_policy_id']) + + def _test_delete_policy_rule(self, plugin, q_ctx, + t_ctx, pod_id, bottom_policy): + project_id = 'test_prject_id' + t_policy = { + 'policy': { + 'name': 'test_qos', + 'description': 'This policy limits the ports to 10Mbit max.', + 'project_id': project_id + } + } + + res = plugin.create_policy(q_ctx, t_policy) + + b_policy_id = uuidutils.generate_uuid() + b_policy = { + 'id': b_policy_id, 'name': b_policy_id, 'description': '', + 'tenant_id': project_id, 'rules': [] + } + bottom_policy.append(b_policy) + db_api.create_resource_mapping(t_ctx, res['id'], b_policy_id, + pod_id, project_id, constants.RT_QOS) + + rule_data = { + "bandwidth_limit_rule": { + "max_kbps": "10000" + } + } + + res1 = plugin.create_policy_rule( + q_ctx, rule.QosBandwidthLimitRule, res['id'], rule_data) + + self.assertEqual(1, len(bottom_policy[0]['rules'])) + b_rule = bottom_policy[0]['rules'][0] + self.assertEqual(b_policy_id, b_rule['qos_policy_id']) + + plugin.delete_policy_rule( + q_ctx, rule.QosBandwidthLimitRule, res1['id'], res['id']) + self.assertEqual(0, len(bottom_policy[0]['rules'])) + + @staticmethod + def _create_policy_in_top(self, plugin, q_ctx, t_ctx, + pod_id, bottom_policy): + project_id = 'test_prject_id' + t_policy = { + 'policy': { + 'name': 'test_qos', + 'description': 'This policy limits the ports to 10Mbit max.', + 'project_id': project_id, + } + } + + return plugin.create_policy(q_ctx, t_policy) + + def _test_update_network_with_qos_policy(self, plugin, client, q_ctx, + t_ctx, pod_id, t_net_id, + bottom_policy): + res = \ + self._create_policy_in_top(self, plugin, q_ctx, t_ctx, + pod_id, bottom_policy) + + update_body = { + 'network': { + 'qos_policy_id': res['id']} + } + top_net = plugin.update_network(q_ctx, t_net_id, update_body) + self.assertEqual(top_net['qos_policy_id'], res['id']) + + route_res = \ + db_api.get_bottom_mappings_by_top_id(t_ctx, res['id'], + constants.RT_QOS) + bottom_net = client.get_networks(q_ctx, t_net_id) + self.assertEqual(bottom_net['qos_policy_id'], route_res[0][1]) + + def _test_update_port_with_qos_policy(self, plugin, client, q_ctx, + t_ctx, pod_id, t_port_id, + b_port_id, bottom_policy): + res = \ + self._create_policy_in_top(self, plugin, q_ctx, t_ctx, + pod_id, bottom_policy) + + update_body = { + 'port': { + 'qos_policy_id': res['id']} + } + top_port = plugin.update_port(q_ctx, t_port_id, update_body) + self.assertEqual(top_port['qos_policy_id'], res['id']) + + route_res = \ + db_api.get_bottom_mappings_by_top_id(t_ctx, res['id'], + constants.RT_QOS) + bottom_port = client.get_ports(q_ctx, b_port_id) + self.assertEqual(bottom_port['qos_policy_id'], route_res[0][1]) diff --git a/tricircle/tests/unit/utils.py b/tricircle/tests/unit/utils.py index 8146c19b..f9b8c9c1 100644 --- a/tricircle/tests/unit/utils.py +++ b/tricircle/tests/unit/utils.py @@ -55,7 +55,10 @@ class ResourceStore(object): ('sfc_port_pairs', constants.RT_PORT_PAIR), ('sfc_port_pair_groups', constants.RT_PORT_PAIR_GROUP), ('sfc_port_chains', constants.RT_PORT_CHAIN), - ('sfc_flow_classifiers', constants.RT_FLOW_CLASSIFIER)] + ('sfc_flow_classifiers', constants.RT_FLOW_CLASSIFIER), + ('qos_policies', constants.RT_QOS), + ('qos_bandwidth_limit_rules', + 'qos_bandwidth_limit_rules')] def __init__(self): self.store_list = [] @@ -556,9 +559,13 @@ class FakeClient(object): def create_resources(self, _type, ctx, body): res_list = self._res_map[self.region_name][_type] + if _type == 'qos_policy': + _type = 'policy' res = dict(body[_type]) if 'id' not in res: res['id'] = uuidutils.generate_uuid() + if _type == 'policy' and 'rules' not in res: + res['rules'] = [] res_list.append(res) return res @@ -586,6 +593,8 @@ class FakeClient(object): return None def delete_resources(self, _type, ctx, _id): + if _type is 'policy': + _type = 'qos_policy' index = -1 res_list = self._res_map[self.region_name][_type] for i, res in enumerate(res_list): @@ -595,7 +604,10 @@ class FakeClient(object): del res_list[index] def update_resources(self, _type, ctx, _id, body): - res_list = self._res_map[self.region_name][_type] + if _type == 'policy': + res_list = self._res_map[self.region_name]['qos_policy'] + else: + res_list = self._res_map[self.region_name][_type] updated = False for res in res_list: if res['id'] == _id: diff --git a/tricircle/xjob/xmanager.py b/tricircle/xjob/xmanager.py index 9da1babb..a4ee4fe0 100644 --- a/tricircle/xjob/xmanager.py +++ b/tricircle/xjob/xmanager.py @@ -159,7 +159,12 @@ class XManager(PeriodicTasks): constants.JT_NETWORK_UPDATE: self.update_network, constants.JT_SUBNET_UPDATE: self.update_subnet, constants.JT_SHADOW_PORT_SETUP: self.setup_shadow_ports, - constants.JT_TRUNK_SYNC: self.sync_trunk} + constants.JT_TRUNK_SYNC: self.sync_trunk, + constants.JT_QOS_CREATE: self.create_qos_policy, + constants.JT_QOS_UPDATE: self.update_qos_policy, + constants.JT_QOS_DELETE: self.delete_qos_policy, + constants.JT_SYNC_QOS_RULE: self.sync_qos_policy_rules + } self.helper = helper.NetworkHelper() self.xjob_handler = xrpcapi.XJobAPI() super(XManager, self).__init__() @@ -898,8 +903,9 @@ class XManager(PeriodicTasks): ctx, t_network_id, constants.RT_NETWORK) b_pods = [mapping[0] for mapping in mappings] for b_pod in b_pods: - self.xjob_handler.update_network(ctx, project_id, - t_network_id, b_pod['pod_id']) + self.xjob_handler.update_network( + ctx, project_id, t_network_id, + b_pod['pod_id']) return b_pod = db_api.get_pod(ctx, b_pod_id) @@ -918,6 +924,9 @@ class XManager(PeriodicTasks): } } + if not t_network.get('qos_policy_id', None): + body['network']['qos_policy_id'] = None + try: b_client.update_networks(ctx, b_network_id, body) except q_cli_exceptions.NotFound: @@ -1527,3 +1536,287 @@ class XManager(PeriodicTasks): fc_id=b_fc_ids[0]) self.xjob_handler.recycle_resources(ctx, t_pc['project_id']) + + @_job_handle(constants.JT_QOS_CREATE) + def create_qos_policy(self, ctx, payload): + (b_pod_id, t_policy_id, res_type, res_id) = payload[ + constants.JT_QOS_CREATE].split('#') + + t_client = self._get_client() + t_policy = t_client.get_qos_policies(ctx, t_policy_id) + + if not t_policy: + # we just end this job if top policy no longer exists + return + + project_id = t_policy['tenant_id'] + + if b_pod_id == constants.POD_NOT_SPECIFIED: + mappings = db_api.get_bottom_mappings_by_top_id( + ctx, res_id, res_type) + for b_pod, _ in mappings: + self.xjob_handler.create_qos_policy(ctx, project_id, + t_policy_id, + b_pod['pod_id'], + res_type, + res_id) + return + + b_pod = db_api.get_pod(ctx, b_pod_id) + b_region_name = b_pod['region_name'] + b_client = self._get_client(region_name=b_region_name) + + body = { + 'policy': { + 'description': t_policy.get('description', ''), + 'tenant_id': t_policy.get('tenant_id', ''), + 'project_id': t_policy.get('project_id', ''), + 'shared': t_policy.get('shared', False), + 'name': t_policy.get('name', '') + } + } + + try: + _, b_policy_id = self.helper.prepare_bottom_element( + ctx, project_id, b_pod, t_policy, constants.RT_QOS, body) + if res_id: + if res_type == constants.RT_NETWORK: + body = { + "network": { + "qos_policy_id": b_policy_id + } + } + b_client.update_networks(ctx, res_id, body) + if res_type == constants.RT_PORT: + body = { + "port": { + "qos_policy_id": b_policy_id + } + } + b_client.update_ports(ctx, res_id, body) + if t_policy['rules']: + self.xjob_handler.sync_qos_policy_rules( + ctx, project_id, t_policy_id) + except q_cli_exceptions.NotFound: + LOG.error('qos policy: %(policy_id)s not found,' + 'pod name: %(name)s', + {'policy_id': t_policy_id, 'name': b_region_name}) + + @_job_handle(constants.JT_QOS_UPDATE) + def update_qos_policy(self, ctx, payload): + (b_pod_id, t_policy_id) = payload[ + constants.JT_QOS_UPDATE].split('#') + + t_client = self._get_client() + t_policy = t_client.get_qos_policies(ctx, t_policy_id) + if not t_policy: + return + project_id = t_policy['tenant_id'] + + if b_pod_id == constants.POD_NOT_SPECIFIED: + mappings = db_api.get_bottom_mappings_by_top_id( + ctx, t_policy_id, constants.RT_QOS) + for b_pod, _ in mappings: + self.xjob_handler.update_qos_policy(ctx, project_id, + t_policy_id, + b_pod['pod_id']) + return + + b_pod = db_api.get_pod(ctx, b_pod_id) + b_region_name = b_pod['region_name'] + b_client = self._get_client(region_name=b_region_name) + b_policy_id = db_api.get_bottom_id_by_top_id_region_name( + ctx, t_policy_id, b_region_name, constants.RT_QOS) + + if not b_policy_id: + return + + body = { + 'policy': { + 'description': t_policy.get('description', ''), + 'shared': t_policy.get('shared', ''), + 'name': t_policy.get('name', '') + } + } + + try: + b_client.update_qos_policies(ctx, b_policy_id, body) + except q_cli_exceptions.NotFound: + LOG.error('qos policy: %(policy_id)s not found,' + 'pod name: %(name)s', + {'policy_id': t_policy_id, 'name': b_region_name}) + + @_job_handle(constants.JT_QOS_DELETE) + def delete_qos_policy(self, ctx, payload): + (b_pod_id, t_policy_id) = payload[ + constants.JT_QOS_DELETE].split('#') + + project_id = ctx.tenant_id + if b_pod_id == constants.POD_NOT_SPECIFIED: + mappings = db_api.get_bottom_mappings_by_top_id( + ctx, t_policy_id, constants.RT_QOS) + for b_pod, _ in mappings: + self.xjob_handler.delete_qos_policy(ctx, project_id, + t_policy_id, + b_pod['pod_id']) + return + + b_pod = db_api.get_pod(ctx, b_pod_id) + b_region_name = b_pod['region_name'] + b_client = self._get_client(region_name=b_region_name) + b_policy_id = db_api.get_bottom_id_by_top_id_region_name( + ctx, t_policy_id, b_region_name, constants.RT_QOS) + + try: + b_client.delete_qos_policies(ctx, b_policy_id) + db_api.delete_mappings_by_bottom_id(ctx, b_policy_id) + except q_cli_exceptions.NotFound: + LOG.error('qos policy: %(policy_id)s not found,' + 'pod name: %(name)s', + {'policy_id': t_policy_id, 'name': b_region_name}) + + @staticmethod + def _safe_create_policy_rule( + t_context, client, rule_type, policy_id, body): + try: + return getattr(client, 'create_%s_rules' % rule_type)( + t_context, policy_id, body) + except q_exceptions.Conflict: + return + + @staticmethod + def _safe_get_policy_rule(t_context, client, rule_type, rule_id, + policy_id): + combine_id = '%s#%s' % (rule_id, policy_id) + return getattr(client, 'get_%s_rules' % rule_type)( + t_context, combine_id) + + @staticmethod + def _safe_delete_policy_rule(t_context, client, rule_type, rule_id, + policy_id): + combine_id = '%s#%s' % (rule_id, policy_id) + getattr(client, 'delete_%s_rules' % rule_type)( + t_context, combine_id) + + @staticmethod + def _construct_bottom_bandwidth_limit_rule(t_rule): + return { + "max_kbps": t_rule["max_kbps"], + "max_burst_kbps": t_rule["max_burst_kbps"] + } + + @staticmethod + def _construct_bottom_dscp_marking_rule(t_rule): + return { + "dscp_mark": t_rule["dscp_mark"] + } + + @staticmethod + def _construct_bottom_minimum_bandwidth_rule(t_rule): + return { + "min_kbps": t_rule["min_kbps"], + "direction": t_rule["direction"] + } + + def _construct_bottom_policy_rule(self, rule_type, rule_data): + return getattr(self, '_construct_bottom_%s_rule' % rule_type)( + rule_data) + + @staticmethod + def _compare_bandwidth_limit_rule(rule1, rule2): + for key in ('max_kbps', 'max_burst_kbps'): + if rule1[key] != rule2[key]: + return False + return True + + @staticmethod + def _compare_dscp_marking_rule(rule1, rule2): + for key in ('dscp_mark',): + if rule1[key] != rule2[key]: + return False + return True + + @staticmethod + def _compare_minimum_bandwidth_rule(rule1, rule2): + for key in ('min_kbps', 'direction'): + if rule1[key] != rule2[key]: + return False + return True + + def _compare_policy_rule(self, rule_type, rule1, rule2): + return getattr(self, '_compare_%s_rule' % rule_type)(rule1, rule2) + + @_job_handle(constants.JT_SYNC_QOS_RULE) + def sync_qos_policy_rules(self, ctx, payload): + policy_id = payload[constants.JT_SYNC_QOS_RULE] + top_client = self._get_client() + + bandwidth_limit_rules = \ + top_client.list_bandwidth_limit_rules(ctx, filters=[{ + 'key': 'policy_id', 'comparator': 'eq', 'value': policy_id}]) + dscp_marking_rules = \ + top_client.list_dscp_marking_rules(ctx, filters=[{ + 'key': 'policy_id', 'comparator': 'eq', 'value': policy_id}]) + minimum_bandwidth_rules = \ + top_client.list_minimum_bandwidth_rules(ctx, filters=[{ + 'key': 'policy_id', 'comparator': 'eq', 'value': policy_id}]) + mappings = db_api.get_bottom_mappings_by_top_id( + ctx, policy_id, constants.RT_QOS) + + self._sync_policy_rules( + ctx, mappings, 'bandwidth_limit_rule', bandwidth_limit_rules) + + self._sync_policy_rules( + ctx, mappings, 'dscp_marking_rule', dscp_marking_rules) + + self._sync_policy_rules( + ctx, mappings, 'minimum_bandwidth_rule', minimum_bandwidth_rules) + + def _sync_policy_rules(self, ctx, mappings, rule_type, rules): + if rule_type == 'bandwidth_limit_rule': + rule_types = 'bandwidth_limit_rules' + prefix = 'bandwidth_limit' + elif rule_type == 'dscp_marking_rule': + rule_types = 'dscp_marking_rules' + prefix = 'dscp_marking' + else: + rule_types = 'minimum_bandwidth_rules' + prefix = 'minimum_bandwidth' + + new_b_rules = [] + for t_rule in rules: + new_b_rules.append( + getattr(self, '_construct_bottom_%s' % rule_type)(t_rule)) + + for pod, b_policy_id in mappings: + client = self._get_client(pod['region_name']) + b_rules = getattr(client, 'list_%s' % rule_types)( + ctx, filters=[{'key': 'policy_id', + 'comparator': 'eq', + 'value': b_policy_id}]) + add_rules = [] + del_rules = [] + match_index = set() + for b_rule in b_rules: + match = False + for i, rule in enumerate(new_b_rules): + if getattr(self, '_compare_%s' % rule_type)(b_rule, rule): + match = True + match_index.add(i) + break + if not match: + del_rules.append(b_rule) + for i, rule in enumerate(new_b_rules): + if i not in match_index: + add_rules.append(rule) + + for del_rule in del_rules: + self._safe_delete_policy_rule( + ctx, client, prefix, del_rule['id'], + b_policy_id) + if add_rules: + for new_rule in add_rules: + rule_body = {rule_type: new_rule} + self._safe_create_policy_rule( + ctx, client, prefix, + b_policy_id, rule_body)