Merge "NSX|V: add in support for DHCP options"

This commit is contained in:
Jenkins 2017-04-05 18:33:22 +00:00 committed by Gerrit Code Review
commit 350cb07e10
4 changed files with 267 additions and 2 deletions

View File

@ -52,6 +52,7 @@ 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
@ -67,6 +68,7 @@ from neutron.db import vlantransparent_db
from neutron.extensions import allowedaddresspairs as addr_pair
from neutron.extensions import availability_zone as az_ext
from neutron.extensions import external_net as ext_net_extn
from neutron.extensions import extra_dhcp_opt as ext_edo
from neutron.extensions import flavors
from neutron.extensions import l3
from neutron.extensions import multiprovidernet as mpnet
@ -141,6 +143,7 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
rt_rtr.RouterType_mixin,
external_net_db.External_net_db_mixin,
extraroute_db.ExtraRoute_db_mixin,
extradhcpopt_db.ExtraDhcpOptMixin,
router_az_db.RouterAvailabilityZoneMixin,
l3_gwmode_db.L3_NAT_db_mixin,
portbindings_db.PortBindingMixin,
@ -164,6 +167,7 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
"provider",
"quotas",
"external-net",
"extra_dhcp_opt",
"extraroute",
"router",
"security-group",
@ -1638,8 +1642,37 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
port_id=port_data['id'],
vnic_type=vnic_type)
def _validate_extra_dhcp_options(self, opts):
if not opts:
return
for opt in opts:
opt_name = opt['opt_name']
opt_val = opt['opt_value']
if opt_name == 'classless-static-route':
# separate validation for option121
if opt_val is not None:
try:
net, ip = opt_val.split(',')
except Exception:
msg = (_("Bad value %(val)s for DHCP option "
"%(name)s") % {'name': opt_name,
'val': opt_val})
raise n_exc.InvalidInput(error_message=msg)
elif opt_name not in vcns_const.SUPPORTED_DHCP_OPTIONS:
try:
option = int(opt_name)
except ValueError:
option = 255
if option >= 255:
msg = (_("DHCP option %s is not supported") % opt_name)
LOG.error(msg)
raise n_exc.InvalidInput(error_message=msg)
def create_port(self, context, port):
port_data = port['port']
dhcp_opts = port_data.get(ext_edo.EXTRADHCPOPTS)
self._validate_extra_dhcp_options(dhcp_opts)
with db_api.context_manager.writer.using(context):
# First we allocate port in neutron database
neutron_db = super(NsxVPluginV2, self).create_port(context, port)
@ -1700,6 +1733,8 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
self._process_vnic_type(context, port_data, attrs,
has_security_groups,
port_security)
self._process_port_create_extra_dhcp_opts(
context, port_data, dhcp_opts)
# Invoking the manager callback under transaction fails so here
# we do it outside. If this fails we will blow away the port
@ -1718,7 +1753,7 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
try:
# Configure NSX - this should not be done in the DB transaction
# Configure the DHCP Edge service
self._create_dhcp_static_binding(context, neutron_db)
self._create_dhcp_static_binding(context, port_data)
except Exception:
with excutils.save_and_reraise_exception():
LOG.exception('Failed to create port')
@ -1833,6 +1868,8 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
device_id):
attrs = port[attr.PORT]
port_data = port['port']
dhcp_opts = port_data.get(ext_edo.EXTRADHCPOPTS)
self._validate_extra_dhcp_options(dhcp_opts)
if addr_pair.ADDRESS_PAIRS in attrs:
self._validate_address_pairs(attrs, original_port)
orig_has_port_security = (cfg.CONF.nsxv.spoofguard_enabled and
@ -1946,11 +1983,13 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
self._process_vnic_type(context, ret_port, attrs,
has_security_groups,
has_port_security)
self._update_extra_dhcp_opts_on_port(context, id, port,
ret_port)
if comp_owner_update:
# Create dhcp bindings, the port is now owned by an instance
self._create_dhcp_static_binding(context, ret_port)
elif port_ip_change:
elif port_ip_change or dhcp_opts:
owner = original_port['device_owner']
# If port IP has changed we should update according to device
# owner

View File

@ -72,6 +72,14 @@ ALLOWED_EDGE_SIZES = (nsxv_constants.COMPACT,
ALLOWED_EDGE_TYPES = (nsxv_constants.SERVICE_EDGE,
nsxv_constants.VDR_EDGE)
SUPPORTED_DHCP_OPTIONS = {
'interface-mtu': 'option26',
'tftp-server-name': 'option66',
'bootfile-name': 'option67',
'classless-static-route': 'option121',
'tftp-server-address': 'option150',
}
# router status by number
class RouterStatus(object):

View File

@ -32,6 +32,7 @@ from oslo_utils import timeutils
from oslo_utils import uuidutils
from six import moves
from neutron.extensions import extra_dhcp_opt as ext_edo
from neutron.extensions import l3
from neutron.plugins.common import constants as plugin_const
@ -926,6 +927,35 @@ class EdgeManager(object):
with locking.LockManager.get_lock(str(edge_binding['edge_id'])):
self.update_dhcp_service_config(context, edge_binding['edge_id'])
def _add_dhcp_option(self, static_config, opt):
if 'dhcpOptions' not in static_config:
static_config['dhcpOptions'] = {}
opt_name = opt['opt_name']
opt_val = opt['opt_value']
if opt_name in vcns_const.SUPPORTED_DHCP_OPTIONS:
key = vcns_const.SUPPORTED_DHCP_OPTIONS[opt_name]
if opt_name == 'classless-static-route':
if 'option121' not in static_config['dhcpOptions']:
static_config['dhcpOptions']['option121'] = {
'staticRoutes': []}
opt121 = static_config['dhcpOptions']['option121']
net, ip = opt_val.split(',')
opt121['staticRoutes'].append({'destinationSubnet': net,
'router': ip})
elif opt_name == 'tftp-server-address':
if 'option150' not in static_config['dhcpOptions']:
static_config['dhcpOptions']['option150'] = {
'tftpServers': []}
opt150 = static_config['dhcpOptions']['option150']
opt150['tftpServers'].append(opt_val)
else:
static_config['dhcpOptions'][key] = opt_val
else:
if 'other' not in static_config['dhcpOptions']:
static_config['dhcpOptions']['others'] = []
static_config['dhcpOptions']['others'].append(
{'code': opt_name, 'value': opt_val})
def create_static_binding(self, context, port):
"""Create the DHCP Edge static binding configuration
@ -993,6 +1023,11 @@ class EdgeManager(object):
host_route['destination'],
host_route['nexthop'])
dhcp_opts = port.get(ext_edo.EXTRADHCPOPTS)
if dhcp_opts is not None:
for opt in dhcp_opts:
self._add_dhcp_option(static_config, opt)
static_bindings.append(static_config)
return static_bindings

View File

@ -14,6 +14,7 @@
# limitations under the License.
import contextlib
import copy
from eventlet import greenthread
import mock
import netaddr
@ -23,6 +24,7 @@ from neutron.api.v2 import attributes
from neutron.extensions import allowedaddresspairs as addr_pair
from neutron.extensions import dvr as dist_router
from neutron.extensions import external_net
from neutron.extensions import extra_dhcp_opt as edo_ext
from neutron.extensions import l3
from neutron.extensions import l3_ext_gw_mode
from neutron.extensions import l3_flavors
@ -35,6 +37,7 @@ from neutron.tests.unit import _test_extension_portbindings as test_bindings
import neutron.tests.unit.db.test_allowedaddresspairs_db as test_addr_pair
import neutron.tests.unit.db.test_db_base_plugin_v2 as test_plugin
from neutron.tests.unit.extensions import base as extension
from neutron.tests.unit.extensions import test_extra_dhcp_opt as test_dhcpopts
import neutron.tests.unit.extensions.test_l3 as test_l3_plugin
import neutron.tests.unit.extensions.test_l3_ext_gw_mode as test_ext_gw_mode
import neutron.tests.unit.extensions.test_portsecurity as test_psec
@ -5305,3 +5308,183 @@ class TestRouterFlavorTestCase(extension.ExtensionTestCase,
self._test_router_create_with_flavor_error(
metainfo, n_exc.BadRequest,
create_az=['az2'])
class DHCPOptsTestCase(test_dhcpopts.TestExtraDhcpOpt,
NsxVPluginV2TestCase):
def setUp(self, plugin=None):
super(test_dhcpopts.ExtraDhcpOptDBTestCase, self).setUp(
plugin=PLUGIN_NAME)
def test_create_port_with_extradhcpopts(self):
opt_list = [{'opt_name': 'bootfile-name',
'opt_value': 'pxelinux.0'},
{'opt_name': 'tftp-server-address',
'opt_value': '123.123.123.123'}]
params = {edo_ext.EXTRADHCPOPTS: opt_list,
'arg_list': (edo_ext.EXTRADHCPOPTS,)}
with self.port(**params) as port:
self._check_opts(opt_list,
port['port'][edo_ext.EXTRADHCPOPTS])
def test_create_port_with_extradhcpopts_ipv6_opt_version(self):
self.skipTest('No DHCP v6 Support yet')
def test_create_port_with_extradhcpopts_ipv4_opt_version(self):
opt_list = [{'opt_name': 'bootfile-name',
'opt_value': 'pxelinux.0',
'ip_version': 4},
{'opt_name': 'tftp-server-address',
'opt_value': '123.123.123.123',
'ip_version': 4}]
params = {edo_ext.EXTRADHCPOPTS: opt_list,
'arg_list': (edo_ext.EXTRADHCPOPTS,)}
with self.port(**params) as port:
self._check_opts(opt_list,
port['port'][edo_ext.EXTRADHCPOPTS])
def test_update_port_with_extradhcpopts_with_same(self):
opt_list = [{'opt_name': 'bootfile-name', 'opt_value': 'pxelinux.0'},
{'opt_name': 'tftp-server-address',
'opt_value': '123.123.123.123'}]
upd_opts = [{'opt_name': 'bootfile-name', 'opt_value': 'changeme.0'}]
expected_opts = opt_list[:]
for i in expected_opts:
if i['opt_name'] == upd_opts[0]['opt_name']:
i['opt_value'] = upd_opts[0]['opt_value']
break
self._test_update_port_with_extradhcpopts(opt_list, upd_opts,
expected_opts)
def test_update_port_with_additional_extradhcpopt(self):
opt_list = [{'opt_name': 'tftp-server-address',
'opt_value': '123.123.123.123'}]
upd_opts = [{'opt_name': 'bootfile-name', 'opt_value': 'changeme.0'}]
expected_opts = copy.deepcopy(opt_list)
expected_opts.append(upd_opts[0])
self._test_update_port_with_extradhcpopts(opt_list, upd_opts,
expected_opts)
def test_update_port_with_extradhcpopts(self):
opt_list = [{'opt_name': 'bootfile-name', 'opt_value': 'pxelinux.0'},
{'opt_name': 'tftp-server-address',
'opt_value': '123.123.123.123'}]
upd_opts = [{'opt_name': 'bootfile-name', 'opt_value': 'changeme.0'}]
expected_opts = copy.deepcopy(opt_list)
for i in expected_opts:
if i['opt_name'] == upd_opts[0]['opt_name']:
i['opt_value'] = upd_opts[0]['opt_value']
break
self._test_update_port_with_extradhcpopts(opt_list, upd_opts,
expected_opts)
def test_update_port_with_extradhcpopt_delete(self):
opt_list = [{'opt_name': 'bootfile-name', 'opt_value': 'pxelinux.0'},
{'opt_name': 'tftp-server-address',
'opt_value': '123.123.123.123'}]
upd_opts = [{'opt_name': 'bootfile-name', 'opt_value': None}]
expected_opts = []
expected_opts = [opt for opt in opt_list
if opt['opt_name'] != 'bootfile-name']
self._test_update_port_with_extradhcpopts(opt_list, upd_opts,
expected_opts)
def test_update_port_adding_extradhcpopts(self):
opt_list = []
upd_opts = [{'opt_name': 'bootfile-name', 'opt_value': 'pxelinux.0'},
{'opt_name': 'tftp-server-address',
'opt_value': '123.123.123.123'}]
expected_opts = copy.deepcopy(upd_opts)
self._test_update_port_with_extradhcpopts(opt_list, upd_opts,
expected_opts)
def test_update_port_with_blank_name_extradhcpopt(self):
opt_list = [{'opt_name': 'bootfile-name', 'opt_value': 'pxelinux.0'},
{'opt_name': 'tftp-server-address',
'opt_value': '123.123.123.123'}]
upd_opts = [{'opt_name': ' ', 'opt_value': 'pxelinux.0'}]
params = {edo_ext.EXTRADHCPOPTS: opt_list,
'arg_list': (edo_ext.EXTRADHCPOPTS,)}
with self.port(**params) as port:
update_port = {'port': {edo_ext.EXTRADHCPOPTS: upd_opts}}
req = self.new_update_request('ports', update_port,
port['port']['id'])
res = req.get_response(self.api)
self.assertEqual(webob.exc.HTTPBadRequest.code, res.status_int)
def test_create_port_with_empty_router_extradhcpopts(self):
self.skipTest('No DHCP support option for router')
def test_update_port_with_blank_router_extradhcpopt(self):
self.skipTest('No DHCP support option for router')
def test_update_port_with_extradhcpopts_ipv6_change_value(self):
self.skipTest('No DHCP v6 Support yet')
def test_update_port_with_extradhcpopts_add_another_ver_opt(self):
self.skipTest('No DHCP v6 Support yet')
def test_update_port_with_blank_string_extradhcpopt(self):
opt_list = [{'opt_name': 'bootfile-name', 'opt_value': 'pxelinux.0'},
{'opt_name': 'tftp-server-address',
'opt_value': '123.123.123.123'}]
upd_opts = [{'opt_name': 'bootfile-name', 'opt_value': ' '}]
params = {edo_ext.EXTRADHCPOPTS: opt_list,
'arg_list': (edo_ext.EXTRADHCPOPTS,)}
with self.port(**params) as port:
update_port = {'port': {edo_ext.EXTRADHCPOPTS: upd_opts}}
req = self.new_update_request('ports', update_port,
port['port']['id'])
res = req.get_response(self.api)
self.assertEqual(webob.exc.HTTPBadRequest.code, res.status_int)
def test_create_port_with_none_extradhcpopts(self):
opt_list = [{'opt_name': 'bootfile-name',
'opt_value': None},
{'opt_name': 'tftp-server-address',
'opt_value': '123.123.123.123'}]
expected = [{'opt_name': 'tftp-server-address',
'opt_value': '123.123.123.123'}]
params = {edo_ext.EXTRADHCPOPTS: opt_list,
'arg_list': (edo_ext.EXTRADHCPOPTS,)}
with self.port(**params) as port:
self._check_opts(expected,
port['port'][edo_ext.EXTRADHCPOPTS])
def test_create_port_with_extradhcpopts_codes(self):
opt_list = [{'opt_name': '85',
'opt_value': 'cafecafe'}]
params = {edo_ext.EXTRADHCPOPTS: opt_list,
'arg_list': (edo_ext.EXTRADHCPOPTS,)}
with self.port(**params) as port:
self._check_opts(opt_list,
port['port'][edo_ext.EXTRADHCPOPTS])
def test_update_port_with_extradhcpopts_codes(self):
opt_list = [{'opt_name': '85',
'opt_value': 'cafecafe'}]
upd_opts = [{'opt_name': '85',
'opt_value': '01010101'}]
expected_opts = copy.deepcopy(opt_list)
for i in expected_opts:
if i['opt_name'] == upd_opts[0]['opt_name']:
i['opt_value'] = upd_opts[0]['opt_value']
break
self._test_update_port_with_extradhcpopts(opt_list, upd_opts,
expected_opts)