NSX|V add dhcp-mtu extension to subnet

Add subnet extension dhcp-mtu and configure it in option26 of the dhcp binding.
Also add this column to the nsxv_subnet_ext_attributes DB table.
This option will be available only from NSX version 6.2.3

DocImpact: Added dhcp-mtu extension to subnets

Change-Id: Id2a74a3c089beb61fde6b7c0fd02b207e444c3b7
This commit is contained in:
Adit Sarfaty 2016-07-24 11:50:01 +03:00
parent beb61a55ca
commit d4fa95168c
8 changed files with 342 additions and 22 deletions

View File

@ -1 +1 @@
081af0e396d7
dbe29d208ac6

View File

@ -0,0 +1,38 @@
# Copyright 2016 VMware, Inc.
#
# 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.
"""NSXv add DHCP MTU to subnets
Revision ID: dbe29d208ac6
Revises: 081af0e396d7
Create Date: 2016-07-21 05:03:35.369938
"""
# revision identifiers, used by Alembic.
revision = 'dbe29d208ac6'
down_revision = '081af0e396d7'
from alembic import op
import sqlalchemy as sa
def upgrade():
# Add a new column and make the previous column nullable,
# because it is enough that one of them is non-null
op.add_column('nsxv_subnet_ext_attributes',
sa.Column('dhcp_mtu', sa.Integer, nullable=True))
op.alter_column('nsxv_subnet_ext_attributes', 'dns_search_domain',
nullable=True, existing_type=sa.String(length=255),
existing_nullable=False)

View File

@ -30,6 +30,7 @@ from vmware_nsx._i18n import _, _LE, _LW
from vmware_nsx.common import exceptions as nsx_exc
from vmware_nsx.common import nsxv_constants
from vmware_nsx.db import nsxv_models
from vmware_nsx.extensions import dhcp_mtu as ext_dhcp_mtu
from vmware_nsx.extensions import dns_search_domain as ext_dns_search_domain
from vmware_nsx.plugins.nsx_v.vshield.common import constants
@ -773,11 +774,14 @@ def del_nsxv_lbaas_certificate_binding(session, cert_id, edge_id):
edge_id=edge_id).delete())
def add_nsxv_subnet_ext_attributes(session, subnet_id, dns_search_domain):
def add_nsxv_subnet_ext_attributes(session, subnet_id,
dns_search_domain=None,
dhcp_mtu=None):
with session.begin(subtransactions=True):
binding = nsxv_models.NsxvSubnetExtAttributes(
subnet_id=subnet_id,
dns_search_domain=dns_search_domain)
dns_search_domain=dns_search_domain,
dhcp_mtu=dhcp_mtu)
session.add(binding)
return binding
@ -791,9 +795,12 @@ def get_nsxv_subnet_ext_attributes(session, subnet_id):
return
def update_nsxv_subnet_ext_attributes(session, subnet_id, dns_search_domain):
def update_nsxv_subnet_ext_attributes(session, subnet_id,
dns_search_domain=None,
dhcp_mtu=None):
with session.begin(subtransactions=True):
binding = (session.query(nsxv_models.NsxvSubnetExtAttributes).
filter_by(subnet_id=subnet_id).one())
binding[ext_dns_search_domain.DNS_SEARCH_DOMAIN] = dns_search_domain
binding[ext_dhcp_mtu.DHCP_MTU] = dhcp_mtu
return binding

View File

@ -342,7 +342,8 @@ class NsxvSubnetExtAttributes(model_base.BASEV2, models.TimestampMixin):
subnet_id = sa.Column(sa.String(36),
sa.ForeignKey('subnets.id', ondelete="CASCADE"),
primary_key=True)
dns_search_domain = sa.Column(sa.String(255), nullable=False)
dns_search_domain = sa.Column(sa.String(255), nullable=True)
dhcp_mtu = sa.Column(sa.Integer, nullable=True)
# Add a relationship to the Subnet model in order to instruct
# SQLAlchemy to eagerly load this association
subnet = orm.relationship(

View File

@ -0,0 +1,54 @@
# Copyright 2016 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 neutron.api import extensions
from neutron_lib import constants
DHCP_MTU = 'dhcp_mtu'
EXTENDED_ATTRIBUTES_2_0 = {
'subnets': {
DHCP_MTU: {
'allow_post': True, 'allow_put': True,
'default': constants.ATTR_NOT_SPECIFIED,
# This is the legal range for the backend MTU
'validate': {'type:range': (68, 65535)},
'is_visible': True},
}
}
class Dhcp_mtu(extensions.ExtensionDescriptor):
"""Extension class supporting DHCP MTU for subnets."""
@classmethod
def get_name(cls):
return "DHCP MTU"
@classmethod
def get_alias(cls):
return "dhcp-mtu"
@classmethod
def get_description(cls):
return "Enable the ability to add DHCP MTU for Subnets"
@classmethod
def get_updated(cls):
return "2016-7-21T10:00:00-00:00"
def get_extended_resources(self, version):
if version == "2.0":
return EXTENDED_ATTRIBUTES_2_0
return {}

View File

@ -89,6 +89,7 @@ from vmware_nsx.extensions import (
advancedserviceproviders as as_providers)
from vmware_nsx.extensions import (
vnicindex as ext_vnic_idx)
from vmware_nsx.extensions import dhcp_mtu as ext_dhcp_mtu
from vmware_nsx.extensions import dns_search_domain as ext_dns_search_domain
from vmware_nsx.extensions import routersize
from vmware_nsx.extensions import secgroup_rule_local_ip_prefix
@ -213,6 +214,10 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
else:
self._dvs = None
if self.edge_manager.is_dhcp_opt_enabled:
# Only expose the extension if it is supported
self.supported_extension_aliases.append("dhcp-mtu")
# Bind QoS notifications
callbacks_registry.subscribe(self._handle_qos_notification,
callbacks_resources.QOS_POLICY)
@ -1847,38 +1852,50 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
def _process_subnet_ext_attr_create(self, session, subnet_db,
subnet_req):
# Verify if dns search domain for subnet is configured
# Verify if dns search domain/dhcp mtu for subnet are configured
dns_search_domain = subnet_req.get(
ext_dns_search_domain.DNS_SEARCH_DOMAIN)
if not validators.is_attr_set(dns_search_domain):
dhcp_mtu = subnet_req.get(
ext_dhcp_mtu.DHCP_MTU)
if (not validators.is_attr_set(dns_search_domain) and
not validators.is_attr_set(dhcp_mtu)):
return
if not validators.is_attr_set(dns_search_domain):
dns_search_domain = None
if not validators.is_attr_set(dhcp_mtu):
dhcp_mtu = None
sub_binding = nsxv_db.get_nsxv_subnet_ext_attributes(
session=session,
subnet_id=subnet_db['id'])
# Create a DNS search domain entry for subnet if it does not exist
# Create a subnet extensions for subnet if it does not exist
if not sub_binding:
nsxv_db.add_nsxv_subnet_ext_attributes(
session=session,
subnet_id=subnet_db['id'],
dns_search_domain=dns_search_domain)
# Else update only if a new value for dns search domain is provided
elif sub_binding.dns_search_domain != dns_search_domain:
dns_search_domain=dns_search_domain,
dhcp_mtu=dhcp_mtu)
# Else update only if a new values for subnet extensions are provided
elif (sub_binding.dns_search_domain != dns_search_domain or
sub_binding.dhcp_mtu != dhcp_mtu):
nsxv_db.update_nsxv_subnet_ext_attributes(
session=session,
subnet_id=subnet_db['id'],
dns_search_domain=dns_search_domain)
dns_search_domain=dns_search_domain,
dhcp_mtu=dhcp_mtu)
subnet_db['dns_search_domain'] = dns_search_domain
subnet_db['dhcp_mtu'] = dhcp_mtu
def _process_subnet_ext_attr_update(self, session, subnet_db,
subnet_req):
update_dns_search_domain = False
# Update dns search domain attribute for subnet
if ext_dns_search_domain.DNS_SEARCH_DOMAIN in subnet_req:
update_dhcp_config = False
# Update extended attributes for subnet
if (ext_dns_search_domain.DNS_SEARCH_DOMAIN in subnet_req or
ext_dhcp_mtu.DHCP_MTU in subnet_req):
self._process_subnet_ext_attr_create(session,
subnet_db,
subnet_req)
update_dns_search_domain = True
return update_dns_search_domain
update_dhcp_config = True
return update_dhcp_config
def update_subnet(self, context, id, subnet):
s = subnet['subnet']
@ -1890,9 +1907,9 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
orig_enable_dhcp=enable_dhcp,
orig_host_routes=orig_host_routes)
subnet = super(NsxVPluginV2, self).update_subnet(context, id, subnet)
update_dns_search_domain = self._process_subnet_ext_attr_update(
update_dhcp_config = self._process_subnet_ext_attr_update(
context.session, subnet, s)
if (gateway_ip != subnet['gateway_ip'] or update_dns_search_domain or
if (gateway_ip != subnet['gateway_ip'] or update_dhcp_config or
set(orig['dns_nameservers']) != set(subnet['dns_nameservers']) or
orig_host_routes != subnet['host_routes'] or
enable_dhcp and not subnet['enable_dhcp']):
@ -1908,12 +1925,13 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
return subnet
db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
attr.SUBNETS, ['_extend_subnet_dict_dns_search_domain'])
attr.SUBNETS, ['_extend_subnet_dict_extended_attributes'])
def _extend_subnet_dict_dns_search_domain(self, subnet_res, subnet_db):
def _extend_subnet_dict_extended_attributes(self, subnet_res, subnet_db):
subnet_attr = subnet_db.get('nsxv_subnet_attributes')
if subnet_attr:
subnet_res['dns_search_domain'] = subnet_attr.dns_search_domain
subnet_res['dhcp_mtu'] = subnet_attr.dhcp_mtu
def _update_subnet_dhcp_status(self, subnet, context):
network_id = subnet['network_id']

View File

@ -847,7 +847,12 @@ class EdgeManager(object):
context.session,
subnet_id)
if sub_binding:
static_config['domainName'] = sub_binding.dns_search_domain
if sub_binding.dns_search_domain is not None:
static_config['domainName'] = sub_binding.dns_search_domain
if sub_binding.dhcp_mtu:
static_config = self.add_mtu_on_static_binding(
static_config, sub_binding.dhcp_mtu)
self.handle_meta_static_route(
context, subnet_id, [static_config])
for host_route in subnet['routes']:
@ -879,6 +884,17 @@ class EdgeManager(object):
'router': nexthop})
return static_bindings
def add_mtu_on_static_binding(self, static_binding, mtu):
"""Add the pre-configured MTU to a static binding config.
We can add the MTU via dhcp option26.
This func can only works at NSXv version 6.2.3 or higher.
"""
if 'dhcpOptions' not in six.iterkeys(static_binding):
static_binding['dhcpOptions'] = {}
static_binding['dhcpOptions']['option26'] = mtu
return static_binding
def handle_meta_static_route(self, context, subnet_id, static_bindings):
is_dhcp_option121 = (
self.is_dhcp_opt_enabled and

View File

@ -0,0 +1,186 @@
# Copyright 2016 VMware, Inc.
#
# 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.
import mock
import neutron.db.api as db
from neutron.tests.unit.db import test_db_base_plugin_v2 as test_db
from vmware_nsx.db import nsxv_db
from vmware_nsx.extensions import dhcp_mtu as ext_dhcp_mtu
from vmware_nsx.plugins.nsx_v.vshield import edge_utils
from vmware_nsx.tests.unit.nsx_v import test_plugin
from vmware_nsx.tests.unit.nsx_v.vshield import fake_vcns
PLUGIN_NAME = 'vmware_nsx.plugin.NsxVPlugin'
class DhcpMtuExtensionManager(object):
def get_resources(self):
return []
def get_actions(self):
return []
def get_request_extensions(self):
return []
def get_extended_resources(self, version):
return ext_dhcp_mtu.get_extended_resources(version)
class DhcpMtuExtensionTestCase(test_plugin.NsxVPluginV2TestCase):
"""Test API extension dhcp-mtu attribute of subnets."""
@mock.patch.object(edge_utils.EdgeManager, '_deploy_edge')
def setUp(self, plugin=PLUGIN_NAME):
ext_mgr = DhcpMtuExtensionManager()
# This feature is enabled only since 6.2.3
with mock.patch.object(fake_vcns.FakeVcns,
'get_version',
return_value="6.2.3"):
super(DhcpMtuExtensionTestCase, self).setUp(ext_mgr=ext_mgr)
def _create_subnet_with_dhcp_mtu(self, dhcp_mtu):
with self.network() as net:
tenant_id = net['network']['tenant_id']
net_id = net['network']['id']
data = {'subnet': {'network_id': net_id,
'cidr': '10.0.0.0/24',
'ip_version': 4,
'name': 'test-mtu-subnet',
'tenant_id': tenant_id,
'dhcp_mtu': dhcp_mtu}}
subnet_req = self.new_create_request('subnets', data)
res = subnet_req.get_response(self.api)
return res
def test_subnet_create_with_dhcp_mtu(self):
for mtu in (68, 2000, 65535):
res = self._create_subnet_with_dhcp_mtu(mtu)
sub = self.deserialize(self.fmt, res)
self.assertEqual(mtu, sub['subnet']['dhcp_mtu'])
def test_subnet_create_with_invalid_dhcp_mtu_fail(self):
res = self._create_subnet_with_dhcp_mtu(67)
self.assertEqual(400, res.status_int)
res = self._create_subnet_with_dhcp_mtu(100000)
self.assertEqual(400, res.status_int)
def test_subnet_update_with_dhcp_mtu(self):
res = self._create_subnet_with_dhcp_mtu(2000)
sub = self.deserialize(self.fmt, res)
data = {'subnet': {'dhcp_mtu': 3000}}
req = self.new_update_request('subnets', data, sub['subnet']['id'])
updated_sub = self.deserialize(self.fmt, req.get_response(self.api))
self.assertEqual(3000, updated_sub['subnet']['dhcp_mtu'])
def _create_subnet_with_dhcp_mtu_and_dns(self, dhcp_mtu,
dns_search_domain):
with self.network() as net:
tenant_id = net['network']['tenant_id']
net_id = net['network']['id']
data = {'subnet': {'network_id': net_id,
'cidr': '10.0.0.0/24',
'ip_version': 4,
'name': 'test-mtu-subnet',
'tenant_id': tenant_id,
'dhcp_mtu': dhcp_mtu,
'dns_search_domain': dns_search_domain}}
subnet_req = self.new_create_request('subnets', data)
res = subnet_req.get_response(self.api)
return res
def test_subnet_create_with_dhcp_mtu_and_dns(self):
res = self._create_subnet_with_dhcp_mtu_and_dns(2000, 'vmware.com')
sub = self.deserialize(self.fmt, res)
self.assertEqual(2000, sub['subnet']['dhcp_mtu'])
self.assertEqual('vmware.com', sub['subnet']['dns_search_domain'])
def test_subnet_update_with_dhcp_mtu_and_dns(self):
res = self._create_subnet_with_dhcp_mtu_and_dns(2000, 'vmware.com')
sub = self.deserialize(self.fmt, res)
data = {'subnet': {'dhcp_mtu': 3000,
'dns_search_domain': 'eng.vmware.com'}}
req = self.new_update_request('subnets', data, sub['subnet']['id'])
updated_sub = self.deserialize(self.fmt, req.get_response(self.api))
self.assertEqual(3000, updated_sub['subnet']['dhcp_mtu'])
self.assertEqual('eng.vmware.com',
updated_sub['subnet']['dns_search_domain'])
class DhcpMtuDBTestCase(test_db.NeutronDbPluginV2TestCase):
def setUp(self):
super(DhcpMtuDBTestCase, self).setUp()
self.session = db.get_session()
def test_get_nsxv_subnet_ext_attributes_no_dhcp_mtu(self):
with self.subnet() as sub:
sub_binding = nsxv_db.get_nsxv_subnet_ext_attributes(
session=self.session, subnet_id=sub['subnet']['id'])
self.assertIsNone(sub_binding)
def test_add_nsxv_subnet_ext_attributes_dhcp_mtu(self):
with self.subnet() as sub:
nsxv_db.add_nsxv_subnet_ext_attributes(
session=self.session,
subnet_id=sub['subnet']['id'],
dhcp_mtu=2000)
sub_binding = nsxv_db.get_nsxv_subnet_ext_attributes(
session=self.session, subnet_id=sub['subnet']['id'])
self.assertEqual(2000, sub_binding.dhcp_mtu)
self.assertEqual(sub['subnet']['id'], sub_binding.subnet_id)
def test_update_nsxv_subnet_ext_attributes_dhcp_mtu(self):
with self.subnet() as sub:
nsxv_db.add_nsxv_subnet_ext_attributes(
session=self.session,
subnet_id=sub['subnet']['id'],
dhcp_mtu=2000)
sub_binding = nsxv_db.update_nsxv_subnet_ext_attributes(
session=self.session,
subnet_id=sub['subnet']['id'],
dhcp_mtu=3000)
self.assertEqual(3000, sub_binding.dhcp_mtu)
def test_add_nsxv_subnet_ext_attributes_dhcp_mtu_and_dns(self):
with self.subnet() as sub:
nsxv_db.add_nsxv_subnet_ext_attributes(
session=self.session,
subnet_id=sub['subnet']['id'],
dhcp_mtu=2000,
dns_search_domain='eng.vmware.com')
sub_binding = nsxv_db.get_nsxv_subnet_ext_attributes(
session=self.session, subnet_id=sub['subnet']['id'])
self.assertEqual(2000, sub_binding.dhcp_mtu)
self.assertEqual('eng.vmware.com', sub_binding.dns_search_domain)
self.assertEqual(sub['subnet']['id'], sub_binding.subnet_id)
def test_update_nsxv_subnet_ext_attributes_dhcp_mtu_and_dns(self):
with self.subnet() as sub:
nsxv_db.add_nsxv_subnet_ext_attributes(
session=self.session,
subnet_id=sub['subnet']['id'],
dhcp_mtu=2000,
dns_search_domain='eng.vmware.com')
sub_binding = nsxv_db.update_nsxv_subnet_ext_attributes(
session=self.session,
subnet_id=sub['subnet']['id'],
dhcp_mtu=3000,
dns_search_domain='nsx.vmware.com')
self.assertEqual(3000, sub_binding.dhcp_mtu)
self.assertEqual('nsx.vmware.com', sub_binding.dns_search_domain)