Keep dns nameserver order consistency

Currently, there is no dns servers prioritization for subnets
for Neutron.

Generally speaking, it is useful to keep the order of dns
nameservers consistent. Add a new column named 'order' in table
'dnsnameservers' and add nameserver into DB one by one.

Closes-Bug: #1218629
Implements: blueprint keep-dns-nameserver-orderconsistency
Change-Id: Id937aea411397d39370368a4eb45be26c4eefa9e
This commit is contained in:
changzhi 2015-07-16 10:14:16 +08:00
parent 6b0ab7fd63
commit cb60d0bb4e
9 changed files with 188 additions and 19 deletions

View File

@ -0,0 +1,74 @@
Keep DNS Nameserver Order Consistency In Neutron
================================================
In Neutron subnets, DNS nameservers are given priority when created or updated.
This means if you create a subnet with multiple DNS servers, the order will
be retained and guests will receive the DNS servers in the order you
created them in when the subnet was created. The same thing applies for update
operations on subnets to add, remove, or update DNS servers.
Get Subnet Details Info
-----------------------
::
changzhi@stack:~/devstack$ neutron subnet-list
+--------------------------------------+------+-------------+--------------------------------------------+
| id | name | cidr | allocation_pools |
+--------------------------------------+------+-------------+--------------------------------------------+
| 1a2d261b-b233-3ab9-902e-88576a82afa6 | | 10.0.0.0/24 | {"start": "10.0.0.2", "end": "10.0.0.254"} |
+--------------------------------------+------+-------------+--------------------------------------------+
changzhi@stack:~/devstack$ neutron subnet-show 1a2d261b-b233-3ab9-902e-88576a82afa6
+------------------+--------------------------------------------+
| Field | Value |
+------------------+--------------------------------------------+
| allocation_pools | {"start": "10.0.0.2", "end": "10.0.0.254"} |
| cidr | 10.0.0.0/24 |
| dns_nameservers | 1.1.1.1 |
| | 2.2.2.2 |
| | 3.3.3.3 |
| enable_dhcp | True |
| gateway_ip | 10.0.0.1 |
| host_routes | |
| id | 1a2d26fb-b733-4ab3-992e-88554a87afa6 |
| ip_version | 4 |
| name | |
| network_id | a404518c-800d-2353-9193-57dbb42ac5ee |
| tenant_id | 3868290ab10f417390acbb754160dbb2 |
+------------------+--------------------------------------------+
Update Subnet DNS Nameservers
-----------------------------
::
neutron subnet-update 1a2d261b-b233-3ab9-902e-88576a82afa6 \
--dns_nameservers list=true 3.3.3.3 2.2.2.2 1.1.1.1
changzhi@stack:~/devstack$ neutron subnet-show 1a2d261b-b233-3ab9-902e-88576a82afa6
+------------------+--------------------------------------------+
| Field | Value |
+------------------+--------------------------------------------+
| allocation_pools | {"start": "10.0.0.2", "end": "10.0.0.254"} |
| cidr | 10.0.0.0/24 |
| dns_nameservers | 3.3.3.3 |
| | 2.2.2.2 |
| | 1.1.1.1 |
| enable_dhcp | True |
| gateway_ip | 10.0.0.1 |
| host_routes | |
| id | 1a2d26fb-b733-4ab3-992e-88554a87afa6 |
| ip_version | 4 |
| name | |
| network_id | a404518c-800d-2353-9193-57dbb42ac5ee |
| tenant_id | 3868290ab10f417390acbb754160dbb2 |
+------------------+--------------------------------------------+
As shown in above output, the order of the DNS nameservers has been updated.
New virtual machines deployed to this subnet will receive the DNS nameservers
in this new priority order. Existing virtual machines that have already been
deployed will not be immediately affected by changing the DNS nameserver order
on the neutron subnet. Virtual machines that are configured to get their IP
address via DHCP will detect the DNS nameserver order change
when their DHCP lease expires or when the virtual machine is restarted.
Existing virtual machines configured with a static IP address will never
detect the updated DNS nameserver order.

View File

@ -53,6 +53,7 @@ Neutron Internals
advanced_services advanced_services
oslo-incubator oslo-incubator
callbacks callbacks
dns_order
Testing Testing
------- -------

View File

@ -172,7 +172,8 @@ class DbBasePluginCommon(common_db_mixin.CommonDbMixin):
def _get_dns_by_subnet(self, context, subnet_id): def _get_dns_by_subnet(self, context, subnet_id):
dns_qry = context.session.query(models_v2.DNSNameServer) dns_qry = context.session.query(models_v2.DNSNameServer)
return dns_qry.filter_by(subnet_id=subnet_id).all() return dns_qry.filter_by(subnet_id=subnet_id).order_by(
models_v2.DNSNameServer.order).all()
def _get_route_by_subnet(self, context, subnet_id): def _get_route_by_subnet(self, context, subnet_id):
route_qry = context.session.query(models_v2.SubnetRoute) route_qry = context.session.query(models_v2.SubnetRoute)

View File

@ -138,22 +138,22 @@ class IpamBackendMixin(db_base_plugin_common.DbBasePluginCommon):
def _update_subnet_dns_nameservers(self, context, id, s): def _update_subnet_dns_nameservers(self, context, id, s):
old_dns_list = self._get_dns_by_subnet(context, id) old_dns_list = self._get_dns_by_subnet(context, id)
new_dns_addr_set = set(s["dns_nameservers"]) new_dns_addr_list = s["dns_nameservers"]
old_dns_addr_set = set([dns['address']
for dns in old_dns_list])
new_dns = list(new_dns_addr_set) # NOTE(changzhi) delete all dns nameservers from db
for dns_addr in old_dns_addr_set - new_dns_addr_set: # when update subnet's DNS nameservers. And store new
for dns in old_dns_list: # nameservers with order one by one.
if dns['address'] == dns_addr: for dns in old_dns_list:
context.session.delete(dns) context.session.delete(dns)
for dns_addr in new_dns_addr_set - old_dns_addr_set:
for order, server in enumerate(new_dns_addr_list):
dns = models_v2.DNSNameServer( dns = models_v2.DNSNameServer(
address=dns_addr, address=server,
order=order,
subnet_id=id) subnet_id=id)
context.session.add(dns) context.session.add(dns)
del s["dns_nameservers"] del s["dns_nameservers"]
return new_dns return new_dns_addr_list
def _update_subnet_allocation_pools(self, context, subnet_id, s): def _update_subnet_allocation_pools(self, context, subnet_id, s):
context.session.query(models_v2.IPAllocationPool).filter_by( context.session.query(models_v2.IPAllocationPool).filter_by(
@ -424,11 +424,15 @@ class IpamBackendMixin(db_base_plugin_common.DbBasePluginCommon):
subnet = models_v2.Subnet(**subnet_args) subnet = models_v2.Subnet(**subnet_args)
context.session.add(subnet) context.session.add(subnet)
# NOTE(changzhi) Store DNS nameservers with order into DB one
# by one when create subnet with DNS nameservers
if attributes.is_attr_set(dns_nameservers): if attributes.is_attr_set(dns_nameservers):
for addr in dns_nameservers: for order, server in enumerate(dns_nameservers):
ns = models_v2.DNSNameServer(address=addr, dns = models_v2.DNSNameServer(
subnet_id=subnet.id) address=server,
context.session.add(ns) order=order,
subnet_id=subnet.id)
context.session.add(dns)
if attributes.is_attr_set(host_routes): if attributes.is_attr_set(host_routes):
for rt in host_routes: for rt in host_routes:

View File

@ -1,3 +1,3 @@
2a16083502f3 1c844d1677f7
45f955889773 45f955889773
kilo kilo

View File

@ -0,0 +1,35 @@
# Copyright 2015 OpenStack Foundation
#
# 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.
#
"""add order to dnsnameservers
Revision ID: 1c844d1677f7
Revises: 2a16083502f3
Create Date: 2015-07-21 22:59:03.383850
"""
# revision identifiers, used by Alembic.
revision = '1c844d1677f7'
down_revision = '2a16083502f3'
from alembic import op
import sqlalchemy as sa
def upgrade():
op.add_column('dnsnameservers',
sa.Column('order', sa.Integer(),
server_default='0', nullable=False))

View File

@ -178,6 +178,7 @@ class DNSNameServer(model_base.BASEV2):
sa.ForeignKey('subnets.id', sa.ForeignKey('subnets.id',
ondelete="CASCADE"), ondelete="CASCADE"),
primary_key=True) primary_key=True)
order = sa.Column(sa.Integer, nullable=False, server_default='0')
class Subnet(model_base.BASEV2, HasId, HasTenant): class Subnet(model_base.BASEV2, HasId, HasTenant):
@ -201,6 +202,7 @@ class Subnet(model_base.BASEV2, HasId, HasTenant):
dns_nameservers = orm.relationship(DNSNameServer, dns_nameservers = orm.relationship(DNSNameServer,
backref='subnet', backref='subnet',
cascade='all, delete, delete-orphan', cascade='all, delete, delete-orphan',
order_by=DNSNameServer.order,
lazy='joined') lazy='joined')
routes = orm.relationship(SubnetRoute, routes = orm.relationship(SubnetRoute,
backref='subnet', backref='subnet',

View File

@ -333,6 +333,17 @@ class FakeV4SubnetMultipleAgentsWithoutDnsProvided(object):
host_routes = [] host_routes = []
class FakeV4SubnetAgentWithManyDnsProvided(object):
id = 'dddddddd-dddd-dddd-dddd-dddddddddddd'
ip_version = 4
cidr = '192.168.0.0/24'
gateway_ip = '192.168.0.1'
enable_dhcp = True
dns_nameservers = ['2.2.2.2', '9.9.9.9', '1.1.1.1',
'3.3.3.3']
host_routes = []
class FakeV4MultipleAgentsWithoutDnsProvided(object): class FakeV4MultipleAgentsWithoutDnsProvided(object):
id = 'ffffffff-ffff-ffff-ffff-ffffffffffff' id = 'ffffffff-ffff-ffff-ffff-ffffffffffff'
subnets = [FakeV4SubnetMultipleAgentsWithoutDnsProvided()] subnets = [FakeV4SubnetMultipleAgentsWithoutDnsProvided()]
@ -341,6 +352,14 @@ class FakeV4MultipleAgentsWithoutDnsProvided(object):
namespace = 'qdhcp-ns' namespace = 'qdhcp-ns'
class FakeV4AgentWithManyDnsProvided(object):
id = 'ffffffff-ffff-ffff-ffff-ffffffffffff'
subnets = [FakeV4SubnetAgentWithManyDnsProvided()]
ports = [FakePort1(), FakePort2(), FakePort3(), FakeRouterPort(),
FakePortMultipleAgents1()]
namespace = 'qdhcp-ns'
class FakeV4SubnetMultipleAgentsWithDnsProvided(object): class FakeV4SubnetMultipleAgentsWithDnsProvided(object):
id = 'dddddddd-dddd-dddd-dddd-dddddddddddd' id = 'dddddddd-dddd-dddd-dddd-dddddddddddd'
ip_version = 4 ip_version = 4
@ -1135,6 +1154,18 @@ class TestDnsmasq(TestBase):
self._test_output_opts_file(expected, self._test_output_opts_file(expected,
FakeV4MultipleAgentsWithoutDnsProvided()) FakeV4MultipleAgentsWithoutDnsProvided())
def test_output_opts_file_agent_with_many_dns_provided(self):
expected = ('tag:tag0,'
'option:dns-server,2.2.2.2,9.9.9.9,1.1.1.1,3.3.3.3\n'
'tag:tag0,option:classless-static-route,'
'169.254.169.254/32,192.168.0.1,0.0.0.0/0,192.168.0.1\n'
'tag:tag0,249,169.254.169.254/32,192.168.0.1,0.0.0.0/0,'
'192.168.0.1\n'
'tag:tag0,option:router,192.168.0.1').lstrip()
self._test_output_opts_file(expected,
FakeV4AgentWithManyDnsProvided())
def test_output_opts_file_multiple_agents_with_dns_provided(self): def test_output_opts_file_multiple_agents_with_dns_provided(self):
expected = ('tag:tag0,option:dns-server,8.8.8.8\n' expected = ('tag:tag0,option:dns-server,8.8.8.8\n'
'tag:tag0,option:classless-static-route,' 'tag:tag0,option:classless-static-route,'

View File

@ -3921,8 +3921,8 @@ class TestSubnetsV2(NeutronDbPluginV2TestCase):
res = self.deserialize(self.fmt, req.get_response(self.api)) res = self.deserialize(self.fmt, req.get_response(self.api))
self.assertEqual(sorted(res['subnet']['host_routes']), self.assertEqual(sorted(res['subnet']['host_routes']),
sorted(host_routes)) sorted(host_routes))
self.assertEqual(sorted(res['subnet']['dns_nameservers']), self.assertEqual(res['subnet']['dns_nameservers'],
sorted(dns_nameservers)) dns_nameservers)
def test_update_subnet_shared_returns_400(self): def test_update_subnet_shared_returns_400(self):
with self.network(shared=True) as network: with self.network(shared=True) as network:
@ -4463,6 +4463,27 @@ class TestSubnetsV2(NeutronDbPluginV2TestCase):
self.assertEqual(res['subnet']['dns_nameservers'], self.assertEqual(res['subnet']['dns_nameservers'],
data['subnet']['dns_nameservers']) data['subnet']['dns_nameservers'])
def test_subnet_lifecycle_dns_retains_order(self):
cfg.CONF.set_override('max_dns_nameservers', 3)
with self.subnet(dns_nameservers=['1.1.1.1', '2.2.2.2',
'3.3.3.3']) as subnet:
subnets = self._show('subnets', subnet['subnet']['id'],
expected_code=webob.exc.HTTPOk.code)
self.assertEqual(['1.1.1.1', '2.2.2.2', '3.3.3.3'],
subnets['subnet']['dns_nameservers'])
data = {'subnet': {'dns_nameservers': ['2.2.2.2', '3.3.3.3',
'1.1.1.1']}}
req = self.new_update_request('subnets',
data,
subnet['subnet']['id'])
res = self.deserialize(self.fmt, req.get_response(self.api))
self.assertEqual(data['subnet']['dns_nameservers'],
res['subnet']['dns_nameservers'])
subnets = self._show('subnets', subnet['subnet']['id'],
expected_code=webob.exc.HTTPOk.code)
self.assertEqual(data['subnet']['dns_nameservers'],
subnets['subnet']['dns_nameservers'])
def test_update_subnet_dns_to_None(self): def test_update_subnet_dns_to_None(self):
with self.subnet(dns_nameservers=['11.0.0.1']) as subnet: with self.subnet(dns_nameservers=['11.0.0.1']) as subnet:
data = {'subnet': {'dns_nameservers': None}} data = {'subnet': {'dns_nameservers': None}}