summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorÉdouard Thuleau <edouard.thuleau@orange.com>2012-11-14 18:59:01 +0100
committerÉdouard Thuleau <edouard.thuleau@orange.com>2012-11-29 19:49:37 +0100
commitbdf53c30394f6fc6558d3be52db7166f3796b399 (patch)
tree898554b66618e8df2c201e5617c9c6ebedae6fed
parentb57628a7d3ffbe86be7f9a6e6da763e2aba04af5 (diff)
downloadnova-bdf53c30394f6fc6558d3be52db7166f3796b399.tar.gz
nova-bdf53c30394f6fc6558d3be52db7166f3796b399.tar.xz
nova-bdf53c30394f6fc6558d3be52db7166f3796b399.zip
Multi host DHCP networking and local DNS resolving
Configure all dnsmasq instances to use the hosts file instead of the default local file '/etc/host' when the network is configured in 'multi_host' mode. This hosts file contains all DNS entries of the network and it is replicated on all host where a 'nova-network' daemon is instantiated. There is a hosts file for each networks. It needs to add a new network API to update the host file on all network hosts. When a network host is called to (de)allocate a VM, it (de)allocate the instance fixed IP adress(es) on the concerned network host and when it's done, it calls all networks to update their DNS db entries if it's needed through a fanout cast. DocImpact: new config options Fixes LP bug #1078808 Change-Id: I5c13bd895ebd31f276eb14e996025ddcfb67c3d9
-rw-r--r--nova/network/linux_net.py25
-rw-r--r--nova/network/manager.py42
-rw-r--r--nova/network/rpcapi.py5
-rw-r--r--nova/tests/fake_network.py2
-rw-r--r--nova/tests/network/test_linux_net.py24
-rw-r--r--nova/tests/network/test_manager.py3
-rw-r--r--nova/tests/network/test_rpcapi.py10
7 files changed, 103 insertions, 8 deletions
diff --git a/nova/network/linux_net.py b/nova/network/linux_net.py
index f5d3b19ac..7929c235c 100644
--- a/nova/network/linux_net.py
+++ b/nova/network/linux_net.py
@@ -729,6 +729,15 @@ def get_dhcp_hosts(context, network_ref):
return '\n'.join(hosts)
+def get_dns_hosts(context, network_ref):
+ """Get network's DNS hosts in hosts format."""
+ hosts = []
+ for data in db.network_get_associated_fixed_ips(context,
+ network_ref['id']):
+ hosts.append(_host_dns(data))
+ return '\n'.join(hosts)
+
+
def _add_dnsmasq_accept_rules(dev):
"""Allow DHCP and DNS traffic through to dnsmasq."""
table = iptables_manager.ipv4['filter']
@@ -780,6 +789,12 @@ def update_dhcp(context, dev, network_ref):
restart_dhcp(context, dev, network_ref)
+def update_dns(context, dev, network_ref):
+ hostsfile = _dhcp_file(dev, 'hosts')
+ write_to_file(hostsfile, get_dns_hosts(context, network_ref))
+ restart_dhcp(context, dev, network_ref)
+
+
def update_dhcp_hostfile_with_text(dev, hosts_text):
conffile = _dhcp_file(dev, 'conf')
write_to_file(conffile, hosts_text)
@@ -858,6 +873,8 @@ def restart_dhcp(context, dev, network_ref):
'--dhcp-hostsfile=%s' % _dhcp_file(dev, 'conf'),
'--dhcp-script=%s' % CONF.dhcpbridge,
'--leasefile-ro']
+ if network_ref['multi_host'] and not CONF.dns_server:
+ cmd += ['--no-hosts', '--addn-hosts=%s' % _dhcp_file(dev, 'hosts')]
if CONF.dns_server:
cmd += ['-h', '-R', '--server=%s' % CONF.dns_server]
@@ -945,6 +962,12 @@ def _host_dhcp(data):
data['address'])
+def _host_dns(data):
+ return '%s\t%s.%s' % (data['address'],
+ data['instance_hostname'],
+ CONF.dhcp_domain)
+
+
def _host_dhcp_opts(data):
"""Return an empty gateway option."""
return '%s,%s' % (_host_dhcp_network(data), 3)
@@ -967,7 +990,7 @@ def device_exists(device):
def _dhcp_file(dev, kind):
- """Return path to a pid, leases or conf file for a bridge/device."""
+ """Return path to a pid, leases, hosts or conf file for a bridge/device."""
fileutils.ensure_tree(CONF.networks_path)
return os.path.abspath('%s/nova-%s.%s' % (CONF.networks_path,
dev,
diff --git a/nova/network/manager.py b/nova/network/manager.py
index 92b0ec0ae..a7934159b 100644
--- a/nova/network/manager.py
+++ b/nova/network/manager.py
@@ -157,6 +157,11 @@ network_opts = [
default=False,
help='If True in multi_host mode, all compute hosts share '
'the same dhcp address.'),
+ cfg.BoolOpt('update_dns_entries',
+ default=False,
+ help='If True, when a DNS entry must be updated, it sends a '
+ 'fanout cast to all network hosts to update their DNS '
+ 'entries in multi host mode'),
cfg.StrOpt('dhcp_domain',
default='novalocal',
help='domain to use for building the hostnames'),
@@ -871,7 +876,7 @@ class NetworkManager(manager.SchedulerDependentManager):
The one at a time part is to flatten the layout to help scale
"""
- RPC_API_VERSION = '1.2'
+ RPC_API_VERSION = '1.3'
# If True, this manager requires VIF to create a bridge.
SHOULD_CREATE_BRIDGE = False
@@ -949,6 +954,9 @@ class NetworkManager(manager.SchedulerDependentManager):
ctxt = context.get_admin_context()
for network in self.db.network_get_all_by_host(ctxt, self.host):
self._setup_network_on_host(ctxt, network)
+ if CONF.update_dns_entries:
+ dev = self.driver.get_dev(network)
+ self.driver.update_dns(ctxt, dev, network)
@manager.periodic_task
def _disassociate_stale_fixed_ips(self, context):
@@ -1102,6 +1110,11 @@ class NetworkManager(manager.SchedulerDependentManager):
self._allocate_fixed_ips(admin_context, instance_id,
host, networks, vpn=vpn,
requested_networks=requested_networks)
+
+ if CONF.update_dns_entries:
+ network_ids = [network['id'] for network in networks]
+ self.network_rpcapi.update_dns(context, network_ids)
+
return self.get_instance_nw_info(context, instance_id, instance_uuid,
rxtx_factor, host)
@@ -1119,6 +1132,7 @@ class NetworkManager(manager.SchedulerDependentManager):
instance_id = kwargs.pop('instance_id')
instance = self.db.instance_get(read_deleted_context, instance_id)
+ host = kwargs.get('host')
try:
fixed_ips = (kwargs.get('fixed_ips') or
@@ -1130,8 +1144,11 @@ class NetworkManager(manager.SchedulerDependentManager):
context=read_deleted_context)
# deallocate fixed ips
for fixed_ip in fixed_ips:
- self.deallocate_fixed_ip(context, fixed_ip['address'],
- host=kwargs.get('host'))
+ self.deallocate_fixed_ip(context, fixed_ip['address'], host=host)
+
+ if CONF.update_dns_entries:
+ network_ids = [fixed_ip['network_id'] for fixed_ip in fixed_ips]
+ self.network_rpcapi.update_dns(context, network_ids)
# deallocate vifs (mac addresses)
self.db.virtual_interface_delete_by_instance(read_deleted_context,
@@ -1910,6 +1927,21 @@ class NetworkManager(manager.SchedulerDependentManager):
return self.db.virtual_interface_get_by_address(context,
mac_address)
+ def update_dns(self, context, network_ids):
+ """Called when fixed IP is allocated or deallocated"""
+ if CONF.fake_network:
+ return
+
+ for network_id in network_ids:
+ network = self.db.network_get(context, network_id)
+ if not network['multi_host']:
+ continue
+ host_networks = self.db.network_get_all_by_host(context, self.host)
+ for host_network in host_networks:
+ if host_network['id'] == network_id:
+ dev = self.driver.get_dev(network)
+ self.driver.update_dns(context, dev, network)
+
class FlatManager(NetworkManager):
"""Basic network where no vlans are used.
@@ -2017,6 +2049,10 @@ class FlatManager(NetworkManager):
source=None, dest=None):
pass
+ def update_dns(self, context, network_ids):
+ """Called when fixed IP is allocated or deallocated"""
+ pass
+
class FlatDHCPManager(RPCAllocateFixedIP, FloatingIP, NetworkManager):
"""Flat networking with dhcp.
diff --git a/nova/network/rpcapi.py b/nova/network/rpcapi.py
index 8dd7ca9ae..dc2ae6698 100644
--- a/nova/network/rpcapi.py
+++ b/nova/network/rpcapi.py
@@ -35,6 +35,7 @@ class NetworkAPI(rpc_proxy.RpcProxy):
1.0 - Initial version.
1.1 - Adds migrate_instance_[start|finish]
1.2 - Make migrate_instance_[start|finish] a little more flexible
+ 1.3 - Adds fanout cast update_dns for multi_host networks
'''
#
@@ -240,6 +241,10 @@ class NetworkAPI(rpc_proxy.RpcProxy):
address=address, host=host),
topic=rpc.queue_get_for(ctxt, self.topic, host))
+ def update_dns(self, ctxt, network_ids):
+ return self.fanout_cast(ctxt, self.make_msg('update_dns',
+ network_ids=network_ids), version='1.3')
+
# NOTE(russellb): Ideally this would not have a prefix of '_' since it is
# a part of the rpc API. However, this is how it was being called when the
# 1.0 API was being documented using this client proxy class. It should be
diff --git a/nova/tests/fake_network.py b/nova/tests/fake_network.py
index fc8e4c249..1b96f95dd 100644
--- a/nova/tests/fake_network.py
+++ b/nova/tests/fake_network.py
@@ -24,6 +24,7 @@ from nova.network import api as network_api
from nova.network import manager as network_manager
from nova.network import model as network_model
from nova.network import nova_ipam_lib
+from nova.network import rpcapi as network_rpcapi
from nova.openstack.common import cfg
from nova import utils
from nova.virt.libvirt import config as libvirt_config
@@ -146,6 +147,7 @@ class FakeNetworkManager(network_manager.NetworkManager):
self.db = self.FakeDB()
self.deallocate_called = None
self.deallocate_fixed_ip_calls = []
+ self.network_rpcapi = network_rpcapi.NetworkAPI()
# TODO(matelakat) method signature should align with the faked one's
def deallocate_fixed_ip(self, context, address=None, host=None):
diff --git a/nova/tests/network/test_linux_net.py b/nova/tests/network/test_linux_net.py
index 4f94eec95..55a9c7777 100644
--- a/nova/tests/network/test_linux_net.py
+++ b/nova/tests/network/test_linux_net.py
@@ -306,6 +306,24 @@ class LinuxNetworkTestCase(test.TestCase):
self.assertEquals(actual_hosts, expected)
+ def test_get_dns_hosts_for_nw00(self):
+ expected = (
+ "192.168.0.100\tfake_instance00.novalocal\n"
+ "192.168.1.101\tfake_instance01.novalocal\n"
+ "192.168.0.102\tfake_instance00.novalocal"
+ )
+ actual_hosts = self.driver.get_dns_hosts(self.context, networks[0])
+ self.assertEquals(actual_hosts, expected)
+
+ def test_get_dns_hosts_for_nw01(self):
+ expected = (
+ "192.168.1.100\tfake_instance00.novalocal\n"
+ "192.168.0.101\tfake_instance01.novalocal\n"
+ "192.168.1.102\tfake_instance01.novalocal"
+ )
+ actual_hosts = self.driver.get_dns_hosts(self.context, networks[1])
+ self.assertEquals(actual_hosts, expected)
+
def test_get_dhcp_opts_for_nw00(self):
expected_opts = 'NW-3,3\nNW-4,3'
actual_opts = self.driver.get_dhcp_opts(self.context, networks[0])
@@ -333,6 +351,12 @@ class LinuxNetworkTestCase(test.TestCase):
actual = self.driver._host_dhcp(data)
self.assertEquals(actual, expected)
+ def test_host_dns_without_default_gateway_network(self):
+ expected = "192.168.0.100\tfake_instance00.novalocal"
+ data = get_associated(self.context, 0)[0]
+ actual = self.driver._host_dns(data)
+ self.assertEquals(actual, expected)
+
def test_linux_bridge_driver_plug(self):
"""Makes sure plug doesn't drop FORWARD by default.
diff --git a/nova/tests/network/test_manager.py b/nova/tests/network/test_manager.py
index d33b0e582..8d4a511b6 100644
--- a/nova/tests/network/test_manager.py
+++ b/nova/tests/network/test_manager.py
@@ -1120,7 +1120,8 @@ class CommonNetworkTestCase(test.TestCase):
db.virtual_interface_delete_by_instance = lambda _x, _y: None
ctx = context.RequestContext('igonre', 'igonre')
- db.fixed_ip_get_by_instance = lambda x, y: [dict(address='1.2.3.4')]
+ db.fixed_ip_get_by_instance = lambda x, y: [dict(address='1.2.3.4',
+ network_id='ignoredid')]
manager.deallocate_for_instance(
ctx, instance_id='ignore', host='somehost')
diff --git a/nova/tests/network/test_rpcapi.py b/nova/tests/network/test_rpcapi.py
index 760db7ecb..c31b34a51 100644
--- a/nova/tests/network/test_rpcapi.py
+++ b/nova/tests/network/test_rpcapi.py
@@ -43,10 +43,10 @@ class NetworkRpcAPITestCase(test.TestCase):
args['dest'] = args.pop('dest_compute')
targeted_methods = [
'lease_fixed_ip', 'release_fixed_ip', 'rpc_setup_network_on_host',
- '_rpc_allocate_fixed_ip', 'deallocate_fixed_ip',
+ '_rpc_allocate_fixed_ip', 'deallocate_fixed_ip', 'update_dns',
'_associate_floating_ip', '_disassociate_floating_ip',
- 'lease_fixed_ip', 'release_fixed_ip',
- 'migrate_instance_start', 'migrate_instance_finish',
+ 'lease_fixed_ip', 'release_fixed_ip', 'migrate_instance_start',
+ 'migrate_instance_finish',
]
if method in targeted_methods and 'host' in kwargs:
if method != 'deallocate_fixed_ip':
@@ -250,6 +250,10 @@ class NetworkRpcAPITestCase(test.TestCase):
self._test_network_api('deallocate_fixed_ip', rpc_method='call',
address='fake_addr', host='fake_host')
+ def test_update_dns(self):
+ self._test_network_api('update_dns', rpc_method='fanout_cast',
+ network_ids='fake_id', version='1.3')
+
def test__associate_floating_ip(self):
self._test_network_api('_associate_floating_ip', rpc_method='call',
floating_address='fake_addr', fixed_address='fixed_address',