Add dhcp metadata host-route support

At NSXv version 6.2.3 and higher, dhcp options 121,66/67,150 and 26 is
supported. This patch enhanced dhcp metadata support via using option121
to insert metadata host route into VMs. So that VM doesn't
need to insert a metadata host route manually for dhcp metadata support
case.

Change-Id: I1e051903f5b136308634346c6d546118bfc9bbe9
This commit is contained in:
linb 2016-03-02 09:25:42 +08:00 committed by Kobi Samoray
parent c946784134
commit b882b0cacf
6 changed files with 137 additions and 10 deletions

View File

@ -199,6 +199,15 @@
# logged.
# log_security_groups_allowed_traffic = False
# (Optional) In some cases the Neutron router is not present to provide the
# metadata IP but the DHCP server can be used to provide this info. Setting
# this value will force the DHCP edge server to append specific host routes
# to the DHCP request. If this option is set, then the metadata service will
# be activated for all the dhcp enabled networks.
# Note: this option can only be supported at NSX manager version 6.2.3 or
# higher
# dhcp_force_metadata = True
[nsx]
# Maximum number of ports for each bridged logical switch
# The recommended value for this parameter varies with NSX version

View File

@ -417,6 +417,14 @@ nsxv_opts = [
default=False,
help=_("Indicates whether distributed-firewall "
"security-groups allowed traffic is logged")),
cfg.BoolOpt('dhcp_force_metadata', default=True,
help=_("In some cases the Neutron router is not present to "
"provide the metadata IP but the DHCP server can be "
"used to provide this info. Setting this value will "
"force the DHCP server to append specific host routes "
"to the DHCP request. If this option is set, then the "
"metadata service will be activated for all the "
"dhcp enabled networks.")),
]
# Register the configuration options

View File

@ -1196,6 +1196,24 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
return False
def _get_dhcp_ip_addr_from_subnet(self, context, subnet_id):
dhcp_port_filters = {'fixed_ips': {'subnet_id': [subnet_id]},
'device_owner': [constants.DEVICE_OWNER_DHCP]}
dhcp_ports = self.get_ports(context, filters=dhcp_port_filters)
if dhcp_ports and dhcp_ports[0].get('fixed_ips'):
return dhcp_ports[0]['fixed_ips'][0]['ip_address']
def is_dhcp_metadata(self, context, subnet_id):
try:
subnet = self.get_subnet(context, subnet_id)
except n_exc.SubnetNotFound:
LOG.debug("subnet %s not found to determine its dhcp meta",
subnet_id)
return False
return bool(subnet['enable_dhcp'] and
self.metadata_proxy_handler and
cfg.CONF.nsxv.dhcp_force_metadata)
def create_subnet(self, context, subnet):
"""Create subnet on nsx_v provider network.
@ -1284,7 +1302,8 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
update_dns_search_domain = self._process_subnet_ext_attr_update(
context.session, subnet, s)
if (gateway_ip != subnet['gateway_ip'] or update_dns_search_domain or
set(orig['dns_nameservers']) != set(subnet['dns_nameservers'])):
set(orig['dns_nameservers']) != set(subnet['dns_nameservers']) or
enable_dhcp and not subnet['enable_dhcp']):
# Need to ensure that all of the subnet attributes will be reloaded
# when creating the edge bindings. Without adding this the original
# subnet details are provided.

View File

@ -17,6 +17,7 @@ from distutils import version
import eventlet
import netaddr
import random
import six
from sqlalchemy import exc as db_base_exc
import time
@ -115,6 +116,19 @@ class EdgeManager(object):
self.per_interface_rp_filter = self._get_per_edge_rp_filter_state()
self.worker_pool = eventlet.GreenPool(WORKER_POOL_SIZE)
self._check_backup_edge_pools()
self._validate_new_features()
def _validate_new_features(self):
self.is_dhcp_opt_enabled = False
ver = self.nsxv_manager.vcns.get_version()
if version.LooseVersion(ver) >= version.LooseVersion('6.2.3'):
self.is_dhcp_opt_enabled = True
elif cfg.CONF.nsxv.dhcp_force_metadata:
LOG.warning(_LW("Skipping dhcp_force_metadata param since dhcp "
"option feature can only be supported at version "
"6.2.3 or higher"))
self.is_dhcp_opt_enabled = False
def _get_per_edge_rp_filter_state(self):
ver = self.nsxv_manager.vcns.get_version()
@ -726,10 +740,50 @@ class EdgeManager(object):
subnet_id)
if sub_binding:
static_config['domainName'] = sub_binding.dns_search_domain
self.handle_meta_static_route(
context, subnet_id, [static_config])
static_bindings.append(static_config)
return static_bindings
def add_host_route_on_static_bindings(self, static_bindings,
dest_cidr, nexthop):
"""Add one host route on a bulk of static bindings config.
We can add host route on VM via dhcp option121. this func can only
works at NSXv version 6.2.3 or higher.
"""
for binding in static_bindings:
if 'dhcpOptions' not in six.iterkeys(binding):
binding['dhcpOptions'] = {}
if 'option121' not in six.iterkeys(binding['dhcpOptions']):
binding['dhcpOptions']['option121'] = {'staticRoutes': []}
binding_opt121 = binding['dhcpOptions']['option121']
if 'staticRoutes' not in six.iterkeys(binding_opt121):
binding_opt121['staticRoutes'] = []
binding_opt121['staticRoutes'].append({
'destinationSubnet': dest_cidr,
'router': nexthop})
return static_bindings
def handle_meta_static_route(self, context, subnet_id, static_bindings):
is_dhcp_option121 = (
self.is_dhcp_opt_enabled and
self.nsxv_plugin.is_dhcp_metadata(
context, subnet_id))
if is_dhcp_option121:
dhcp_ip = self.nsxv_plugin._get_dhcp_ip_addr_from_subnet(
context, subnet_id)
if dhcp_ip:
self.add_host_route_on_static_bindings(
static_bindings,
'169.254.169.254/32',
dhcp_ip)
else:
LOG.error(_LE("Failed to find the dhcp port on subnet "
"%s to do metadata host route insertion"),
subnet_id)
def update_dhcp_service_config(self, context, edge_id):
"""Reconfigure the DHCP to the edge."""
# Get all networks attached to the edge
@ -737,16 +791,23 @@ class EdgeManager(object):
context.session, edge_id)
dhcp_networks = [edge_vnic_binding.network_id
for edge_vnic_binding in edge_vnic_bindings]
ports = self.nsxv_plugin.get_ports(
context.elevated(), filters={'network_id': dhcp_networks})
inst_ports = [port
for port in ports
if port['device_owner'].startswith("compute")]
subnets = self.nsxv_plugin.get_subnets(
context.elevated(), filters={'network_id': dhcp_networks,
'enable_dhcp': [True]})
static_bindings = []
for port in inst_ports:
static_bindings.extend(
self.create_static_binding(
context.elevated(), port))
for subnet in subnets:
ports = self.nsxv_plugin.get_ports(
context.elevated(),
filters={'network_id': [subnet['network_id']],
'fixed_ips': {'subnet_id': [subnet['id']]}})
inst_ports = [port for port in ports
if port['device_owner'].startswith('compute')]
for port in inst_ports:
static_bindings.extend(
self.create_static_binding(
context.elevated(), port))
dhcp_request = {
'featureType': "dhcp_4.0",
'enabled': True,

View File

@ -1912,6 +1912,29 @@ class L3NatTestCaseBase(test_l3_plugin.L3NatTestCaseMixin):
None,
expected_code=error_code)
def test_subnet_dhcp_metadata_with_update(self):
cfg.CONF.set_override('dhcp_force_metadata', True, group='nsxv')
self.plugin_instance.metadata_proxy_handler = mock.Mock()
with self.subnet(cidr="10.0.0.0/24", enable_dhcp=True) as s1:
subnet_id = s1['subnet']['id']
is_dhcp_meta = self.plugin_instance.is_dhcp_metadata(
context.get_admin_context(), subnet_id)
self.assertTrue(is_dhcp_meta)
port_data = {'port': {'tenant_id': s1['subnet']['tenant_id'],
'network_id': s1['subnet']['network_id'],
'device_owner': 'compute:None'}}
req = self.new_create_request(
'ports', port_data).get_response(self.api)
port_req = self.deserialize(self.fmt, req)
subnet_data = {'subnet': {'enable_dhcp': False}}
self.new_update_request(
'subnets', subnet_data,
s1['subnet']['id']).get_response(self.api)
is_dhcp_meta = self.plugin_instance.is_dhcp_metadata(
context.get_admin_context(), subnet_id)
self.assertFalse(is_dhcp_meta)
self.new_delete_request('ports', port_req['port']['id'])
def test_router_delete_ipv6_slaac_subnet_inuse_returns_409(self):
self.skipTest('No DHCP v6 Support yet')

View File

@ -1130,3 +1130,10 @@ class FakeVcns(object):
}
response = {}
return (header, response)
def get_routes(self, edge_id):
header = {
'status': 204
}
response = {'staticRoutes': {'staticRoutes': []}}
return (header, response)