diff options
| 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 |
| commit | bdf53c30394f6fc6558d3be52db7166f3796b399 (patch) | |
| tree | 898554b66618e8df2c201e5617c9c6ebedae6fed | |
| parent | b57628a7d3ffbe86be7f9a6e6da763e2aba04af5 (diff) | |
| download | nova-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.py | 25 | ||||
| -rw-r--r-- | nova/network/manager.py | 42 | ||||
| -rw-r--r-- | nova/network/rpcapi.py | 5 | ||||
| -rw-r--r-- | nova/tests/fake_network.py | 2 | ||||
| -rw-r--r-- | nova/tests/network/test_linux_net.py | 24 | ||||
| -rw-r--r-- | nova/tests/network/test_manager.py | 3 | ||||
| -rw-r--r-- | nova/tests/network/test_rpcapi.py | 10 |
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', |
