NSX-V3| network availability zones support

Adding availability zones for nsx-v3 for native dhcp parameters

configuration:

[nsx_v3]
availability_zones = zone1,zone2,zone3

[az:zone1]
metadata_proxy = a87d92f3-0106-47dc-a494-de68345fecc8 <profile-name-or-uuid, mandatory>
dhcp_profile = 8a4fb2ca-60aa-4291-aab8-d0d6b7790292 profile-name-or-uuid <mandatory>
native_metadata_route = 179.254.169.254/31 <optional>
dns_domain = aaa.com <optional>
nameservers = 1.1.1.1, 2.2.2.2 <optional>

Change-Id: I006d922908d5a061480f43eeb92d373fcb4db616
This commit is contained in:
Adit Sarfaty 2017-03-06 10:36:52 +02:00
parent 58f3691bcd
commit 84be0ea6a5
11 changed files with 569 additions and 76 deletions

View File

@ -0,0 +1,9 @@
---
prelude: >
The NSX-v3 plugin supports availability zones hints on networks
creation in order to separate the native dhcp configuration.
features:
- The NSX-v3 plugin supports availability zones hints on networks
creation in order to separate the native dhcp configuration.
The availability zones configuration includes the metadata_proxy,
dhcp_profile, native_metadata_route and dns related parameters.

View File

@ -153,11 +153,7 @@ class NSXAvailabilityZonesPluginCommon(object):
def get_obj_az_by_hints(self, obj):
if az_ext.AZ_HINTS in obj:
hints = obj[az_ext.AZ_HINTS]
# if this is a string and not a list - need to convert it
if not isinstance(hints, list):
hints = az_ext.convert_az_string_to_list(hints)
for hint in hints:
for hint in obj[az_ext.AZ_HINTS]:
# For now we use only the first hint
return self.get_az_by_hint(hint)

View File

@ -416,6 +416,12 @@ nsx_v3_opts = [
default=False,
help=_("(Optional) Indicates whether distributed-firewall "
"security-groups rules are logged.")),
cfg.ListOpt('availability_zones',
default=[],
help=_('Optional parameter defining the networks availability '
'zones names for the native dhcp configuration. The '
'configuration of each zone will be under a group '
'names [az:<name>]')),
]
DEFAULT_STATUS_CHECK_INTERVAL = 2000
@ -668,8 +674,8 @@ nsxv_opts = [
help=_("(Optional) Have exclusive DHCP edge per network.")),
]
# define the configuration of each availability zone.
# the list of expected zones in under nsxv group: availability_zones
# define the configuration of each NSX-V availability zone.
# the list of expected zones is under nsxv group: availability_zones
# Note: if any of the optional arguments is missing - the global one will be
# used instead.
nsxv_az_opts = [
@ -726,6 +732,32 @@ nsxv_az_opts = [
help=_("(Optional) Have exclusive DHCP edge per network.")),
]
# define the configuration of each NSX-V3 availability zone.
# 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 = [
cfg.StrOpt('metadata_proxy',
help=_("The name or UUID of the NSX Metadata Proxy "
"that will be used to enable native metadata service. "
"It needs to be created in NSX before starting Neutron "
"with the NSX plugin.")),
cfg.StrOpt('dhcp_profile',
help=_("The name or UUID of the NSX DHCP Profile "
"that will be used to enable native DHCP service. It "
"needs to be created in NSX before starting Neutron "
"with the NSX plugin")),
cfg.StrOpt('native_metadata_route',
help=_("(Optional) The metadata route used for native metadata "
"proxy service.")),
cfg.StrOpt('dns_domain',
help=_("(Optional) Domain to use for building the hostnames.")),
cfg.ListOpt('nameservers',
help=_("(Optional) List of nameservers to configure for the "
"DHCP binding entries. These will be used if there are "
"no nameservers defined on the subnet.")),
]
# Register the configuration options
cfg.CONF.register_opts(connection_opts)
cfg.CONF.register_opts(cluster_opts)
@ -740,8 +772,7 @@ cfg.CONF.register_opts(sync_opts, group="NSX_SYNC")
cfg.CONF.register_opts(l3_hamode_db.L3_HA_OPTS)
# register a group for each nsxv availability zones
def register_nsxv_azs(conf, availability_zones):
def _register_nsx_azs(conf, availability_zones, az_opts):
# first verify that the availability zones are in the format of a
# list of names. The old format was a list of values for each az,
# separated with ':'
@ -753,13 +784,23 @@ def register_nsxv_azs(conf, availability_zones):
conf.register_group(cfg.OptGroup(
name=az_group,
title="Configuration for availability zone %s" % az))
conf.register_opts(nsxv_az_opts, group=az_group)
conf.register_opts(az_opts, group=az_group)
# register a group for each nsxv/v3 availability zones
def register_nsxv_azs(conf, availability_zones):
_register_nsx_azs(conf, availability_zones, nsxv_az_opts)
def register_nsxv3_azs(conf, availability_zones):
_register_nsx_azs(conf, availability_zones, nsxv3_az_opts)
register_nsxv_azs(cfg.CONF, cfg.CONF.nsxv.availability_zones)
register_nsxv3_azs(cfg.CONF, cfg.CONF.nsx_v3.availability_zones)
def get_nsxv_az_opts(az):
def _get_nsx_az_opts(az, opts):
az_info = dict()
group = 'az:%s' % az
if group not in cfg.CONF:
@ -767,11 +808,19 @@ def get_nsxv_az_opts(az):
opt_name=group,
opt_value='None',
reason=(_("Configuration group \'%s\' must be defined") % group))
for opt in nsxv_az_opts:
for opt in opts:
az_info[opt.name] = cfg.CONF[group][opt.name]
return az_info
def get_nsxv_az_opts(az):
return _get_nsx_az_opts(az, nsxv_az_opts)
def get_nsxv3_az_opts(az):
return _get_nsx_az_opts(az, nsxv3_az_opts)
def validate_nsxv_config_options():
if (cfg.CONF.nsxv.manager_uri is None or
cfg.CONF.nsxv.user is None or

View File

@ -15,10 +15,12 @@
# under the License.
from neutron_lib.api import validators
from neutron_lib import context as n_context
from neutron_lib.plugins import directory
from oslo_config import cfg
from oslo_log import log as logging
from neutron.extensions import availability_zone as az_ext
from neutron.extensions import dns
from neutron.objects import network as net_obj
from neutron.objects import ports as port_obj
@ -26,11 +28,13 @@ from neutron.services.externaldns import driver
from vmware_nsx._i18n import _LE, _LI
from vmware_nsx.common import driver_api
from vmware_nsx.plugins.nsx_v3 import availability_zones as nsx_az
LOG = logging.getLogger(__name__)
DNS_DOMAIN_DEFAULT = 'openstacklocal.'
# TODO(asarfaty) use dns-domain/nameserver from network az instead of global
class DNSExtensionDriver(driver_api.ExtensionDriver):
_supported_extension_alias = 'dns-integration'
@ -80,7 +84,7 @@ class DNSExtensionDriver(driver_api.ExtensionDriver):
if not request_data.get(dns.DNSNAME):
return
dns_name, is_dns_domain_default = self._get_request_dns_name(
request_data)
request_data, db_data['network_id'])
if is_dns_domain_default:
return
network = self._get_network(plugin_context, db_data['network_id'])
@ -143,7 +147,7 @@ class DNSExtensionDriver(driver_api.ExtensionDriver):
return
if dns_name is not None:
dns_name, is_dns_domain_default = self._get_request_dns_name(
request_data)
request_data, db_data['network_id'])
if is_dns_domain_default:
self._extend_port_dict(plugin_context.session, db_data,
db_data, None)
@ -198,31 +202,31 @@ class DNSExtensionDriver(driver_api.ExtensionDriver):
response_data[dns.DNSDOMAIN] = db_data.dns_domain[dns.DNSDOMAIN]
return response_data
def _get_dns_domain(self):
def _get_dns_domain(self, network_id):
if not cfg.CONF.dns_domain:
return ''
if cfg.CONF.dns_domain.endswith('.'):
return cfg.CONF.dns_domain
return '%s.' % cfg.CONF.dns_domain
def _get_request_dns_name(self, port):
dns_domain = self._get_dns_domain()
def _get_request_dns_name(self, port, network_id):
dns_domain = self._get_dns_domain(network_id)
if ((dns_domain and dns_domain != DNS_DOMAIN_DEFAULT)):
return (port.get(dns.DNSNAME, ''), False)
return ('', True)
def _get_request_dns_name_and_domain_name(self, dns_data_db):
dns_domain = self._get_dns_domain()
def _get_request_dns_name_and_domain_name(self, dns_data_db, network_id):
dns_domain = self._get_dns_domain(network_id)
dns_name = ''
if ((dns_domain and dns_domain != DNS_DOMAIN_DEFAULT)):
if dns_data_db:
dns_name = dns_data_db.dns_name
return dns_name, dns_domain
def _get_dns_names_for_port(self, ips, dns_data_db):
def _get_dns_names_for_port(self, ips, dns_data_db, network_id):
dns_assignment = []
dns_name, dns_domain = self._get_request_dns_name_and_domain_name(
dns_data_db)
dns_data_db, network_id)
for ip in ips:
if dns_name:
hostname = dns_name
@ -242,7 +246,8 @@ class DNSExtensionDriver(driver_api.ExtensionDriver):
def _get_dns_name_for_port_get(self, port, dns_data_db):
if port['fixed_ips']:
return self._get_dns_names_for_port(port['fixed_ips'], dns_data_db)
return self._get_dns_names_for_port(
port['fixed_ips'], dns_data_db, port['network_id'])
return []
def _extend_port_dict(self, session, db_data, response_data, dns_data_db):
@ -281,10 +286,24 @@ class DNSExtensionDriverNSXv(DNSExtensionDriver):
class DNSExtensionDriverNSXv3(DNSExtensionDriver):
def initialize(self):
self._availability_zones = nsx_az.NsxV3AvailabilityZones()
LOG.info(_LI("DNSExtensionDriverNSXv3 initialization complete"))
def _get_dns_domain(self):
if cfg.CONF.nsx_v3.dns_domain:
def _get_network_az(self, network_id):
context = n_context.get_admin_context()
network = self._get_network(context, network_id)
if az_ext.AZ_HINTS in network and network[az_ext.AZ_HINTS]:
az_name = network[az_ext.AZ_HINTS][0]
return self._availability_zones.get_availability_zone(az_name)
return self._availability_zones.get_default_availability_zone()
def _get_dns_domain(self, network_id):
# try to get the dns-domain from the specific availability zone
# of this network
az = self._get_network_az(network_id)
if az.dns_domain:
dns_domain = az.dns_domain
elif cfg.CONF.nsx_v3.dns_domain:
dns_domain = cfg.CONF.nsx_v3.dns_domain
elif cfg.CONF.dns_domain:
dns_domain = cfg.CONF.dns_domain

View File

@ -0,0 +1,92 @@
# Copyright 2017 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 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
class NsxV3AvailabilityZone(common_az.ConfiguredAvailabilityZone):
def init_from_config_line(self, config_line):
# Not supported for nsx_v3 (old configuration)
raise nsx_exc.NsxInvalidConfiguration(
opt_name="availability_zones",
opt_value=config_line,
reason=_("Expected a list of names"))
def init_from_config_section(self, az_name):
az_info = config.get_nsxv3_az_opts(self.name)
# The optional parameters will get the global values if not
# defined for this AZ
self.metadata_proxy = az_info.get('metadata_proxy')
if not self.metadata_proxy:
raise nsx_exc.NsxInvalidConfiguration(
opt_name="metadata_proxy",
opt_value='None',
reason=(_("metadata_proxy for availability zone %s "
"must be defined") % az_name))
self.dhcp_profile = az_info.get('dhcp_profile')
if not self.dhcp_profile:
raise nsx_exc.NsxInvalidConfiguration(
opt_name="dhcp_profile",
opt_value='None',
reason=(_("dhcp_profile for availability zone %s "
"must be defined") % az_name))
self.native_metadata_route = az_info.get('native_metadata_route')
if self.native_metadata_route is None:
self.native_metadata_route = cfg.CONF.nsx_v3.native_metadata_route
self.dns_domain = az_info.get('dns_domain')
if self.dns_domain is None:
self.dns_domain = cfg.CONF.nsx_v3.dns_domain
self.nameservers = az_info.get('nameservers')
if self.nameservers is None:
self.nameservers = cfg.CONF.nsx_v3.nameservers
def init_default_az(self):
# use the default configuration
self.metadata_proxy = cfg.CONF.nsx_v3.metadata_proxy
self.dhcp_profile = cfg.CONF.nsx_v3.dhcp_profile
self.native_metadata_route = cfg.CONF.nsx_v3.native_metadata_route
self.dns_domain = cfg.CONF.nsx_v3.dns_domain
self.nameservers = cfg.CONF.nsx_v3.nameservers
def translate_configured_names_to_uuids(self, nsxlib):
dhcp_id = nsxlib.native_dhcp_profile.get_id_by_name_or_id(
self.dhcp_profile)
self._native_dhcp_profile_uuid = dhcp_id
proxy_id = nsxlib.native_md_proxy.get_id_by_name_or_id(
self.metadata_proxy)
self._native_md_proxy_uuid = proxy_id
class NsxV3AvailabilityZones(common_az.ConfiguredAvailabilityZones):
def __init__(self):
super(NsxV3AvailabilityZones, self).__init__(
cfg.CONF.nsx_v3.availability_zones,
NsxV3AvailabilityZone)

View File

@ -78,6 +78,7 @@ from sqlalchemy import exc as sql_exc
from vmware_nsx._i18n import _, _LE, _LI, _LW
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
@ -93,6 +94,7 @@ from vmware_nsx.extensions import advancedserviceproviders as as_providers
from vmware_nsx.extensions import maclearning as mac_ext
from vmware_nsx.extensions import providersecuritygroup as provider_sg
from vmware_nsx.extensions import securitygrouplogging as sg_logging
from vmware_nsx.plugins.nsx_v3 import availability_zones as nsx_az
from vmware_nsx.plugins.nsx_v3 import utils as v3_utils
from vmware_nsx.services.qos.common import utils as qos_com_utils
from vmware_nsx.services.qos.nsx_v3 import driver as qos_driver
@ -134,7 +136,8 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
portsecurity_db.PortSecurityDbMixin,
extradhcpopt_db.ExtraDhcpOptMixin,
dns_db.DNSDbMixin,
mac_db.MacLearningDbMixin):
mac_db.MacLearningDbMixin,
nsx_com_az.NSXAvailabilityZonesPluginCommon):
__native_bulk_support = True
__native_pagination_support = True
@ -195,6 +198,11 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
self.cfg_group = 'nsx_v3' # group name for nsx_v3 section in nsx.ini
self.tier0_groups_dict = {}
# Initialize the network availability zones, which will be used only
# when native_dhcp_metadata is True
self.init_availability_zones()
# Translate configured transport zones, routers, dhcp profile and
# metadata proxy names to uuid.
self._translate_configured_names_to_uuids()
@ -240,12 +248,24 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
# Each extension driver that supports extend attribute for the resources
# can add those attribute to the result.
db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
attributes.NETWORKS, ['_ext_extend_network_dict'])
attributes.NETWORKS, ['_ext_extend_network_dict',
'_extend_availability_zone_hints'])
db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
attributes.PORTS, ['_ext_extend_port_dict'])
db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
attributes.SUBNETS, ['_ext_extend_subnet_dict'])
def init_availability_zones(self):
# availability zones are supported only with native dhcp
# if not - the default az will be loaded and used internally only
if (cfg.CONF.nsx_v3.availability_zones and
not cfg.CONF.nsx_v3.native_dhcp_metadata):
msg = _("Availability zones are not supported without native "
"DHCP metadata")
LOG.error(msg)
raise n_exc.InvalidInput(error_message=msg)
self._availability_zones_data = nsx_az.NsxV3AvailabilityZones()
def _init_nsx_profiles(self):
LOG.debug("Initializing NSX v3 port spoofguard switching profile")
if not self._init_port_security_profile():
@ -304,23 +324,17 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
cfg.CONF.nsx_v3.default_tier0_router)
self._default_tier0_router = rtr_id
self._native_dhcp_profile_uuid = None
self._native_md_proxy_uuid = None
# Validate and translate native dhcp profiles per az
if cfg.CONF.nsx_v3.native_dhcp_metadata:
if cfg.CONF.nsx_v3.dhcp_profile:
id = self.nsxlib.native_dhcp_profile.get_id_by_name_or_id(
cfg.CONF.nsx_v3.dhcp_profile)
self._native_dhcp_profile_uuid = id
else:
if not cfg.CONF.nsx_v3.dhcp_profile:
raise cfg.RequiredOptError("dhcp_profile")
if cfg.CONF.nsx_v3.metadata_proxy:
proxy_id = self.nsxlib.native_md_proxy.get_id_by_name_or_id(
cfg.CONF.nsx_v3.metadata_proxy)
self._native_md_proxy_uuid = proxy_id
else:
if not cfg.CONF.nsx_v3.metadata_proxy:
raise cfg.RequiredOptError("metadata_proxy")
for az in self.get_azs_list():
az.translate_configured_names_to_uuids(self.nsxlib)
def _extend_port_dict_binding(self, context, port_data):
port_data[pbin.VIF_TYPE] = pbin.VIF_TYPE_OVS
port_data[pbin.VNIC_TYPE] = pbin.VNIC_NORMAL
@ -515,25 +529,27 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
def _init_native_dhcp(self):
try:
for az in self.get_azs_list():
nsx_resources.DhcpProfile(self._nsx_client).get(
self._native_dhcp_profile_uuid)
az._native_dhcp_profile_uuid)
self._dhcp_server = nsx_resources.LogicalDhcpServer(
self._nsx_client)
except nsx_lib_exc.ManagerError:
with excutils.save_and_reraise_exception():
LOG.error(_LE("Unable to retrieve DHCP Profile %s, "
"native DHCP service is not supported"),
self._native_dhcp_profile_uuid)
az._native_dhcp_profile_uuid)
def _init_native_metadata(self):
try:
for az in self.get_azs_list():
nsx_resources.MetaDataProxy(self._nsx_client).get(
self._native_md_proxy_uuid)
az._native_md_proxy_uuid)
except nsx_lib_exc.ManagerError:
with excutils.save_and_reraise_exception():
LOG.error(_LE("Unable to retrieve Metadata Proxy %s, "
"native metadata service is not supported"),
self._native_md_proxy_uuid)
az._native_md_proxy_uuid)
def _setup_rpc(self):
self.endpoints = [dhcp_rpc.DhcpRpcCallback(),
@ -778,6 +794,7 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
self._create_network_at_the_backend(context, net_data))
is_backend_network = True
try:
az_name = nsx_az.DEFAULT_NAME
with context.session.begin(subtransactions=True):
# Create network in Neutron
created_net = super(NsxV3Plugin, self).create_network(context,
@ -791,14 +808,16 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
self._process_l3_create(context, created_net, net_data)
if az_ext.AZ_HINTS in net_data:
net_hints = net_data[az_ext.AZ_HINTS]
self.validate_availability_zones(context, 'network',
net_data[az_ext.AZ_HINTS])
az_hints = az_ext.convert_az_list_to_string(
net_data[az_ext.AZ_HINTS])
net_id = created_net['id']
super(NsxV3Plugin, self).update_network(context,
net_id, {'network': {az_ext.AZ_HINTS: az_hints}})
created_net[az_ext.AZ_HINTS] = az_hints
net_hints)
if net_hints:
az_name = net_hints[0]
az_hints = az_ext.convert_az_list_to_string(net_hints)
super(NsxV3Plugin, self).update_network(
context,
created_net['id'],
{'network': {az_ext.AZ_HINTS: az_hints}})
if is_provider_net:
# Save provider network fields, needed by get_network()
@ -817,6 +836,7 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
nsx_net_id)
if is_backend_network and cfg.CONF.nsx_v3.native_dhcp_metadata:
az = self.get_az_by_hint(az_name)
# Enable native metadata proxy for this network.
tags = self.nsxlib.build_v3_tags_payload(
net_data, resource_type='os-neutron-net-id',
@ -825,7 +845,7 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
'mdproxy', created_net['name'] or 'network'),
created_net['id'])
md_port = self._port_client.create(
nsx_net_id, self._native_md_proxy_uuid,
nsx_net_id, az._native_md_proxy_uuid,
tags=tags, name=name,
attachment_type=nsxlib_consts.ATTACHMENT_MDPROXY)
LOG.debug("Created MD-Proxy logical port %(port)s "
@ -1018,10 +1038,11 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
existing_ports = super(NsxV3Plugin, self).get_ports(
context, filters={'network_id': [network['id']],
'fixed_ips': {'subnet_id': [subnet['id']]}})
az = self.get_network_az(network)
port_data = {
"name": "",
"admin_state_up": True,
"device_id": self._native_dhcp_profile_uuid,
"device_id": az._native_dhcp_profile_uuid,
"device_owner": const.DEVICE_OWNER_DHCP,
"network_id": network['id'],
"tenant_id": network["tenant_id"],
@ -1034,8 +1055,10 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
network, resource_type='os-neutron-net-id',
project_name=context.tenant_name)
server_data = self.nsxlib.native_dhcp.build_server_config(
network, subnet, neutron_port, net_tags)
server_data['dhcp_profile_id'] = self._native_dhcp_profile_uuid
network, subnet, neutron_port, net_tags,
default_dns_nameservers=az.nameservers,
default_dns_domain=az.dns_domain)
server_data['dhcp_profile_id'] = az._native_dhcp_profile_uuid
nsx_net_id = self._get_network_nsx_id(context, network['id'])
port_tags = self.nsxlib.build_v3_tags_payload(
neutron_port, resource_type='os-neutron-dport-id',
@ -1383,7 +1406,8 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
port = self._get_port(context, binding['port_id'])
self._update_dhcp_binding_on_server(
context, binding, port['mac_address'],
binding['ip_address'], kwargs['gateway_ip'])
binding['ip_address'], kwargs['gateway_ip'],
port['network_id'])
if (cfg.CONF.nsx_v3.metadata_on_demand and
not cfg.CONF.nsx_v3.native_dhcp_metadata):
@ -1762,10 +1786,11 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
msg = (_("DHCP option %s is not supported") % opt_name)
raise n_exc.InvalidInput(error_message=msg)
def _get_dhcp_options(self, ip, extra_dhcp_opts):
def _get_dhcp_options(self, context, ip, extra_dhcp_opts, net_id):
# Always add option121.
net_az = self.get_network_az_by_net_id(context, net_id)
options = {'option121': {'static_routes': [
{'network': '%s' % cfg.CONF.nsx_v3.native_metadata_route,
{'network': '%s' % net_az.native_metadata_route,
'next_hop': ip}]}}
# Adding extra options only if configured on port
if extra_dhcp_opts:
@ -1797,7 +1822,8 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
gateway_ip = self.get_subnet(
context, subnet_id).get('gateway_ip')
options = self._get_dhcp_options(
ip, port.get(ext_edo.EXTRADHCPOPTS))
context, ip, port.get(ext_edo.EXTRADHCPOPTS),
port['network_id'])
binding = self._dhcp_server.create_binding(
dhcp_service_id, port['mac_address'], ip, hostname,
cfg.CONF.nsx_v3.dhcp_lease_time, options, gateway_ip)
@ -1933,7 +1959,8 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
if binding:
self._update_dhcp_binding_on_server(
context, binding, new_port['mac_address'],
ips_to_add[i][1], dhcp_opts=dhcp_opts)
ips_to_add[i][1], old_port['network_id'],
dhcp_opts=dhcp_opts)
else:
for (subnet_id, ip) in ips_to_delete:
binding = self._find_dhcp_binding(subnet_id, ip,
@ -1957,18 +1984,21 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
for binding in bindings:
self._update_dhcp_binding_on_server(
context, binding, new_port['mac_address'],
binding['ip_address'],
binding['ip_address'], old_port['network_id'],
dhcp_opts=dhcp_opts if dhcp_opts_changed else None)
def _update_dhcp_binding_on_server(self, context, binding, mac, ip,
gateway_ip=False, dhcp_opts=None):
net_id, gateway_ip=False,
dhcp_opts=None):
try:
data = {'mac_address': mac, 'ip_address': ip}
if ip != binding['ip_address']:
data['host_name'] = 'host-%s' % ip.replace('.', '-')
data['options'] = self._get_dhcp_options(ip, dhcp_opts)
data['options'] = self._get_dhcp_options(
context, ip, dhcp_opts, net_id)
elif dhcp_opts is not None:
data['options'] = self._get_dhcp_options(ip, dhcp_opts)
data['options'] = self._get_dhcp_options(
context, ip, dhcp_opts, net_id)
if gateway_ip is not False:
# Note that None is valid for gateway_ip, means deleting it.
data['gateway_ip'] = gateway_ip
@ -3479,3 +3509,46 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
def save_security_group_rule_mappings(self, context, firewall_rules):
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)
#TODO(asarfaty): We may need to use the filters arg, but now it
# is here only for overriding the original api
result = {}
for az in self._availability_zones_data.list_availability_zones():
# Add this availability zone as a network resource
result[(az, 'network')] = True
return result
def validate_availability_zones(self, context, resource_type,
availability_zones):
# 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 _extend_availability_zone_hints(self, net_res, net_db):
net_res[az_ext.AZ_HINTS] = az_ext.convert_az_string_to_list(
net_db[az_ext.AZ_HINTS])
if cfg.CONF.nsx_v3.native_dhcp_metadata:
# When using the configured AZs, the az will always be the same
# as the hint (or default if none)
if net_res[az_ext.AZ_HINTS]:
az_name = net_res[az_ext.AZ_HINTS][0]
else:
az_name = nsx_az.DEFAULT_NAME
net_res[az_ext.AVAILABILITY_ZONES] = [az_name]
def get_network_az_by_net_id(self, context, network_id):
try:
network = self.get_network(context, network_id)
except Exception:
return self.get_default_az()
return self.get_network_az(network)

View File

@ -144,6 +144,5 @@ def get_nsxlib_wrapper(nsx_username=None, nsx_password=None, basic_auth=False):
plugin_tag=NSX_NEUTRON_PLUGIN,
plugin_ver=n_version.version_info.release_string(),
dns_nameservers=cfg.CONF.nsx_v3.nameservers,
dns_domain=cfg.CONF.nsx_v3.dns_domain,
dhcp_profile_uuid=cfg.CONF.nsx_v3.dhcp_profile)
dns_domain=cfg.CONF.nsx_v3.dns_domain)
return v3.NsxLib(nsxlib_config)

View File

@ -55,6 +55,7 @@ def nsx_update_dhcp_bindings(resource, event, trigger, **kwargs):
return
dhcp_profile_uuid = None
# TODO(asarfaty) Add availability zones support here
if kwargs.get('property'):
properties = admin_utils.parse_multi_keyval_opt(kwargs['property'])
dhcp_profile_uuid = properties.get('dhcp_profile_uuid')
@ -91,6 +92,8 @@ def nsx_update_dhcp_bindings(resource, event, trigger, **kwargs):
net_tags = nsxlib.build_v3_tags_payload(
network, resource_type='os-neutron-net-id',
project_name='admin')
# TODO(asarfaty): add default_dns_nameservers & dns_domain
# from availability zone
server_data = nsxlib.native_dhcp.build_server_config(
network, subnet, port, net_tags)
server_data['dhcp_profile_id'] = dhcp_profile_uuid

View File

@ -0,0 +1,112 @@
# Copyright 2017 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_v3 import availability_zones as nsx_az
class Nsxv3AvailabilityZonesTestCase(base.BaseTestCase):
def setUp(self):
super(Nsxv3AvailabilityZonesTestCase, 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_v3")
self.global_dhcp_profile = uuidutils.generate_uuid()
cfg.CONF.set_override(
"dhcp_profile", self.global_dhcp_profile, group="nsx_v3")
cfg.CONF.set_override(
"native_metadata_route", "1.1.1.1", group="nsx_v3")
cfg.CONF.set_override("dns_domain", "xxx.com", group="nsx_v3")
cfg.CONF.set_override("nameservers", ["10.1.1.1"], group="nsx_v3")
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"]):
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)
def test_simple_availability_zone(self):
self._config_az()
az = nsx_az.NsxV3AvailabilityZone(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)
def test_missing_group_section(self):
self.assertRaises(
nsx_exc.NsxInvalidConfiguration,
nsx_az.NsxV3AvailabilityZone,
"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.NsxV3AvailabilityZone,
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.NsxV3AvailabilityZone,
self.az_name)
def test_availability_zone_missing_md_route(self):
self._config_az(native_metadata_route=None)
az = nsx_az.NsxV3AvailabilityZone(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.NsxV3AvailabilityZone(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.NsxV3AvailabilityZone(self.az_name)
self.assertEqual(["10.1.1.1"], az.nameservers)

View File

@ -16,15 +16,17 @@
import mock
import netaddr
from neutron.extensions import securitygroup as secgrp
from oslo_config import cfg
from oslo_utils import uuidutils
from neutron.extensions import securitygroup as secgrp
from neutron_lib.api.definitions import provider_net as pnet
from neutron_lib import constants
from neutron_lib import context
from neutron_lib import exceptions as n_exc
from oslo_config import cfg
from oslo_utils import uuidutils
from neutron_lib.plugins import directory
from vmware_nsx.common import config
from vmware_nsx.common import exceptions as nsx_exc
from vmware_nsx.common import utils
from vmware_nsx.db import db as nsx_db
@ -34,6 +36,26 @@ from vmware_nsxlib.v3 import nsx_constants
from vmware_nsxlib.v3 import resources as nsx_resources
def set_az_in_config(name, metadata_proxy="metadata_proxy1",
dhcp_profile="dhcp_profile1",
native_metadata_route="2.2.2.2",
dns_domain='aaaa',
nameservers=['bbbb']):
group_name = 'az:%s' % name
cfg.CONF.set_override('availability_zones', [name], group="nsx_v3")
config.register_nsxv3_azs(cfg.CONF, [name])
cfg.CONF.set_override("metadata_proxy", metadata_proxy,
group=group_name)
cfg.CONF.set_override("dhcp_profile", dhcp_profile,
group=group_name)
cfg.CONF.set_override("native_metadata_route", native_metadata_route,
group=group_name)
cfg.CONF.set_override("dns_domain", dns_domain,
group=group_name)
cfg.CONF.set_override("nameservers", nameservers,
group=group_name)
class NsxNativeDhcpTestCase(test_plugin.NsxV3PluginTestCaseMixin):
def setUp(self):
@ -42,11 +64,15 @@ class NsxNativeDhcpTestCase(test_plugin.NsxV3PluginTestCaseMixin):
self._orig_native_dhcp_metadata = cfg.CONF.nsx_v3.native_dhcp_metadata
cfg.CONF.set_override('dhcp_agent_notification', False)
cfg.CONF.set_override('native_dhcp_metadata', True, 'nsx_v3')
self._az_name = 'zone1'
self.az_metadata_route = '3.3.3.3'
set_az_in_config(self._az_name,
native_metadata_route=self.az_metadata_route)
self._patcher = mock.patch.object(nsx_resources.DhcpProfile, 'get')
self._patcher.start()
# Need to run _translate_configured_names_to_uuids and
# _init_dhcp_metadata() manually because plugin was started
# before setUp() overrides CONF.nsx_v3.native_dhcp_metadata.
# Need to run some plugin init methods manually because plugin was
# started before setUp() overrides CONF.nsx_v3.native_dhcp_metadata.
self.plugin.init_availability_zones()
self.plugin._translate_configured_names_to_uuids()
self.plugin._init_dhcp_metadata()
@ -741,6 +767,96 @@ class NsxNativeDhcpTestCase(test_plugin.NsxV3PluginTestCaseMixin):
port['port']['id'])
self.assertEqual(delete_dhcp_binding.call_count, 2)
def test_create_network_with_bad_az_hint(self):
p = directory.get_plugin()
ctx = context.get_admin_context()
data = {'network': {
'name': 'test-az',
'tenant_id': self._tenant_id,
'port_security_enabled': False,
'admin_state_up': True,
'shared': False,
'availability_zone_hints': ['bad_hint']
}}
self.assertRaises(n_exc.NeutronException,
p.create_network,
ctx, data)
def test_create_network_with_az_hint(self):
p = directory.get_plugin()
ctx = context.get_admin_context()
data = {'network': {
'name': 'test-az',
'tenant_id': self._tenant_id,
'port_security_enabled': False,
'admin_state_up': True,
'shared': False,
'availability_zone_hints': [self._az_name]
}}
# network creation should succeed
net = p.create_network(ctx, data)
self.assertEqual([self._az_name],
net['availability_zone_hints'])
self.assertEqual([self._az_name],
net['availability_zones'])
def test_create_network_with_no_az_hint(self):
p = directory.get_plugin()
ctx = context.get_admin_context()
data = {'network': {
'name': 'test-az',
'tenant_id': self._tenant_id,
'port_security_enabled': False,
'admin_state_up': True,
'shared': False
}}
# network creation should succeed
net = p.create_network(ctx, data)
self.assertEqual([],
net['availability_zone_hints'])
self.assertEqual(['default'],
net['availability_zones'])
def test_dhcp_service_with_create_az_network(self):
# Test if DHCP service is disabled on a network when it is created.
with self.network(availability_zone_hints=[self._az_name],
arg_list=('availability_zone_hints',)) as network:
self._verify_dhcp_service(network['network']['id'],
network['network']['tenant_id'], False)
def test_dhcp_binding_with_create_az_port(self):
# Test if DHCP binding is added when a compute port is created.
with mock.patch.object(nsx_resources.LogicalDhcpServer,
'create_binding',
return_value={"id": uuidutils.generate_uuid()}
) as create_dhcp_binding:
with self.network(
availability_zone_hints=[self._az_name],
arg_list=('availability_zone_hints',)) as network:
with self.subnet(enable_dhcp=True, network=network) as subnet:
device_owner = constants.DEVICE_OWNER_COMPUTE_PREFIX + 'X'
device_id = uuidutils.generate_uuid()
with self.port(subnet=subnet, device_owner=device_owner,
device_id=device_id) as port:
dhcp_service = nsx_db.get_nsx_service_binding(
context.get_admin_context().session,
subnet['subnet']['network_id'],
nsx_constants.SERVICE_DHCP)
ip = port['port']['fixed_ips'][0]['ip_address']
hostname = 'host-%s' % ip.replace('.', '-')
options = {'option121': {'static_routes': [
{'network': '%s' % self.az_metadata_route,
'next_hop': ip}]}}
create_dhcp_binding.assert_called_once_with(
dhcp_service['nsx_service_id'],
port['port']['mac_address'], ip, hostname,
cfg.CONF.nsx_v3.dhcp_lease_time, options,
subnet['subnet']['gateway_ip'])
class NsxNativeMetadataTestCase(test_plugin.NsxV3PluginTestCaseMixin):
@ -750,11 +866,12 @@ class NsxNativeMetadataTestCase(test_plugin.NsxV3PluginTestCaseMixin):
self._orig_native_dhcp_metadata = cfg.CONF.nsx_v3.native_dhcp_metadata
cfg.CONF.set_override('dhcp_agent_notification', False)
cfg.CONF.set_override('native_dhcp_metadata', True, 'nsx_v3')
self._az_name = 'zone1'
self._az_metadata_proxy = 'dummy'
set_az_in_config(self._az_name, metadata_proxy=self._az_metadata_proxy)
self._patcher = mock.patch.object(nsx_resources.MetaDataProxy, 'get')
self._patcher.start()
# Need to run _translate_configured_names_to_uuids and
# _init_dhcp_metadata() manually because plugin was started
# before setUp() overrides CONF.nsx_v3.native_dhcp_metadata.
self.plugin.init_availability_zones()
self.plugin._translate_configured_names_to_uuids()
self.plugin._init_dhcp_metadata()
@ -801,6 +918,27 @@ class NsxNativeMetadataTestCase(test_plugin.NsxV3PluginTestCaseMixin):
tags=tags, name=name,
attachment_type=nsx_constants.ATTACHMENT_MDPROXY)
def test_metadata_proxy_with_create_az_network(self):
# Test if native metadata proxy is enabled on a network when it is
# created.
with mock.patch.object(nsx_resources.LogicalPort,
'create') as create_logical_port:
with self.network(
availability_zone_hints=[self._az_name],
arg_list=('availability_zone_hints',)) as network:
nsx_net_id = self.plugin._get_network_nsx_id(
context.get_admin_context(), network['network']['id'])
tags = self.plugin.nsxlib.build_v3_tags_payload(
network['network'], resource_type='os-neutron-net-id',
project_name=None)
name = utils.get_name_and_uuid('%s-%s' % (
'mdproxy', network['network']['name'] or 'network'),
network['network']['id'])
create_logical_port.assert_called_once_with(
nsx_net_id, self._az_metadata_proxy,
tags=tags, name=name,
attachment_type=nsx_constants.ATTACHMENT_MDPROXY)
def test_metadata_proxy_with_get_subnets(self):
# Test if get_subnets() handles advanced-service-provider extension,
# which is used when processing metadata requests.

View File

@ -81,6 +81,9 @@ def _mock_nsx_backend_calls():
def _return_id(*args, **kwargs):
return uuidutils.generate_uuid()
def _return_same(key, *args, **kwargs):
return key
mock.patch(
"vmware_nsxlib.v3.resources.SwitchingProfile.find_by_display_name",
return_value=[fake_profile]
@ -120,7 +123,7 @@ def _mock_nsx_backend_calls():
mock.patch(
"vmware_nsxlib.v3.NsxLibMetadataProxy.get_id_by_name_or_id",
return_value=NSX_METADATA_PROXY_ID).start()
side_effect=_return_same).start()
mock.patch(
"vmware_nsxlib.v3.resources.LogicalPort.create",