NSX|P: QoS support

Change-Id: I719c1adfa94676b5e8b3a7b60f8d9d034d54eeb3
This commit is contained in:
Adit Sarfaty 2018-12-20 15:28:47 +02:00
parent 17759dba87
commit 74f3831027
11 changed files with 877 additions and 134 deletions

View File

@ -211,6 +211,8 @@ class NSXClient(object):
segment_id, p['id']) segment_id, p['id'])
self.nsxpolicy.segment_port_discovery_profiles.delete( self.nsxpolicy.segment_port_discovery_profiles.delete(
segment_id, p['id']) segment_id, p['id'])
self.nsxpolicy.segment_port_qos_profiles.delete(
segment_id, p['id'])
self.nsxpolicy.segment_port.delete(segment_id, p['id']) self.nsxpolicy.segment_port.delete(segment_id, p['id'])
except exceptions.ManagerError as e: except exceptions.ManagerError as e:
print("Failed to delete segment port %s: %s" % (p['id'], e)) print("Failed to delete segment port %s: %s" % (p['id'], e))

View File

@ -283,6 +283,25 @@ Add octavia repo as an external repository and configure following flags in ``lo
[oslo_messaging] [oslo_messaging]
topic=vmwarensxv_edge_lb 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 = <i.e 10.0>
NSX-TVD NSX-TVD
------- -------

View File

@ -351,19 +351,23 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
if is_external_net: if is_external_net:
raise nsx_exc.QoSOnExternalNet() 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 """Validate the updated parameters of a network
This method includes general validations that does not depend on This method includes general validations that does not depend on
provider attributes, or plugin specific configurations 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( with_qos = validators.is_attr_set(
net_data.get(qos_consts.QOS_POLICY_ID)) net_data.get(qos_consts.QOS_POLICY_ID))
# Do not allow QoS on external networks # Do not allow QoS on external networks
if with_qos and extern_net: if with_qos:
raise nsx_exc.QoSOnExternalNet() 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 # Do not support changing external/non-external networks
if (extnet_apidef.EXTERNAL in net_data and 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") err_msg = _("Cannot change the router:external flag of a network")
raise n_exc.InvalidInput(error_message=err_msg) 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): def _assert_on_illegal_port_with_qos(self, device_owner):
# Prevent creating/update port with QoS policy # Prevent creating/update port with QoS policy
# on router-interface/network-dhcp ports. # on router-interface/network-dhcp ports.
@ -392,6 +400,23 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
LOG.warning(err_msg) LOG.warning(err_msg)
raise n_exc.InvalidInput(error_message=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): def _validate_create_port(self, context, port_data):
self._validate_max_ips_per_port(port_data.get('fixed_ips', []), self._validate_max_ips_per_port(port_data.get('fixed_ips', []),
port_data.get('device_owner')) port_data.get('device_owner'))
@ -416,6 +441,10 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
self._assert_on_port_admin_state(port_data, device_owner) 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): def _assert_on_vpn_port_change(self, port_data):
if port_data['device_owner'] == ipsec_utils.VPN_PORT_OWNER: if port_data['device_owner'] == ipsec_utils.VPN_PORT_OWNER:
msg = _('Can not update/delete VPNaaS port %s') % port_data['id'] msg = _('Can not update/delete VPNaaS port %s') % port_data['id']
@ -478,16 +507,6 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
LOG.warning(err_msg) LOG.warning(err_msg)
raise n_exc.InvalidInput(error_message=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): def _validate_update_port(self, context, id, original_port, port_data):
qos_selected = validators.is_attr_set(port_data.get qos_selected = validators.is_attr_set(port_data.get
(qos_consts.QOS_POLICY_ID)) (qos_consts.QOS_POLICY_ID))
@ -496,6 +515,7 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
device_owner = (port_data['device_owner'] device_owner = (port_data['device_owner']
if 'device_owner' in port_data if 'device_owner' in port_data
else original_port.get('device_owner')) else original_port.get('device_owner'))
is_ens_tz_port = self._is_ens_tz_port(context, original_port)
# QoS validations # QoS validations
if qos_selected: if qos_selected:
@ -504,6 +524,9 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
if is_external_net: if is_external_net:
raise nsx_exc.QoSOnExternalNet() raise nsx_exc.QoSOnExternalNet()
self._assert_on_illegal_port_with_qos(device_owner) 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: # External networks validations:
if is_external_net: if is_external_net:
@ -620,6 +643,44 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
err_msg = _("Cannot configure QOS on ENS networks") err_msg = _("Cannot configure QOS on ENS networks")
raise n_exc.InvalidInput(error_message=err_msg) 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): def _ens_psec_supported(self):
"""Should be implemented by each plugin""" """Should be implemented by each plugin"""
pass pass
@ -646,13 +707,13 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
""" """
pass 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""" """Should be implemented by each plugin"""
pass pass
def _is_ens_tz_port(self, context, port_data): def _is_ens_tz_port(self, context, port_data):
"""Should be implemented by each plugin""" # Check the host-switch-mode of the TZ connected to the ports network
pass return self._is_ens_tz_net(context, port_data['network_id'])
def _is_overlay_network(self, network_id): def _is_overlay_network(self, network_id):
"""Should be implemented by each plugin""" """Should be implemented by each plugin"""
@ -691,7 +752,7 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
- net_type: provider network type or None - net_type: provider network type or None
- physical_net: the uuid of the relevant transport zone or None - physical_net: the uuid of the relevant transport zone or None
- vlan_id: vlan tag, 0 or None - vlan_id: vlan tag, 0 or None
- switch_mode: standard ot ENS - switch_mode: standard or ENS
""" """
is_provider_net = any( is_provider_net = any(
validators.is_attr_set(network_data.get(f)) validators.is_attr_set(network_data.get(f))
@ -870,6 +931,12 @@ class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
port_data[pbin.VIF_DETAILS]['segmentation-id'] = ( port_data[pbin.VIF_DETAILS]['segmentation-id'] = (
self._get_network_segmentation_id(context, net_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): def fix_direct_vnic_port_sec(self, direct_vnic_type, port_data):
if direct_vnic_type: if direct_vnic_type:
if validators.is_attr_set(port_data.get(psec.PORTSECURITY)): if validators.is_attr_set(port_data.get(psec.PORTSECURITY)):

View File

@ -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 resource_extend
from neutron_lib.db import utils as db_utils from neutron_lib.db import utils as db_utils
from neutron_lib import exceptions as n_exc 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._i18n import _
from vmware_nsx.common import config # noqa 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.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_p import availability_zones as nsxp_az
from vmware_nsx.plugins.nsx_v3 import utils as v3_utils 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 exceptions as nsx_lib_exc
from vmware_nsxlib.v3 import nsx_constants as nsxlib_consts from vmware_nsxlib.v3 import nsx_constants as nsxlib_consts
@ -172,6 +178,9 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
self._init_native_dhcp() self._init_native_dhcp()
# Init QoS
qos_driver.register(qos_utils.PolicyQosNotificationsHandler())
# subscribe the init complete method last, so it will be called only # subscribe the init complete method last, so it will be called only
# if init was successful # if init was successful
registry.subscribe(self.init_complete, 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. # update the network name to indicate the neutron id too.
net_name = utils.get_name_and_uuid(net_data['name'] or 'network', net_name = utils.get_name_and_uuid(net_data['name'] or 'network',
net_data['id']) net_data['id'])
tags = self.nsxpolicy.build_v3_tags_payload( tags = self.nsxpolicy.build_v3_api_version_project_tag(
net_data, resource_type='os-neutron-net-id', context.tenant_name)
project_name=context.tenant_name)
# TODO(annak): admin state config is missing on policy # TODO(annak): admin state config is missing on policy
# should we not create networks that are down? # 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 # latest db model for the extension functions
net_model = self._get_network(context, created_net['id']) net_model = self._get_network(context, created_net['id'])
resource_extend.apply_funcs('networks', created_net, net_model) 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 return created_net
def delete_network(self, context, network_id): def delete_network(self, context, network_id):
@ -438,6 +456,10 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
context, network_id) context, network_id)
net_data = network['network'] 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 # Neutron does not support changing provider network values
providernet._raise_if_updates_provider_attributes(net_data) providernet._raise_if_updates_provider_attributes(net_data)
extern_net = self._network_is_external(context, network_id) 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._process_l3_update(context, updated_net, network['network'])
self._extend_network_dict_provider(context, updated_net) 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 # Update the backend segment
if (not extern_net and not is_nsx_net and if (not extern_net and not is_nsx_net and
('name' in net_data or 'description' in net_data)): ('name' in net_data or 'description' in net_data)):
@ -562,7 +597,8 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
return tags 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 # TODO(annak): admin_state not supported by policy
name = self._build_port_name(context, port_data) name = self._build_port_name(context, port_data)
address_bindings = self._build_port_address_bindings( address_bindings = self._build_port_address_bindings(
@ -620,6 +656,12 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
name, segment_id, port_data['id'], name, segment_id, port_data['id'],
mac_discovery_profile_id=mac_discovery_profile) 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): def base_create_port(self, context, port):
neutron_db = super(NsxPolicyPlugin, self).create_port(context, port) neutron_db = super(NsxPolicyPlugin, self).create_port(context, port)
self._extension_manager.process_create_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): def create_port(self, context, port, l2gw_port_check=False):
port_data = port['port'] port_data = port['port']
self._validate_max_ips_per_port(port_data.get('fixed_ips', []), # validate the new port parameters
port_data.get('device_owner')) self._validate_create_port(context, port_data)
# Validate the vnic type (the same types as for the NSX-T plugin) # Validate the vnic type (the same types as for the NSX-T plugin)
direct_vnic_type = self._validate_port_vnic_type( direct_vnic_type = self._validate_port_vnic_type(
@ -673,9 +715,13 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
# ATTR_NOT_SPECIFIED # ATTR_NOT_SPECIFIED
port_data.pop(mac_ext.MAC_LEARNING) 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: if not is_external_net:
try: 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: except Exception as e:
with excutils.save_and_reraise_exception(): with excutils.save_and_reraise_exception():
LOG.error('Failed to create port %(id)s on NSX ' 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( super(NsxPolicyPlugin, self).delete_port(
context, neutron_db['id']) 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 # this extra lookup is necessary to get the
# latest db model for the extension functions # latest db model for the extension functions
port_model = self._get_port(context, port_data['id']) port_model = self._get_port(context, port_data['id'])
@ -716,6 +767,9 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
segment_id, port_id) segment_id, port_id)
self.nsxpolicy.segment_port_discovery_profiles.delete( self.nsxpolicy.segment_port_discovery_profiles.delete(
segment_id, port_id) 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) self.nsxpolicy.segment_port.delete(segment_id, port_id)
except Exception as ex: except Exception as ex:
LOG.error("Failed to delete port %(id)s on NSX backend " 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, def _update_port_on_backend(self, context, lport_id,
original_port, updated_port, original_port, updated_port,
is_psec_on): is_psec_on, qos_policy_id):
# For now port create and update are the same # For now port create and update are the same
# Update might evolve with more features # 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): def update_port(self, context, port_id, port):
with db_api.CONTEXT_WRITER.using(context): 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, self._update_mac_learning_state(context, port_id,
mac_learning_state) 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 # update the port in the backend, only if it exists in the DB
# (i.e not external net) # (i.e not external net)
if not is_external_net: if not is_external_net:
try: try:
self._update_port_on_backend(context, port_id, self._update_port_on_backend(context, port_id,
original_port, updated_port, original_port, updated_port,
port_security) port_security, qos_policy_id)
except Exception as e: except Exception as e:
LOG.error('Failed to update port %(id)s on NSX ' LOG.error('Failed to update port %(id)s on NSX '
'backend. Exception: %(e)s', 'backend. Exception: %(e)s',
@ -833,6 +894,7 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
port_model = self._get_port(context, port['id']) port_model = self._get_port(context, port['id'])
resource_extend.apply_funcs('ports', port, port_model) resource_extend.apply_funcs('ports', port, port_model)
self._extend_nsx_port_dict_binding(context, port) self._extend_nsx_port_dict_binding(context, port)
self._extend_qos_port_dict_binding(context, port)
self._remove_provider_security_groups_from_list(port) self._remove_provider_security_groups_from_list(port)
return db_utils.resource_fields(port, fields) return db_utils.resource_fields(port, fields)
@ -859,6 +921,7 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
ports.remove(port) ports.remove(port)
continue continue
self._extend_nsx_port_dict_binding(context, port) self._extend_nsx_port_dict_binding(context, port)
self._extend_qos_port_dict_binding(context, port)
self._remove_provider_security_groups_from_list(port) self._remove_provider_security_groups_from_list(port)
return (ports if not fields else return (ports if not fields else
[db_utils.resource_fields(port, fields) for port in ports]) [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 #TODO(annak): handle ENS case
return False return False
def _is_ens_tz_port(self, context, port_data):
#TODO(annak): handle ENS case
return False
def _has_native_dhcp_metadata(self): def _has_native_dhcp_metadata(self):
return True return True

View File

@ -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.lbaas.octavia import octavia_listener
from vmware_nsx.services.qos.common import utils as qos_com_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 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_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 core_resources as nsx_resources
from vmware_nsxlib.v3 import exceptions as nsx_lib_exc 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 ) % NSX_V3_EXCLUDED_PORT_NSGROUP_NAME
raise nsx_exc.NsxPluginException(err_msg=msg) raise nsx_exc.NsxPluginException(err_msg=msg)
qos_driver.register() qos_driver.register(qos_utils.QosNotificationsHandler())
self.start_rpc_listeners_called = False self.start_rpc_listeners_called = False
@ -1196,17 +1197,12 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base,
net_data = network['network'] net_data = network['network']
# Neutron does not support changing provider network values # Neutron does not support changing provider network values
providernet._raise_if_updates_provider_attributes(net_data) 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) extern_net = self._network_is_external(context, id)
is_nsx_net = self._network_is_nsx_net(context, id) is_nsx_net = self._network_is_nsx_net(context, id)
is_ens_net = self._is_ens_tz_net(context, id) is_ens_net = self._is_ens_tz_net(context, id)
# Validate the updated parameters # Validate the updated parameters
self._validate_update_network(context, id, original_net, net_data) 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, updated_net = super(NsxV3Plugin, self).update_network(context, id,
network) network)
@ -1766,13 +1762,8 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base,
profiles.append(self._dhcp_profile) profiles.append(self._dhcp_profile)
# Add QoS switching profile, if exists # Add QoS switching profile, if exists
qos_policy_id = None qos_policy_id = self._get_port_qos_policy_id(
if validators.is_attr_set(port_data.get(qos_consts.QOS_POLICY_ID)): context, None, port_data)
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'])
if qos_policy_id: if qos_policy_id:
qos_profile_id = self._get_qos_profile_id(context, qos_policy_id) qos_profile_id = self._get_qos_profile_id(context, qos_policy_id)
profiles.append(qos_profile_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) mode = self.nsxlib.transport_zone.get_host_switch_mode(tz_id)
return mode == self.nsxlib.transport_zone.HOST_SWITCH_MODE_ENS 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): def _has_native_dhcp_metadata(self):
return cfg.CONF.nsx_v3.native_dhcp_metadata 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( self.nsxlib.ns_group.update_lport_nsgroups(
lport_id, nsx_origial, nsx_updated) lport_id, nsx_origial, nsx_updated)
def base_create_port(self, context, port): def _disable_ens_portsec(self, port_data):
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)
if (cfg.CONF.nsx_v3.disable_port_security_for_ens and if (cfg.CONF.nsx_v3.disable_port_security_for_ens and
not self._ens_psec_supported()): not self._ens_psec_supported()):
LOG.warning("Disabling port security for network %s", 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[psec.PORTSECURITY] = False
port_data['security_groups'] = [] 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): def create_port(self, context, port, l2gw_port_check=False):
port_data = port['port'] port_data = port['port']
@ -2253,7 +2234,14 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base,
self._assert_on_dhcp_relay_without_router(context, port_data) self._assert_on_dhcp_relay_without_router(context, port_data)
is_ens_tz_port = self._is_ens_tz_port(context, port_data) is_ens_tz_port = self._is_ens_tz_port(context, port_data)
if is_ens_tz_port: 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( is_external_net = self._network_is_external(
context, port_data['network_id']) context, port_data['network_id'])
@ -2567,14 +2555,8 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base,
switch_profile_ids.append(self._dhcp_profile) switch_profile_ids.append(self._dhcp_profile)
# Update QoS switch profile # Update QoS switch profile
orig_compute = original_device_owner.startswith( qos_policy_id, qos_profile_id = self._get_port_qos_ids(
const.DEVICE_OWNER_COMPUTE_PREFIX) context, original_port, updated_port)
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)
if qos_profile_id is not None: if qos_profile_id is not None:
switch_profile_ids.append(qos_profile_id) switch_profile_ids.append(qos_profile_id)
@ -2619,28 +2601,13 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base,
updated_port['id'], updated_port['id'],
qos_policy_id) qos_policy_id)
def _get_port_qos_ids(self, context, updated_port, is_new_compute): def _get_port_qos_ids(self, context, original_port, updated_port):
# when a port is updated, get the current QoS policy/profile ids qos_policy_id = self._get_port_qos_policy_id(
policy_id = None context, original_port, updated_port)
profile_id = None profile_id = None
if (qos_consts.QOS_POLICY_ID in updated_port): if qos_policy_id is not None:
policy_id = updated_port[qos_consts.QOS_POLICY_ID] profile_id = self._get_qos_profile_id(context, qos_policy_id)
else: return qos_policy_id, profile_id
# 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
def update_port(self, context, id, port): def update_port(self, context, id, port):
with db_api.CONTEXT_WRITER.using(context): 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, self._assert_on_dhcp_relay_without_router(context, port_data,
original_port) original_port)
is_ens_tz_port = self._is_ens_tz_port(context, 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) dhcp_opts = port_data.get(ext_edo.EXTRADHCPOPTS)
self._validate_extra_dhcp_options(dhcp_opts) 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): def _extend_get_port_dict_qos_and_binding(self, context, port):
# Not using the register api for this because we need the context # Not using the register api for this because we need the context
self._extend_nsx_port_dict_binding(context, port) self._extend_nsx_port_dict_binding(context, port)
self._extend_qos_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'])
def get_port(self, context, id, fields=None): def get_port(self, context, id, fields=None):
port = super(NsxV3Plugin, self).get_port(context, id, fields=None) port = super(NsxV3Plugin, self).get_port(context, id, fields=None)

View File

@ -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 # attach the policy to the network in the neutron DB
update_network_policy_binding( update_network_policy_binding(
context, context,
net_data['id'], created_net['id'],
qos_policy_id) qos_policy_id)
created_net[qos_consts.QOS_POLICY_ID] = qos_policy_id created_net[qos_consts.QOS_POLICY_ID] = qos_policy_id
return qos_policy_id return qos_policy_id

View File

@ -20,8 +20,6 @@ from neutron_lib.services.qos import base
from neutron_lib.services.qos import constants as qos_consts from neutron_lib.services.qos import constants as qos_consts
from oslo_log import log as logging from oslo_log import log as logging
from vmware_nsx.services.qos.nsx_v3 import utils as qos_utils
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
DRIVER = None DRIVER = None
@ -45,16 +43,17 @@ SUPPORTED_RULES = {
class NSXv3QosDriver(base.DriverBase): class NSXv3QosDriver(base.DriverBase):
@staticmethod @staticmethod
def create(): def create(handler):
return NSXv3QosDriver( return NSXv3QosDriver(
name='NSXv3QosDriver', name='NSXv3QosDriver',
vif_types=None, vif_types=None,
vnic_types=None, vnic_types=None,
supported_rules=SUPPORTED_RULES, supported_rules=SUPPORTED_RULES,
requires_rpc_notifications=False) requires_rpc_notifications=False,
handler=handler)
def __init__(self, **kwargs): def __init__(self, handler=None, **kwargs):
self.handler = qos_utils.QosNotificationsHandler() self.handler = handler
super(NSXv3QosDriver, self).__init__(**kwargs) super(NSXv3QosDriver, self).__init__(**kwargs)
def is_vif_type_compatible(self, vif_type): def is_vif_type_compatible(self, vif_type):
@ -67,11 +66,12 @@ class NSXv3QosDriver(base.DriverBase):
self.handler.create_policy(context, policy) self.handler.create_policy(context, policy)
def update_policy(self, context, policy): def update_policy(self, context, policy):
# Update the rules
if (hasattr(policy, "rules")): if (hasattr(policy, "rules")):
self.handler.update_policy_rules( self.handler.update_policy_rules(
context, policy.id, 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) self.handler.update_policy(context, policy.id, policy)
def delete_policy(self, context, policy): def delete_policy(self, context, policy):
@ -84,9 +84,9 @@ class NSXv3QosDriver(base.DriverBase):
self.handler.validate_policy_rule(context, policy.id, rule) self.handler.validate_policy_rule(context, policy.id, rule)
def register(): def register(handler):
"""Register the NSX-V3 QoS driver.""" """Register the NSX-V3 QoS driver."""
global DRIVER global DRIVER
if not DRIVER: if not DRIVER:
DRIVER = NSXv3QosDriver.create() DRIVER = NSXv3QosDriver.create(handler)
LOG.debug('NSXv3QosDriver QoS driver registered') LOG.debug('NSXv3QosDriver QoS driver registered')

View File

@ -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)

View File

@ -97,12 +97,7 @@ class QosNotificationsHandler(object):
description=policy.description) description=policy.description)
def _validate_bw_values(self, bw_rule): def _validate_bw_values(self, bw_rule):
"""Validate that the configured values are allowed by the NSX backend. """Validate that the 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 the max bandwidth value minimum value # Validate the max bandwidth value minimum value
# (max value is above what neutron allows so no need to check it) # (max value is above what neutron allows so no need to check it)
if (bw_rule.max_kbps < MAX_KBPS_MIN_VALUE): if (bw_rule.max_kbps < MAX_KBPS_MIN_VALUE):

View File

@ -43,6 +43,7 @@ from neutron_lib.callbacks import resources
from neutron_lib import constants from neutron_lib import constants
from neutron_lib import context from neutron_lib import context
from neutron_lib import exceptions as n_exc from neutron_lib import exceptions as n_exc
from neutron_lib.objects import registry as obj_reg
from neutron_lib.plugins import directory from neutron_lib.plugins import directory
from vmware_nsx.common import utils from vmware_nsx.common import utils
@ -86,6 +87,7 @@ class NsxPPluginTestCaseMixin(
self.setup_conf_overrides() self.setup_conf_overrides()
super(NsxPPluginTestCaseMixin, self).setUp(plugin=plugin, super(NsxPPluginTestCaseMixin, self).setUp(plugin=plugin,
ext_mgr=ext_mgr) ext_mgr=ext_mgr)
self.ctx = context.get_admin_context()
def _mock_nsx_policy_backend_calls(self): def _mock_nsx_policy_backend_calls(self):
resource_list_result = {'results': [{'id': 'test', resource_list_result = {'results': [{'id': 'test',
@ -181,6 +183,17 @@ class NsxPPluginTestCaseMixin(
'', tenant_id) '', tenant_id)
return network_req.get_response(self.api) 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, class NsxPTestNetworks(test_db_base_plugin_v2.TestNetworksV2,
NsxPPluginTestCaseMixin): NsxPPluginTestCaseMixin):
@ -419,6 +432,47 @@ class NsxPTestNetworks(test_db_base_plugin_v2.TestNetworksV2,
az_hints = net['network']['availability_zone_hints'] az_hints = net['network']['availability_zone_hints']
self.assertListEqual(az_hints, zone) 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, class NsxPTestPorts(test_db_base_plugin_v2.TestPortsV2,
NsxPPluginTestCaseMixin): NsxPPluginTestCaseMixin):
@ -557,6 +611,81 @@ class NsxPTestPorts(test_db_base_plugin_v2.TestPortsV2,
self.assertEqual(res['port']['fixed_ips'], self.assertEqual(res['port']['fixed_ips'],
data['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): def test_create_port_with_mac_learning_true(self):
plugin = directory.get_plugin() plugin = directory.get_plugin()
ctx = context.get_admin_context() ctx = context.get_admin_context()
@ -564,7 +693,7 @@ class NsxPTestPorts(test_db_base_plugin_v2.TestPortsV2,
data = {'port': { data = {'port': {
'network_id': network['network']['id'], 'network_id': network['network']['id'],
'tenant_id': self._tenant_id, 'tenant_id': self._tenant_id,
'name': 'qos_port', 'name': 'port',
'admin_state_up': True, 'admin_state_up': True,
'device_id': 'fake_device', 'device_id': 'fake_device',
'device_owner': 'fake_owner', 'device_owner': 'fake_owner',
@ -583,7 +712,7 @@ class NsxPTestPorts(test_db_base_plugin_v2.TestPortsV2,
data = {'port': { data = {'port': {
'network_id': network['network']['id'], 'network_id': network['network']['id'],
'tenant_id': self._tenant_id, 'tenant_id': self._tenant_id,
'name': 'qos_port', 'name': 'port',
'admin_state_up': True, 'admin_state_up': True,
'device_id': 'fake_device', 'device_id': 'fake_device',
'device_owner': 'fake_owner', 'device_owner': 'fake_owner',
@ -602,7 +731,7 @@ class NsxPTestPorts(test_db_base_plugin_v2.TestPortsV2,
data = {'port': { data = {'port': {
'network_id': network['network']['id'], 'network_id': network['network']['id'],
'tenant_id': self._tenant_id, 'tenant_id': self._tenant_id,
'name': 'qos_port', 'name': 'port',
'admin_state_up': True, 'admin_state_up': True,
'device_id': 'fake_device', 'device_id': 'fake_device',
'device_owner': 'fake_owner', 'device_owner': 'fake_owner',
@ -622,7 +751,7 @@ class NsxPTestPorts(test_db_base_plugin_v2.TestPortsV2,
data = {'port': { data = {'port': {
'network_id': network['network']['id'], 'network_id': network['network']['id'],
'tenant_id': self._tenant_id, 'tenant_id': self._tenant_id,
'name': 'qos_port', 'name': 'port',
'admin_state_up': True, 'admin_state_up': True,
'device_id': 'fake_device', 'device_id': 'fake_device',
'device_owner': 'fake_owner', 'device_owner': 'fake_owner',
@ -642,7 +771,7 @@ class NsxPTestPorts(test_db_base_plugin_v2.TestPortsV2,
data = {'port': { data = {'port': {
'network_id': network['network']['id'], 'network_id': network['network']['id'],
'tenant_id': self._tenant_id, 'tenant_id': self._tenant_id,
'name': 'qos_port', 'name': 'port',
'admin_state_up': True, 'admin_state_up': True,
'device_id': 'fake_device', 'device_id': 'fake_device',
'device_owner': constants.DEVICE_OWNER_FLOATINGIP, 'device_owner': constants.DEVICE_OWNER_FLOATINGIP,
@ -1032,17 +1161,6 @@ class NsxPTestL3NatTest(common_v3.FixExternalNetBaseTest,
arg_list=(pnet.NETWORK_TYPE, arg_list=(pnet.NETWORK_TYPE,
pnet.PHYSICAL_NETWORK)) 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): def test_floatingip_create_different_fixed_ip_same_port(self):
self.skipTest('Multiple fixed ips on a port are not supported') self.skipTest('Multiple fixed ips on a port are not supported')

View File

@ -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)