From 5b95817834b523d52203454cbbad50685f19fac1 Mon Sep 17 00:00:00 2001 From: Adit Sarfaty Date: Tue, 18 Dec 2018 07:26:15 +0200 Subject: [PATCH] NSX|P Availability zones support For networks & routers Change-Id: I338147bafdf4e1950db4c2cbb8166c515404d5c1 --- vmware_nsx/common/config.py | 20 ++- .../plugins/common_v3/availability_zones.py | 9 +- vmware_nsx/plugins/common_v3/plugin.py | 129 ++++++++++++++++- .../plugins/nsx_p/availability_zones.py | 84 ++++++++++- vmware_nsx/plugins/nsx_p/plugin.py | 115 ++++----------- .../plugins/nsx_v3/availability_zones.py | 9 +- vmware_nsx/plugins/nsx_v3/plugin.py | 133 +----------------- .../admin/plugins/nsxp/resources/utils.py | 5 + .../plugins/nsxv3/resources/metadata_proxy.py | 2 +- .../unit/nsx_p/test_availability_zones.py | 129 +++++++++++++++++ vmware_nsx/tests/unit/nsx_p/test_plugin.py | 22 +++ .../tests/unit/nsx_v3/test_dhcp_metadata.py | 2 +- 12 files changed, 432 insertions(+), 227 deletions(-) create mode 100644 vmware_nsx/tests/unit/nsx_p/test_availability_zones.py diff --git a/vmware_nsx/common/config.py b/vmware_nsx/common/config.py index e0ecde7263..098c3bd521 100644 --- a/vmware_nsx/common/config.py +++ b/vmware_nsx/common/config.py @@ -881,7 +881,7 @@ nsxv_az_opts = [ # the list of expected zones is under nsx_v3 group: availability_zones # Note: if any of the optional arguments is missing - the global one will be # used instead. -nsxv3_az_opts = [ +nsx_v3_and_p_az_opts = [ cfg.StrOpt('metadata_proxy', help=_("The name or UUID of the NSX Metadata Proxy " "that will be used to enable native metadata service. " @@ -913,6 +913,13 @@ nsxv3_az_opts = [ "transport zone that will be used for bridging between " "Neutron networks, if no physical network has been " "specified")), + cfg.StrOpt('default_tier0_router', + help=_("Name or UUID of the default tier0 router that will be " + "used for connecting to tier1 logical routers and " + "configuring external networks")), +] + +nsxv3_az_opts = nsx_v3_and_p_az_opts + [ cfg.ListOpt('switching_profiles', help=_("(Optional) list switching profiles uuids that will be " "attached to all neutron created nsx ports.")), @@ -930,6 +937,8 @@ nsxv3_az_opts = [ " the Tier0 router")), ] +nsxp_az_opts = nsx_v3_and_p_az_opts + nsx_tvd_opts = [ cfg.ListOpt('nsx_v_extension_drivers', default=[], @@ -1009,8 +1018,13 @@ def register_nsxv3_azs(conf, availability_zones): _register_nsx_azs(conf, availability_zones, nsxv3_az_opts) +def register_nsxp_azs(conf, availability_zones): + _register_nsx_azs(conf, availability_zones, nsxp_az_opts) + + register_nsxv_azs(cfg.CONF, cfg.CONF.nsxv.availability_zones) register_nsxv3_azs(cfg.CONF, cfg.CONF.nsx_v3.availability_zones) +register_nsxp_azs(cfg.CONF, cfg.CONF.nsx_p.availability_zones) def _get_nsx_az_opts(az, opts): @@ -1034,6 +1048,10 @@ def get_nsxv3_az_opts(az): return _get_nsx_az_opts(az, nsxv3_az_opts) +def get_nsxp_az_opts(az): + return _get_nsx_az_opts(az, nsxp_az_opts) + + def validate_nsxv_config_options(): if (cfg.CONF.nsxv.manager_uri is None or cfg.CONF.nsxv.user is None or diff --git a/vmware_nsx/plugins/common_v3/availability_zones.py b/vmware_nsx/plugins/common_v3/availability_zones.py index d80081d1b6..21be1af6af 100644 --- a/vmware_nsx/plugins/common_v3/availability_zones.py +++ b/vmware_nsx/plugins/common_v3/availability_zones.py @@ -15,11 +15,8 @@ from vmware_nsx._i18n import _ from vmware_nsx.common import availability_zones as common_az -from vmware_nsx.common import config from vmware_nsx.common import exceptions as nsx_exc -DEFAULT_NAME = common_az.DEFAULT_NAME + 'v3' - class NsxV3AvailabilityZone(common_az.ConfiguredAvailabilityZone): @@ -34,8 +31,12 @@ class NsxV3AvailabilityZone(common_az.ConfiguredAvailabilityZone): # May be overriden by children return True + def get_az_opts(self): + # Should be implemented by children + pass + def init_from_config_section(self, az_name): - az_info = config.get_nsxv3_az_opts(self.name) + az_info = self.get_az_opts() if self._has_native_dhcp_metadata(): # The optional parameters will get the global values if not diff --git a/vmware_nsx/plugins/common_v3/plugin.py b/vmware_nsx/plugins/common_v3/plugin.py index f72e3be843..aae3848072 100644 --- a/vmware_nsx/plugins/common_v3/plugin.py +++ b/vmware_nsx/plugins/common_v3/plugin.py @@ -23,14 +23,29 @@ from sqlalchemy import exc as sql_exc from six import moves +from neutron.db import agentschedulers_db +from neutron.db import allowedaddresspairs_db as addr_pair_db +from neutron.db.availability_zone import router as router_az_db +from neutron.db import dns_db +from neutron.db import external_net_db +from neutron.db import extradhcpopt_db +from neutron.db import extraroute_db +from neutron.db import l3_attrs_db from neutron.db import l3_db +from neutron.db import l3_gwmode_db +from neutron.db import portbindings_db +from neutron.db import portsecurity_db +from neutron.db import securitygroups_db +from neutron.db import vlantransparent_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 availability_zone as az_def 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.api.validators import availability_zone as az_validator from neutron_lib import constants from neutron_lib.db import api as db_api from neutron_lib.db import utils as db_utils @@ -42,11 +57,14 @@ from neutron_lib.services.qos import constants as qos_consts from neutron_lib.utils import helpers from neutron_lib.utils import net as nl_net_utils +from vmware_nsx.common import availability_zones as nsx_com_az 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 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.extensions import maclearning as mac_ext from vmware_nsx.extensions import providersecuritygroup as provider_sg @@ -61,9 +79,31 @@ from vmware_nsxlib.v3 import nsx_constants as nsxlib_consts LOG = logging.getLogger(__name__) -class NsxPluginV3Base(plugin.NsxPluginBase, +# NOTE(asarfaty): the order of inheritance here is important. in order for the +# QoS notification to work, the AgentScheduler init must be called first +# NOTE(arosen): same is true with the ExtendedSecurityGroupPropertiesMixin +# this needs to be above securitygroups_db.SecurityGroupDbMixin. +# FIXME(arosen): we can solve this inheritance order issue by just mixining in +# the classes into a new class to handle the order correctly. +class NsxPluginV3Base(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, + addr_pair_db.AllowedAddressPairsMixin, + plugin.NsxPluginBase, extended_sec.ExtendedSecurityGroupPropertiesMixin, - pbin_db.NsxPortBindingMixin): + pbin_db.NsxPortBindingMixin, + extend_sg_rule.ExtendedSecurityGroupRuleMixin, + securitygroups_db.SecurityGroupDbMixin, + external_net_db.External_net_db_mixin, + extraroute_db.ExtraRoute_db_mixin, + router_az_db.RouterAvailabilityZoneMixin, + l3_gwmode_db.L3_NAT_db_mixin, + portbindings_db.PortBindingMixin, + portsecurity_db.PortSecurityDbMixin, + extradhcpopt_db.ExtraDhcpOptMixin, + dns_db.DNSDbMixin, + vlantransparent_db.Vlantransparent_db_mixin, + mac_db.MacLearningDbMixin, + l3_attrs_db.ExtraAttributesMixin, + nsx_com_az.NSXAvailabilityZonesPluginCommon): """Common methods for NSX-V3 plugins (NSX-V3 & Policy)""" def __init__(self): @@ -1194,3 +1234,88 @@ class NsxPluginV3Base(plugin.NsxPluginBase, elif not port_security: return True return False + + def _validate_obj_az_on_creation(self, context, obj_data, obj_type): + # validate the availability zone, and get the AZ object + if az_def.AZ_HINTS in obj_data: + self._validate_availability_zones_forced( + context, obj_type, obj_data[az_def.AZ_HINTS]) + return self.get_obj_az_by_hints(obj_data) + + def _add_az_to_net(self, context, net_id, net_data): + if az_def.AZ_HINTS in net_data: + # Update the AZ hints in the neutron object + az_hints = az_validator.convert_az_list_to_string( + net_data[az_def.AZ_HINTS]) + super(NsxPluginV3Base, self).update_network( + context, net_id, + {'network': {az_def.AZ_HINTS: az_hints}}) + + def _add_az_to_router(self, context, router_id, router_data): + if az_def.AZ_HINTS in router_data: + # Update the AZ hints in the neutron object + az_hints = az_validator.convert_az_list_to_string( + router_data[az_def.AZ_HINTS]) + super(NsxPluginV3Base, self).update_router( + context, router_id, + {'router': {az_def.AZ_HINTS: az_hints}}) + + def get_network_availability_zones(self, net_db): + if self._has_native_dhcp_metadata(): + hints = az_validator.convert_az_string_to_list( + net_db[az_def.AZ_HINTS]) + # When using the configured AZs, the az will always be the same + # as the hint (or default if none) + if hints: + az_name = hints[0] + else: + az_name = self.get_default_az().name + return [az_name] + else: + return [] + + def _get_router_az_obj(self, router): + l3_attrs_db.ExtraAttributesMixin._extend_extra_router_dict( + router, router) + return self.get_router_az(router) + + def get_router_availability_zones(self, router): + """Return availability zones which a router belongs to.""" + return [self._get_router_az_obj(router).name] + + def _validate_availability_zones_forced(self, context, resource_type, + availability_zones): + return self.validate_availability_zones(context, resource_type, + availability_zones, + force=True) + + def _list_availability_zones(self, context, filters=None): + # If no native_dhcp_metadata - use neutron AZs + if not self._has_native_dhcp_metadata(): + return super(NsxPluginV3Base, self)._list_availability_zones( + context, filters=filters) + + result = {} + for az in self._availability_zones_data.list_availability_zones(): + # Add this availability zone as a network & router resource + if filters: + if 'name' in filters and az not in filters['name']: + continue + for res in ['network', 'router']: + if 'resource' not in filters or res in filters['resource']: + result[(az, res)] = True + return result + + def validate_availability_zones(self, context, resource_type, + availability_zones, force=False): + # This method is called directly from this plugin but also from + # registered callbacks + if self._is_sub_plugin and not force: + # validation should be done together for both plugins + return + # If no native_dhcp_metadata - use neutron AZs + if not self._has_native_dhcp_metadata(): + return super(NsxPluginV3Base, self).validate_availability_zones( + context, resource_type, availability_zones) + # Validate against the configured AZs + return self.validate_obj_azs(availability_zones) diff --git a/vmware_nsx/plugins/nsx_p/availability_zones.py b/vmware_nsx/plugins/nsx_p/availability_zones.py index 86b696c8f1..6aecb025f3 100644 --- a/vmware_nsx/plugins/nsx_p/availability_zones.py +++ b/vmware_nsx/plugins/nsx_p/availability_zones.py @@ -14,16 +14,24 @@ # under the License. from oslo_config import cfg +from oslo_log import log from vmware_nsx.common import availability_zones as common_az +from vmware_nsx.common import config +from vmware_nsx.common import exceptions as nsx_exc from vmware_nsx.plugins.common_v3 import availability_zones as v3_az +from vmware_nsxlib.v3 import exceptions as nsx_lib_exc +LOG = log.getLogger(__name__) -DEFAULT_NAME = common_az.DEFAULT_NAME + 'v3' +DEFAULT_NAME = common_az.DEFAULT_NAME + 'p' class NsxPAvailabilityZone(v3_az.NsxV3AvailabilityZone): + def get_az_opts(self): + return config.get_nsxp_az_opts(self.name) + def init_defaults(self): # use the default configuration self.metadata_proxy = cfg.CONF.nsx_p.metadata_proxy @@ -32,6 +40,80 @@ class NsxPAvailabilityZone(v3_az.NsxV3AvailabilityZone): self.default_overlay_tz = cfg.CONF.nsx_p.default_overlay_tz self.default_vlan_tz = cfg.CONF.nsx_p.default_vlan_tz self.default_tier0_router = cfg.CONF.nsx_p.default_tier0_router + self.dns_domain = cfg.CONF.nsx_p.dns_domain + self.nameservers = cfg.CONF.nsx_p.nameservers + + def _init_default_resource(self, resource_api, config_name, + filter_list_results=None, + auto_config=False, + is_mandatory=True): + # NOTE(annak): we may need to generalize this for API calls + # requiring path ids + name_or_id = getattr(self, config_name) + if not name_or_id: + if auto_config: + # If the field 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'] + + if is_mandatory: + if self.is_default(): + raise cfg.RequiredOptError(config_name, + group=cfg.OptGroup('nsx_p')) + else: + msg = (_("No %(res)s provided for availability " + "zone %(az)s") % { + 'res': config_name, + 'az': self.name}) + raise nsx_exc.NsxPluginException(err_msg=msg) + return None + + try: + # Check if the configured value is the ID + resource_api.get(name_or_id, silent=True) + return name_or_id + except nsx_lib_exc.ResourceNotFound: + # Check if the configured value is the name + resource = resource_api.get_by_name(name_or_id) + if resource: + return resource['id'] + else: + if self.is_default(): + raise cfg.RequiredOptError(config_name, + group=cfg.OptGroup('nsx_p')) + else: + msg = (_("Could not find %(res)s %(id)s for availability " + "zone %(az)s") % { + 'res': config_name, + 'id': name_or_id, + 'az': self.name}) + raise nsx_exc.NsxPluginException(err_msg=msg) + + def translate_configured_names_to_uuids(self, nsxpolicy): + super(NsxPAvailabilityZone, self).translate_configured_names_to_uuids( + nsxpolicy) + + # TODO(asarfaty): add DHCP/metadata parameters + # TODO(asarfaty): add support for init_objects_by_tags + self._default_overlay_tz_uuid = self._init_default_resource( + nsxpolicy.transport_zone, 'default_overlay_tz', + auto_config=True, is_mandatory=True, + filter_list_results=lambda tzs: [ + tz for tz in tzs if tz['tz_type'].startswith('OVERLAY')]) + + self._default_vlan_tz_uuid = self._init_default_resource( + nsxpolicy.transport_zone, 'default_vlan_tz', + auto_config=True, is_mandatory=False, + filter_list_results=lambda tzs: [ + tz for tz in tzs if tz['tz_type'].startswith('VLAN')]) + + self._default_tier0_router = self._init_default_resource( + nsxpolicy.tier0, 'default_tier0_router', + auto_config=True, is_mandatory=True) class NsxPAvailabilityZones(common_az.ConfiguredAvailabilityZones): diff --git a/vmware_nsx/plugins/nsx_p/plugin.py b/vmware_nsx/plugins/nsx_p/plugin.py index abf2871d00..1f3b9090d4 100644 --- a/vmware_nsx/plugins/nsx_p/plugin.py +++ b/vmware_nsx/plugins/nsx_p/plugin.py @@ -22,22 +22,10 @@ from oslo_utils import excutils from oslo_utils import uuidutils import webob.exc -from neutron.db import agentschedulers_db -from neutron.db import allowedaddresspairs_db as addr_pair_db -from neutron.db import dns_db -from neutron.db import external_net_db -from neutron.db import extradhcpopt_db -from neutron.db import extraroute_db -from neutron.db import l3_attrs_db from neutron.db import l3_db -from neutron.db import l3_gwmode_db from neutron.db.models import l3 as l3_db_models from neutron.db.models import securitygroup as securitygroup_model # noqa from neutron.db import models_v2 -from neutron.db import portbindings_db -from neutron.db import portsecurity_db -from neutron.db import securitygroups_db -from neutron.db import vlantransparent_db from neutron.extensions import providernet from neutron.extensions import securitygroup as ext_sg from neutron.quota import resource_registry @@ -66,8 +54,6 @@ from vmware_nsx.common import locking from vmware_nsx.common import managers 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.extensions import maclearning as mac_ext from vmware_nsx.extensions import projectpluginmap from vmware_nsx.extensions import providersecuritygroup as provider_sg @@ -105,21 +91,7 @@ SEG_SECURITY_PROFILE_UUID = ( @resource_extend.has_resource_extenders -class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, - addr_pair_db.AllowedAddressPairsMixin, - nsx_plugin_common.NsxPluginV3Base, - extend_sg_rule.ExtendedSecurityGroupRuleMixin, - securitygroups_db.SecurityGroupDbMixin, - external_net_db.External_net_db_mixin, - extraroute_db.ExtraRoute_db_mixin, - l3_gwmode_db.L3_NAT_db_mixin, - portbindings_db.PortBindingMixin, - portsecurity_db.PortSecurityDbMixin, - extradhcpopt_db.ExtraDhcpOptMixin, - dns_db.DNSDbMixin, - vlantransparent_db.Vlantransparent_db_mixin, - mac_db.MacLearningDbMixin, - l3_attrs_db.ExtraAttributesMixin): +class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base): __native_bulk_support = True __native_pagination_support = True @@ -140,6 +112,9 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, "external-net", "extraroute", "router", + "availability_zone", + "network_availability_zone", + "router_availability_zone", "subnet_allocation", "security-group-logging", "provider-security-group", @@ -159,6 +134,7 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, def __init__(self): self.fwaas_callbacks = None self.init_is_complete = False + self._is_sub_plugin = False nsxlib_utils.set_is_attr_callback(validators.is_attr_set) self._extend_fault_map() extension_drivers = cfg.CONF.nsx_extension_drivers @@ -189,60 +165,11 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, resources.PROCESS, events.AFTER_INIT) - # NOTE(annak): we may need to generalize this for API calls - # requiring path ids - 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: - return None - - try: - resource_api.get(name_or_id, silent=True) - return name_or_id - except nsx_lib_exc.ResourceNotFound: - try: - resource = resource_api.get_by_name(name_or_id) - if resource: - return resource['id'] - except nsx_lib_exc.ResourceNotFound: - return None - def _init_default_config(self): - """Validate the configuration & initialize default values""" - # Default Tier0 router - self.default_tier0_router = self._init_default_resource( - self.nsxpolicy.tier0, - cfg.CONF.nsx_p.default_tier0_router) - - if not self.default_tier0_router: - 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')]) + # Default tier0/transport zones are initialized via the default AZ + # Init AZ resources + for az in self.get_azs_list(): + az.translate_configured_names_to_uuids(self.nsxpolicy) def init_availability_zones(self): self._availability_zones_data = nsxp_az.NsxPAvailabilityZones() @@ -419,6 +346,9 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, is_external_net = validators.is_attr_set(external) and external tenant_id = net_data['tenant_id'] + # validate the availability zone, and get the AZ object + az = self._validate_obj_az_on_creation(context, net_data, 'network') + self._ensure_default_security_group(context, tenant_id) vlt = vlan_apidef.get_vlan_transparent(net_data) @@ -427,7 +357,7 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, if is_external_net: is_provider_net, net_type, physical_net, vlan_id = ( self._validate_external_net_create( - net_data, self.default_tier0_router, + net_data, az._default_tier0_router, self._tier0_validator)) provider_data = {'is_provider_net': is_provider_net, 'net_type': net_type, @@ -437,8 +367,8 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, else: provider_data = self._validate_provider_create( context, net_data, - self.default_vlan_tz, - self.default_overlay_tz, + az._default_vlan_tz_uuid, + az._default_overlay_tz_uuid, self.nsxpolicy.transport_zone, self.nsxpolicy.segment, transparent_vlan=vlt) @@ -464,6 +394,7 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, self._process_network_port_security_create( context, net_data, created_net) self._process_l3_create(context, created_net, net_data) + self._add_az_to_net(context, created_net['id'], net_data) if provider_data['is_provider_net']: # Save provider network fields, needed by get_network() @@ -942,7 +873,8 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, return network = self.get_network(context, network_id) if not network.get(pnet.PHYSICAL_NETWORK): - return self.default_tier0_router + az = self.get_network_az(network) + return az._default_tier0_router else: return network.get(pnet.PHYSICAL_NETWORK) @@ -1104,6 +1036,11 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, def create_router(self, context, router): r = router['router'] gw_info = self._extract_external_gw(context, router, is_extract=True) + + # validate the availability zone, and get the AZ object + # TODO(asarfaty): router AZ is not used for anything yet + self._validate_obj_az_on_creation(context, r, 'router') + with db_api.CONTEXT_WRITER.using(context): router = super(NsxPolicyPlugin, self).create_router( context, router) @@ -1917,9 +1854,6 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, tz) return type == nsxlib_consts.TRANSPORT_TYPE_OVERLAY - def _has_native_dhcp_metadata(self): - return True - def _is_ens_tz_net(self, context, net_id): #TODO(annak): handle ENS case return False @@ -1927,3 +1861,6 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, def _is_ens_tz_port(self, context, port_data): #TODO(annak): handle ENS case return False + + def _has_native_dhcp_metadata(self): + return True diff --git a/vmware_nsx/plugins/nsx_v3/availability_zones.py b/vmware_nsx/plugins/nsx_v3/availability_zones.py index f79fbc76ed..c2b4be14e7 100644 --- a/vmware_nsx/plugins/nsx_v3/availability_zones.py +++ b/vmware_nsx/plugins/nsx_v3/availability_zones.py @@ -21,16 +21,21 @@ from vmware_nsx.plugins.common_v3 import availability_zones as v3_az from vmware_nsxlib.v3 import core_resources from vmware_nsxlib.v3 import nsx_constants as nsxlib_consts +DEFAULT_NAME = common_az.DEFAULT_NAME + 'v3' + class NsxV3AvailabilityZone(v3_az.NsxV3AvailabilityZone): + def get_az_opts(self): + return config.get_nsxv3_az_opts(self.name) + def _has_native_dhcp_metadata(self): return cfg.CONF.nsx_v3.native_dhcp_metadata def init_from_config_section(self, az_name): super(NsxV3AvailabilityZone, self).init_from_config_section(az_name) - az_info = config.get_nsxv3_az_opts(self.name) + az_info = self.get_az_opts() switching_profiles = az_info.get('switching_profiles') if switching_profiles: @@ -197,7 +202,7 @@ class NsxV3AvailabilityZone(v3_az.NsxV3AvailabilityZone): class NsxV3AvailabilityZones(common_az.ConfiguredAvailabilityZones): - default_name = v3_az.DEFAULT_NAME + default_name = DEFAULT_NAME def __init__(self, use_tvd_config=False): if use_tvd_config: diff --git a/vmware_nsx/plugins/nsx_v3/plugin.py b/vmware_nsx/plugins/nsx_v3/plugin.py index 104b5ac2c8..fcf8a23510 100644 --- a/vmware_nsx/plugins/nsx_v3/plugin.py +++ b/vmware_nsx/plugins/nsx_v3/plugin.py @@ -19,13 +19,11 @@ import mock import netaddr from neutron_lib.agent import topics from neutron_lib.api.definitions import allowedaddresspairs as addr_apidef -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 l3 as l3_apidef from neutron_lib.api.definitions import port_security as psec from neutron_lib.api import extensions from neutron_lib.api import faults -from neutron_lib.api.validators import availability_zone as az_validator from neutron_lib.db import api as db_api from neutron_lib.db import resource_extend from neutron_lib.db import utils as db_utils @@ -39,24 +37,11 @@ from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api from neutron.api.rpc.handlers import dhcp_rpc from neutron.api.rpc.handlers import metadata_rpc from neutron.db import agents_db -from neutron.db import agentschedulers_db -from neutron.db import allowedaddresspairs_db as addr_pair_db -from neutron.db.availability_zone import router as router_az_db from neutron.db import db_base_plugin_v2 -from neutron.db import dns_db -from neutron.db import external_net_db -from neutron.db import extradhcpopt_db -from neutron.db import extraroute_db -from neutron.db import l3_attrs_db from neutron.db import l3_db -from neutron.db import l3_gwmode_db from neutron.db.models import l3 as l3_db_models from neutron.db.models import securitygroup as securitygroup_model # noqa from neutron.db import models_v2 -from neutron.db import portbindings_db -from neutron.db import portsecurity_db -from neutron.db import securitygroups_db -from neutron.db import vlantransparent_db from neutron.extensions import providernet from neutron.extensions import securitygroup as ext_sg from neutron.quota import resource_registry @@ -82,7 +67,6 @@ import webob.exc from vmware_nsx._i18n import _ from vmware_nsx.api_replay import utils as api_replay_utils -from vmware_nsx.common import availability_zones as nsx_com_az from vmware_nsx.common import config # noqa from vmware_nsx.common import exceptions as nsx_exc from vmware_nsx.common import l3_rpc_agent_api @@ -91,8 +75,6 @@ from vmware_nsx.common import managers 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_rule as extend_sg_rule -from vmware_nsx.db import maclearning as mac_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 @@ -145,30 +127,8 @@ NSX_V3_CLIENT_SSL_PROFILE = 'nsx-default-client-ssl-profile' NSX_V3_OS_DFW_UUID = '00000000-def0-0000-0fed-000000000000' -# NOTE(asarfaty): the order of inheritance here is important. in order for the -# QoS notification to work, the AgentScheduler init must be called first -# NOTE(arosen): same is true with the ExtendedSecurityGroupPropertiesMixin -# this needs to be above securitygroups_db.SecurityGroupDbMixin. -# FIXME(arosen): we can solve this inheritance order issue by just mixining in -# the classes into a new class to handle the order correctly. @resource_extend.has_resource_extenders -class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, - addr_pair_db.AllowedAddressPairsMixin, - nsx_plugin_common.NsxPluginV3Base, - extend_sg_rule.ExtendedSecurityGroupRuleMixin, - securitygroups_db.SecurityGroupDbMixin, - external_net_db.External_net_db_mixin, - extraroute_db.ExtraRoute_db_mixin, - router_az_db.RouterAvailabilityZoneMixin, - l3_gwmode_db.L3_NAT_db_mixin, - portbindings_db.PortBindingMixin, - portsecurity_db.PortSecurityDbMixin, - extradhcpopt_db.ExtraDhcpOptMixin, - dns_db.DNSDbMixin, - vlantransparent_db.Vlantransparent_db_mixin, - mac_db.MacLearningDbMixin, - nsx_com_az.NSXAvailabilityZonesPluginCommon, - l3_attrs_db.ExtraAttributesMixin, +class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base, hk_ext.Housekeeper): __native_bulk_support = True @@ -879,11 +839,6 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, return self.conn.consume_in_threads() - def _get_router_az_obj(self, router): - l3_attrs_db.ExtraAttributesMixin._extend_extra_router_dict( - router, router) - return self.get_router_az(router) - def _get_edge_cluster(self, tier0_uuid, router): az = self._get_router_az_obj(router) if az and az._edge_cluster_uuid: @@ -1046,10 +1001,7 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, tenant_id = net_data['tenant_id'] # validate the availability zone, and get the AZ object - if az_def.AZ_HINTS in net_data: - self._validate_availability_zones_forced( - context, 'network', net_data[az_def.AZ_HINTS]) - az = self.get_obj_az_by_hints(net_data) + az = self._validate_obj_az_on_creation(context, net_data, 'network') self._ensure_default_security_group(context, tenant_id) @@ -1086,15 +1038,7 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, self._process_network_port_security_create( context, net_data, created_net) self._process_l3_create(context, created_net, net_data) - - if az_def.AZ_HINTS in net_data: - # Update the AZ hints in the neutron object - az_hints = az_validator.convert_az_list_to_string( - net_data[az_def.AZ_HINTS]) - super(NsxV3Plugin, self).update_network( - context, - created_net['id'], - {'network': {az_def.AZ_HINTS: az_hints}}) + self._add_az_to_net(context, created_net['id'], net_data) if is_provider_net: # Save provider network fields, needed by get_network() @@ -3004,7 +2948,7 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, return network = self.get_network(context, network_id) if not network.get(pnet.PHYSICAL_NETWORK): - az = self.get_network_az_by_net_id(context, network_id) + az = self.get_network_az(network) return az._default_tier0_router else: return network.get(pnet.PHYSICAL_NETWORK) @@ -3221,9 +3165,7 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, self.validate_router_dhcp_relay(context) # validate the availability zone - if az_def.AZ_HINTS in r: - self._validate_availability_zones_forced(context, 'router', - r[az_def.AZ_HINTS]) + self._validate_obj_az_on_creation(context, r, 'router') gw_info = self._extract_external_gw(context, router, is_extract=True) r['id'] = (r.get('id') or uuidutils.generate_uuid()) @@ -3231,14 +3173,8 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, r, resource_type='os-neutron-router-id', project_name=context.tenant_name) router = super(NsxV3Plugin, self).create_router(context, router) - if az_def.AZ_HINTS in r: - # Update the AZ hints in the neutron object - az_hints = az_validator.convert_az_list_to_string( - r[az_def.AZ_HINTS]) - super(NsxV3Plugin, self).update_router( - context, - router['id'], - {'router': {az_def.AZ_HINTS: az_hints}}) + self._add_az_to_router(context, router['id'], r) + router_db = self._get_router(context, r['id']) with db_api.CONTEXT_WRITER.using(context): self._process_extra_attr_router_create(context, router_db, r) @@ -3319,10 +3255,6 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, return ret_val - def get_router_availability_zones(self, router): - """Return availability zones which a router belongs to.""" - return [self._get_router_az_obj(router).name] - def _update_router_wrapper(self, context, router_id, router): if cfg.CONF.api_replay_mode: # NOTE(arosen): the mock.patch here is needed for api_replay_mode @@ -4325,57 +4257,6 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, rules = [(rule['display_name'], rule['id']) for rule in firewall_rules] nsx_db.save_sg_rule_mappings(context.session, rules) - def _list_availability_zones(self, context, filters=None): - # If no native_dhcp_metadata - use neutron AZs - if not cfg.CONF.nsx_v3.native_dhcp_metadata: - return super(NsxV3Plugin, self)._list_availability_zones( - context, filters=filters) - - result = {} - for az in self._availability_zones_data.list_availability_zones(): - # Add this availability zone as a network & router resource - if filters: - if 'name' in filters and az not in filters['name']: - continue - for res in ['network', 'router']: - if 'resource' not in filters or res in filters['resource']: - result[(az, res)] = True - return result - - def _validate_availability_zones_forced(self, context, resource_type, - availability_zones): - return self.validate_availability_zones(context, resource_type, - availability_zones, - force=True) - - def validate_availability_zones(self, context, resource_type, - availability_zones, force=False): - # This method is called directly from this plugin but also from - # registered callbacks - if self._is_sub_plugin and not force: - # validation should be done together for both plugins - return - # If no native_dhcp_metadata - use neutron AZs - if not cfg.CONF.nsx_v3.native_dhcp_metadata: - return super(NsxV3Plugin, self).validate_availability_zones( - context, resource_type, availability_zones) - # Validate against the configured AZs - return self.validate_obj_azs(availability_zones) - - def get_network_availability_zones(self, net_db): - if cfg.CONF.nsx_v3.native_dhcp_metadata: - hints = az_validator.convert_az_string_to_list( - net_db[az_def.AZ_HINTS]) - # When using the configured AZs, the az will always be the same - # as the hint (or default if none) - if hints: - az_name = hints[0] - else: - az_name = self.get_default_az().name - return [az_name] - else: - return [] - def recalculate_snat_rules_for_router(self, context, router, subnets): """Recalculate router snat rules for specific subnets. Invoked when subnetpool address scope changes. diff --git a/vmware_nsx/shell/admin/plugins/nsxp/resources/utils.py b/vmware_nsx/shell/admin/plugins/nsxp/resources/utils.py index 4025c15327..b419acb7e5 100644 --- a/vmware_nsx/shell/admin/plugins/nsxp/resources/utils.py +++ b/vmware_nsx/shell/admin/plugins/nsxp/resources/utils.py @@ -12,12 +12,15 @@ # License for the specific language governing permissions and limitations # under the License. +from oslo_config import cfg + from neutron.db import l3_dvr_db # noqa from neutron_lib import context from neutron_lib.plugins import constants as const from neutron_lib.plugins import directory from oslo_log import log as logging +from vmware_nsx.common import config from vmware_nsx.plugins.nsx_p import plugin from vmware_nsx.plugins.nsx_v3 import utils as v3_utils from vmware_nsx.shell.admin.plugins.common import formatters @@ -70,6 +73,8 @@ def get_realization_info(resource, *realization_args): class NsxPolicyPluginWrapper(plugin.NsxPolicyPlugin): def __init__(self): + # initialize the availability zones + config.register_nsxp_azs(cfg.CONF, cfg.CONF.nsx_p.availability_zones) super(NsxPolicyPluginWrapper, self).__init__() self.context = context.get_admin_context() diff --git a/vmware_nsx/shell/admin/plugins/nsxv3/resources/metadata_proxy.py b/vmware_nsx/shell/admin/plugins/nsxv3/resources/metadata_proxy.py index 38e05c16bf..3be3cb8e52 100644 --- a/vmware_nsx/shell/admin/plugins/nsxv3/resources/metadata_proxy.py +++ b/vmware_nsx/shell/admin/plugins/nsxv3/resources/metadata_proxy.py @@ -24,7 +24,7 @@ from oslo_log import log as logging from vmware_nsx.common import config # noqa from vmware_nsx.common import utils as nsx_utils from vmware_nsx.dhcp_meta import rpc as nsx_rpc -from vmware_nsx.plugins.common_v3 import availability_zones as nsx_az +from vmware_nsx.plugins.nsx_v3 import availability_zones as nsx_az from vmware_nsx.shell.admin.plugins.common import constants from vmware_nsx.shell.admin.plugins.common import formatters from vmware_nsx.shell.admin.plugins.common import utils as admin_utils diff --git a/vmware_nsx/tests/unit/nsx_p/test_availability_zones.py b/vmware_nsx/tests/unit/nsx_p/test_availability_zones.py new file mode 100644 index 0000000000..c61f54cb37 --- /dev/null +++ b/vmware_nsx/tests/unit/nsx_p/test_availability_zones.py @@ -0,0 +1,129 @@ +# 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_utils import uuidutils + +from neutron.tests import base + +from vmware_nsx.common import config +from vmware_nsx.common import exceptions as nsx_exc +from vmware_nsx.plugins.nsx_p import availability_zones as nsx_az + + +class NsxPAvailabilityZonesTestCase(base.BaseTestCase): + + def setUp(self): + super(NsxPAvailabilityZonesTestCase, self).setUp() + self.az_name = "zone1" + self.group_name = "az:%s" % self.az_name + config.register_nsxv3_azs(cfg.CONF, [self.az_name]) + self.global_md_proxy = uuidutils.generate_uuid() + cfg.CONF.set_override( + "metadata_proxy", self.global_md_proxy, group="nsx_p") + self.global_dhcp_profile = uuidutils.generate_uuid() + cfg.CONF.set_override( + "dhcp_profile", self.global_dhcp_profile, group="nsx_p") + cfg.CONF.set_override( + "native_metadata_route", "1.1.1.1", group="nsx_p") + cfg.CONF.set_override("dns_domain", "xxx.com", group="nsx_p") + cfg.CONF.set_override("nameservers", ["10.1.1.1"], group="nsx_p") + cfg.CONF.set_override( + "default_tier0_router", "uuidrtr1", group="nsx_p") + + def _config_az(self, + metadata_proxy="metadata_proxy1", + dhcp_profile="dhcp_profile1", + native_metadata_route="2.2.2.2", + dns_domain="aaa.com", + nameservers=["20.1.1.1"], + default_overlay_tz='otz', + default_vlan_tz='vtz', + default_tier0_router="uuidrtr2"): + if metadata_proxy is not None: + cfg.CONF.set_override("metadata_proxy", metadata_proxy, + group=self.group_name) + if dhcp_profile is not None: + cfg.CONF.set_override("dhcp_profile", dhcp_profile, + group=self.group_name) + if native_metadata_route is not None: + cfg.CONF.set_override("native_metadata_route", + native_metadata_route, + group=self.group_name) + if dns_domain is not None: + cfg.CONF.set_override("dns_domain", dns_domain, + group=self.group_name) + if nameservers is not None: + cfg.CONF.set_override("nameservers", nameservers, + group=self.group_name) + if default_overlay_tz is not None: + cfg.CONF.set_override("default_overlay_tz", default_overlay_tz, + group=self.group_name) + if default_vlan_tz is not None: + cfg.CONF.set_override("default_vlan_tz", default_vlan_tz, + group=self.group_name) + if default_tier0_router is not None: + cfg.CONF.set_override("default_tier0_router", default_tier0_router, + group=self.group_name) + + def test_simple_availability_zone(self): + self._config_az() + az = nsx_az.NsxPAvailabilityZone(self.az_name) + self.assertEqual(self.az_name, az.name) + self.assertEqual("metadata_proxy1", az.metadata_proxy) + self.assertEqual("dhcp_profile1", az.dhcp_profile) + self.assertEqual("2.2.2.2", az.native_metadata_route) + self.assertEqual("aaa.com", az.dns_domain) + self.assertEqual(["20.1.1.1"], az.nameservers) + self.assertEqual("otz", az.default_overlay_tz) + self.assertEqual("vtz", az.default_vlan_tz) + self.assertEqual("uuidrtr2", az.default_tier0_router) + + def test_missing_group_section(self): + self.assertRaises( + nsx_exc.NsxInvalidConfiguration, + nsx_az.NsxPAvailabilityZone, + "doesnt_exist") + + def test_availability_zone_missing_metadata_proxy(self): + # Mandatory parameter + self._config_az(metadata_proxy=None) + self.assertRaises( + nsx_exc.NsxInvalidConfiguration, + nsx_az.NsxPAvailabilityZone, + self.az_name) + + def test_availability_zone_missing_dhcp_profile(self): + # Mandatory parameter + self._config_az(dhcp_profile=None) + self.assertRaises( + nsx_exc.NsxInvalidConfiguration, + nsx_az.NsxPAvailabilityZone, + self.az_name) + + def test_availability_zone_missing_md_route(self): + self._config_az(native_metadata_route=None) + az = nsx_az.NsxPAvailabilityZone(self.az_name) + self.assertEqual("1.1.1.1", az.native_metadata_route) + + def test_availability_zone_missing_dns_domain(self): + self._config_az(dns_domain=None) + az = nsx_az.NsxPAvailabilityZone(self.az_name) + self.assertEqual("xxx.com", az.dns_domain) + + def test_availability_zone_missing_nameservers(self): + self._config_az(nameservers=None) + az = nsx_az.NsxPAvailabilityZone(self.az_name) + self.assertEqual(["10.1.1.1"], az.nameservers) diff --git a/vmware_nsx/tests/unit/nsx_p/test_plugin.py b/vmware_nsx/tests/unit/nsx_p/test_plugin.py index e2752c1dc1..e2c4b8de58 100644 --- a/vmware_nsx/tests/unit/nsx_p/test_plugin.py +++ b/vmware_nsx/tests/unit/nsx_p/test_plugin.py @@ -44,6 +44,7 @@ from neutron_lib import exceptions as n_exc from neutron_lib.plugins import directory from vmware_nsx.common import utils +from vmware_nsx.plugins.nsx_p import plugin as nsx_plugin from vmware_nsx.tests import unit as vmware from vmware_nsx.tests.unit.common_plugin import common_v3 from vmware_nsxlib.v3 import exceptions as nsxlib_exc @@ -362,6 +363,17 @@ class NsxPTestNetworks(test_db_base_plugin_v2.TestNetworksV2, self.assertEqual('InvalidInput', res['NeutronError']['type']) + @mock.patch.object(nsx_plugin.NsxPolicyPlugin, + 'validate_availability_zones') + def test_create_network_with_availability_zone(self, mock_validate_az): + name = 'net-with-zone' + zone = ['zone1'] + + mock_validate_az.return_value = None + with self.network(name=name, availability_zone_hints=zone) as net: + az_hints = net['network']['availability_zone_hints'] + self.assertListEqual(az_hints, zone) + class NsxPTestPorts(test_db_base_plugin_v2.TestPortsV2, NsxPPluginTestCaseMixin): @@ -1140,3 +1152,13 @@ class NsxPTestL3NatTestCase(NsxPTestL3NatTest, body = self._show('routers', r['router']['id']) gw_info = body['router']['external_gateway_info'] self.assertIsNone(gw_info) + + @mock.patch.object(nsx_plugin.NsxPolicyPlugin, + 'validate_availability_zones') + def test_create_router_with_availability_zone(self, mock_validate_az): + name = 'rtr-with-zone' + zone = ['zone1'] + mock_validate_az.return_value = None + with self.router(name=name, availability_zone_hints=zone) as rtr: + az_hints = rtr['router']['availability_zone_hints'] + self.assertListEqual(zone, az_hints) diff --git a/vmware_nsx/tests/unit/nsx_v3/test_dhcp_metadata.py b/vmware_nsx/tests/unit/nsx_v3/test_dhcp_metadata.py index d9f0fa77ab..e2838ff228 100644 --- a/vmware_nsx/tests/unit/nsx_v3/test_dhcp_metadata.py +++ b/vmware_nsx/tests/unit/nsx_v3/test_dhcp_metadata.py @@ -31,7 +31,7 @@ from vmware_nsx.common import exceptions as nsx_exc from vmware_nsx.common import utils from vmware_nsx.db import db as nsx_db from vmware_nsx.extensions import advancedserviceproviders as as_providers -from vmware_nsx.plugins.common_v3 import availability_zones as nsx_az +from vmware_nsx.plugins.nsx_v3 import availability_zones as nsx_az from vmware_nsx.tests.unit.nsx_v3 import test_plugin from vmware_nsxlib.v3 import core_resources from vmware_nsxlib.v3 import nsx_constants