From 88a0f06ac7f104220b2d92f66599089a9e1d4c92 Mon Sep 17 00:00:00 2001 From: Andrew Bogott Date: Fri, 2 Dec 2011 13:29:13 -0600 Subject: Handle the 'instance' half of blueprint public-and-private-dns Added a minimalist flat-file DNS example driver, MiniDNS. Added tests for MiniDNS and a test that uses MiniDNS to validate instance DNS creation. Change-Id: I512018b7ed90ac2f388277443ee69b872ed60ef2 --- Authors | 1 + nova/flags.py | 5 ++ nova/network/instance_dns_driver.py | 41 +++++++++++ nova/network/manager.py | 11 +++ nova/network/minidns.py | 134 ++++++++++++++++++++++++++++++++++++ nova/tests/test_network.py | 62 +++++++++++++++++ 6 files changed, 254 insertions(+) create mode 100644 nova/network/instance_dns_driver.py create mode 100644 nova/network/minidns.py diff --git a/Authors b/Authors index 11e1daa8c..e2fbe441e 100644 --- a/Authors +++ b/Authors @@ -6,6 +6,7 @@ Ahmad Hassan Alex Meade Alexander Sakhnov Alvaro Lopez Garcia +Andrew Bogott Andrey Brindeyev Andy Smith Andy Southgate diff --git a/nova/flags.py b/nova/flags.py index dc4e648f0..97933a707 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -397,6 +397,11 @@ DEFINE_string('compute_manager', 'nova.compute.manager.ComputeManager', 'Manager for compute') DEFINE_string('console_manager', 'nova.console.manager.ConsoleProxyManager', 'Manager for console proxy') +DEFINE_string('instance_dns_manager', + 'nova.network.instance_dns_driver.InstanceDNSManagerDriver', + 'DNS Manager for instance IPs') +DEFINE_string('instance_dns_zone', '', + 'DNS Zone for instance IPs') DEFINE_string('network_manager', 'nova.network.manager.VlanManager', 'Manager for network') DEFINE_string('volume_manager', 'nova.volume.manager.VolumeManager', diff --git a/nova/network/instance_dns_driver.py b/nova/network/instance_dns_driver.py new file mode 100644 index 000000000..c0c6af893 --- /dev/null +++ b/nova/network/instance_dns_driver.py @@ -0,0 +1,41 @@ +# Copyright 2011 Andrew Bogott for the Wikimedia 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. + + +class InstanceDNSManagerDriver(object): + """ Defines the instance DNS interface. Does nothing. """ + + def __init__(self): + pass + + def get_zones(self): + pass + + def create_entry(self, _name, _address, _type, _dnszone): + pass + + def delete_entry(self, _name, _dnszone=""): + pass + + def rename_entry(self, _address, _name, _dnszone): + pass + + def modify_address(self, _name, _address, _dnszone): + pass + + def get_entries_by_address(self, _address, _dnszone=""): + return [] + + def get_entries_by_name(self, _name, _dnszone=""): + return [] diff --git a/nova/network/manager.py b/nova/network/manager.py index b3e2e55f9..2d62581e0 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -466,6 +466,8 @@ class NetworkManager(manager.SchedulerDependentManager): if not network_driver: network_driver = FLAGS.network_driver self.driver = utils.import_object(network_driver) + temp = utils.import_object(FLAGS.instance_dns_manager) + self.instance_dns_manager = temp self.network_api = network_api.API() self.compute_api = compute_api.API() super(NetworkManager, self).__init__(service_name='network', @@ -822,6 +824,11 @@ class NetworkManager(manager.SchedulerDependentManager): 'virtual_interface_id': vif['id']} self.db.fixed_ip_update(context, address, values) + instance_ref = self.db.instance_get(context, instance_id) + name = instance_ref['display_name'] + self.instance_dns_manager.create_entry(name, address, + "type", FLAGS.instance_dns_zone) + self._setup_network(context, network) return address @@ -835,6 +842,10 @@ class NetworkManager(manager.SchedulerDependentManager): instance_id = instance_ref['id'] self._do_trigger_security_group_members_refresh_for_instance( instance_id) + + for name in self.instance_dns_manager.get_entries_by_address(address): + self.instance_dns_manager.delete_entry(name) + if FLAGS.force_dhcp_release: dev = self.driver.get_dev(fixed_ip_ref['network']) vif = self.db.virtual_interface_get_by_instance_and_network( diff --git a/nova/network/minidns.py b/nova/network/minidns.py new file mode 100644 index 000000000..3b9331318 --- /dev/null +++ b/nova/network/minidns.py @@ -0,0 +1,134 @@ +# Copyright 2011 Andrew Bogott for the Wikimedia 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. + +import os +import shutil +import tempfile + +from nova import flags + + +class MiniDNS(object): + """ Trivial DNS driver. This will read/write to a local, flat file + and have no effect on your actual DNS system. This class is + strictly for testing purposes, and should keep you out of dependency + hell. + + Note that there is almost certainly a race condition here that + will manifest anytime instances are rapidly created and deleted. + A proper implementation will need some manner of locking.""" + + def __init__(self): + if flags.FLAGS.logdir: + self.filename = os.path.join(flags.FLAGS.logdir, "dnstest.txt") + else: + self.filename = "dnstest.txt" + + if not os.path.exists(self.filename): + f = open(self.filename, "w+") + f.write("# minidns\n\n\n") + f.close() + + def get_zones(self): + return ["zone1.example.org", "zone2.example.org", "zone3.example.org"] + + def qualify(self, name, zone): + if zone: + qualified = "%s.%s" % (name, zone) + else: + qualified = name + + return qualified + + def create_entry(self, name, address, type, dnszone): + outfile = open(self.filename, 'a+') + outfile.write("%s %s %s\n" % + (address, self.qualify(name, dnszone), type)) + outfile.close() + + def parse_line(self, line): + vals = line.split() + if len(vals) < 3: + return None + else: + entry = {} + entry['address'] = vals[0] + entry['name'] = vals[1] + entry['type'] = vals[2] + return entry + + def delete_entry(self, name, dnszone=""): + infile = open(self.filename, 'r') + outfile = tempfile.NamedTemporaryFile('w', delete=False) + for line in infile: + entry = self.parse_line(line) + if ((not entry) or + entry['name'] != self.qualify(name, dnszone).lower()): + outfile.write(line) + infile.close() + outfile.close() + shutil.move(outfile.name, self.filename) + + def rename_entry(self, address, name, dnszone): + infile = open(self.filename, 'r') + outfile = tempfile.NamedTemporaryFile('w', delete=False) + for line in infile: + entry = self.parse_line(line) + if entry and entry['address'] == address.lower(): + outfile.write("%s %s %s\n" % + (address, self.qualify(name, dnszone), entry['type'])) + else: + outfile.write(line) + infile.close() + outfile.close() + shutil.move(outfile.name, self.filename) + + def modify_address(self, name, address, dnszone): + infile = open(self.filename, 'r') + outfile = tempfile.NamedTemporaryFile('w', delete=False) + for line in infile: + entry = self.parse_line(line) + if (entry and + entry['name'].lower() == self.qualify(name, dnszone).lower()): + outfile.write("%s %s %s\n" % + (address, self.qualify(name, dnszone), entry['type'])) + else: + outfile.write(line) + infile.close() + outfile.close() + shutil.move(outfile.name, self.filename) + + def get_entries_by_address(self, address, _dnszone=""): + entries = [] + infile = open(self.filename, 'r') + for line in infile: + entry = self.parse_line(line) + if entry and entry['address'].lower() == address.lower(): + entries.append(entry['name']) + infile.close() + return entries + + def get_entries_by_name(self, name, dnszone=""): + entries = [] + infile = open(self.filename, 'r') + for line in infile: + entry = self.parse_line(line) + if (entry and + entry['name'].lower() == self.qualify(name, dnszone).lower()): + entries.append(entry['address']) + infile.close() + return entries + + def delete_dns_file(self): + os.remove(self.filename) diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py index 1cb049e4b..dfcc13f45 100644 --- a/nova/tests/test_network.py +++ b/nova/tests/test_network.py @@ -22,6 +22,7 @@ from nova import exception from nova import log as logging from nova import rpc from nova import test +from nova import utils from nova.network import manager as network_manager from nova.tests import fake_network @@ -133,10 +134,16 @@ class FlatNetworkTestCase(test.TestCase): def setUp(self): super(FlatNetworkTestCase, self).setUp() self.network = network_manager.FlatManager(host=HOST) + temp = utils.import_object('nova.network.minidns.MiniDNS') + self.network.instance_dns_manager = temp self.network.db = db self.context = context.RequestContext('testuser', 'testproject', is_admin=False) + def tearDown(self): + super(FlatNetworkTestCase, self).tearDown() + self.network.instance_dns_manager.delete_dns_file() + def test_get_instance_nw_info(self): fake_get_instance_nw_info = fake_network.fake_get_instance_nw_info @@ -272,6 +279,8 @@ class FlatNetworkTestCase(test.TestCase): db.instance_get(mox.IgnoreArg(), mox.IgnoreArg()).AndReturn({'security_groups': [{'id': 0}]}) + db.instance_get(self.context, + 1).AndReturn({'display_name': HOST}) db.fixed_ip_associate_pool(mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg()).AndReturn('192.168.0.101') @@ -282,6 +291,59 @@ class FlatNetworkTestCase(test.TestCase): self.network.add_fixed_ip_to_instance(self.context, 1, HOST, networks[0]['id']) + def test_mini_dns_driver(self): + driver = self.network.instance_dns_manager + driver.create_entry("hostone", "10.0.0.1", 0, "foozone") + driver.create_entry("hosttwo", "10.0.0.2", 0, "foozone") + driver.create_entry("hostthree", "10.0.0.3", 0, "foozone") + driver.create_entry("hostfour", "10.0.0.4", 0, "foozone") + driver.delete_entry("hosttwo", "foozone") + driver.rename_entry("10.0.0.3", "hostone", "foozone") + driver.modify_address("hostfour", "10.0.0.1", "foozone") + names = driver.get_entries_by_address("10.0.0.1", "foozone") + self.assertEqual(len(names), 2) + self.assertIn('hostone.foozone', names) + self.assertIn('hostfour.foozone', names) + addresses = driver.get_entries_by_name("hostone", "foozone") + self.assertEqual(len(addresses), 2) + self.assertIn('10.0.0.1', addresses) + self.assertIn('10.0.0.3', addresses) + + def test_instance_dns(self): + fixedip = '192.168.0.101' + self.mox.StubOutWithMock(db, 'network_get') + self.mox.StubOutWithMock(db, 'network_update') + self.mox.StubOutWithMock(db, 'fixed_ip_associate_pool') + self.mox.StubOutWithMock(db, 'instance_get') + self.mox.StubOutWithMock(db, + 'virtual_interface_get_by_instance_and_network') + self.mox.StubOutWithMock(db, 'fixed_ip_update') + + db.fixed_ip_update(mox.IgnoreArg(), + mox.IgnoreArg(), + mox.IgnoreArg()) + db.virtual_interface_get_by_instance_and_network(mox.IgnoreArg(), + mox.IgnoreArg(), mox.IgnoreArg()).AndReturn({'id': 0}) + + db.instance_get(mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn({'security_groups': + [{'id': 0}]}) + + db.instance_get(self.context, + 1).AndReturn({'display_name': HOST}) + db.fixed_ip_associate_pool(mox.IgnoreArg(), + mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(fixedip) + db.network_get(mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(networks[0]) + db.network_update(mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg()) + self.mox.ReplayAll() + self.network.add_fixed_ip_to_instance(self.context, 1, HOST, + networks[0]['id']) + addresses = self.network.instance_dns_manager.get_entries_by_name(HOST) + self.assertEqual(len(addresses), 1) + self.assertEqual(addresses[0], fixedip) + class VlanNetworkTestCase(test.TestCase): def setUp(self): -- cgit