diff --git a/vmware_nsx/db/migration/alembic_migrations/versions/EXPAND_HEAD b/vmware_nsx/db/migration/alembic_migrations/versions/EXPAND_HEAD
index 74adfb566d..222149bbd5 100644
--- a/vmware_nsx/db/migration/alembic_migrations/versions/EXPAND_HEAD
+++ b/vmware_nsx/db/migration/alembic_migrations/versions/EXPAND_HEAD
@@ -1 +1 @@
-2af850eb3970
+69fb78b33d41
diff --git a/vmware_nsx/db/migration/alembic_migrations/versions/mitaka/expand/69fb78b33d41_nsxv_add_search_domain_to_subnets.py b/vmware_nsx/db/migration/alembic_migrations/versions/mitaka/expand/69fb78b33d41_nsxv_add_search_domain_to_subnets.py
new file mode 100644
index 0000000000..9dea6defd1
--- /dev/null
+++ b/vmware_nsx/db/migration/alembic_migrations/versions/mitaka/expand/69fb78b33d41_nsxv_add_search_domain_to_subnets.py
@@ -0,0 +1,40 @@
+# 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 dns search domain to subnets
+
+Revision ID: 69fb78b33d41
+Revises: 2af850eb3970
+Create Date: 2016-01-27 07:28:35.369938
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '69fb78b33d41'
+down_revision = '2af850eb3970'
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade():
+ op.create_table(
+ 'nsxv_subnet_ext_attributes',
+ sa.Column('subnet_id', sa.String(length=36), nullable=False),
+ sa.Column('dns_search_domain', sa.String(length=255), nullable=False),
+ sa.ForeignKeyConstraint(['subnet_id'],
+ ['subnets.id'],
+ ondelete='CASCADE'),
+ sa.PrimaryKeyConstraint('subnet_id')
+ )
diff --git a/vmware_nsx/db/nsxv_db.py b/vmware_nsx/db/nsxv_db.py
index f16473061c..6cf3769242 100644
--- a/vmware_nsx/db/nsxv_db.py
+++ b/vmware_nsx/db/nsxv_db.py
@@ -27,6 +27,7 @@ from vmware_nsx._i18n import _, _LE
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 dns_search_domain as ext_dns_search_domain
from vmware_nsx.plugins.nsx_v.vshield.common import constants
NsxvEdgeDhcpStaticBinding = nsxv_models.NsxvEdgeDhcpStaticBinding
@@ -715,3 +716,29 @@ def del_nsxv_lbaas_certificate_binding(session, cert_id, edge_id):
return (session.query(nsxv_models.NsxvLbaasCertificateBinding).
filter_by(cert_id=cert_id,
edge_id=edge_id).delete())
+
+
+def add_nsxv_subnet_ext_attributes(session, subnet_id, dns_search_domain):
+ with session.begin(subtransactions=True):
+ binding = nsxv_models.NsxvSubnetExtAttributes(
+ subnet_id=subnet_id,
+ dns_search_domain=dns_search_domain)
+ session.add(binding)
+ return binding
+
+
+def get_nsxv_subnet_ext_attributes(session, subnet_id):
+ try:
+ return session.query(
+ nsxv_models.NsxvSubnetExtAttributes).filter_by(
+ subnet_id=subnet_id).one()
+ except exc.NoResultFound:
+ return
+
+
+def update_nsxv_subnet_ext_attributes(session, subnet_id, dns_search_domain):
+ 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
+ return binding
diff --git a/vmware_nsx/db/nsxv_models.py b/vmware_nsx/db/nsxv_models.py
index 07e83ffced..7a817ee7bf 100644
--- a/vmware_nsx/db/nsxv_models.py
+++ b/vmware_nsx/db/nsxv_models.py
@@ -310,3 +310,20 @@ class NsxvLbaasCertificateBinding(model_base.BASEV2):
cert_id = sa.Column(sa.String(36), primary_key=True)
edge_id = sa.Column(sa.String(36), primary_key=True)
edge_cert_id = sa.Column(sa.String(36), nullable=False)
+
+
+class NsxvSubnetExtAttributes(model_base.BASEV2):
+ """Subnet attributes managed by NSX plugin extensions."""
+
+ __tablename__ = 'nsxv_subnet_ext_attributes'
+
+ 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)
+ # Add a relationship to the Subnet model in order to instruct
+ # SQLAlchemy to eagerly load this association
+ subnet = orm.relationship(
+ models_v2.Subnet,
+ backref=orm.backref("nsxv_subnet_attributes", lazy='joined',
+ uselist=False, cascade='delete'))
diff --git a/vmware_nsx/extensions/dns_search_domain.py b/vmware_nsx/extensions/dns_search_domain.py
new file mode 100644
index 0000000000..afaf10398c
--- /dev/null
+++ b/vmware_nsx/extensions/dns_search_domain.py
@@ -0,0 +1,52 @@
+# 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.api.v2 import attributes
+
+
+DNS_SEARCH_DOMAIN = 'dns_search_domain'
+EXTENDED_ATTRIBUTES_2_0 = {
+ 'subnets': {
+ DNS_SEARCH_DOMAIN: {'allow_post': True, 'allow_put': True,
+ 'default': attributes.ATTR_NOT_SPECIFIED,
+ 'validate': {'type:string': None},
+ 'is_visible': True},
+ }
+}
+
+
+class Dns_search_domain(extensions.ExtensionDescriptor):
+ """Extension class supporting dns search domains for subnets."""
+
+ @classmethod
+ def get_name(cls):
+ return "DNS search Domains"
+
+ @classmethod
+ def get_alias(cls):
+ return "dns-search-domain"
+
+ @classmethod
+ def get_description(cls):
+ return "Enable the ability to add DNS search domain name for Subnets"
+
+ @classmethod
+ def get_updated(cls):
+ return "2016-1-22T10:00:00-00:00"
+
+ def get_extended_resources(self, version):
+ if version == "2.0":
+ return EXTENDED_ATTRIBUTES_2_0
+ return {}
diff --git a/vmware_nsx/plugins/nsx_v/plugin.py b/vmware_nsx/plugins/nsx_v/plugin.py
index cc885f29de..98279e3679 100644
--- a/vmware_nsx/plugins/nsx_v/plugin.py
+++ b/vmware_nsx/plugins/nsx_v/plugin.py
@@ -69,6 +69,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 dns_search_domain as ext_dns_search_domain
from vmware_nsx.extensions import routersize
from vmware_nsx.plugins.nsx_v import managers
from vmware_nsx.plugins.nsx_v import md_proxy as nsx_v_md_proxy
@@ -102,6 +103,7 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
supported_extension_aliases = ["agent",
"allowed-address-pairs",
"binding",
+ "dns-search-domain",
"dvr",
"ext-gw-mode",
"multi-provider",
@@ -1136,12 +1138,51 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
s = super(NsxVPluginV2, self).create_subnet(context, subnet)
if s['enable_dhcp']:
try:
+ self._process_subnet_ext_attr_create(
+ session=context.session,
+ subnet_db=s,
+ subnet_req=data)
self._update_dhcp_service_with_subnet(context, s)
except Exception:
with excutils.save_and_reraise_exception():
self.delete_subnet(context, s['id'])
return s
+ def _process_subnet_ext_attr_create(self, session, subnet_db,
+ subnet_req):
+ # Verify if dns search domain for subnet is configured
+ dns_search_domain = subnet_req.get(
+ ext_dns_search_domain.DNS_SEARCH_DOMAIN)
+ if not attr.is_attr_set(dns_search_domain):
+ return
+ 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
+ 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:
+ nsxv_db.update_nsxv_subnet_ext_attributes(
+ session=session,
+ subnet_id=subnet_db['id'],
+ dns_search_domain=dns_search_domain)
+ subnet_db['dns_search_domain'] = dns_search_domain
+
+ 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:
+ self._process_subnet_ext_attr_create(session,
+ subnet_db,
+ subnet_req)
+ update_dns_search_domain = True
+ return update_dns_search_domain
+
def update_subnet(self, context, id, subnet):
s = subnet['subnet']
if "host_routes" in s and s["host_routes"]:
@@ -1151,7 +1192,9 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
gateway_ip = orig['gateway_ip']
enable_dhcp = orig['enable_dhcp']
subnet = super(NsxVPluginV2, self).update_subnet(context, id, subnet)
- if (gateway_ip != subnet['gateway_ip'] or
+ 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'])):
# Need to ensure that all of the subnet attributes will be reloaded
# when creating the edge bindings. Without adding this the original
@@ -1164,6 +1207,14 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
self._update_subnet_dhcp_status(subnet, context)
return subnet
+ db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
+ attr.SUBNETS, ['_extend_subnet_dict_dns_search_domain'])
+
+ def _extend_subnet_dict_dns_search_domain(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
+
def _update_subnet_dhcp_status(self, subnet, context):
network_id = subnet['network_id']
if subnet['enable_dhcp']:
diff --git a/vmware_nsx/plugins/nsx_v/vshield/edge_utils.py b/vmware_nsx/plugins/nsx_v/vshield/edge_utils.py
index 97e40bb6df..9b40b3c846 100644
--- a/vmware_nsx/plugins/nsx_v/vshield/edge_utils.py
+++ b/vmware_nsx/plugins/nsx_v/vshield/edge_utils.py
@@ -679,6 +679,7 @@ class EdgeManager(object):
+
"""
static_bindings = []
@@ -709,6 +710,12 @@ class EdgeManager(object):
elif len(name_servers) >= 2:
static_config['primaryNameServer'] = name_servers[0]
static_config['secondaryNameServer'] = name_servers[1]
+ # Set search domain for static binding
+ sub_binding = nsxv_db.get_nsxv_subnet_ext_attributes(
+ context.session,
+ subnet_id)
+ if sub_binding:
+ static_config['domainName'] = sub_binding.dns_search_domain
static_bindings.append(static_config)
return static_bindings
diff --git a/vmware_nsx/tests/unit/extensions/test_dns_search_domain.py b/vmware_nsx/tests/unit/extensions/test_dns_search_domain.py
new file mode 100644
index 0000000000..f26f784030
--- /dev/null
+++ b/vmware_nsx/tests/unit/extensions/test_dns_search_domain.py
@@ -0,0 +1,111 @@
+# 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 dns_search_domain as ext_dns_search_domain
+from vmware_nsx.plugins.nsx_v.vshield import edge_utils
+from vmware_nsx.tests.unit.nsx_v import test_plugin
+
+PLUGIN_NAME = 'vmware_nsx.plugin.NsxVPlugin'
+
+
+class DnsSearchDomainExtensionManager(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_dns_search_domain.get_extended_resources(version)
+
+
+class DnsSearchDomainExtensionTestCase(test_plugin.NsxVPluginV2TestCase):
+ """Test API extension dns-search-domain attribute."""
+
+ @mock.patch.object(edge_utils.EdgeManager, '_deploy_edge')
+ def setUp(self, plugin=PLUGIN_NAME):
+ ext_mgr = DnsSearchDomainExtensionManager()
+ super(DnsSearchDomainExtensionTestCase, self).setUp(ext_mgr=ext_mgr)
+
+ def _create_subnet_with_dns_search_domain(self, 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-dns-search-domain-subnet',
+ 'tenant_id': tenant_id,
+ 'dns_search_domain': dns_search_domain}}
+ subnet_req = self.new_create_request('subnets', data)
+ res = subnet_req.get_response(self.api)
+ return self.deserialize(self.fmt, res)
+
+ def test_subnet_create_with_dns_search_domain(self):
+ sub = self._create_subnet_with_dns_search_domain('vmware.com')
+ self.assertEqual('vmware.com', sub['subnet']['dns_search_domain'])
+
+ def test_subnet_update_with_dns_search_domain(self):
+ sub = self._create_subnet_with_dns_search_domain('vmware.com')
+ data = {'subnet': {'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('eng.vmware.com',
+ updated_sub['subnet']['dns_search_domain'])
+
+
+class DnsSearchDomainDBTestCase(test_db.NeutronDbPluginV2TestCase):
+
+ def setUp(self):
+ super(DnsSearchDomainDBTestCase, self).setUp()
+ self.session = db.get_session()
+
+ def test_get_nsxv_subnet_ext_attributes_no_dns_search_domain(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_dns_search_domain(self):
+ with self.subnet() as sub:
+ nsxv_db.add_nsxv_subnet_ext_attributes(
+ session=self.session,
+ subnet_id=sub['subnet']['id'],
+ 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('eng.vmware.com', sub_binding.dns_search_domain)
+ self.assertEqual(sub['subnet']['id'], sub_binding.subnet_id)
+
+ def test_update_nsxv_subnet_ext_attributes_dns_search_domain(self):
+ with self.subnet() as sub:
+ nsxv_db.add_nsxv_subnet_ext_attributes(
+ session=self.session,
+ subnet_id=sub['subnet']['id'],
+ dns_search_domain='eng.vmware.com')
+ sub_binding = nsxv_db.update_nsxv_subnet_ext_attributes(
+ session=self.session,
+ subnet_id=sub['subnet']['id'],
+ dns_search_domain='nsx.vmware.com')
+ self.assertEqual('nsx.vmware.com', sub_binding.dns_search_domain)