diff --git a/devstack/tools/nsxp_cleanup.py b/devstack/tools/nsxp_cleanup.py index 25f4affcc9..55656282b8 100755 --- a/devstack/tools/nsxp_cleanup.py +++ b/devstack/tools/nsxp_cleanup.py @@ -211,6 +211,8 @@ class NSXClient(object): segment_id, p['id']) self.nsxpolicy.segment_port_discovery_profiles.delete( segment_id, p['id']) + self.nsxpolicy.segment_port_qos_profiles.delete( + segment_id, p['id']) self.nsxpolicy.segment_port.delete(segment_id, p['id']) except exceptions.ManagerError as e: print("Failed to delete segment port %s: %s" % (p['id'], e)) diff --git a/doc/source/devstack.rst b/doc/source/devstack.rst index fa2ec5107c..ed29bcb9c5 100644 --- a/doc/source/devstack.rst +++ b/doc/source/devstack.rst @@ -283,6 +283,25 @@ Add octavia repo as an external repository and configure following flags in ``lo [oslo_messaging] topic=vmwarensxv_edge_lb + +NSX-P +----- + +QoS Driver +~~~~~~~~~~ + +Enable the qos in ``local.conf``:: + + [[local|localrc]] + ENABLED_SERVICES+=,q-qos + Q_SERVICE_PLUGIN_CLASSES+=,neutron.services.qos.qos_plugin.QoSPlugin + +Optional: Update the nsx qos_peak_bw_multiplier in nsx.ini (default value is 2.0):: + + [NSX] + qos_peak_bw_multiplier = + + NSX-TVD ------- diff --git a/vmware_nsx/plugins/common_v3/plugin.py b/vmware_nsx/plugins/common_v3/plugin.py index 9273c71bc2..ff90f9aca3 100644 --- a/vmware_nsx/plugins/common_v3/plugin.py +++ b/vmware_nsx/plugins/common_v3/plugin.py @@ -351,19 +351,23 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, if is_external_net: raise nsx_exc.QoSOnExternalNet() - def _validate_update_network(self, context, id, original_net, net_data): + def _validate_update_network(self, context, net_id, original_net, + net_data): """Validate the updated parameters of a network This method includes general validations that does not depend on provider attributes, or plugin specific configurations """ - extern_net = self._network_is_external(context, id) + extern_net = self._network_is_external(context, net_id) with_qos = validators.is_attr_set( net_data.get(qos_consts.QOS_POLICY_ID)) # Do not allow QoS on external networks - if with_qos and extern_net: - raise nsx_exc.QoSOnExternalNet() + if with_qos: + if extern_net: + raise nsx_exc.QoSOnExternalNet() + self._validate_qos_policy_id( + context, net_data.get(qos_consts.QOS_POLICY_ID)) # Do not support changing external/non-external networks if (extnet_apidef.EXTERNAL in net_data and @@ -371,6 +375,10 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, err_msg = _("Cannot change the router:external flag of a network") raise n_exc.InvalidInput(error_message=err_msg) + is_ens_net = self._is_ens_tz_net(context, net_id) + if is_ens_net: + self._assert_on_ens_with_qos(net_data) + def _assert_on_illegal_port_with_qos(self, device_owner): # Prevent creating/update port with QoS policy # on router-interface/network-dhcp ports. @@ -392,6 +400,23 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, LOG.warning(err_msg) raise n_exc.InvalidInput(error_message=err_msg) + def _validate_ens_create_port(self, context, port_data): + qos_selected = validators.is_attr_set(port_data.get( + qos_consts.QOS_POLICY_ID)) + if qos_selected: + err_msg = _("Cannot configure QOS on ENS networks") + raise n_exc.InvalidInput(error_message=err_msg) + + def _assert_on_port_admin_state(self, port_data, device_owner): + """Do not allow changing the admin state of some ports""" + if (device_owner == l3_db.DEVICE_OWNER_ROUTER_INTF or + device_owner == l3_db.DEVICE_OWNER_ROUTER_GW): + if port_data.get("admin_state_up") is False: + err_msg = _("admin_state_up=False router ports are not " + "supported") + LOG.warning(err_msg) + raise n_exc.InvalidInput(error_message=err_msg) + def _validate_create_port(self, context, port_data): self._validate_max_ips_per_port(port_data.get('fixed_ips', []), port_data.get('device_owner')) @@ -416,6 +441,10 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, self._assert_on_port_admin_state(port_data, device_owner) + is_ens_tz_port = self._is_ens_tz_port(context, port_data) + if is_ens_tz_port: + self._validate_ens_create_port(context, port_data) + def _assert_on_vpn_port_change(self, port_data): if port_data['device_owner'] == ipsec_utils.VPN_PORT_OWNER: msg = _('Can not update/delete VPNaaS port %s') % port_data['id'] @@ -478,16 +507,6 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, LOG.warning(err_msg) raise n_exc.InvalidInput(error_message=err_msg) - def _assert_on_port_admin_state(self, port_data, device_owner): - """Do not allow changing the admin state of some ports""" - if (device_owner == l3_db.DEVICE_OWNER_ROUTER_INTF or - device_owner == l3_db.DEVICE_OWNER_ROUTER_GW): - if port_data.get("admin_state_up") is False: - err_msg = _("admin_state_up=False router ports are not " - "supported") - LOG.warning(err_msg) - raise n_exc.InvalidInput(error_message=err_msg) - def _validate_update_port(self, context, id, original_port, port_data): qos_selected = validators.is_attr_set(port_data.get (qos_consts.QOS_POLICY_ID)) @@ -496,6 +515,7 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, device_owner = (port_data['device_owner'] if 'device_owner' in port_data else original_port.get('device_owner')) + is_ens_tz_port = self._is_ens_tz_port(context, original_port) # QoS validations if qos_selected: @@ -504,6 +524,9 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, if is_external_net: raise nsx_exc.QoSOnExternalNet() self._assert_on_illegal_port_with_qos(device_owner) + if is_ens_tz_port: + err_msg = _("Cannot configure QOS on ENS networks") + raise n_exc.InvalidInput(error_message=err_msg) # External networks validations: if is_external_net: @@ -620,6 +643,44 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, err_msg = _("Cannot configure QOS on ENS networks") raise n_exc.InvalidInput(error_message=err_msg) + def _get_port_qos_policy_id(self, context, original_port, + updated_port): + """Return the QoS policy Id of a port that is being created/updated + + Return the QoS policy assigned directly to the port (after update or + originally), or the policy of the network, if it is a compute port that + should inherit it. + original_port: the neutron port data before this update + (or None in a case of a new port creation) + updated_ports: the modified fields of this port + (or all th attributes of the new port) + """ + orig_compute = False + if original_port: + orig_compute = original_port.get('device_owner', '').startswith( + constants.DEVICE_OWNER_COMPUTE_PREFIX) + updated_compute = updated_port.get('device_owner', '').startswith( + constants.DEVICE_OWNER_COMPUTE_PREFIX) + is_new_compute = updated_compute and not orig_compute + + qos_policy_id = None + if validators.is_attr_set(updated_port.get(qos_consts.QOS_POLICY_ID)): + qos_policy_id = updated_port[qos_consts.QOS_POLICY_ID] + elif original_port: + # Look for the original QoS policy of this port + qos_policy_id = qos_com_utils.get_port_policy_id( + context, original_port['id']) + # If the port is now a 'compute' port (attached to a vm) and + # Qos policy was not configured on the port directly, + # try to take it from the ports network + if qos_policy_id is None and is_new_compute: + # check if the network of this port has a policy + net_id = (original_port.get('network_id') if original_port + else updated_port.get('network_id')) + qos_policy_id = qos_com_utils.get_network_policy_id( + context, net_id) + return qos_policy_id + def _ens_psec_supported(self): """Should be implemented by each plugin""" pass @@ -646,13 +707,13 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, """ pass - def _is_ens_tz_net(self, context, net_id): + def _is_ens_tz_net(self, context, network_id): """Should be implemented by each plugin""" pass def _is_ens_tz_port(self, context, port_data): - """Should be implemented by each plugin""" - pass + # Check the host-switch-mode of the TZ connected to the ports network + return self._is_ens_tz_net(context, port_data['network_id']) def _is_overlay_network(self, network_id): """Should be implemented by each plugin""" @@ -691,7 +752,7 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, - net_type: provider network type or None - physical_net: the uuid of the relevant transport zone or None - vlan_id: vlan tag, 0 or None - - switch_mode: standard ot ENS + - switch_mode: standard or ENS """ is_provider_net = any( validators.is_attr_set(network_data.get(f)) @@ -870,6 +931,12 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, port_data[pbin.VIF_DETAILS]['segmentation-id'] = ( self._get_network_segmentation_id(context, net_id)) + def _extend_qos_port_dict_binding(self, context, port): + # add the qos policy id from the DB + if 'id' in port: + port[qos_consts.QOS_POLICY_ID] = qos_com_utils.get_port_policy_id( + context, port['id']) + def fix_direct_vnic_port_sec(self, direct_vnic_type, port_data): if direct_vnic_type: if validators.is_attr_set(port_data.get(psec.PORTSECURITY)): diff --git a/vmware_nsx/plugins/nsx_p/plugin.py b/vmware_nsx/plugins/nsx_p/plugin.py index 8aabc533ef..0d2aff61a4 100644 --- a/vmware_nsx/plugins/nsx_p/plugin.py +++ b/vmware_nsx/plugins/nsx_p/plugin.py @@ -43,6 +43,9 @@ from neutron_lib.db import api as db_api from neutron_lib.db import resource_extend from neutron_lib.db import utils as db_utils from neutron_lib import exceptions as n_exc +from neutron_lib.plugins import constants as plugin_const +from neutron_lib.plugins import directory +from neutron_lib.services.qos import constants as qos_consts from vmware_nsx._i18n import _ from vmware_nsx.common import config # noqa @@ -60,6 +63,9 @@ from vmware_nsx.extensions import securitygrouplogging as sg_logging from vmware_nsx.plugins.common_v3 import plugin as nsx_plugin_common from vmware_nsx.plugins.nsx_p import availability_zones as nsxp_az from vmware_nsx.plugins.nsx_v3 import utils as v3_utils +from vmware_nsx.services.qos.common import utils as qos_com_utils +from vmware_nsx.services.qos.nsx_v3 import driver as qos_driver +from vmware_nsx.services.qos.nsx_v3 import pol_utils as qos_utils from vmware_nsxlib.v3 import exceptions as nsx_lib_exc from vmware_nsxlib.v3 import nsx_constants as nsxlib_consts @@ -172,6 +178,9 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base): self._init_native_dhcp() + # Init QoS + qos_driver.register(qos_utils.PolicyQosNotificationsHandler()) + # subscribe the init complete method last, so it will be called only # if init was successful registry.subscribe(self.init_complete, @@ -288,9 +297,8 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base): # update the network name to indicate the neutron id too. net_name = utils.get_name_and_uuid(net_data['name'] or 'network', net_data['id']) - tags = self.nsxpolicy.build_v3_tags_payload( - net_data, resource_type='os-neutron-net-id', - project_name=context.tenant_name) + tags = self.nsxpolicy.build_v3_api_version_project_tag( + context.tenant_name) # TODO(annak): admin state config is missing on policy # should we not create networks that are down? @@ -418,6 +426,16 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base): # latest db model for the extension functions net_model = self._get_network(context, created_net['id']) resource_extend.apply_funcs('networks', created_net, net_model) + + # Update the QoS policy (will affect only future compute ports) + qos_com_utils.set_qos_policy_on_new_net( + context, net_data, created_net) + if net_data.get(qos_consts.QOS_POLICY_ID): + LOG.info("QoS Policy %(qos)s will be applied to future compute " + "ports of network %(net)s", + {'qos': net_data[qos_consts.QOS_POLICY_ID], + 'net': created_net['id']}) + return created_net def delete_network(self, context, network_id): @@ -438,6 +456,10 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base): context, network_id) net_data = network['network'] + # Validate the updated parameters + self._validate_update_network(context, network_id, original_net, + net_data) + # Neutron does not support changing provider network values providernet._raise_if_updates_provider_attributes(net_data) extern_net = self._network_is_external(context, network_id) @@ -457,6 +479,19 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base): self._process_l3_update(context, updated_net, network['network']) self._extend_network_dict_provider(context, updated_net) + if qos_consts.QOS_POLICY_ID in net_data: + # attach the policy to the network in neutron DB + #(will affect only future compute ports) + qos_com_utils.update_network_policy_binding( + context, network_id, net_data[qos_consts.QOS_POLICY_ID]) + updated_net[qos_consts.QOS_POLICY_ID] = net_data[ + qos_consts.QOS_POLICY_ID] + if net_data[qos_consts.QOS_POLICY_ID]: + LOG.info("QoS Policy %(qos)s will be applied to future " + "compute ports of network %(net)s", + {'qos': net_data[qos_consts.QOS_POLICY_ID], + 'net': network_id}) + # Update the backend segment if (not extern_net and not is_nsx_net and ('name' in net_data or 'description' in net_data)): @@ -562,7 +597,8 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base): return tags - def _create_port_on_backend(self, context, port_data, is_psec_on): + def _create_port_on_backend(self, context, port_data, is_psec_on, + qos_policy_id): # TODO(annak): admin_state not supported by policy name = self._build_port_name(context, port_data) address_bindings = self._build_port_address_bindings( @@ -620,6 +656,12 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base): name, segment_id, port_data['id'], mac_discovery_profile_id=mac_discovery_profile) + # Add QoS segment profile (only if QoS is enabled) + if directory.get_plugin(plugin_const.QOS): + self.nsxpolicy.segment_port_qos_profiles.create_or_overwrite( + name, segment_id, port_data['id'], + qos_profile_id=qos_policy_id) + def base_create_port(self, context, port): neutron_db = super(NsxPolicyPlugin, self).create_port(context, port) self._extension_manager.process_create_port( @@ -628,8 +670,8 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base): def create_port(self, context, port, l2gw_port_check=False): port_data = port['port'] - self._validate_max_ips_per_port(port_data.get('fixed_ips', []), - port_data.get('device_owner')) + # validate the new port parameters + self._validate_create_port(context, port_data) # Validate the vnic type (the same types as for the NSX-T plugin) direct_vnic_type = self._validate_port_vnic_type( @@ -673,9 +715,13 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base): # ATTR_NOT_SPECIFIED port_data.pop(mac_ext.MAC_LEARNING) + qos_policy_id = self._get_port_qos_policy_id( + context, None, port_data) + if not is_external_net: try: - self._create_port_on_backend(context, port_data, is_psec_on) + self._create_port_on_backend(context, port_data, is_psec_on, + qos_policy_id) except Exception as e: with excutils.save_and_reraise_exception(): LOG.error('Failed to create port %(id)s on NSX ' @@ -684,6 +730,11 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base): super(NsxPolicyPlugin, self).delete_port( context, neutron_db['id']) + # Attach the policy to the port in the neutron DB + if qos_policy_id: + qos_com_utils.update_port_policy_binding(context, + neutron_db['id'], + qos_policy_id) # this extra lookup is necessary to get the # latest db model for the extension functions port_model = self._get_port(context, port_data['id']) @@ -716,6 +767,9 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base): segment_id, port_id) self.nsxpolicy.segment_port_discovery_profiles.delete( segment_id, port_id) + if directory.get_plugin(plugin_const.QOS): + self.nsxpolicy.segment_port_qos_profiles.delete( + segment_id, port_id) self.nsxpolicy.segment_port.delete(segment_id, port_id) except Exception as ex: LOG.error("Failed to delete port %(id)s on NSX backend " @@ -724,10 +778,11 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base): def _update_port_on_backend(self, context, lport_id, original_port, updated_port, - is_psec_on): + is_psec_on, qos_policy_id): # For now port create and update are the same # Update might evolve with more features - return self._create_port_on_backend(context, updated_port, is_psec_on) + return self._create_port_on_backend(context, updated_port, is_psec_on, + qos_policy_id) def update_port(self, context, port_id, port): with db_api.CONTEXT_WRITER.using(context): @@ -792,13 +847,19 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base): self._update_mac_learning_state(context, port_id, mac_learning_state) + # Update the QoS policy + qos_policy_id = self._get_port_qos_policy_id( + context, original_port, updated_port) + qos_com_utils.update_port_policy_binding(context, port_id, + qos_policy_id) + # update the port in the backend, only if it exists in the DB # (i.e not external net) if not is_external_net: try: self._update_port_on_backend(context, port_id, original_port, updated_port, - port_security) + port_security, qos_policy_id) except Exception as e: LOG.error('Failed to update port %(id)s on NSX ' 'backend. Exception: %(e)s', @@ -833,6 +894,7 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base): port_model = self._get_port(context, port['id']) resource_extend.apply_funcs('ports', port, port_model) self._extend_nsx_port_dict_binding(context, port) + self._extend_qos_port_dict_binding(context, port) self._remove_provider_security_groups_from_list(port) return db_utils.resource_fields(port, fields) @@ -859,6 +921,7 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base): ports.remove(port) continue self._extend_nsx_port_dict_binding(context, port) + self._extend_qos_port_dict_binding(context, port) self._remove_provider_security_groups_from_list(port) return (ports if not fields else [db_utils.resource_fields(port, fields) for port in ports]) @@ -1845,10 +1908,6 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base): #TODO(annak): handle ENS case return False - def _is_ens_tz_port(self, context, port_data): - #TODO(annak): handle ENS case - return False - def _has_native_dhcp_metadata(self): return True diff --git a/vmware_nsx/plugins/nsx_v3/plugin.py b/vmware_nsx/plugins/nsx_v3/plugin.py index 4c68e392cb..f9624b77ed 100644 --- a/vmware_nsx/plugins/nsx_v3/plugin.py +++ b/vmware_nsx/plugins/nsx_v3/plugin.py @@ -100,6 +100,7 @@ from vmware_nsx.services.lbaas.octavia import constants as oct_const from vmware_nsx.services.lbaas.octavia import octavia_listener from vmware_nsx.services.qos.common import utils as qos_com_utils from vmware_nsx.services.qos.nsx_v3 import driver as qos_driver +from vmware_nsx.services.qos.nsx_v3 import utils as qos_utils from vmware_nsx.services.trunk.nsx_v3 import driver as trunk_driver from vmware_nsxlib.v3 import core_resources as nsx_resources from vmware_nsxlib.v3 import exceptions as nsx_lib_exc @@ -231,7 +232,7 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base, ) % NSX_V3_EXCLUDED_PORT_NSGROUP_NAME raise nsx_exc.NsxPluginException(err_msg=msg) - qos_driver.register() + qos_driver.register(qos_utils.QosNotificationsHandler()) self.start_rpc_listeners_called = False @@ -1196,17 +1197,12 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base, net_data = network['network'] # Neutron does not support changing provider network values providernet._raise_if_updates_provider_attributes(net_data) - self._validate_qos_policy_id( - context, net_data.get(qos_consts.QOS_POLICY_ID)) extern_net = self._network_is_external(context, id) is_nsx_net = self._network_is_nsx_net(context, id) is_ens_net = self._is_ens_tz_net(context, id) # Validate the updated parameters self._validate_update_network(context, id, original_net, net_data) - # add some plugin specific validations - if is_ens_net: - self._assert_on_ens_with_qos(net_data) updated_net = super(NsxV3Plugin, self).update_network(context, id, network) @@ -1766,13 +1762,8 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base, profiles.append(self._dhcp_profile) # Add QoS switching profile, if exists - qos_policy_id = None - if validators.is_attr_set(port_data.get(qos_consts.QOS_POLICY_ID)): - qos_policy_id = port_data[qos_consts.QOS_POLICY_ID] - elif device_owner.startswith(const.DEVICE_OWNER_COMPUTE_PREFIX): - # check if the network of this port has a policy - qos_policy_id = qos_com_utils.get_network_policy_id( - context, port_data['network_id']) + qos_policy_id = self._get_port_qos_policy_id( + context, None, port_data) if qos_policy_id: qos_profile_id = self._get_qos_profile_id(context, qos_policy_id) profiles.append(qos_profile_id) @@ -1845,10 +1836,6 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base, mode = self.nsxlib.transport_zone.get_host_switch_mode(tz_id) return mode == self.nsxlib.transport_zone.HOST_SWITCH_MODE_ENS - def _is_ens_tz_port(self, context, port_data): - # Check the host-switch-mode of the TZ connected to the ports network - return self._is_ens_tz_net(context, port_data['network_id']) - def _has_native_dhcp_metadata(self): return cfg.CONF.nsx_v3.native_dhcp_metadata @@ -2222,19 +2209,7 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base, self.nsxlib.ns_group.update_lport_nsgroups( lport_id, nsx_origial, nsx_updated) - def base_create_port(self, context, port): - neutron_db = super(NsxV3Plugin, self).create_port(context, port) - self._extension_manager.process_create_port( - context, port['port'], neutron_db) - return neutron_db - - def _validate_ens_create_port(self, context, port_data): - qos_selected = validators.is_attr_set(port_data.get( - qos_consts.QOS_POLICY_ID)) - if qos_selected: - err_msg = _("Cannot configure QOS on ENS networks") - raise n_exc.InvalidInput(error_message=err_msg) - + def _disable_ens_portsec(self, port_data): if (cfg.CONF.nsx_v3.disable_port_security_for_ens and not self._ens_psec_supported()): LOG.warning("Disabling port security for network %s", @@ -2242,6 +2217,12 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base, port_data[psec.PORTSECURITY] = False port_data['security_groups'] = [] + def base_create_port(self, context, port): + neutron_db = super(NsxV3Plugin, self).create_port(context, port) + self._extension_manager.process_create_port( + context, port['port'], neutron_db) + return neutron_db + def create_port(self, context, port, l2gw_port_check=False): port_data = port['port'] @@ -2253,7 +2234,14 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base, self._assert_on_dhcp_relay_without_router(context, port_data) is_ens_tz_port = self._is_ens_tz_port(context, port_data) if is_ens_tz_port: - self._validate_ens_create_port(context, port_data) + self._disable_ens_portsec(port_data) + + if (cfg.CONF.nsx_v3.disable_port_security_for_ens and + not self._ens_psec_supported()): + LOG.warning("Disabling port security for network %s", + port_data['network_id']) + port_data[psec.PORTSECURITY] = False + port_data['security_groups'] = [] is_external_net = self._network_is_external( context, port_data['network_id']) @@ -2567,14 +2555,8 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base, switch_profile_ids.append(self._dhcp_profile) # Update QoS switch profile - orig_compute = original_device_owner.startswith( - const.DEVICE_OWNER_COMPUTE_PREFIX) - updated_compute = updated_device_owner.startswith( - const.DEVICE_OWNER_COMPUTE_PREFIX) - is_new_compute = updated_compute and not orig_compute - qos_policy_id, qos_profile_id = self._get_port_qos_ids(context, - updated_port, - is_new_compute) + qos_policy_id, qos_profile_id = self._get_port_qos_ids( + context, original_port, updated_port) if qos_profile_id is not None: switch_profile_ids.append(qos_profile_id) @@ -2619,28 +2601,13 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base, updated_port['id'], qos_policy_id) - def _get_port_qos_ids(self, context, updated_port, is_new_compute): - # when a port is updated, get the current QoS policy/profile ids - policy_id = None + def _get_port_qos_ids(self, context, original_port, updated_port): + qos_policy_id = self._get_port_qos_policy_id( + context, original_port, updated_port) profile_id = None - if (qos_consts.QOS_POLICY_ID in updated_port): - policy_id = updated_port[qos_consts.QOS_POLICY_ID] - else: - # Look for the previous QoS policy - policy_id = qos_com_utils.get_port_policy_id( - context, updated_port['id']) - # If the port is now a 'compute' port (attached to a vm) and - # Qos policy was not configured on the port directly, - # try to take it from the ports network - if policy_id is None and is_new_compute: - # check if the network of this port has a policy - policy_id = qos_com_utils.get_network_policy_id( - context, updated_port.get('network_id')) - - if policy_id is not None: - profile_id = self._get_qos_profile_id(context, policy_id) - - return policy_id, profile_id + if qos_policy_id is not None: + profile_id = self._get_qos_profile_id(context, qos_policy_id) + return qos_policy_id, profile_id def update_port(self, context, id, port): with db_api.CONTEXT_WRITER.using(context): @@ -2660,11 +2627,6 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base, self._assert_on_dhcp_relay_without_router(context, port_data, original_port) is_ens_tz_port = self._is_ens_tz_port(context, original_port) - qos_selected = validators.is_attr_set(port_data.get - (qos_consts.QOS_POLICY_ID)) - if is_ens_tz_port and qos_selected: - err_msg = _("Cannot configure QOS on ENS networks") - raise n_exc.InvalidInput(error_message=err_msg) dhcp_opts = port_data.get(ext_edo.EXTRADHCPOPTS) self._validate_extra_dhcp_options(dhcp_opts) @@ -2780,11 +2742,7 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base, def _extend_get_port_dict_qos_and_binding(self, context, port): # Not using the register api for this because we need the context self._extend_nsx_port_dict_binding(context, port) - - # add the qos policy id from the DB - if 'id' in port: - port[qos_consts.QOS_POLICY_ID] = qos_com_utils.get_port_policy_id( - context, port['id']) + self._extend_qos_port_dict_binding(context, port) def get_port(self, context, id, fields=None): port = super(NsxV3Plugin, self).get_port(context, id, fields=None) diff --git a/vmware_nsx/services/qos/common/utils.py b/vmware_nsx/services/qos/common/utils.py index 0a40567f2d..a86ea33880 100644 --- a/vmware_nsx/services/qos/common/utils.py +++ b/vmware_nsx/services/qos/common/utils.py @@ -93,7 +93,7 @@ def set_qos_policy_on_new_net(context, net_data, created_net): # attach the policy to the network in the neutron DB update_network_policy_binding( context, - net_data['id'], + created_net['id'], qos_policy_id) created_net[qos_consts.QOS_POLICY_ID] = qos_policy_id return qos_policy_id diff --git a/vmware_nsx/services/qos/nsx_v3/driver.py b/vmware_nsx/services/qos/nsx_v3/driver.py index 103c7071a3..2240b0f89f 100644 --- a/vmware_nsx/services/qos/nsx_v3/driver.py +++ b/vmware_nsx/services/qos/nsx_v3/driver.py @@ -20,8 +20,6 @@ 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 vmware_nsx.services.qos.nsx_v3 import utils as qos_utils - LOG = logging.getLogger(__name__) DRIVER = None @@ -45,16 +43,17 @@ SUPPORTED_RULES = { class NSXv3QosDriver(base.DriverBase): @staticmethod - def create(): + def create(handler): return NSXv3QosDriver( name='NSXv3QosDriver', vif_types=None, vnic_types=None, supported_rules=SUPPORTED_RULES, - requires_rpc_notifications=False) + requires_rpc_notifications=False, + handler=handler) - def __init__(self, **kwargs): - self.handler = qos_utils.QosNotificationsHandler() + def __init__(self, handler=None, **kwargs): + self.handler = handler super(NSXv3QosDriver, self).__init__(**kwargs) def is_vif_type_compatible(self, vif_type): @@ -67,11 +66,12 @@ class NSXv3QosDriver(base.DriverBase): self.handler.create_policy(context, policy) def update_policy(self, context, policy): + # Update the rules if (hasattr(policy, "rules")): self.handler.update_policy_rules( context, policy.id, policy["rules"]) - # May also need to update name / description + # Update the entire policy self.handler.update_policy(context, policy.id, policy) def delete_policy(self, context, policy): @@ -84,9 +84,9 @@ class NSXv3QosDriver(base.DriverBase): self.handler.validate_policy_rule(context, policy.id, rule) -def register(): +def register(handler): """Register the NSX-V3 QoS driver.""" global DRIVER if not DRIVER: - DRIVER = NSXv3QosDriver.create() + DRIVER = NSXv3QosDriver.create(handler) LOG.debug('NSXv3QosDriver QoS driver registered') diff --git a/vmware_nsx/services/qos/nsx_v3/pol_utils.py b/vmware_nsx/services/qos/nsx_v3/pol_utils.py new file mode 100644 index 0000000000..55b92f7f64 --- /dev/null +++ b/vmware_nsx/services/qos/nsx_v3/pol_utils.py @@ -0,0 +1,160 @@ +# Copyright 2018 VMware, Inc. +# +# 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 oslo_config import cfg +from oslo_log import log as logging + +from neutron_lib import constants as n_consts +from neutron_lib import exceptions as n_exc +from neutron_lib.plugins import directory +from neutron_lib.services.qos import constants as qos_consts + +from vmware_nsx._i18n import _ +from vmware_nsx.common import utils + +LOG = logging.getLogger(__name__) + +MAX_KBPS_MIN_VALUE = 1024 +# The max limit is calculated so that the value sent to the backed will +# be smaller than 2**31 +MAX_BURST_MAX_VALUE = int((2 ** 31 - 1) / 128) + + +class PolicyQosNotificationsHandler(object): + + def __init__(self): + super(PolicyQosNotificationsHandler, self).__init__() + self._core_plugin = None + + @property + def core_plugin(self): + if not self._core_plugin: + self._core_plugin = directory.get_plugin() + return self._core_plugin + + @property + def _nsxpolicy(self): + return self.core_plugin.nsxpolicy + + def create_or_update_policy(self, context, policy): + policy_id = policy.id + tags = self._nsxpolicy.build_v3_api_version_project_tag( + context.tenant_name, project_id=policy.tenant_id) + pol_name = utils.get_name_and_uuid(policy.name or 'policy', + policy.id) + + shapers = [] + dscp = None + if (hasattr(policy, "rules")): + for rule in policy["rules"]: + if rule.rule_type == qos_consts.RULE_TYPE_BANDWIDTH_LIMIT: + # the NSX direction is opposite to the neutron one + is_ingress = rule.direction == n_consts.EGRESS_DIRECTION + shapers.append(self._get_shaper_from_rule( + rule, is_ingress=is_ingress)) + elif rule.rule_type == qos_consts.RULE_TYPE_DSCP_MARKING: + dscp = self._get_dscp_from_rule(rule) + else: + LOG.warning("The NSX-Policy plugin does not support QoS " + "rule of type %s", rule.rule_type) + + self._nsxpolicy.qos_profile.create_or_overwrite( + pol_name, profile_id=policy_id, + description=policy.get('description'), + dscp=dscp, shaper_configurations=shapers, + tags=tags) + + def create_policy(self, context, policy): + return self.create_or_update_policy(context, policy) + + def delete_policy(self, context, policy_id): + self._nsxpolicy.qos_profile.delete(policy_id) + + def update_policy(self, context, policy_id, policy): + return self.create_or_update_policy(context, policy) + + def _validate_bw_values(self, bw_rule): + """Validate that the values are allowed by the NSX backend""" + # Validate the max bandwidth value minimum value + # (max value is above what neutron allows so no need to check it) + if (bw_rule.max_kbps < MAX_KBPS_MIN_VALUE): + msg = (_("Invalid input for max_kbps. " + "The minimal legal value is %s") % MAX_KBPS_MIN_VALUE) + LOG.error(msg) + raise n_exc.InvalidInput(error_message=msg) + + # validate the burst size value max value + # (max value is 0, and neutron already validates this) + if (bw_rule.max_burst_kbps > MAX_BURST_MAX_VALUE): + msg = (_("Invalid input for burst_size. " + "The maximal legal value is %s") % MAX_BURST_MAX_VALUE) + LOG.error(msg) + raise n_exc.InvalidInput(error_message=msg) + + def _get_shaper_from_rule(self, bw_rule, is_ingress=True): + """Translate the neutron bandwidth_limit_rule values into the + NSX-lib Policy QoS shaper + """ + kwargs = {} + if is_ingress: + shaper = self._nsxpolicy.qos_profile.build_ingress_rate_limiter + else: + shaper = self._nsxpolicy.qos_profile.build_egress_rate_limiter + + if bw_rule: + kwargs['enabled'] = True + + # translate kbps -> bytes + kwargs['burst_size'] = int(bw_rule.max_burst_kbps) * 128 + + # value in kbps -> Mb/s + kwargs['average_bandwidth'] = int( + round(float(bw_rule.max_kbps) / 1024)) + + # peakBandwidth: a Multiplying on the average BW because the + # neutron qos configuration supports only 1 value + kwargs['peak_bandwidth'] = int( + round(kwargs['average_bandwidth'] * + cfg.CONF.NSX.qos_peak_bw_multiplier)) + else: + kwargs['enabled'] = False + + return shaper(**kwargs) + + def _get_dscp_from_rule(self, dscp_rule): + """Translate the neutron DSCP marking rule values into NSX-lib + Policy QoS Dscp object + """ + trusted = False if dscp_rule else True + priority = dscp_rule.dscp_mark if dscp_rule else 0 + return self._nsxpolicy.qos_profile.build_dscp( + trusted=trusted, priority=priority) + + def update_policy_rules(self, context, policy_id, rules): + """This handler will do all the updates through the create_or_update""" + pass + + def validate_policy_rule(self, context, policy_id, rule): + """Raise an exception if the rule values are not supported""" + if rule.rule_type == qos_consts.RULE_TYPE_BANDWIDTH_LIMIT: + self._validate_bw_values(rule) + elif rule.rule_type == qos_consts.RULE_TYPE_DSCP_MARKING: + pass + else: + msg = (_("The NSX-Policy plugin does not support QoS rule of type " + "%s") % rule.rule_type) + LOG.error(msg) + raise n_exc.InvalidInput(error_message=msg) diff --git a/vmware_nsx/services/qos/nsx_v3/utils.py b/vmware_nsx/services/qos/nsx_v3/utils.py index 76f1b6d9d3..84215b7d12 100644 --- a/vmware_nsx/services/qos/nsx_v3/utils.py +++ b/vmware_nsx/services/qos/nsx_v3/utils.py @@ -97,12 +97,7 @@ class QosNotificationsHandler(object): description=policy.description) def _validate_bw_values(self, bw_rule): - """Validate that the configured values are allowed by the NSX backend. - - Since failing the action from the notification callback - is not possible, just log the warning and use the minimal/maximal - values. - """ + """Validate that the values are allowed by the NSX backend""" # Validate the max bandwidth value minimum value # (max value is above what neutron allows so no need to check it) if (bw_rule.max_kbps < MAX_KBPS_MIN_VALUE): diff --git a/vmware_nsx/tests/unit/nsx_p/test_plugin.py b/vmware_nsx/tests/unit/nsx_p/test_plugin.py index e9f903ad95..eceb525e8c 100644 --- a/vmware_nsx/tests/unit/nsx_p/test_plugin.py +++ b/vmware_nsx/tests/unit/nsx_p/test_plugin.py @@ -43,6 +43,7 @@ from neutron_lib.callbacks import resources from neutron_lib import constants from neutron_lib import context from neutron_lib import exceptions as n_exc +from neutron_lib.objects import registry as obj_reg from neutron_lib.plugins import directory from vmware_nsx.common import utils @@ -86,6 +87,7 @@ class NsxPPluginTestCaseMixin( self.setup_conf_overrides() super(NsxPPluginTestCaseMixin, self).setUp(plugin=plugin, ext_mgr=ext_mgr) + self.ctx = context.get_admin_context() def _mock_nsx_policy_backend_calls(self): resource_list_result = {'results': [{'id': 'test', @@ -181,6 +183,17 @@ class NsxPPluginTestCaseMixin( '', tenant_id) return network_req.get_response(self.api) + def _create_l3_ext_network(self, physical_network='abc'): + name = 'l3_ext_net' + net_type = utils.NetworkTypes.L3_EXT + providernet_args = {pnet.NETWORK_TYPE: net_type, + pnet.PHYSICAL_NETWORK: physical_network} + return self.network(name=name, + router__external=True, + providernet_args=providernet_args, + arg_list=(pnet.NETWORK_TYPE, + pnet.PHYSICAL_NETWORK)) + class NsxPTestNetworks(test_db_base_plugin_v2.TestNetworksV2, NsxPPluginTestCaseMixin): @@ -419,6 +432,47 @@ class NsxPTestNetworks(test_db_base_plugin_v2.TestNetworksV2, az_hints = net['network']['availability_zone_hints'] self.assertListEqual(az_hints, zone) + def test_create_net_with_qos(self): + policy_id = uuidutils.generate_uuid() + data = {'network': { + 'tenant_id': self._tenant_id, + 'qos_policy_id': policy_id, + 'name': 'qos_net', + 'admin_state_up': True, + 'shared': False} + } + dummy = mock.Mock() + dummy.id = policy_id + with mock.patch.object(self.plugin, '_validate_qos_policy_id'),\ + mock.patch.object(obj_reg.load_class('QosPolicy'), + 'get_network_policy', + return_value=dummy): + net = self.plugin.create_network(self.ctx, data) + self.assertEqual(policy_id, net['qos_policy_id']) + net = self.plugin.get_network(self.ctx, net['id']) + self.assertEqual(policy_id, net['qos_policy_id']) + + def test_update_net_with_qos(self): + data = {'network': { + 'tenant_id': self._tenant_id, + 'name': 'qos_net', + 'admin_state_up': True, + 'shared': False} + } + net = self.plugin.create_network(self.ctx, data) + policy_id = uuidutils.generate_uuid() + data['network']['qos_policy_id'] = policy_id + dummy = mock.Mock() + dummy.id = policy_id + with mock.patch.object(self.plugin, '_validate_qos_policy_id'),\ + mock.patch.object(obj_reg.load_class('QosPolicy'), + 'get_network_policy', + return_value=dummy): + res = self.plugin.update_network(self.ctx, net['id'], data) + self.assertEqual(policy_id, res['qos_policy_id']) + res = self.plugin.get_network(self.ctx, net['id']) + self.assertEqual(policy_id, res['qos_policy_id']) + class NsxPTestPorts(test_db_base_plugin_v2.TestPortsV2, NsxPPluginTestCaseMixin): @@ -557,6 +611,81 @@ class NsxPTestPorts(test_db_base_plugin_v2.TestPortsV2, self.assertEqual(res['port']['fixed_ips'], data['port']['fixed_ips']) + def test_create_port_with_qos(self): + with self.network() as network: + policy_id = uuidutils.generate_uuid() + data = {'port': { + 'network_id': network['network']['id'], + 'tenant_id': self._tenant_id, + 'qos_policy_id': policy_id, + 'name': 'qos_port', + 'admin_state_up': True, + 'device_id': 'fake_device', + 'device_owner': 'fake_owner', + 'fixed_ips': [], + 'mac_address': '00:00:00:00:00:01'} + } + with mock.patch.object(self.plugin, '_validate_qos_policy_id'): + port = self.plugin.create_port(self.ctx, data) + self.assertEqual(policy_id, port['qos_policy_id']) + # Get port should also return the qos policy id + with mock.patch('vmware_nsx.services.qos.common.utils.' + 'get_port_policy_id', + return_value=policy_id): + port = self.plugin.get_port(self.ctx, port['id']) + self.assertEqual(policy_id, port['qos_policy_id']) + + def test_update_port_with_qos(self): + with self.network() as network: + data = {'port': { + 'network_id': network['network']['id'], + 'tenant_id': self._tenant_id, + 'name': 'qos_port', + 'admin_state_up': True, + 'device_id': 'fake_device', + 'device_owner': 'fake_owner', + 'fixed_ips': [], + 'mac_address': '00:00:00:00:00:01'} + } + port = self.plugin.create_port(self.ctx, data) + policy_id = uuidutils.generate_uuid() + data['port']['qos_policy_id'] = policy_id + with mock.patch.object(self.plugin, '_validate_qos_policy_id'): + res = self.plugin.update_port(self.ctx, port['id'], data) + self.assertEqual(policy_id, res['qos_policy_id']) + # Get port should also return the qos policy id + with mock.patch('vmware_nsx.services.qos.common.utils.' + 'get_port_policy_id', + return_value=policy_id): + res = self.plugin.get_port(self.ctx, port['id']) + self.assertEqual(policy_id, res['qos_policy_id']) + + def test_create_ext_port_with_qos_fail(self): + with self._create_l3_ext_network() as network: + with self.subnet(network=network, cidr='10.0.0.0/24', + enable_dhcp=False),\ + mock.patch.object(self.plugin, '_validate_qos_policy_id'): + policy_id = uuidutils.generate_uuid() + data = {'port': {'network_id': network['network']['id'], + 'tenant_id': self._tenant_id, + 'qos_policy_id': policy_id}} + # Cannot add qos policy to a router port + self.assertRaises(n_exc.InvalidInput, + self.plugin.create_port, self.ctx, data) + + def _test_create_illegal_port_with_qos_fail(self, device_owner): + with self.network() as network: + with self.subnet(network=network, cidr='10.0.0.0/24'),\ + mock.patch.object(self.plugin, '_validate_qos_policy_id'): + policy_id = uuidutils.generate_uuid() + data = {'port': {'network_id': network['network']['id'], + 'tenant_id': self._tenant_id, + 'device_owner': device_owner, + 'qos_policy_id': policy_id}} + # Cannot add qos policy to this type of port + self.assertRaises(n_exc.InvalidInput, + self.plugin.create_port, self.ctx, data) + def test_create_port_with_mac_learning_true(self): plugin = directory.get_plugin() ctx = context.get_admin_context() @@ -564,7 +693,7 @@ class NsxPTestPorts(test_db_base_plugin_v2.TestPortsV2, data = {'port': { 'network_id': network['network']['id'], 'tenant_id': self._tenant_id, - 'name': 'qos_port', + 'name': 'port', 'admin_state_up': True, 'device_id': 'fake_device', 'device_owner': 'fake_owner', @@ -583,7 +712,7 @@ class NsxPTestPorts(test_db_base_plugin_v2.TestPortsV2, data = {'port': { 'network_id': network['network']['id'], 'tenant_id': self._tenant_id, - 'name': 'qos_port', + 'name': 'port', 'admin_state_up': True, 'device_id': 'fake_device', 'device_owner': 'fake_owner', @@ -602,7 +731,7 @@ class NsxPTestPorts(test_db_base_plugin_v2.TestPortsV2, data = {'port': { 'network_id': network['network']['id'], 'tenant_id': self._tenant_id, - 'name': 'qos_port', + 'name': 'port', 'admin_state_up': True, 'device_id': 'fake_device', 'device_owner': 'fake_owner', @@ -622,7 +751,7 @@ class NsxPTestPorts(test_db_base_plugin_v2.TestPortsV2, data = {'port': { 'network_id': network['network']['id'], 'tenant_id': self._tenant_id, - 'name': 'qos_port', + 'name': 'port', 'admin_state_up': True, 'device_id': 'fake_device', 'device_owner': 'fake_owner', @@ -642,7 +771,7 @@ class NsxPTestPorts(test_db_base_plugin_v2.TestPortsV2, data = {'port': { 'network_id': network['network']['id'], 'tenant_id': self._tenant_id, - 'name': 'qos_port', + 'name': 'port', 'admin_state_up': True, 'device_id': 'fake_device', 'device_owner': constants.DEVICE_OWNER_FLOATINGIP, @@ -1032,17 +1161,6 @@ class NsxPTestL3NatTest(common_v3.FixExternalNetBaseTest, arg_list=(pnet.NETWORK_TYPE, pnet.PHYSICAL_NETWORK)) - def _create_l3_ext_network(self, physical_network='abc'): - name = 'l3_ext_net' - net_type = utils.NetworkTypes.L3_EXT - providernet_args = {pnet.NETWORK_TYPE: net_type, - pnet.PHYSICAL_NETWORK: physical_network} - return self.network(name=name, - router__external=True, - providernet_args=providernet_args, - arg_list=(pnet.NETWORK_TYPE, - pnet.PHYSICAL_NETWORK)) - def test_floatingip_create_different_fixed_ip_same_port(self): self.skipTest('Multiple fixed ips on a port are not supported') diff --git a/vmware_nsx/tests/unit/services/qos/test_nsxp_notification.py b/vmware_nsx/tests/unit/services/qos/test_nsxp_notification.py new file mode 100644 index 0000000000..711b32f092 --- /dev/null +++ b/vmware_nsx/tests/unit/services/qos/test_nsxp_notification.py @@ -0,0 +1,365 @@ +# Copyright 2016 VMware, Inc. +# 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. +import mock + +from neutron_lib import context +from neutron_lib import exceptions +from neutron_lib.objects import registry as obj_reg +from oslo_config import cfg +from oslo_utils import uuidutils + +from neutron.services.qos import qos_plugin +from neutron.tests.unit.services.qos import base + +from vmware_nsx.common import utils +from vmware_nsx.plugins.nsx_v3 import utils as v3_utils +from vmware_nsx.services.qos.nsx_v3 import driver as qos_driver +from vmware_nsx.services.qos.nsx_v3 import pol_utils as qos_utils +from vmware_nsx.tests.unit.nsx_p import test_plugin +from vmware_nsxlib.v3.policy import core_defs as policy_defs + +PLUGIN_NAME = 'vmware_nsx.plugins.nsx_p.plugin.NsxPolicyPlugin' +QoSPolicy = obj_reg.load_class('QosPolicy') +QosBandwidthLimitRule = obj_reg.load_class('QosBandwidthLimitRule') +QosDscpMarkingRule = obj_reg.load_class('QosDscpMarkingRule') +QosMinimumBandwidthRule = obj_reg.load_class('QosMinimumBandwidthRule') + + +class TestQosNsxPNotification(base.BaseQosTestCase, + test_plugin.NsxPPluginTestCaseMixin): + + def setUp(self): + # Reset the drive to re-create it + qos_driver.DRIVER = None + super(TestQosNsxPNotification, self).setUp() + self.setup_coreplugin(PLUGIN_NAME) + + self.qos_plugin = qos_plugin.QoSPlugin() + self.ctxt = context.Context('fake_user', 'fake_tenant') + mock.patch.object(self.ctxt.session, 'refresh').start() + mock.patch.object(self.ctxt.session, 'expunge').start() + policy_id = uuidutils.generate_uuid() + self.project_id = uuidutils.generate_uuid() + self.policy_data = { + 'policy': {'id': policy_id, + 'project_id': self.project_id, + 'name': 'test-policy', + 'description': 'Test policy description', + 'shared': True}} + self.rule_data = { + 'bandwidth_limit_rule': {'id': uuidutils.generate_uuid(), + 'max_kbps': 2000, + 'max_burst_kbps': 150}} + self.ingress_rule_data = { + 'bandwidth_limit_rule': {'id': uuidutils.generate_uuid(), + 'max_kbps': 3000, + 'max_burst_kbps': 350, + 'direction': 'ingress'}} + self.dscp_rule_data = { + 'dscp_marking_rule': {'id': uuidutils.generate_uuid(), + 'dscp_mark': 22}} + + self.policy = QoSPolicy( + self.ctxt, **self.policy_data['policy']) + + # egress BW limit rule + self.rule = QosBandwidthLimitRule( + self.ctxt, **self.rule_data['bandwidth_limit_rule']) + # ingress bw limit rule + self.ingress_rule = QosBandwidthLimitRule( + self.ctxt, **self.ingress_rule_data['bandwidth_limit_rule']) + self.dscp_rule = QosDscpMarkingRule( + self.ctxt, **self.dscp_rule_data['dscp_marking_rule']) + + self.fake_profile = {'id': policy_id} + + mock.patch('neutron.objects.db.api.create_object').start() + mock.patch('neutron.objects.db.api.update_object').start() + mock.patch('neutron.objects.db.api.delete_object').start() + + self.peak_bw_multiplier = cfg.CONF.NSX.qos_peak_bw_multiplier + + self.nsxlib = v3_utils.get_nsxlib_wrapper() + + @mock.patch.object(QoSPolicy, 'create_rbac_policy') + def test_policy_create_profile(self, *mocks): + # test the profile creation when a QoS policy is created + with mock.patch('vmware_nsxlib.v3.policy.core_resources.' + 'NsxQosProfileApi.create_or_overwrite', + return_value=self.fake_profile) as create_profile,\ + mock.patch.object(QoSPolicy, 'get_object', + return_value=self.policy),\ + mock.patch.object(QoSPolicy, 'create'): + self.qos_plugin.create_policy(self.ctxt, self.policy_data) + expected_tags = self.nsxlib.build_v3_api_version_project_tag( + project_name=self.ctxt.tenant_name, + project_id=self.project_id) + exp_name = utils.get_name_and_uuid(self.policy.name, + self.policy.id) + + create_profile.assert_called_once_with( + exp_name, + profile_id=self.policy.id, + description=self.policy_data["policy"]["description"], + dscp=None, + shaper_configurations=[], + tags=expected_tags) + + @mock.patch.object(QoSPolicy, '_reload_rules') + def test_bw_rule_create_profile(self, *mocks): + # test the profile update when an egress QoS BW rule is created + _policy = QoSPolicy( + self.ctxt, **self.policy_data['policy']) + # add a rule to the policy + setattr(_policy, "rules", [self.rule]) + with mock.patch.object(QoSPolicy, 'get_object', return_value=_policy),\ + mock.patch('vmware_nsxlib.v3.policy.core_resources.' + 'NsxQosProfileApi.' + 'create_or_overwrite') as create_profile,\ + mock.patch('neutron.objects.db.api.update_object', + return_value=self.rule_data): + self.qos_plugin.update_policy_bandwidth_limit_rule( + self.ctxt, self.rule.id, _policy.id, self.rule_data) + + # validate the data on the profile + rule_dict = self.rule_data['bandwidth_limit_rule'] + expected_bw = int(round(float( + rule_dict['max_kbps']) / 1024)) + expected_burst = rule_dict['max_burst_kbps'] * 128 + expected_peak = int(expected_bw * self.peak_bw_multiplier) + expected_tags = self.nsxlib.build_v3_api_version_project_tag( + project_name=self.ctxt.tenant_name, + project_id=self.project_id) + exp_name = utils.get_name_and_uuid(self.policy.name, + self.policy.id) + # egress neutron rule -> ingress nsx args + shaper_type = policy_defs.QoSRateLimiter.INGRESS_RATE_LIMITER_TYPE + expected_shaper = policy_defs.QoSRateLimiter( + resource_type=shaper_type, + enabled=True, + burst_size=expected_burst, + peak_bandwidth=expected_peak, + average_bandwidth=expected_bw) + create_profile.assert_called_once_with( + exp_name, + profile_id=self.policy.id, + description=self.policy_data["policy"]["description"], + dscp=None, + shaper_configurations=[mock.ANY], + tags=expected_tags) + # Compare the shaper + actual_shaper = create_profile.call_args[1][ + 'shaper_configurations'][0] + self.assertEqual(expected_shaper.get_obj_dict(), + actual_shaper.get_obj_dict()) + + @mock.patch.object(QoSPolicy, '_reload_rules') + def test_ingress_bw_rule_create_profile(self, *mocks): + # test the profile update when a ingress QoS BW rule is created + _policy = QoSPolicy( + self.ctxt, **self.policy_data['policy']) + # add a rule to the policy + setattr(_policy, "rules", [self.ingress_rule]) + with mock.patch.object(QoSPolicy, 'get_object', return_value=_policy),\ + mock.patch('vmware_nsxlib.v3.policy.core_resources.' + 'NsxQosProfileApi.' + 'create_or_overwrite') as create_profile,\ + mock.patch('neutron.objects.db.api.update_object', + return_value=self.ingress_rule_data): + self.qos_plugin.update_policy_bandwidth_limit_rule( + self.ctxt, self.ingress_rule.id, _policy.id, + self.ingress_rule_data) + + # validate the data on the profile + rule_dict = self.ingress_rule_data['bandwidth_limit_rule'] + expected_bw = int(round(float( + rule_dict['max_kbps']) / 1024)) + expected_burst = rule_dict['max_burst_kbps'] * 128 + expected_peak = int(expected_bw * self.peak_bw_multiplier) + exp_name = utils.get_name_and_uuid(self.policy.name, + self.policy.id) + expected_tags = self.nsxlib.build_v3_api_version_project_tag( + project_name=self.ctxt.tenant_name, + project_id=self.project_id) + # ingress neutron rule -> egress nsx args + shaper_type = policy_defs.QoSRateLimiter.EGRESS_RATE_LIMITER_TYPE + expected_shaper = policy_defs.QoSRateLimiter( + resource_type=shaper_type, + enabled=True, + burst_size=expected_burst, + peak_bandwidth=expected_peak, + average_bandwidth=expected_bw) + create_profile.assert_called_once_with( + exp_name, + profile_id=self.policy.id, + description=self.policy_data["policy"]["description"], + dscp=None, + shaper_configurations=[mock.ANY], + tags=expected_tags) + # Compare the shaper + actual_shaper = create_profile.call_args[1][ + 'shaper_configurations'][0] + self.assertEqual(expected_shaper.get_obj_dict(), + actual_shaper.get_obj_dict()) + + @mock.patch.object(QoSPolicy, '_reload_rules') + def test_bw_rule_create_profile_minimal_val(self, *mocks): + # test driver precommit with an invalid limit value + bad_limit = qos_utils.MAX_KBPS_MIN_VALUE - 1 + rule_data = { + 'bandwidth_limit_rule': {'id': uuidutils.generate_uuid(), + 'max_kbps': bad_limit, + 'max_burst_kbps': 150}} + + rule = QosBandwidthLimitRule( + self.ctxt, **rule_data['bandwidth_limit_rule']) + + _policy = QoSPolicy( + self.ctxt, **self.policy_data['policy']) + # add a rule to the policy + setattr(_policy, "rules", [rule]) + with mock.patch.object(QoSPolicy, 'get_object', + return_value=_policy),\ + mock.patch('neutron.objects.db.api.update_object', + return_value=rule_data): + self.assertRaises( + exceptions.DriverCallError, + self.qos_plugin.update_policy_bandwidth_limit_rule, + self.ctxt, rule.id, _policy.id, rule_data) + + @mock.patch.object(QoSPolicy, '_reload_rules') + def test_bw_rule_create_profile_maximal_val(self, *mocks): + # test driver precommit with an invalid burst value + bad_burst = qos_utils.MAX_BURST_MAX_VALUE + 1 + rule_data = { + 'bandwidth_limit_rule': {'id': uuidutils.generate_uuid(), + 'max_kbps': 1025, + 'max_burst_kbps': bad_burst}} + + rule = QosBandwidthLimitRule( + self.ctxt, **rule_data['bandwidth_limit_rule']) + + _policy = QoSPolicy( + self.ctxt, **self.policy_data['policy']) + # add a rule to the policy + setattr(_policy, "rules", [rule]) + with mock.patch.object(QoSPolicy, 'get_object', + return_value=_policy),\ + mock.patch('neutron.objects.db.api.update_object', + return_value=rule_data): + self.assertRaises( + exceptions.DriverCallError, + self.qos_plugin.update_policy_bandwidth_limit_rule, + self.ctxt, rule.id, _policy.id, rule_data) + + @mock.patch.object(QoSPolicy, '_reload_rules') + def test_dscp_rule_create_profile(self, *mocks): + # test the profile update when a QoS DSCP rule is created + _policy = QoSPolicy( + self.ctxt, **self.policy_data['policy']) + # add a rule to the policy + setattr(_policy, "rules", [self.dscp_rule]) + with mock.patch.object(QoSPolicy, 'get_object', return_value=_policy),\ + mock.patch('vmware_nsxlib.v3.policy.core_resources.' + 'NsxQosProfileApi.' + 'create_or_overwrite') as create_profile,\ + mock.patch('neutron.objects.db.api.update_object', + return_value=self.dscp_rule_data): + self.qos_plugin.update_policy_dscp_marking_rule( + self.ctxt, self.dscp_rule.id, + _policy.id, self.dscp_rule_data) + + # validate the data on the profile + rule_dict = self.dscp_rule_data['dscp_marking_rule'] + dscp_mark = rule_dict['dscp_mark'] + + exp_name = utils.get_name_and_uuid(self.policy.name, + self.policy.id) + expected_tags = self.nsxlib.build_v3_api_version_project_tag( + project_name=self.ctxt.tenant_name, + project_id=self.project_id) + expected_dscp = policy_defs.QoSDscp( + mode=policy_defs.QoSDscp.QOS_DSCP_UNTRUSTED, + priority=dscp_mark) + create_profile.assert_called_once_with( + exp_name, + profile_id=self.policy.id, + description=self.policy_data["policy"]["description"], + dscp=mock.ANY, + shaper_configurations=[], + tags=expected_tags) + # Compare the dscp obj + actual_dscp = create_profile.call_args[1]['dscp'] + self.assertEqual(expected_dscp.get_obj_dict(), + actual_dscp.get_obj_dict()) + + @mock.patch.object(QoSPolicy, '_reload_rules') + def test_minimum_bw_rule_create_profile(self, *mocks): + # Minimum BW rules are not supported + policy = QoSPolicy( + self.ctxt, **self.policy_data['policy']) + min_bw_rule_data = { + 'minimum_bandwidth_rule': {'id': uuidutils.generate_uuid(), + 'min_kbps': 10, + 'direction': 'egress'}} + min_bw_rule = QosMinimumBandwidthRule( + self.ctxt, **min_bw_rule_data['minimum_bandwidth_rule']) + # add a rule to the policy + setattr(policy, "rules", [min_bw_rule]) + with mock.patch.object( + QoSPolicy, 'get_object', return_value=policy),\ + mock.patch('neutron.objects.db.api.' + 'update_object', return_value=self.dscp_rule_data): + self.assertRaises( + exceptions.DriverCallError, + self.qos_plugin.update_policy_minimum_bandwidth_rule, + self.ctxt, min_bw_rule.id, + policy.id, min_bw_rule_data) + + def test_rule_delete_profile(self): + # test the profile update when a QoS rule is deleted + _policy = QoSPolicy( + self.ctxt, **self.policy_data['policy']) + # The mock will return the policy without the rule, + # as if it was deleted + with mock.patch.object(QoSPolicy, 'get_object', return_value=_policy),\ + mock.patch('vmware_nsxlib.v3.policy.core_resources.' + 'NsxQosProfileApi.' + 'create_or_overwrite') as set_profile: + setattr(_policy, "rules", [self.rule]) + self.qos_plugin.delete_policy_bandwidth_limit_rule( + self.ctxt, self.rule.id, self.policy.id) + # validate the data on the profile + expected_tags = self.nsxlib.build_v3_api_version_project_tag( + project_name=self.ctxt.tenant_name, + project_id=self.project_id) + exp_name = utils.get_name_and_uuid(self.policy.name, + self.policy.id) + + set_profile.assert_called_once_with( + exp_name, + profile_id=self.policy.id, + description=self.policy_data["policy"]["description"], + dscp=None, + shaper_configurations=[], + tags=expected_tags) + + @mock.patch('neutron.objects.db.api.get_object', return_value=None) + def test_policy_delete_profile(self, *mocks): + # test the profile deletion when a QoS policy is deleted + with mock.patch('vmware_nsxlib.v3.policy.core_resources.' + 'NsxQosProfileApi.delete') as delete_profile: + self.qos_plugin.delete_policy(self.ctxt, self.policy.id) + delete_profile.assert_called_once_with(self.policy.id)