diff --git a/devstack/nsx_p/devstackgaterc b/devstack/nsx_p/devstackgaterc index 9b48ea6b87..8afcd99a6b 100644 --- a/devstack/nsx_p/devstackgaterc +++ b/devstack/nsx_p/devstackgaterc @@ -21,5 +21,5 @@ # Begin list of exclusions. #r="^(?!.*)" -r="$r(tempest\.api\.network\.test_security_groups).*$" +r="$r(tempest\.api\.network\.test_security_groups|tempest\.api\.network\.test_networks|tempest\.api\.network\.test_networks_negative).*$" export DEVSTACK_GATE_TEMPEST_REGEX="$r" \ No newline at end of file diff --git a/vmware_nsx/common/config.py b/vmware_nsx/common/config.py index fc91ff256a..6a7b281c3f 100644 --- a/vmware_nsx/common/config.py +++ b/vmware_nsx/common/config.py @@ -344,6 +344,12 @@ nsx_v3_and_p = [ default=False, help=_("(Optional) Indicates whether distributed-firewall " "security-groups rules are logged.")), + cfg.ListOpt('network_vlan_ranges', + default=[], + help=_("List of :: " + "specifying Transport Zone UUID usable for VLAN " + "provider networks, as well as ranges of VLAN " + "tags on each available for allocation to networks.")), ] nsx_v3_opts = nsx_v3_and_p + [ @@ -456,12 +462,6 @@ nsx_v3_opts = nsx_v3_and_p + [ default=False, help=_("(Optional) Indicates whether ENS transport zones can " "be used")), - cfg.ListOpt('network_vlan_ranges', - default=[], - help=_("List of :: " - "specifying Transport Zone UUID usable for VLAN " - "provider networks, as well as ranges of VLAN " - "tags on each available for allocation to networks.")), cfg.BoolOpt('disable_port_security_for_ens', default=False, help=_("When True, port security will be set to False for " @@ -489,6 +489,22 @@ nsx_p_opts = nsx_v3_and_p + [ "configuring external networks. If only one tier0 " " router is present on backend, it will be assumed " "as default unless this value is provided")), + cfg.StrOpt('default_overlay_tz', + help=_("This is the name or UUID of the default NSX overlay " + "transport zone that will be used for creating " + "tunneled isolated Neutron networks. It needs to be " + "created in NSX before starting Neutron with the NSX " + "plugin. If only one overlay transport zone is present " + "on backend, it will be assumed as default unless this " + "value is provided")), + cfg.StrOpt('default_vlan_tz', + help=_("(Optional) Only required when creating VLAN or flat " + "provider networks. Name or UUID of default NSX VLAN " + "transport zone that will be used for bridging between " + "Neutron networks, if no physical network has been " + "specified. If only one VLAN transport zone is present " + "on backend, it will be assumed as default unless this " + "value is provided")), ] diff --git a/vmware_nsx/common/utils.py b/vmware_nsx/common/utils.py index 560719a313..3824647c40 100644 --- a/vmware_nsx/common/utils.py +++ b/vmware_nsx/common/utils.py @@ -63,7 +63,7 @@ class NsxVNetworkTypes(object): PORTGROUP = 'portgroup' -# Allowed network types for the NSXv3 Plugin +# Allowed network types for the NSXv3 and NSX-Policy Plugin class NsxV3NetworkTypes(object): """Allowed provider network types for the NSXv3 Plugin.""" FLAT = 'flat' diff --git a/vmware_nsx/plugins/common/plugin.py b/vmware_nsx/plugins/common/plugin.py index b19128a251..33327c170b 100644 --- a/vmware_nsx/plugins/common/plugin.py +++ b/vmware_nsx/plugins/common/plugin.py @@ -13,7 +13,6 @@ # License for the specific language governing permissions and limitations # under the License. - from oslo_log import log as logging from neutron.db import _resource_extend as resource_extend @@ -24,10 +23,8 @@ from neutron.db import l3_db from neutron.db import models_v2 from neutron_lib.api.definitions import address_scope as ext_address_scope from neutron_lib.api.definitions import availability_zone as az_def -from neutron_lib.api.definitions import external_net as extnet_apidef from neutron_lib.api.definitions import network as net_def from neutron_lib.api.definitions import port as port_def -from neutron_lib.api.definitions import port_security as psec from neutron_lib.api.definitions import subnet as subnet_def from neutron_lib.api import validators from neutron_lib.api.validators import availability_zone as az_validator @@ -39,15 +36,11 @@ from neutron_lib import context as n_context from neutron_lib.db import api as db_api 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 neutron_lib.utils import net +from neutron_lib.utils import net as nl_net_utils from vmware_nsx._i18n import _ from vmware_nsx.common import exceptions as nsx_exc -from vmware_nsx.common import utils -from vmware_nsx.extensions import maclearning as mac_ext from vmware_nsx.services.qos.common import utils as qos_com_utils -from vmware_nsx.services.vpnaas.nsxv3 import ipsec_utils LOG = logging.getLogger(__name__) @@ -55,7 +48,7 @@ LOG = logging.getLogger(__name__) @resource_extend.has_resource_extenders class NsxPluginBase(db_base_plugin_v2.NeutronDbPluginV2, address_scope_db.AddressScopeDbMixin): - """Common methods for NSX-V and NSX-V3 plugins""" + """Common methods for NSX-V, NSX-V3 and NSX-P plugins""" @property def plugin_type(self): @@ -308,7 +301,7 @@ class NsxPluginBase(db_base_plugin_v2.NeutronDbPluginV2, cannot add multiple static dhcp bindings with the same port """ if (device_owner and - net.is_port_trusted({'device_owner': device_owner})): + nl_net_utils.is_port_trusted({'device_owner': device_owner})): return if validators.is_attr_set(fixed_ip_list) and len(fixed_ip_list) > 1: @@ -392,203 +385,15 @@ class NsxPluginBase(db_base_plugin_v2.NeutronDbPluginV2, if qos_policy_id: qos_com_utils.validate_policy_accessable(context, qos_policy_id) - def _validate_create_network(self, context, net_data): - """Validate the parameters of the new network to be created - - This method includes general validations that does not depend on - provider attributes, or plugin specific configurations - """ - external = net_data.get(extnet_apidef.EXTERNAL) - is_external_net = validators.is_attr_set(external) and external - with_qos = validators.is_attr_set( - net_data.get(qos_consts.QOS_POLICY_ID)) - - if with_qos: - self._validate_qos_policy_id( - context, net_data.get(qos_consts.QOS_POLICY_ID)) - if is_external_net: - raise nsx_exc.QoSOnExternalNet() - - def _validate_update_netowrk(self, context, 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) - 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() - - # Do not support changing external/non-external networks - if (extnet_apidef.EXTERNAL in net_data and - net_data[extnet_apidef.EXTERNAL] != extern_net): - err_msg = _("Cannot change the router:external flag of a network") - raise n_exc.InvalidInput(error_message=err_msg) - - def _assert_on_illegal_port_with_qos(self, device_owner): - # Prevent creating/update port with QoS policy - # on router-interface/network-dhcp ports. - if ((device_owner == l3_db.DEVICE_OWNER_ROUTER_INTF or - device_owner == constants.DEVICE_OWNER_DHCP)): - err_msg = _("Unable to create or update %s port with a QoS " - "policy") % device_owner - LOG.warning(err_msg) - raise n_exc.InvalidInput(error_message=err_msg) - - def _assert_on_external_net_with_compute(self, port_data): - # Prevent creating port with device owner prefix 'compute' - # on external networks. - device_owner = port_data.get('device_owner') - if (device_owner is not None and - device_owner.startswith(constants.DEVICE_OWNER_COMPUTE_PREFIX)): - err_msg = _("Unable to update/create a port with an external " - "network") - 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')) - - is_external_net = self._network_is_external( - context, port_data['network_id']) - qos_selected = validators.is_attr_set(port_data.get( - qos_consts.QOS_POLICY_ID)) - device_owner = port_data.get('device_owner') - - # QoS validations - if qos_selected: - self._validate_qos_policy_id( - context, port_data.get(qos_consts.QOS_POLICY_ID)) - self._assert_on_illegal_port_with_qos(device_owner) - if is_external_net: - raise nsx_exc.QoSOnExternalNet() - - # External network validations: - if is_external_net: - self._assert_on_external_net_with_compute(port_data) - - self._assert_on_port_admin_state(port_data, device_owner) - - 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'] - raise n_exc.InvalidInput(error_message=msg) - - def _assert_on_lb_port_fixed_ip_change(self, port_data, orig_dev_own): - if orig_dev_own == constants.DEVICE_OWNER_LOADBALANCERV2: - if "fixed_ips" in port_data and port_data["fixed_ips"]: - msg = _('Can not update Loadbalancer port with fixed IP') - raise n_exc.InvalidInput(error_message=msg) - - def _assert_on_device_owner_change(self, port_data, orig_dev_own): - """Prevent illegal device owner modifications - """ - if orig_dev_own == constants.DEVICE_OWNER_LOADBALANCERV2: - if ("allowed_address_pairs" in port_data and - port_data["allowed_address_pairs"]): - msg = _('Loadbalancer port can not be updated ' - 'with address pairs') - raise n_exc.InvalidInput(error_message=msg) - - if 'device_owner' not in port_data: - return - new_dev_own = port_data['device_owner'] - if new_dev_own == orig_dev_own: - return - - err_msg = (_("Changing port device owner '%(orig)s' to '%(new)s' is " - "not allowed") % {'orig': orig_dev_own, - 'new': new_dev_own}) - - # Do not allow changing nova <-> neutron device owners - if ((orig_dev_own.startswith(constants.DEVICE_OWNER_COMPUTE_PREFIX) and - new_dev_own.startswith(constants.DEVICE_OWNER_NETWORK_PREFIX)) or - (orig_dev_own.startswith(constants.DEVICE_OWNER_NETWORK_PREFIX) and - new_dev_own.startswith(constants.DEVICE_OWNER_COMPUTE_PREFIX))): - raise n_exc.InvalidInput(error_message=err_msg) - - # Do not allow removing the device owner in some cases - if orig_dev_own == constants.DEVICE_OWNER_DHCP: - raise n_exc.InvalidInput(error_message=err_msg) - - def _assert_on_port_sec_change(self, port_data, device_owner): - """Do not allow enabling port security/mac learning of some ports - - Trusted ports are created with port security and mac learning disabled - in neutron, and it should not change. - """ - if net.is_port_trusted({'device_owner': device_owner}): - if port_data.get(psec.PORTSECURITY) is True: - err_msg = _("port_security_enabled=True is not supported for " - "trusted ports") - LOG.warning(err_msg) - raise n_exc.InvalidInput(error_message=err_msg) - - mac_learning = port_data.get(mac_ext.MAC_LEARNING) - if (validators.is_attr_set(mac_learning) and mac_learning is True): - err_msg = _("mac_learning_enabled=True is not supported for " - "trusted ports") - 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)) - is_external_net = self._network_is_external( - context, original_port['network_id']) - device_owner = (port_data['device_owner'] - if 'device_owner' in port_data - else original_port.get('device_owner')) - - # QoS validations - if qos_selected: - self._validate_qos_policy_id( - context, port_data.get(qos_consts.QOS_POLICY_ID)) - if is_external_net: - raise nsx_exc.QoSOnExternalNet() - self._assert_on_illegal_port_with_qos(device_owner) - - # External networks validations: - if is_external_net: - self._assert_on_external_net_with_compute(port_data) - - # Device owner validations: - orig_dev_owner = original_port.get('device_owner') - self._assert_on_device_owner_change(port_data, orig_dev_owner) - self._assert_on_port_admin_state(port_data, device_owner) - self._assert_on_port_sec_change(port_data, device_owner) - self._validate_max_ips_per_port( - port_data.get('fixed_ips', []), device_owner) - self._assert_on_vpn_port_change(original_port) - self._assert_on_lb_port_fixed_ip_change(port_data, orig_dev_owner) - - def _get_dhcp_port_name(self, net_name, net_id): - return utils.get_name_and_uuid('%s-%s' % ('dhcp', - net_name or 'network'), - net_id) - - def _build_port_name(self, context, port_data): - device_owner = port_data.get('device_owner') - device_id = port_data.get('device_id') - if device_owner == l3_db.DEVICE_OWNER_ROUTER_INTF and device_id: - router = self._get_router(context, device_id) - name = utils.get_name_and_uuid( - router['name'] or 'router', port_data['id'], tag='port') - elif device_owner == constants.DEVICE_OWNER_DHCP: - network = self.get_network(context, port_data['network_id']) - name = self._get_dhcp_port_name(network['name'], - network['id']) - elif device_owner.startswith(constants.DEVICE_OWNER_COMPUTE_PREFIX): - name = utils.get_name_and_uuid( - port_data['name'] or 'instance-port', port_data['id']) - else: - name = port_data['name'] - return name + def _get_interface_network(self, context, interface_info): + is_port, is_sub = self._validate_interface_info(interface_info) + if is_port: + net_id = self.get_port(context, + interface_info['port_id'])['network_id'] + elif is_sub: + net_id = self.get_subnet(context, + interface_info['subnet_id'])['network_id'] + return net_id def _process_extra_attr_router_create(self, context, router_db, r): for extra_attr in l3_attrs_db.get_attr_info().keys(): @@ -597,12 +402,6 @@ class NsxPluginBase(db_base_plugin_v2.NeutronDbPluginV2, self.set_extra_attr_value(context, router_db, extra_attr, r[extra_attr]) - def _validate_ipv4_address_pairs(self, address_pairs): - for pair in address_pairs: - ip = pair.get('ip_address') - if not utils.is_ipv4_ip_address(ip): - raise nsx_exc.InvalidIPAddress(ip_address=ip) - def get_housekeeper(self, context, name, fields=None): # run the job in readonly mode and get the results self.housekeeper.run(context, name, readonly=True) diff --git a/vmware_nsx/plugins/common_v3/plugin.py b/vmware_nsx/plugins/common_v3/plugin.py index d6242192ef..430d28e37e 100644 --- a/vmware_nsx/plugins/common_v3/plugin.py +++ b/vmware_nsx/plugins/common_v3/plugin.py @@ -14,28 +14,57 @@ # under the License. +from oslo_config import cfg from oslo_log import log as logging +from six import moves +from neutron.db import l3_db from neutron.extensions import securitygroup as ext_sg from neutron_lib.api.definitions import allowedaddresspairs as addr_apidef +from neutron_lib.api.definitions import external_net as extnet_apidef from neutron_lib.api.definitions import port_security as psec +from neutron_lib.api.definitions import portbindings as pbin +from neutron_lib.api.definitions import provider_net as pnet from neutron_lib.api import validators +from neutron_lib import constants +from neutron_lib.db import api as db_api +from neutron_lib.db import utils as db_utils +from neutron_lib import exceptions as n_exc from neutron_lib.exceptions import allowedaddresspairs as addr_exc from neutron_lib.exceptions import port_security as psec_exc +from neutron_lib.plugins import utils as plugin_utils +from neutron_lib.services.qos import constants as qos_consts +from neutron_lib.utils import net as nl_net_utils from vmware_nsx.common import exceptions as nsx_exc +from vmware_nsx.common import nsx_constants from vmware_nsx.common import utils +from vmware_nsx.db import db as nsx_db from vmware_nsx.db import extended_security_group as extended_sec +from vmware_nsx.db import nsx_portbindings_db as pbin_db +from vmware_nsx.extensions import maclearning as mac_ext from vmware_nsx.extensions import providersecuritygroup as provider_sg from vmware_nsx.extensions import secgroup_rule_local_ip_prefix as sg_prefix from vmware_nsx.plugins.common import plugin +from vmware_nsx.services.qos.common import utils as qos_com_utils +from vmware_nsx.services.vpnaas.nsxv3 import ipsec_utils + +from vmware_nsxlib.v3 import exceptions as nsx_lib_exc +from vmware_nsxlib.v3 import nsx_constants as nsxlib_consts LOG = logging.getLogger(__name__) class NsxPluginV3Base(plugin.NsxPluginBase, - extended_sec.ExtendedSecurityGroupPropertiesMixin): - """Common methods for NSX-V3 plugins""" + extended_sec.ExtendedSecurityGroupPropertiesMixin, + pbin_db.NsxPortBindingMixin): + """Common methods for NSX-V3 plugins (NSX-V3 & Policy)""" + + def __init__(self): + super(NsxPluginV3Base, self).__init__() + plugin_cfg = getattr(cfg.CONF, self.cfg_group) + self._network_vlans = plugin_utils.parse_network_vlan_ranges( + plugin_cfg.network_vlan_ranges) def _get_interface_network(self, context, interface_info): is_port, is_sub = self._validate_interface_info(interface_info) @@ -127,3 +156,525 @@ class NsxPluginV3Base(plugin.NsxPluginBase, port_data[ext_sg.SECURITYGROUPS] = ( self._get_security_groups_on_port(context, port)) return port_security, has_ip, sgids, psgids + + def _validate_create_network(self, context, net_data): + """Validate the parameters of the new network to be created + + This method includes general validations that does not depend on + provider attributes, or plugin specific configurations + """ + external = net_data.get(extnet_apidef.EXTERNAL) + is_external_net = validators.is_attr_set(external) and external + with_qos = validators.is_attr_set( + net_data.get(qos_consts.QOS_POLICY_ID)) + + if with_qos: + self._validate_qos_policy_id( + context, net_data.get(qos_consts.QOS_POLICY_ID)) + if is_external_net: + raise nsx_exc.QoSOnExternalNet() + + def _validate_update_network(self, context, 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) + 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() + + # Do not support changing external/non-external networks + if (extnet_apidef.EXTERNAL in net_data and + net_data[extnet_apidef.EXTERNAL] != extern_net): + err_msg = _("Cannot change the router:external flag of a network") + raise n_exc.InvalidInput(error_message=err_msg) + + def _assert_on_illegal_port_with_qos(self, device_owner): + # Prevent creating/update port with QoS policy + # on router-interface/network-dhcp ports. + if ((device_owner == l3_db.DEVICE_OWNER_ROUTER_INTF or + device_owner == constants.DEVICE_OWNER_DHCP)): + err_msg = _("Unable to create or update %s port with a QoS " + "policy") % device_owner + LOG.warning(err_msg) + raise n_exc.InvalidInput(error_message=err_msg) + + def _assert_on_external_net_with_compute(self, port_data): + # Prevent creating port with device owner prefix 'compute' + # on external networks. + device_owner = port_data.get('device_owner') + if (device_owner is not None and + device_owner.startswith(constants.DEVICE_OWNER_COMPUTE_PREFIX)): + err_msg = _("Unable to update/create a port with an external " + "network") + 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')) + + is_external_net = self._network_is_external( + context, port_data['network_id']) + qos_selected = validators.is_attr_set(port_data.get( + qos_consts.QOS_POLICY_ID)) + device_owner = port_data.get('device_owner') + + # QoS validations + if qos_selected: + self._validate_qos_policy_id( + context, port_data.get(qos_consts.QOS_POLICY_ID)) + self._assert_on_illegal_port_with_qos(device_owner) + if is_external_net: + raise nsx_exc.QoSOnExternalNet() + + # External network validations: + if is_external_net: + self._assert_on_external_net_with_compute(port_data) + + self._assert_on_port_admin_state(port_data, device_owner) + + 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'] + raise n_exc.InvalidInput(error_message=msg) + + def _assert_on_lb_port_fixed_ip_change(self, port_data, orig_dev_own): + if orig_dev_own == constants.DEVICE_OWNER_LOADBALANCERV2: + if "fixed_ips" in port_data and port_data["fixed_ips"]: + msg = _('Can not update Loadbalancer port with fixed IP') + raise n_exc.InvalidInput(error_message=msg) + + def _assert_on_device_owner_change(self, port_data, orig_dev_own): + """Prevent illegal device owner modifications + """ + if orig_dev_own == constants.DEVICE_OWNER_LOADBALANCERV2: + if ("allowed_address_pairs" in port_data and + port_data["allowed_address_pairs"]): + msg = _('Loadbalancer port can not be updated ' + 'with address pairs') + raise n_exc.InvalidInput(error_message=msg) + + if 'device_owner' not in port_data: + return + new_dev_own = port_data['device_owner'] + if new_dev_own == orig_dev_own: + return + + err_msg = (_("Changing port device owner '%(orig)s' to '%(new)s' is " + "not allowed") % {'orig': orig_dev_own, + 'new': new_dev_own}) + + # Do not allow changing nova <-> neutron device owners + if ((orig_dev_own.startswith(constants.DEVICE_OWNER_COMPUTE_PREFIX) and + new_dev_own.startswith(constants.DEVICE_OWNER_NETWORK_PREFIX)) or + (orig_dev_own.startswith(constants.DEVICE_OWNER_NETWORK_PREFIX) and + new_dev_own.startswith(constants.DEVICE_OWNER_COMPUTE_PREFIX))): + raise n_exc.InvalidInput(error_message=err_msg) + + # Do not allow removing the device owner in some cases + if orig_dev_own == constants.DEVICE_OWNER_DHCP: + raise n_exc.InvalidInput(error_message=err_msg) + + def _assert_on_port_sec_change(self, port_data, device_owner): + """Do not allow enabling port security/mac learning of some ports + + Trusted ports are created with port security and mac learning disabled + in neutron, and it should not change. + """ + if nl_net_utils.is_port_trusted({'device_owner': device_owner}): + if port_data.get(psec.PORTSECURITY) is True: + err_msg = _("port_security_enabled=True is not supported for " + "trusted ports") + LOG.warning(err_msg) + raise n_exc.InvalidInput(error_message=err_msg) + + mac_learning = port_data.get(mac_ext.MAC_LEARNING) + if (validators.is_attr_set(mac_learning) and mac_learning is True): + err_msg = _("mac_learning_enabled=True is not supported for " + "trusted ports") + 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)) + is_external_net = self._network_is_external( + context, original_port['network_id']) + device_owner = (port_data['device_owner'] + if 'device_owner' in port_data + else original_port.get('device_owner')) + + # QoS validations + if qos_selected: + self._validate_qos_policy_id( + context, port_data.get(qos_consts.QOS_POLICY_ID)) + if is_external_net: + raise nsx_exc.QoSOnExternalNet() + self._assert_on_illegal_port_with_qos(device_owner) + + # External networks validations: + if is_external_net: + self._assert_on_external_net_with_compute(port_data) + + # Device owner validations: + orig_dev_owner = original_port.get('device_owner') + self._assert_on_device_owner_change(port_data, orig_dev_owner) + self._assert_on_port_admin_state(port_data, device_owner) + self._assert_on_port_sec_change(port_data, device_owner) + self._validate_max_ips_per_port( + port_data.get('fixed_ips', []), device_owner) + self._assert_on_vpn_port_change(original_port) + self._assert_on_lb_port_fixed_ip_change(port_data, orig_dev_owner) + + def _get_dhcp_port_name(self, net_name, net_id): + return utils.get_name_and_uuid('%s-%s' % ('dhcp', + net_name or 'network'), + net_id) + + def _build_port_name(self, context, port_data): + device_owner = port_data.get('device_owner') + device_id = port_data.get('device_id') + if device_owner == l3_db.DEVICE_OWNER_ROUTER_INTF and device_id: + router = self._get_router(context, device_id) + name = utils.get_name_and_uuid( + router['name'] or 'router', port_data['id'], tag='port') + elif device_owner == constants.DEVICE_OWNER_DHCP: + network = self.get_network(context, port_data['network_id']) + name = self._get_dhcp_port_name(network['name'], + network['id']) + elif device_owner.startswith(constants.DEVICE_OWNER_COMPUTE_PREFIX): + name = utils.get_name_and_uuid( + port_data['name'] or 'instance-port', port_data['id']) + else: + name = port_data['name'] + return name + + def _validate_external_net_create(self, net_data, default_tier0_router, + tier0_validator=None): + """Validate external network configuration + + Returns a tuple of: + - Boolean is provider network (always True) + - Network type (always L3_EXT) + - tier 0 router id + - vlan id + """ + if not validators.is_attr_set(net_data.get(pnet.PHYSICAL_NETWORK)): + tier0_uuid = default_tier0_router + else: + tier0_uuid = net_data[pnet.PHYSICAL_NETWORK] + if ((validators.is_attr_set(net_data.get(pnet.NETWORK_TYPE)) and + net_data.get(pnet.NETWORK_TYPE) != utils.NetworkTypes.L3_EXT and + net_data.get(pnet.NETWORK_TYPE) != utils.NetworkTypes.LOCAL) or + validators.is_attr_set(net_data.get(pnet.SEGMENTATION_ID))): + msg = (_("External network cannot be created with %s provider " + "network or segmentation id") % + net_data.get(pnet.NETWORK_TYPE)) + raise n_exc.InvalidInput(error_message=msg) + if tier0_validator: + tier0_validator(tier0_uuid) + return (True, utils.NetworkTypes.L3_EXT, tier0_uuid, 0) + + def _extend_network_dict_provider(self, context, network, bindings=None): + """Add network provider fields to the network dict from the DB""" + if 'id' not in network: + return + if not bindings: + bindings = nsx_db.get_network_bindings(context.session, + network['id']) + + # With NSX plugin, "normal" overlay networks will have no binding + if bindings: + # Network came in through provider networks API + network[pnet.NETWORK_TYPE] = bindings[0].binding_type + network[pnet.PHYSICAL_NETWORK] = bindings[0].phy_uuid + network[pnet.SEGMENTATION_ID] = bindings[0].vlan_id + + def _extend_get_network_dict_provider(self, context, network): + self._extend_network_dict_provider(context, network) + network[qos_consts.QOS_POLICY_ID] = (qos_com_utils. + get_network_policy_id(context, network['id'])) + + def get_network(self, context, id, fields=None): + with db_api.CONTEXT_READER.using(context): + # Get network from Neutron database + network = self._get_network(context, id) + # Don't do field selection here otherwise we won't be able to add + # provider networks fields + net = self._make_network_dict(network, context=context) + self._extend_get_network_dict_provider(context, net) + return db_utils.resource_fields(net, fields) + + def get_networks(self, context, filters=None, fields=None, + sorts=None, limit=None, marker=None, + page_reverse=False): + # Get networks from Neutron database + filters = filters or {} + with db_api.CONTEXT_READER.using(context): + networks = super(NsxPluginV3Base, self).get_networks( + context, filters, fields, sorts, + limit, marker, page_reverse) + # Add provider network fields + for net in networks: + self._extend_get_network_dict_provider(context, net) + return (networks if not fields else + [db_utils.resource_fields(network, + fields) for network in networks]) + + def _assert_on_ens_with_qos(self, net_data): + qos_id = net_data.get(qos_consts.QOS_POLICY_ID) + if validators.is_attr_set(qos_id): + err_msg = _("Cannot configure QOS on ENS networks") + raise n_exc.InvalidInput(error_message=err_msg) + + def _ens_psec_supported(self): + """Should be implemented by each plugin""" + pass + + def _get_nsx_net_tz_id(self, nsx_net): + """Should be implemented by each plugin""" + pass + + def _validate_ens_net_portsecurity(self, net_data): + """Validate/Update the port security of the new network for ENS TZ + Should be implemented by the plugin if necessary + """ + pass + + def _generate_segment_id(self, context, physical_network, net_data): + bindings = nsx_db.get_network_bindings_by_phy_uuid( + context.session, physical_network) + vlan_ranges = self._network_vlans.get(physical_network, []) + if vlan_ranges: + vlan_ids = set() + for vlan_min, vlan_max in vlan_ranges: + vlan_ids |= set(moves.range(vlan_min, vlan_max + 1)) + else: + vlan_min = constants.MIN_VLAN_TAG + vlan_max = constants.MAX_VLAN_TAG + vlan_ids = set(moves.range(vlan_min, vlan_max + 1)) + used_ids_in_range = set([binding.vlan_id for binding in bindings + if binding.vlan_id in vlan_ids]) + free_ids = list(vlan_ids ^ used_ids_in_range) + if len(free_ids) == 0: + raise n_exc.NoNetworkAvailable() + net_data[pnet.SEGMENTATION_ID] = free_ids[0] + return net_data[pnet.SEGMENTATION_ID] + + def _validate_provider_create(self, context, network_data, + default_vlan_tz_uuid, + default_overlay_tz_uuid, + nsxlib_tz, nsxlib_network, + transparent_vlan=False): + """Validate the parameters of a new provider network + + raises an error if illegal + returns a dictionary with the relevant processed data: + - is_provider_net: boolean + - 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 + """ + is_provider_net = any( + validators.is_attr_set(network_data.get(f)) + for f in (pnet.NETWORK_TYPE, + pnet.PHYSICAL_NETWORK, + pnet.SEGMENTATION_ID)) + + physical_net = network_data.get(pnet.PHYSICAL_NETWORK) + if not validators.is_attr_set(physical_net): + physical_net = None + + vlan_id = network_data.get(pnet.SEGMENTATION_ID) + if not validators.is_attr_set(vlan_id): + vlan_id = None + + if vlan_id and transparent_vlan: + err_msg = (_("Segmentation ID cannot be set with transparent " + "vlan!")) + raise n_exc.InvalidInput(error_message=err_msg) + + err_msg = None + net_type = network_data.get(pnet.NETWORK_TYPE) + tz_type = nsxlib_consts.TRANSPORT_TYPE_VLAN + switch_mode = nsxlib_consts.HOST_SWITCH_MODE_STANDARD + if validators.is_attr_set(net_type): + if net_type == utils.NsxV3NetworkTypes.FLAT: + if vlan_id is not None: + err_msg = (_("Segmentation ID cannot be specified with " + "%s network type") % + utils.NsxV3NetworkTypes.FLAT) + else: + if not transparent_vlan: + # Set VLAN id to 0 for flat networks + vlan_id = '0' + if physical_net is None: + physical_net = default_vlan_tz_uuid + elif net_type == utils.NsxV3NetworkTypes.VLAN: + # Use default VLAN transport zone if physical network not given + if physical_net is None: + physical_net = default_vlan_tz_uuid + + if not transparent_vlan: + # Validate VLAN id + if not vlan_id: + vlan_id = self._generate_segment_id(context, + physical_net, + network_data) + elif not plugin_utils.is_valid_vlan_tag(vlan_id): + err_msg = (_('Segmentation ID %(seg_id)s out of ' + 'range (%(min_id)s through %(max_id)s)') % + {'seg_id': vlan_id, + 'min_id': constants.MIN_VLAN_TAG, + 'max_id': constants.MAX_VLAN_TAG}) + else: + # Verify VLAN id is not already allocated + bindings = nsx_db.\ + get_network_bindings_by_vlanid_and_physical_net( + context.session, vlan_id, physical_net) + if bindings: + raise n_exc.VlanIdInUse( + vlan_id=vlan_id, physical_network=physical_net) + elif net_type == utils.NsxV3NetworkTypes.GENEVE: + if vlan_id: + err_msg = (_("Segmentation ID cannot be specified with " + "%s network type") % + utils.NsxV3NetworkTypes.GENEVE) + tz_type = nsxlib_consts.TRANSPORT_TYPE_OVERLAY + elif net_type == utils.NsxV3NetworkTypes.NSX_NETWORK: + # Linking neutron networks to an existing NSX logical switch + if not physical_net: + err_msg = (_("Physical network must be specified with " + "%s network type") % net_type) + # Validate the logical switch existence + else: + try: + nsx_net = nsxlib_network.get(physical_net) + tz_id = self._get_nsx_net_tz_id(nsx_net) + switch_mode = nsxlib_tz.get_host_switch_mode(tz_id) + except nsx_lib_exc.ResourceNotFound: + err_msg = (_('Logical switch %s does not exist') % + physical_net) + # make sure no other neutron network is using it + bindings = ( + nsx_db.get_network_bindings_by_vlanid_and_physical_net( + context.elevated().session, 0, physical_net)) + if bindings: + err_msg = (_('Logical switch %s is already used by ' + 'another network') % physical_net) + else: + err_msg = (_('%(net_type_param)s %(net_type_value)s not ' + 'supported') % + {'net_type_param': pnet.NETWORK_TYPE, + 'net_type_value': net_type}) + elif is_provider_net: + # FIXME: Ideally provider-network attributes should be checked + # at the NSX backend. For now, the network_type is required, + # so the plugin can do a quick check locally. + err_msg = (_('%s is required for creating a provider network') % + pnet.NETWORK_TYPE) + else: + net_type = None + + if physical_net is None: + # Default to transport type overlay + physical_net = default_overlay_tz_uuid + + # validate the transport zone existence and type + if (not err_msg and physical_net and + net_type != utils.NsxV3NetworkTypes.NSX_NETWORK): + if is_provider_net: + try: + backend_type = nsxlib_tz.get_transport_type( + physical_net) + except nsx_lib_exc.ResourceNotFound: + err_msg = (_('Transport zone %s does not exist') % + physical_net) + else: + if backend_type != tz_type: + err_msg = (_('%(tz)s transport zone is required for ' + 'creating a %(net)s provider network') % + {'tz': tz_type, 'net': net_type}) + if not err_msg: + switch_mode = nsxlib_tz.get_host_switch_mode(physical_net) + + if err_msg: + raise n_exc.InvalidInput(error_message=err_msg) + + if (switch_mode == nsxlib_consts.HOST_SWITCH_MODE_ENS): + if not self._allow_ens_networks(): + raise NotImplementedError(_("ENS support is disabled")) + self._assert_on_ens_with_qos(network_data) + self._validate_ens_net_portsecurity(network_data) + + return {'is_provider_net': is_provider_net, + 'net_type': net_type, + 'physical_net': physical_net, + 'vlan_id': vlan_id, + 'switch_mode': switch_mode} + + def _network_is_nsx_net(self, context, network_id): + bindings = nsx_db.get_network_bindings(context.session, network_id) + if not bindings: + return False + return (bindings[0].binding_type == + utils.NsxV3NetworkTypes.NSX_NETWORK) + + def _vif_type_by_vnic_type(self, direct_vnic_type): + return (nsx_constants.VIF_TYPE_DVS if direct_vnic_type + else pbin.VIF_TYPE_OVS) + + def _get_network_segmentation_id(self, context, neutron_id): + bindings = nsx_db.get_network_bindings(context.session, neutron_id) + if bindings: + return bindings[0].vlan_id + + def _extend_nsx_port_dict_binding(self, context, port_data): + # Not using the register api for this because we need the context + # Some attributes were already initialized by _extend_port_portbinding + if pbin.VIF_TYPE not in port_data: + port_data[pbin.VIF_TYPE] = pbin.VIF_TYPE_OVS + if pbin.VNIC_TYPE not in port_data: + port_data[pbin.VNIC_TYPE] = pbin.VNIC_NORMAL + if 'network_id' in port_data: + net_id = port_data['network_id'] + if pbin.VIF_DETAILS not in port_data: + port_data[pbin.VIF_DETAILS] = {} + port_data[pbin.VIF_DETAILS][pbin.OVS_HYBRID_PLUG] = False + if (port_data.get('device_owner') == + constants.DEVICE_OWNER_FLOATINGIP): + # floatingip belongs to an external net without nsx-id + port_data[pbin.VIF_DETAILS]['nsx-logical-switch-id'] = None + else: + port_data[pbin.VIF_DETAILS]['nsx-logical-switch-id'] = ( + self._get_network_nsx_id(context, net_id)) + if port_data[pbin.VNIC_TYPE] != pbin.VNIC_NORMAL: + port_data[pbin.VIF_DETAILS]['segmentation-id'] = ( + self._get_network_segmentation_id(context, net_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)): + # 'direct' and 'direct-physical' vnic types ports requires + # port-security to be disabled. + if port_data[psec.PORTSECURITY]: + err_msg = _("Security features are not supported for " + "ports with direct/direct-physical VNIC " + "type") + raise n_exc.InvalidInput(error_message=err_msg) + else: + # Implicitly disable port-security for direct vnic types. + port_data[psec.PORTSECURITY] = False + + def _validate_network_type(self, context, network_id, net_types): + net = self.get_network(context, network_id) + if net.get(pnet.NETWORK_TYPE) in net_types: + return True + return False diff --git a/vmware_nsx/plugins/nsx_p/plugin.py b/vmware_nsx/plugins/nsx_p/plugin.py index 5fd0cefd61..d82b03a9d3 100644 --- a/vmware_nsx/plugins/nsx_p/plugin.py +++ b/vmware_nsx/plugins/nsx_p/plugin.py @@ -45,6 +45,7 @@ from neutron_lib.api.definitions import allowedaddresspairs as addr_apidef from neutron_lib.api.definitions import external_net from neutron_lib.api.definitions import l3 as l3_apidef from neutron_lib.api.definitions import port_security as psec +from neutron_lib.api.definitions import vlantransparent as vlan_apidef from neutron_lib.api import faults from neutron_lib.api import validators from neutron_lib.callbacks import events @@ -129,7 +130,8 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, "subnet_allocation", "security-group-logging", "provider-security-group", - "port-security-groups-filtering"] + "port-security-groups-filtering", + "vlan-transparent"] @resource_registry.tracked_resources( network=models_v2.Network, @@ -148,6 +150,7 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, extension_drivers = cfg.CONF.nsx_extension_drivers self._extension_manager = managers.ExtensionManager( extension_drivers=extension_drivers) + self.cfg_group = 'nsx_p' # group name for nsx_p section in nsx.ini super(NsxPolicyPlugin, self).__init__() # Bind the dummy L3 notifications self.l3_rpc_notifier = l3_rpc_agent_api.L3NotifyAPI() @@ -160,8 +163,6 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, nsxlib_utils.set_inject_headers_callback(v3_utils.inject_headers) self._validate_nsx_policy_version() - self.cfg_group = 'nsx_p' # group name for nsx_p section in nsx.ini - self._init_default_config() self._prepare_default_rules() @@ -173,11 +174,14 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, # NOTE(annak): we may need to generalize this for API calls # requiring path ids - def _init_default_resource(self, resource_api, name_or_id): + def _init_default_resource(self, resource_api, name_or_id, + filter_list_results=None): if not name_or_id: # If not specified, the system will auto-configure # in case only single resource is present resources = resource_api.list() + if filter_list_results: + resources = filter_list_results(resources) if len(resources) == 1: return resources[0]['id'] else: @@ -195,6 +199,7 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, return None def _init_default_config(self): + # Default Tier0 router self.default_tier0_router = self._init_default_resource( self.nsxpolicy.tier0, cfg.CONF.nsx_p.default_tier0_router) @@ -203,6 +208,24 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, raise cfg.RequiredOptError("default_tier0_router", group=cfg.OptGroup('nsx_p')) + # Default overlay transport zone + self.default_overlay_tz = self._init_default_resource( + self.nsxpolicy.transport_zone, + cfg.CONF.nsx_p.default_overlay_tz, + filter_list_results=lambda tzs: [ + tz for tz in tzs if tz['tz_type'].startswith('OVERLAY')]) + + if not self.default_overlay_tz: + raise cfg.RequiredOptError("default_overlay_tz", + group=cfg.OptGroup('nsx_p')) + + # Default VLAN transport zone (not mandatory) + self.default_vlan_tz = self._init_default_resource( + self.nsxpolicy.transport_zone, + cfg.CONF.nsx_p.default_vlan_tz, + filter_list_results=lambda tzs: [ + tz for tz in tzs if tz['tz_type'].startswith('VLAN')]) + def _validate_nsx_policy_version(self): self._nsx_version = self.nsxpolicy.get_version() LOG.info("NSX Version: %s", self._nsx_version) @@ -252,48 +275,107 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, webob.exc.HTTPBadRequest, }) - def _create_network_on_backend(self, context, net_data): - # TODO(annak): provider network + def _create_network_on_backend(self, context, net_data, + transparent_vlan, + provider_data): net_data['id'] = net_data.get('id') or uuidutils.generate_uuid() + # 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_api_version_project_tag( - context.tenant_name) + tags = self.nsxpolicy.build_v3_tags_payload( + net_data, resource_type='os-neutron-net-id', + project_name=context.tenant_name) # TODO(annak): admin state config is missing on policy # should we not create networks that are down? # alternative - configure status on manager for time being - # admin_state = net_data.get('admin_state_up', True) + admin_state = net_data.get('admin_state_up', True) + LOG.debug('create_network: %(net_name)s, %(physical_net)s, ' + '%(tags)s, %(admin_state)s, %(vlan_id)s', + {'net_name': net_name, + 'physical_net': provider_data['physical_net'], + 'tags': tags, + 'admin_state': admin_state, + 'vlan_id': provider_data['vlan_id']}) + if transparent_vlan: + # all vlan tags are allowed for guest vlan + vlan_ids = ["0-%s" % const.MAX_VLAN_TAG] + elif provider_data['vlan_id']: + vlan_ids = [provider_data['vlan_id']] + else: + vlan_ids = None self.nsxpolicy.segment.create_or_overwrite( net_name, segment_id=net_data['id'], description=net_data.get('description'), + vlan_ids=vlan_ids, + transport_zone_id=provider_data['physical_net'], tags=tags) - def _validate_external_net_create(self, net_data): - #TODO(asarfaty): implement - pass + def _tier0_validator(self, tier0_uuid): + # Fail of the tier0 uuid was not found on the BSX + self.nsxpolicy.tier0.get(tier0_uuid) + + def _get_nsx_net_tz_id(self, nsx_net): + return nsx_net['transport_zone_path'].split('/')[-1] + + def _allow_ens_networks(self): + return True + + def _ens_psec_supported(self): + """ENS security features are always enabled on NSX versions which + the policy plugin supports. + """ + return True def create_network(self, context, network): net_data = network['network'] - #TODO(asarfaty): network validation + #TODO(asarfaty): add ENS support external = net_data.get(external_net.EXTERNAL) is_external_net = validators.is_attr_set(external) and external tenant_id = net_data['tenant_id'] self._ensure_default_security_group(context, tenant_id) + vlt = vlan_apidef.get_vlan_transparent(net_data) + + self._validate_create_network(context, net_data) if is_external_net: - self._validate_external_net_create(net_data) + is_provider_net, net_type, physical_net, vlan_id = ( + self._validate_external_net_create( + net_data, self.default_tier0_router, + self._tier0_validator)) + provider_data = {'is_provider_net': is_provider_net, + 'net_type': net_type, + 'physical_net': physical_net, + 'vlan_id': vlan_id} + is_backend_network = False + else: + provider_data = self._validate_provider_create( + context, net_data, + self.default_vlan_tz, + self.default_overlay_tz, + self.nsxpolicy.transport_zone, + self.nsxpolicy.segment, + transparent_vlan=vlt) + if (provider_data['is_provider_net'] and + provider_data['net_type'] == + utils.NsxV3NetworkTypes.NSX_NETWORK): + is_backend_network = False + else: + is_backend_network = True # Create the neutron network with db_api.CONTEXT_WRITER.using(context): # Create network in Neutron created_net = super(NsxPolicyPlugin, self).create_network( context, network) + super(NsxPolicyPlugin, self).update_network(context, + created_net['id'], + {'network': {'vlan_transparent': vlt}}) self._extension_manager.process_create_network( context, net_data, created_net) if psec.PORTSECURITY not in net_data: @@ -302,10 +384,21 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, context, net_data, created_net) self._process_l3_create(context, created_net, net_data) + if provider_data['is_provider_net']: + # Save provider network fields, needed by get_network() + net_bindings = [nsx_db.add_network_binding( + context.session, created_net['id'], + provider_data['net_type'], + provider_data['physical_net'], + provider_data['vlan_id'])] + self._extend_network_dict_provider(context, created_net, + bindings=net_bindings) + # Create the backend NSX network - if not is_external_net: + if is_backend_network: try: - self._create_network_on_backend(context, created_net) + self._create_network_on_backend( + context, created_net, vlt, provider_data) except Exception as e: LOG.exception("Failed to create NSX network network: %s", e) with excutils.save_and_reraise_exception(): @@ -316,24 +409,30 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, # 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) - return created_net def delete_network(self, context, network_id): + is_nsx_net = self._network_is_nsx_net(context, network_id) + is_external_net = self._network_is_external(context, network_id) with db_api.CONTEXT_WRITER.using(context): self._process_l3_delete(context, network_id) super(NsxPolicyPlugin, self).delete_network( context, network_id) - if not self._network_is_external(context, network_id): + if not is_external_net and not is_nsx_net: self.nsxpolicy.segment.delete(network_id) + else: + # TODO(asarfaty): for NSX network we may need to delete DHCP conf + pass - def update_network(self, context, id, network): - original_net = super(NsxPolicyPlugin, self).get_network(context, id) + def update_network(self, context, network_id, network): + original_net = super(NsxPolicyPlugin, self).get_network( + context, network_id) net_data = network['network'] - LOG.debug("Updating network %s %s->%s", 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, id) + extern_net = self._network_is_external(context, network_id) + is_nsx_net = self._network_is_nsx_net(context, network_id) # Do not support changing external/non-external networks if (external_net.EXTERNAL in net_data and @@ -341,41 +440,34 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, err_msg = _("Cannot change the router:external flag of a network") raise n_exc.InvalidInput(error_message=err_msg) - updated_net = super(NsxPolicyPlugin, self).update_network(context, id, - network) + # Update the neutron network + updated_net = super(NsxPolicyPlugin, self).update_network( + context, network_id, network) self._extension_manager.process_update_network(context, net_data, updated_net) self._process_l3_update(context, updated_net, network['network']) + self._extend_network_dict_provider(context, updated_net) - #TODO(asarfaty): update the Policy manager + # Update the backend segment + if (not extern_net and not is_nsx_net and + ('name' in net_data or 'description' in net_data)): + # TODO(asarfaty): handle admin state changes as well + net_name = utils.get_name_and_uuid( + updated_net['name'] or 'network', network_id) + try: + self.nsxpolicy.segment.update( + network_id, + name=net_name, + description=net_data.get('description')) + except nsx_lib_exc.ManagerError: + LOG.exception("Unable to update NSX backend, rolling " + "back changes on neutron") + with excutils.save_and_reraise_exception(): + super(NsxPolicyPlugin, self).update_network( + context, network_id, {'network': original_net}) return updated_net - def get_network(self, context, id, fields=None): - with db_api.CONTEXT_READER.using(context): - # Get network from Neutron database - network = self._get_network(context, id) - # Don't do field selection here otherwise we won't be able to add - # provider networks fields - net = self._make_network_dict(network, context=context) - return db_utils.resource_fields(net, fields) - - def get_networks(self, context, filters=None, fields=None, - sorts=None, limit=None, marker=None, - page_reverse=False): - # Get networks from Neutron database - filters = filters or {} - with db_api.CONTEXT_READER.using(context): - networks = ( - super(NsxPolicyPlugin, self).get_networks( - context, filters, fields, sorts, - limit, marker, page_reverse)) - # TODO(asarfaty) Add plugin/provider network fields - - return (networks if not fields else - [db_utils.resource_fields(network, - fields) for network in networks]) - def create_subnet(self, context, subnet): self._validate_host_routes_input(subnet) created_subnet = super( @@ -423,6 +515,20 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, return address_bindings + def _get_network_nsx_id(self, context, network_id): + """Return the NSX segment ID matching the neutron network id + + Usually the NSX ID is the same as the neutron ID. The exception is + when this is a provider NSX_NETWORK, which means the network already + existed on the NSX backend, and it is being consumed by the plugin. + """ + bindings = nsx_db.get_network_bindings(context.session, network_id) + if (bindings and + bindings[0].binding_type == utils.NsxV3NetworkTypes.NSX_NETWORK): + # return the ID of the NSX network + return bindings[0].phy_uuid + return network_id + def _build_port_tags(self, port_data): sec_groups = port_data.get(ext_sg.SECURITYGROUPS, []) sec_groups += port_data.get(provider_sg.PROVIDER_SECURITYGROUPS, []) @@ -453,22 +559,16 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, tags.extend(self.nsxpolicy.build_v3_api_version_project_tag( context.tenant_name)) + segment_id = self._get_network_nsx_id( + context, port_data['network_id']) self.nsxpolicy.segment_port.create_or_overwrite( - name, - port_data['network_id'], + name, segment_id, port_id=port_data['id'], description=port_data.get('description'), address_bindings=address_bindings, vif_id=vif_id, tags=tags) - def _cleanup_port(self, context, port_id, lport_id): - super(NsxPolicyPlugin, self).delete_port(context, port_id) - - port_data = self.get_port(context, port_id) - self.nsxpolicy.segment_port.delete( - port_data['network_id'], port_data['id']) - def base_create_port(self, context, port): neutron_db = super(NsxPolicyPlugin, self).create_port(context, port) self._extension_manager.process_create_port( @@ -480,6 +580,11 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, self._validate_max_ips_per_port(port_data.get('fixed_ips', []), port_data.get('device_owner')) + # Validate the vnic type (the same types as for the NSX-T plugin) + direct_vnic_type = self._validate_port_vnic_type( + context, port_data, port_data['network_id'], + projectpluginmap.NsxPlugins.NSX_T) + with db_api.CONTEXT_WRITER.using(context): is_external_net = self._network_is_external( context, port_data['network_id']) @@ -489,10 +594,14 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, neutron_db = self.base_create_port(context, port) port["port"].update(neutron_db) + self.fix_direct_vnic_port_sec(direct_vnic_type, port_data) (is_psec_on, has_ip, sgids, psgids) = ( self._create_port_preprocess_security(context, port, port_data, neutron_db, False)) + self._process_portbindings_create_and_update( + context, port['port'], port_data, + vif_type=self._vif_type_by_vnic_type(direct_vnic_type)) self._process_port_create_security_group(context, port_data, sgids) self._process_port_create_provider_security_group( @@ -506,26 +615,31 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, LOG.error('Failed to create port %(id)s on NSX ' 'backend. Exception: %(e)s', {'id': neutron_db['id'], 'e': e}) - self._cleanup_port(context, neutron_db['id'], None) + super(NsxPolicyPlugin, self).delete_port( + context, neutron_db['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']) resource_extend.apply_funcs('ports', port_data, port_model) + self._extend_nsx_port_dict_binding(context, port_data) + self._remove_provider_security_groups_from_list(port_data) kwargs = {'context': context, 'port': neutron_db} registry.notify(resources.PORT, events.AFTER_CREATE, self, **kwargs) - return neutron_db + return port_data def delete_port(self, context, port_id, l3_port_check=True, l2gw_port_check=True, force_delete_dhcp=False, force_delete_vpn=False): port_data = self.get_port(context, port_id) + segment_id = self._get_network_nsx_id( + context, port_data['network_id']) + if not self._network_is_external(context, port_data['network_id']): try: - self.nsxpolicy.segment_port.delete( - port_data['network_id'], port_data['id']) + self.nsxpolicy.segment_port.delete(segment_id, port_data['id']) except Exception as ex: LOG.error("Failed to delete port %(id)s on NSX backend " "due to %(e)s", @@ -593,6 +707,8 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, if 'id' in port: 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._remove_provider_security_groups_from_list(port) return db_utils.resource_fields(port, fields) def get_ports(self, context, filters=None, fields=None, @@ -617,6 +733,8 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, "process, and is being skipped", port['id']) ports.remove(port) continue + self._extend_nsx_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]) @@ -703,11 +821,12 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, self._validate_interface_address_scope(context, router_db, info) subnet = self.get_subnet(context, info['subnet_ids'][0]) + segment_id = self._get_network_nsx_id(context, network_id) # TODO(annak): Validate TZ try: # This is always an overwrite call # NOTE: Connecting network to multiple routers is not supported - self.nsxpolicy.segment.create_or_overwrite(network_id, + self.nsxpolicy.segment.create_or_overwrite(segment_id, tier1_id=router_id) except Exception as ex: with excutils.save_and_reraise_exception(): @@ -745,11 +864,8 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, fip_id, router_id, port_id) if router_id: - nsx_router_id = nsx_db.get_nsx_router_id(context.session, - router_id) - if nsx_router_id: - #TODO(asarfaty): Update the NSX router - pass + #TODO(asarfaty): Update the NSX router + pass super(NsxPolicyPlugin, self).delete_floatingip(context, fip_id) @@ -764,9 +880,7 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, router_id = new_fip['router_id'] new_port_id = new_fip['port_id'] - nsx_router_id = nsx_db.get_nsx_router_id(context.session, - router_id) - if nsx_router_id: + if router_id: #TODO(asarfaty): Update the NSX router LOG.debug("Updating floating IP %s. Router %s, Port %s " "(old port %s)", @@ -784,9 +898,7 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, for fip_db in fip_dbs: if not fip_db.router_id: continue - nsx_router_id = nsx_db.get_nsx_router_id(context.session, - fip_db.router_id) - if nsx_router_id: + if fip_db.router_id: # TODO(asarfaty): Update the NSX logical router pass self.update_floatingip_status(context, fip_db.id, diff --git a/vmware_nsx/plugins/nsx_v3/plugin.py b/vmware_nsx/plugins/nsx_v3/plugin.py index b548074256..4b70b875da 100644 --- a/vmware_nsx/plugins/nsx_v3/plugin.py +++ b/vmware_nsx/plugins/nsx_v3/plugin.py @@ -63,7 +63,6 @@ from neutron.extensions import providernet from neutron.extensions import securitygroup as ext_sg from neutron.quota import resource_registry from neutron_lib.api.definitions import extra_dhcp_opt as ext_edo -from neutron_lib.api.definitions import portbindings as pbin from neutron_lib.api.definitions import provider_net as pnet from neutron_lib.api.definitions import vlantransparent as vlan_apidef from neutron_lib.api import validators @@ -74,7 +73,6 @@ from neutron_lib.callbacks import resources from neutron_lib import constants as const from neutron_lib import context as q_context from neutron_lib import exceptions as n_exc -from neutron_lib.plugins import utils as plugin_utils from neutron_lib.utils import helpers from oslo_config import cfg from oslo_db import exception as db_exc @@ -82,7 +80,6 @@ from oslo_log import log from oslo_utils import excutils from oslo_utils import importutils from oslo_utils import uuidutils -from six import moves from sqlalchemy import exc as sql_exc import webob.exc @@ -99,7 +96,6 @@ from vmware_nsx.common import utils from vmware_nsx.db import db as nsx_db from vmware_nsx.db import extended_security_group_rule as extend_sg_rule from vmware_nsx.db import maclearning as mac_db -from vmware_nsx.db import nsx_portbindings_db as pbin_db from vmware_nsx.dhcp_meta import rpc as nsx_rpc from vmware_nsx.extensions import advancedserviceproviders as as_providers from vmware_nsx.extensions import housekeeper as hk_ext @@ -168,7 +164,6 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, extraroute_db.ExtraRoute_db_mixin, router_az_db.RouterAvailabilityZoneMixin, l3_gwmode_db.L3_NAT_db_mixin, - pbin_db.NsxPortBindingMixin, portbindings_db.PortBindingMixin, portsecurity_db.PortSecurityDbMixin, extradhcpopt_db.ExtraDhcpOptMixin, @@ -231,6 +226,7 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, extension_drivers = cfg.CONF.nsx_extension_drivers self._extension_manager = managers.ExtensionManager( extension_drivers=extension_drivers) + self.cfg_group = 'nsx_v3' # group name for nsx_v3 section in nsx.ini super(NsxV3Plugin, self).__init__() # Bind the dummy L3 notifications self.l3_rpc_notifier = l3_rpc_agent_api.L3NotifyAPI() @@ -255,11 +251,8 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, self._nsx_version = self.nsxlib.get_version() LOG.info("NSX Version: %s", self._nsx_version) - self.cfg_group = 'nsx_v3' # group name for nsx_v3 section in nsx.ini self.tier0_groups_dict = {} - self._network_vlans = plugin_utils.parse_network_vlan_ranges( - cfg.CONF.nsx_v3.network_vlan_ranges) # Initialize the network availability zones, which will be used only # when native_dhcp_metadata is True self.init_availability_zones() @@ -632,33 +625,6 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, for az in self.get_azs_list(): az.translate_configured_names_to_uuids(self.nsxlib) - def _get_network_segmentation_id(self, context, neutron_id): - bindings = nsx_db.get_network_bindings(context.session, neutron_id) - if bindings: - return bindings[0].vlan_id - - def _extend_nsx_port_dict_binding(self, context, port_data): - # Not using the register api for this because we need the context - # Some attributes were already initialized by _extend_port_portbinding - if pbin.VIF_TYPE not in port_data: - port_data[pbin.VIF_TYPE] = pbin.VIF_TYPE_OVS - if pbin.VNIC_TYPE not in port_data: - port_data[pbin.VNIC_TYPE] = pbin.VNIC_NORMAL - if 'network_id' in port_data: - net_id = port_data['network_id'] - if pbin.VIF_DETAILS not in port_data: - port_data[pbin.VIF_DETAILS] = {} - port_data[pbin.VIF_DETAILS][pbin.OVS_HYBRID_PLUG] = False - if port_data.get('device_owner') == const.DEVICE_OWNER_FLOATINGIP: - # floatingip belongs to an external net without nsx-id - port_data[pbin.VIF_DETAILS]['nsx-logical-switch-id'] = None - else: - port_data[pbin.VIF_DETAILS]['nsx-logical-switch-id'] = ( - self._get_network_nsx_id(context, net_id)) - if port_data[pbin.VNIC_TYPE] != pbin.VNIC_NORMAL: - port_data[pbin.VIF_DETAILS]['segmentation-id'] = ( - self._get_network_segmentation_id(context, net_id)) - @nsxlib_utils.retry_upon_exception( Exception, max_attempts=cfg.CONF.nsx_v3.retries) def _init_default_section_nsgroup(self): @@ -917,179 +883,25 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, return self.conn.consume_in_threads() - def _validate_provider_create(self, context, network_data, az, - transparent_vlan): - is_provider_net = any( - validators.is_attr_set(network_data.get(f)) - for f in (pnet.NETWORK_TYPE, - pnet.PHYSICAL_NETWORK, - pnet.SEGMENTATION_ID)) - - physical_net = network_data.get(pnet.PHYSICAL_NETWORK) - if not validators.is_attr_set(physical_net): - physical_net = None - - vlan_id = network_data.get(pnet.SEGMENTATION_ID) - if not validators.is_attr_set(vlan_id): - vlan_id = None - - if vlan_id and transparent_vlan: - err_msg = (_("Segmentation ID cannot be set with transparent " - "vlan!")) - raise n_exc.InvalidInput(error_message=err_msg) - - err_msg = None - net_type = network_data.get(pnet.NETWORK_TYPE) - nsxlib_tz = self.nsxlib.transport_zone - tz_type = nsxlib_tz.TRANSPORT_TYPE_VLAN - switch_mode = nsxlib_tz.HOST_SWITCH_MODE_STANDARD - if validators.is_attr_set(net_type): - if net_type == utils.NsxV3NetworkTypes.FLAT: - if vlan_id is not None: - err_msg = (_("Segmentation ID cannot be specified with " - "%s network type") % - utils.NsxV3NetworkTypes.FLAT) - else: - if not transparent_vlan: - # Set VLAN id to 0 for flat networks - vlan_id = '0' - if physical_net is None: - physical_net = az._default_vlan_tz_uuid - elif (net_type == utils.NsxV3NetworkTypes.VLAN and - not transparent_vlan): - # Use default VLAN transport zone if physical network not given - if physical_net is None: - physical_net = az._default_vlan_tz_uuid - - # Validate VLAN id - if not vlan_id: - vlan_id = self._generate_segment_id(context, - physical_net, - network_data) - elif not plugin_utils.is_valid_vlan_tag(vlan_id): - err_msg = (_('Segmentation ID %(segmentation_id)s out of ' - 'range (%(min_id)s through %(max_id)s)') % - {'segmentation_id': vlan_id, - 'min_id': const.MIN_VLAN_TAG, - 'max_id': const.MAX_VLAN_TAG}) - else: - # Verify VLAN id is not already allocated - bindings = ( - nsx_db.get_network_bindings_by_vlanid_and_physical_net( - context.session, vlan_id, physical_net) - ) - if bindings: - raise n_exc.VlanIdInUse( - vlan_id=vlan_id, physical_network=physical_net) - elif (net_type == utils.NsxV3NetworkTypes.VLAN and - transparent_vlan): - # Use default VLAN transport zone if physical network not given - if physical_net is None: - physical_net = az._default_vlan_tz_uuid - elif net_type == utils.NsxV3NetworkTypes.GENEVE: - if vlan_id: - err_msg = (_("Segmentation ID cannot be specified with " - "%s network type") % - utils.NsxV3NetworkTypes.GENEVE) - tz_type = nsxlib_tz.TRANSPORT_TYPE_OVERLAY - elif net_type == utils.NsxV3NetworkTypes.NSX_NETWORK: - # Linking neutron networks to an existing NSX logical switch - if physical_net is None: - err_msg = (_("Physical network must be specified with " - "%s network type") % net_type) - # Validate the logical switch existence - try: - nsx_net = self.nsxlib.logical_switch.get(physical_net) - switch_mode = nsxlib_tz.get_host_switch_mode( - nsx_net['transport_zone_id']) - except nsx_lib_exc.ResourceNotFound: - err_msg = (_('Logical switch %s does not exist') % - physical_net) - # make sure no other neutron network is using it - bindings = ( - nsx_db.get_network_bindings_by_vlanid_and_physical_net( - context.elevated().session, 0, physical_net)) - if bindings: - err_msg = (_('Logical switch %s is already used by ' - 'another network') % physical_net) - else: - err_msg = (_('%(net_type_param)s %(net_type_value)s not ' - 'supported') % - {'net_type_param': pnet.NETWORK_TYPE, - 'net_type_value': net_type}) - elif is_provider_net: - # FIXME: Ideally provider-network attributes should be checked - # at the NSX backend. For now, the network_type is required, - # so the plugin can do a quick check locally. - err_msg = (_('%s is required for creating a provider network') % - pnet.NETWORK_TYPE) - else: - net_type = None - - if physical_net is None: - # Default to transport type overlay - physical_net = az._default_overlay_tz_uuid - - # validate the transport zone existence and type - if (not err_msg and physical_net and - net_type != utils.NsxV3NetworkTypes.NSX_NETWORK): - if is_provider_net: - try: - backend_type = nsxlib_tz.get_transport_type( - physical_net) - except nsx_lib_exc.ResourceNotFound: - err_msg = (_('Transport zone %s does not exist') % - physical_net) - else: - if backend_type != tz_type: - err_msg = (_('%(tz)s transport zone is required for ' - 'creating a %(net)s provider network') % - {'tz': tz_type, 'net': net_type}) - if not err_msg: - switch_mode = nsxlib_tz.get_host_switch_mode(physical_net) - - if err_msg: - raise n_exc.InvalidInput(error_message=err_msg) - - return {'is_provider_net': is_provider_net, - 'net_type': net_type, - 'physical_net': physical_net, - 'vlan_id': vlan_id, - 'switch_mode': switch_mode} - def _get_edge_cluster(self, tier0_uuid): self.nsxlib.router.validate_tier0(self.tier0_groups_dict, tier0_uuid) tier0_info = self.tier0_groups_dict[tier0_uuid] return tier0_info['edge_cluster_uuid'] - def _validate_external_net_create(self, net_data, az): - if not validators.is_attr_set(net_data.get(pnet.PHYSICAL_NETWORK)): - tier0_uuid = az._default_tier0_router - else: - tier0_uuid = net_data[pnet.PHYSICAL_NETWORK] - if ((validators.is_attr_set(net_data.get(pnet.NETWORK_TYPE)) and - net_data.get(pnet.NETWORK_TYPE) != utils.NetworkTypes.L3_EXT and - net_data.get(pnet.NETWORK_TYPE) != utils.NetworkTypes.LOCAL) or - validators.is_attr_set(net_data.get(pnet.SEGMENTATION_ID))): - msg = (_("External network cannot be created with %s provider " - "network or segmentation id") % - net_data.get(pnet.NETWORK_TYPE)) - raise n_exc.InvalidInput(error_message=msg) - self.nsxlib.router.validate_tier0(self.tier0_groups_dict, tier0_uuid) - return (True, utils.NetworkTypes.L3_EXT, tier0_uuid, 0) + def _allow_ens_networks(self): + return cfg.CONF.nsx_v3.ens_support def _create_network_at_the_backend(self, context, net_data, az, transparent_vlan): - provider_data = self._validate_provider_create(context, net_data, az, - transparent_vlan) + provider_data = self._validate_provider_create( + context, net_data, + az._default_vlan_tz_uuid, + az._default_overlay_tz_uuid, + self.nsxlib.transport_zone, + self.nsxlib.logical_switch, + transparent_vlan=transparent_vlan) neutron_net_id = net_data.get('id') or uuidutils.generate_uuid() net_data['id'] = neutron_net_id - if (provider_data['switch_mode'] == - self.nsxlib.transport_zone.HOST_SWITCH_MODE_ENS): - if not cfg.CONF.nsx_v3.ens_support: - raise NotImplementedError(_("ENS support is disabled")) - self._assert_on_ens_with_qos(net_data) - self._validate_ens_net_portsecurity(net_data) if (provider_data['is_provider_net'] and provider_data['net_type'] == utils.NsxV3NetworkTypes.NSX_NETWORK): @@ -1148,12 +960,6 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, return (is_overlay or self.nsxlib.feature_supported( nsxlib_consts.FEATURE_VLAN_ROUTER_INTERFACE)), net_type - def _validate_network_type(self, context, network_id, net_types): - net = self.get_network(context, network_id) - if net.get(pnet.NETWORK_TYPE) in net_types: - return True - return False - def _is_ddi_supported_on_network(self, context, network_id): result, _ = self._is_ddi_supported_on_net_with_type( context, network_id) @@ -1199,19 +1005,6 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, self.nsxlib.transport_zone.TRANSPORT_TYPE_OVERLAY) return False - def _extend_network_dict_provider(self, context, network, bindings=None): - if 'id' not in network: - return - if not bindings: - bindings = nsx_db.get_network_bindings(context.session, - network['id']) - # With NSX plugin, "normal" overlay networks will have no binding - if bindings: - # Network came in through provider networks API - network[pnet.NETWORK_TYPE] = bindings[0].binding_type - network[pnet.PHYSICAL_NETWORK] = bindings[0].phy_uuid - network[pnet.SEGMENTATION_ID] = bindings[0].vlan_id - def get_subnets(self, context, filters=None, fields=None, sorts=None, limit=None, marker=None, page_reverse=False): filters = filters or {} @@ -1227,38 +1020,17 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, return super(NsxV3Plugin, self).get_subnets( context, filters, fields, sorts, limit, marker, page_reverse) - def _network_is_nsx_net(self, context, network_id): - bindings = nsx_db.get_network_bindings(context.session, network_id) - if not bindings: - return False - return (bindings[0].binding_type == - utils.NsxV3NetworkTypes.NSX_NETWORK) - - def _generate_segment_id(self, context, physical_network, net_data): - bindings = nsx_db.get_network_bindings_by_phy_uuid( - context.session, physical_network) - vlan_ranges = self._network_vlans.get(physical_network, []) - if vlan_ranges: - vlan_ids = set() - for vlan_min, vlan_max in vlan_ranges: - vlan_ids |= set(moves.range(vlan_min, vlan_max + 1)) - else: - vlan_min = const.MIN_VLAN_TAG - vlan_max = const.MAX_VLAN_TAG - vlan_ids = set(moves.range(vlan_min, vlan_max + 1)) - used_ids_in_range = set([binding.vlan_id for binding in bindings - if binding.vlan_id in vlan_ids]) - free_ids = list(vlan_ids ^ used_ids_in_range) - if len(free_ids) == 0: - raise n_exc.NoNetworkAvailable() - net_data[pnet.SEGMENTATION_ID] = free_ids[0] - return net_data[pnet.SEGMENTATION_ID] - def _get_mdproxy_port_name(self, net_name, net_id): return utils.get_name_and_uuid('%s-%s' % ('mdproxy', net_name or 'network'), net_id) + def _tier0_validator(self, tier0_uuid): + self.nsxlib.router.validate_tier0(self.tier0_groups_dict, tier0_uuid) + + def _get_nsx_net_tz_id(self, nsx_net): + return nsx_net['transport_zone_id'] + def create_network(self, context, network): net_data = network['network'] external = net_data.get(extnet_apidef.EXTERNAL) @@ -1283,7 +1055,9 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, if is_external_net: is_provider_net, net_type, physical_net, vlan_id = ( - self._validate_external_net_create(net_data, az)) + self._validate_external_net_create( + net_data, az._default_tier0_router, + self._tier0_validator)) nsx_net_id = None is_backend_network = False else: @@ -1389,12 +1163,6 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, return created_net - def _assert_on_ens_with_qos(self, net_data): - qos_id = net_data.get(qos_consts.QOS_POLICY_ID) - if validators.is_attr_set(qos_id): - err_msg = _("Cannot configure QOS on ENS networks") - raise n_exc.InvalidInput(error_message=err_msg) - def _ens_psec_supported(self): return self.nsxlib.feature_supported( nsxlib_consts.FEATURE_ENS_WITH_SEC) @@ -1405,8 +1173,7 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, if cfg.CONF.nsx_v3.disable_port_security_for_ens: # Override the port-security to False if net_data[psec.PORTSECURITY]: - LOG.warning("Disabling port security for network %s", - net_data['id']) + LOG.warning("Disabling port security for bew network") # Set the port security to False net_data[psec.PORTSECURITY] = False @@ -1521,7 +1288,7 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, is_ens_net = self._is_ens_tz_net(context, id) # Validate the updated parameters - self._validate_update_netowrk(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) @@ -2205,38 +1972,6 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, return address_bindings - def _extend_get_network_dict_provider(self, context, network): - self._extend_network_dict_provider(context, network) - network[qos_consts.QOS_POLICY_ID] = (qos_com_utils. - get_network_policy_id(context, network['id'])) - - def get_network(self, context, id, fields=None): - with db_api.CONTEXT_READER.using(context): - # Get network from Neutron database - network = self._get_network(context, id) - # Don't do field selection here otherwise we won't be able to add - # provider networks fields - net = self._make_network_dict(network, context=context) - self._extend_get_network_dict_provider(context, net) - return db_utils.resource_fields(net, fields) - - def get_networks(self, context, filters=None, fields=None, - sorts=None, limit=None, marker=None, - page_reverse=False): - # Get networks from Neutron database - filters = filters or {} - with db_api.CONTEXT_READER.using(context): - networks = ( - super(NsxV3Plugin, self).get_networks( - context, filters, fields, sorts, - limit, marker, page_reverse)) - # Add provider network fields - for net in networks: - self._extend_get_network_dict_provider(context, net) - return (networks if not fields else - [db_utils.resource_fields(network, - fields) for network in networks]) - def _get_qos_profile_id(self, context, policy_id): switch_profile_id = nsx_db.get_switch_profile_by_qos_policy( context.session, policy_id) @@ -2820,10 +2555,6 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, context, port['port'], neutron_db) return neutron_db - def _vif_type_by_vnic_type(self, direct_vnic_type): - return (nsx_constants.VIF_TYPE_DVS if direct_vnic_type - else pbin.VIF_TYPE_OVS) - def _validate_ens_create_port(self, context, port_data): qos_selected = validators.is_attr_set(port_data.get( qos_consts.QOS_POLICY_ID)) @@ -2862,19 +2593,7 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, neutron_db = self.base_create_port(context, port) port["port"].update(neutron_db) - if direct_vnic_type: - if validators.is_attr_set(port_data.get(psec.PORTSECURITY)): - # 'direct' and 'direct-physical' vnic types ports requires - # port-security to be disabled. - if port_data[psec.PORTSECURITY]: - err_msg = _("Security features are not supported for " - "ports with direct/direct-physical VNIC " - "type") - raise n_exc.InvalidInput(error_message=err_msg) - else: - # Implicitly disable port-security for direct vnic types. - port_data[psec.PORTSECURITY] = False - + self.fix_direct_vnic_port_sec(direct_vnic_type, port_data) (is_psec_on, has_ip, sgids, psgids) = ( self._create_port_preprocess_security(context, port, port_data, neutron_db, diff --git a/vmware_nsx/tests/unit/nsx_p/test_plugin.py b/vmware_nsx/tests/unit/nsx_p/test_plugin.py index 2e64ab052b..cfcf2b927f 100644 --- a/vmware_nsx/tests/unit/nsx_p/test_plugin.py +++ b/vmware_nsx/tests/unit/nsx_p/test_plugin.py @@ -15,13 +15,35 @@ import mock +from oslo_config import cfg +from oslo_utils import uuidutils +from webob import exc + from neutron.tests.unit.db import test_db_base_plugin_v2 from neutron.tests.unit.extensions import test_securitygroup +from neutron_lib.api.definitions import external_net as extnet_apidef +from neutron_lib.api.definitions import port_security as psec +from neutron_lib.api.definitions import portbindings +from neutron_lib.api.definitions import provider_net as pnet +from neutron_lib.api.definitions import vlantransparent as vlan_apidef +from neutron_lib import context + +from vmware_nsxlib.v3 import exceptions as nsxlib_exc from vmware_nsxlib.v3 import nsx_constants - PLUGIN_NAME = 'vmware_nsx.plugin.NsxPolicyPlugin' +NSX_OVERLAY_TZ_NAME = 'OVERLAY_TZ' +NSX_VLAN_TZ_NAME = 'VLAN_TZ' +DEFAULT_TIER0_ROUTER_UUID = "efad0078-9204-4b46-a2d8-d4dd31ed448f" + + +def _return_id_key(*args, **kwargs): + return {'id': uuidutils.generate_uuid()} + + +def _return_id_key_list(*args, **kwargs): + return [{'id': uuidutils.generate_uuid()}] class NsxPPluginTestCaseMixin( @@ -56,14 +78,38 @@ class NsxPPluginTestCaseMixin( return_value=-1).start() def setup_conf_overrides(self): - #TODO(asarfaty): will be needed in the future - #cfg.CONF.set_override('default_overlay_tz', NSX_TZ_NAME, 'nsx_p') - #cfg.CONF.set_override('native_dhcp_metadata', False, 'nsx_p') - #cfg.CONF.set_override('dhcp_profile', - # NSX_DHCP_PROFILE_ID, 'nsx_p') - #cfg.CONF.set_override('metadata_proxy', - # NSX_METADATA_PROXY_ID, 'nsx_p') - pass + cfg.CONF.set_override('default_overlay_tz', NSX_OVERLAY_TZ_NAME, + 'nsx_p') + cfg.CONF.set_override('default_vlan_tz', NSX_VLAN_TZ_NAME, 'nsx_p') + + def _create_network(self, fmt, name, admin_state_up, + arg_list=None, providernet_args=None, + set_context=False, tenant_id=None, + **kwargs): + tenant_id = tenant_id or self._tenant_id + data = {'network': {'name': name, + 'admin_state_up': admin_state_up, + 'tenant_id': tenant_id}} + # Fix to allow the router:external attribute and any other + # attributes containing a colon to be passed with + # a double underscore instead + kwargs = dict((k.replace('__', ':'), v) for k, v in kwargs.items()) + if extnet_apidef.EXTERNAL in kwargs: + arg_list = (extnet_apidef.EXTERNAL, ) + (arg_list or ()) + + if providernet_args: + kwargs.update(providernet_args) + for arg in (('admin_state_up', 'tenant_id', 'shared', + 'availability_zone_hints') + (arg_list or ())): + # Arg must be present + if arg in kwargs: + data['network'][arg] = kwargs[arg] + network_req = self.new_create_request('networks', data, fmt) + if set_context and tenant_id: + # create a specific auth context for this request + network_req.environ['neutron.context'] = context.Context( + '', tenant_id) + return network_req.get_response(self.api) class NsxPTestNetworks(test_db_base_plugin_v2.TestNetworksV2, @@ -78,6 +124,220 @@ class NsxPTestNetworks(test_db_base_plugin_v2.TestNetworksV2, def tearDown(self): super(NsxPTestNetworks, self).tearDown() + def test_create_provider_flat_network(self): + providernet_args = {pnet.NETWORK_TYPE: 'flat'} + with mock.patch('vmware_nsxlib.v3.policy_resources.' + 'NsxPolicySegmentApi.create_or_overwrite', + side_effect=_return_id_key) as nsx_create, \ + mock.patch('vmware_nsxlib.v3.policy_resources.NsxPolicySegmentApi.' + 'delete') as nsx_delete, \ + mock.patch('vmware_nsxlib.v3.policy_resources.' + 'NsxPolicyTransportZoneApi.get_transport_type', + return_value=nsx_constants.TRANSPORT_TYPE_VLAN), \ + self.network(name='flat_net', + providernet_args=providernet_args, + arg_list=(pnet.NETWORK_TYPE, )) as net: + self.assertEqual('flat', net['network'].get(pnet.NETWORK_TYPE)) + # make sure the network is created at the backend + nsx_create.assert_called_once() + + # Delete the network and make sure it is deleted from the backend + req = self.new_delete_request('networks', net['network']['id']) + res = req.get_response(self.api) + self.assertEqual(exc.HTTPNoContent.code, res.status_int) + nsx_delete.assert_called_once() + + def test_create_provider_flat_network_with_physical_net(self): + physical_network = DEFAULT_TIER0_ROUTER_UUID + providernet_args = {pnet.NETWORK_TYPE: 'flat', + pnet.PHYSICAL_NETWORK: physical_network} + with mock.patch( + 'vmware_nsxlib.v3.policy_resources.NsxPolicyTransportZoneApi.' + 'get_transport_type', + return_value=nsx_constants.TRANSPORT_TYPE_VLAN), \ + self.network(name='flat_net', + providernet_args=providernet_args, + arg_list=(pnet.NETWORK_TYPE, + pnet.PHYSICAL_NETWORK)) as net: + self.assertEqual('flat', net['network'].get(pnet.NETWORK_TYPE)) + + def test_create_provider_flat_network_with_vlan(self): + providernet_args = {pnet.NETWORK_TYPE: 'flat', + pnet.SEGMENTATION_ID: 11} + with mock.patch( + 'vmware_nsxlib.v3.policy_resources.NsxPolicyTransportZoneApi.' + 'get_transport_type', + return_value=nsx_constants.TRANSPORT_TYPE_VLAN): + result = self._create_network(fmt='json', name='bad_flat_net', + admin_state_up=True, + providernet_args=providernet_args, + arg_list=(pnet.NETWORK_TYPE, + pnet.SEGMENTATION_ID)) + data = self.deserialize('json', result) + # should fail + self.assertEqual('InvalidInput', data['NeutronError']['type']) + + def test_create_provider_geneve_network(self): + providernet_args = {pnet.NETWORK_TYPE: 'geneve'} + with mock.patch('vmware_nsxlib.v3.policy_resources.' + 'NsxPolicySegmentApi.create_or_overwrite', + side_effect=_return_id_key) as nsx_create, \ + mock.patch('vmware_nsxlib.v3.policy_resources.NsxPolicySegmentApi.' + 'delete') as nsx_delete, \ + self.network(name='geneve_net', + providernet_args=providernet_args, + arg_list=(pnet.NETWORK_TYPE, )) as net: + self.assertEqual('geneve', net['network'].get(pnet.NETWORK_TYPE)) + # make sure the network is created at the backend + nsx_create.assert_called_once() + + # Delete the network and make sure it is deleted from the backend + req = self.new_delete_request('networks', net['network']['id']) + res = req.get_response(self.api) + self.assertEqual(exc.HTTPNoContent.code, res.status_int) + nsx_delete.assert_called_once() + + def test_create_provider_geneve_network_with_physical_net(self): + physical_network = DEFAULT_TIER0_ROUTER_UUID + providernet_args = {pnet.NETWORK_TYPE: 'geneve', + pnet.PHYSICAL_NETWORK: physical_network} + with mock.patch( + 'vmware_nsxlib.v3.policy_resources.NsxPolicyTransportZoneApi.' + 'get_transport_type', + return_value=nsx_constants.TRANSPORT_TYPE_OVERLAY),\ + self.network(name='geneve_net', + providernet_args=providernet_args, + arg_list=(pnet.NETWORK_TYPE, )) as net: + self.assertEqual('geneve', net['network'].get(pnet.NETWORK_TYPE)) + + def test_create_provider_geneve_network_with_vlan(self): + providernet_args = {pnet.NETWORK_TYPE: 'geneve', + pnet.SEGMENTATION_ID: 11} + with mock.patch( + 'vmware_nsxlib.v3.policy_resources.NsxPolicyTransportZoneApi.' + 'get_transport_type', + return_value=nsx_constants.TRANSPORT_TYPE_OVERLAY): + result = self._create_network(fmt='json', name='bad_geneve_net', + admin_state_up=True, + providernet_args=providernet_args, + arg_list=(pnet.NETWORK_TYPE, + pnet.SEGMENTATION_ID)) + data = self.deserialize('json', result) + # should fail + self.assertEqual('InvalidInput', data['NeutronError']['type']) + + def test_create_provider_vlan_network(self): + providernet_args = {pnet.NETWORK_TYPE: 'vlan', + pnet.SEGMENTATION_ID: 11} + with mock.patch('vmware_nsxlib.v3.policy_resources.' + 'NsxPolicySegmentApi.create_or_overwrite', + side_effect=_return_id_key) as nsx_create, \ + mock.patch('vmware_nsxlib.v3.policy_resources.NsxPolicySegmentApi.' + 'delete') as nsx_delete, \ + mock.patch('vmware_nsxlib.v3.policy_resources.' + 'NsxPolicyTransportZoneApi.get_transport_type', + return_value=nsx_constants.TRANSPORT_TYPE_VLAN), \ + self.network(name='vlan_net', + providernet_args=providernet_args, + arg_list=(pnet.NETWORK_TYPE, + pnet.SEGMENTATION_ID)) as net: + self.assertEqual('vlan', net['network'].get(pnet.NETWORK_TYPE)) + # make sure the network is created at the backend + nsx_create.assert_called_once() + + # Delete the network and make sure it is deleted from the backend + req = self.new_delete_request('networks', net['network']['id']) + res = req.get_response(self.api) + self.assertEqual(exc.HTTPNoContent.code, res.status_int) + nsx_delete.assert_called_once() + + def test_create_provider_nsx_network(self): + physical_network = 'Fake logical switch' + providernet_args = {pnet.NETWORK_TYPE: 'nsx-net', + pnet.PHYSICAL_NETWORK: physical_network} + + with mock.patch( + 'vmware_nsxlib.v3.policy_resources.NsxPolicySegmentApi.' + 'create_or_overwrite', + side_effect=nsxlib_exc.ResourceNotFound) as nsx_create, \ + mock.patch('vmware_nsxlib.v3.policy_resources.NsxPolicySegmentApi.' + 'delete') as nsx_delete, \ + self.network(name='nsx_net', + providernet_args=providernet_args, + arg_list=(pnet.NETWORK_TYPE, + pnet.PHYSICAL_NETWORK)) as net: + self.assertEqual('nsx-net', net['network'].get(pnet.NETWORK_TYPE)) + self.assertEqual(physical_network, + net['network'].get(pnet.PHYSICAL_NETWORK)) + # make sure the network is NOT created at the backend + nsx_create.assert_not_called() + + # Delete the network. It should NOT deleted from the backend + req = self.new_delete_request('networks', net['network']['id']) + res = req.get_response(self.api) + self.assertEqual(exc.HTTPNoContent.code, res.status_int) + nsx_delete.assert_not_called() + + def test_create_provider_bad_nsx_network(self): + physical_network = 'Bad logical switch' + providernet_args = {pnet.NETWORK_TYPE: 'nsx-net', + pnet.PHYSICAL_NETWORK: physical_network} + with mock.patch( + "vmware_nsxlib.v3.policy_resources.NsxPolicySegmentApi.get", + side_effect=nsxlib_exc.ResourceNotFound): + result = self._create_network(fmt='json', name='bad_nsx_net', + admin_state_up=True, + providernet_args=providernet_args, + arg_list=(pnet.NETWORK_TYPE, + pnet.PHYSICAL_NETWORK)) + data = self.deserialize('json', result) + # should fail + self.assertEqual('InvalidInput', data['NeutronError']['type']) + + def test_create_transparent_vlan_network(self): + providernet_args = {vlan_apidef.VLANTRANSPARENT: True} + with mock.patch('vmware_nsxlib.v3.policy_resources.' + 'NsxPolicyTransportZoneApi.get_transport_type', + return_value=nsx_constants.TRANSPORT_TYPE_OVERLAY), \ + self.network(name='vt_net', + providernet_args=providernet_args, + arg_list=(vlan_apidef.VLANTRANSPARENT, )) as net: + self.assertTrue(net['network'].get(vlan_apidef.VLANTRANSPARENT)) + + def test_create_provider_vlan_network_with_transparent(self): + providernet_args = {pnet.NETWORK_TYPE: 'vlan', + vlan_apidef.VLANTRANSPARENT: True} + with mock.patch('vmware_nsxlib.v3.policy_resources.' + 'NsxPolicyTransportZoneApi.get_transport_type', + return_value=nsx_constants.TRANSPORT_TYPE_VLAN): + result = self._create_network(fmt='json', name='badvlan_net', + admin_state_up=True, + providernet_args=providernet_args, + arg_list=( + pnet.NETWORK_TYPE, + pnet.SEGMENTATION_ID, + vlan_apidef.VLANTRANSPARENT)) + data = self.deserialize('json', result) + self.assertEqual('vlan', data['network'].get(pnet.NETWORK_TYPE)) + + def test_network_update_external_failure(self): + data = {'network': {'name': 'net1', + 'router:external': 'True', + 'tenant_id': 'tenant_one', + 'provider:physical_network': 'stam'}} + network_req = self.new_create_request('networks', data) + network = self.deserialize(self.fmt, + network_req.get_response(self.api)) + ext_net_id = network['network']['id'] + + # should fail to update the network to non-external + args = {'network': {'router:external': 'False'}} + req = self.new_update_request('networks', args, + ext_net_id, fmt='json') + res = self.deserialize('json', req.get_response(self.api)) + self.assertEqual('InvalidInput', + res['NeutronError']['type']) + class NsxPTestPorts(test_db_base_plugin_v2.TestPortsV2, NsxPPluginTestCaseMixin): @@ -147,18 +407,125 @@ class NsxPTestSecurityGroup(NsxPPluginTestCaseMixin, for k, v, in keys: self.assertEqual(rule['security_group_rule'][k], v) + def _test_create_direct_network(self, vlan_id=0): + net_type = vlan_id and 'vlan' or 'flat' + name = 'direct_net' + providernet_args = {pnet.NETWORK_TYPE: net_type, + pnet.PHYSICAL_NETWORK: 'tzuuid'} + if vlan_id: + providernet_args[pnet.SEGMENTATION_ID] = vlan_id + + mock_tt = mock.patch('vmware_nsxlib.v3' + '.policy_resources.NsxPolicyTransportZoneApi' + '.get_transport_type', + return_value=nsx_constants.TRANSPORT_TYPE_VLAN) + mock_tt.start() + return self.network(name=name, + providernet_args=providernet_args, + arg_list=(pnet.NETWORK_TYPE, + pnet.PHYSICAL_NETWORK, + pnet.SEGMENTATION_ID)) + + def _test_create_port_vnic_direct(self, vlan_id): + with mock.patch('vmware_nsxlib.v3.policy_resources.' + 'NsxPolicyTransportZoneApi.get_transport_type', + return_value=nsx_constants.TRANSPORT_TYPE_VLAN),\ + self._test_create_direct_network(vlan_id=vlan_id) as network: + # Check that port security conflicts + kwargs = {portbindings.VNIC_TYPE: portbindings.VNIC_DIRECT, + psec.PORTSECURITY: True} + net_id = network['network']['id'] + res = self._create_port(self.fmt, net_id=net_id, + arg_list=(portbindings.VNIC_TYPE, + psec.PORTSECURITY), + **kwargs) + self.assertEqual(res.status_int, exc.HTTPBadRequest.code) + + # Check that security group conflicts + kwargs = {portbindings.VNIC_TYPE: portbindings.VNIC_DIRECT, + 'security_groups': [ + '4cd70774-cc67-4a87-9b39-7d1db38eb087'], + psec.PORTSECURITY: False} + net_id = network['network']['id'] + res = self._create_port(self.fmt, net_id=net_id, + arg_list=(portbindings.VNIC_TYPE, + psec.PORTSECURITY), + **kwargs) + self.assertEqual(res.status_int, exc.HTTPBadRequest.code) + + # All is kosher so we can create the port + kwargs = {portbindings.VNIC_TYPE: portbindings.VNIC_DIRECT} + net_id = network['network']['id'] + res = self._create_port(self.fmt, net_id=net_id, + arg_list=(portbindings.VNIC_TYPE,), + **kwargs) + port = self.deserialize('json', res) + self.assertEqual("direct", port['port'][portbindings.VNIC_TYPE]) + self.assertEqual("dvs", port['port'][portbindings.VIF_TYPE]) + self.assertEqual( + vlan_id, + port['port'][portbindings.VIF_DETAILS]['segmentation-id']) + + # try to get the same port + req = self.new_show_request('ports', port['port']['id'], self.fmt) + sport = self.deserialize(self.fmt, req.get_response(self.api)) + self.assertEqual("dvs", sport['port'][portbindings.VIF_TYPE]) + self.assertEqual("direct", sport['port'][portbindings.VNIC_TYPE]) + self.assertEqual( + vlan_id, + sport['port'][portbindings.VIF_DETAILS]['segmentation-id']) + + def test_create_port_vnic_direct_flat(self): + self._test_create_port_vnic_direct(0) + + def test_create_port_vnic_direct_vlan(self): + self._test_create_port_vnic_direct(10) + + def test_create_port_vnic_direct_invalid_network(self): + with self.network(name='not vlan/flat') as net: + kwargs = {portbindings.VNIC_TYPE: portbindings.VNIC_DIRECT, + psec.PORTSECURITY: False} + net_id = net['network']['id'] + res = self._create_port(self.fmt, net_id=net_id, + arg_list=(portbindings.VNIC_TYPE, + psec.PORTSECURITY), + **kwargs) + self.assertEqual(exc.HTTPBadRequest.code, res.status_int) + + def test_update_vnic_direct(self): + with self._test_create_direct_network(vlan_id=7) as network: + with self.subnet(network=network) as subnet: + with self.port(subnet=subnet) as port: + # need to do two updates as the update for port security + # disabled requires that it can only change 2 items + data = {'port': {psec.PORTSECURITY: False, + 'security_groups': []}} + req = self.new_update_request('ports', + data, port['port']['id']) + res = self.deserialize('json', req.get_response(self.api)) + self.assertEqual(portbindings.VNIC_NORMAL, + res['port'][portbindings.VNIC_TYPE]) + + data = {'port': {portbindings.VNIC_TYPE: + portbindings.VNIC_DIRECT}} + + req = self.new_update_request('ports', + data, port['port']['id']) + res = self.deserialize('json', req.get_response(self.api)) + self.assertEqual(portbindings.VNIC_DIRECT, + res['port'][portbindings.VNIC_TYPE]) + + def test_port_invalid_vnic_type(self): + with self._test_create_direct_network(vlan_id=7) as network: + kwargs = {portbindings.VNIC_TYPE: 'invalid', + psec.PORTSECURITY: False} + net_id = network['network']['id'] + res = self._create_port(self.fmt, net_id=net_id, + arg_list=(portbindings.VNIC_TYPE, + psec.PORTSECURITY), + **kwargs) + self.assertEqual(res.status_int, exc.HTTPBadRequest.code) + # Temporarily skip all port related tests until the plugin supports it - def test_create_port_with_no_security_groups(self): - self.skipTest('Temporarily not supported') - - def test_create_delete_security_group_port_in_use(self): - self.skipTest('Temporarily not supported') - - def test_create_port_with_multiple_security_groups(self): - self.skipTest('Temporarily not supported') - - def test_list_ports_security_group(self): - self.skipTest('Temporarily not supported') - def test_update_port_with_security_group(self): self.skipTest('Temporarily not supported')