summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2011-12-28 21:47:27 +0000
committerGerrit Code Review <review@openstack.org>2011-12-28 21:47:27 +0000
commit599760a1f8da32f4d689249550d23b3ffc119d0b (patch)
treeb160c832277ae7a0cb223f529a410093e7ce6113
parent388e2605358937211070d96cc6353097e465a270 (diff)
parent73c6d161855cf6e0b7f7cb6081891ca475efebb4 (diff)
Merge "Add an API for associating floating IPs with DNS entries."
-rw-r--r--doc/source/api_ext/ext_floating_ip_dns.rst160
-rw-r--r--nova/api/openstack/v2/contrib/floating_ip_dns.py220
-rw-r--r--nova/exception.py4
-rw-r--r--nova/flags.py6
-rw-r--r--nova/network/api.py39
-rw-r--r--nova/network/manager.py20
-rw-r--r--nova/network/minidns.py12
-rw-r--r--nova/tests/api/openstack/v2/contrib/test_floating_ip_dns.py242
-rw-r--r--nova/tests/api/openstack/v2/test_extensions.py1
-rw-r--r--nova/tests/test_network.py71
10 files changed, 774 insertions, 1 deletions
diff --git a/doc/source/api_ext/ext_floating_ip_dns.rst b/doc/source/api_ext/ext_floating_ip_dns.rst
new file mode 100644
index 000000000..e2ee3b763
--- /dev/null
+++ b/doc/source/api_ext/ext_floating_ip_dns.rst
@@ -0,0 +1,160 @@
+About The Floating IP DNS Extension
+================================
+The Floating IP DNS extension provides an interface for managing DNS records associated with IP addresses
+allocated by the Floating Ips extension. Requests are dispatched to a DNS driver selected at startup.
+
+To obtain current information the extensions available to you, issue an EXTENSION query on the OpenStack system where it is installed, such as http://mycloud.com/v1.1/tenant/extension.
+
+Floating IPs Extension Overview
+-------------------------------
+
+Name
+ Floating IP DNS
+
+Namespace
+ http://docs.openstack.org/ext/floating_ip_dns/api/v1.1
+
+Alias
+ OPS-DNS
+
+Contact
+ Andrew Bogott <abogott@wikimedia.org>
+
+Status
+ Alpha
+
+Extension Version
+ v1.0 (2011-12-22)
+
+Dependencies
+ Compute API v1.1
+ Floating IPs Extension, v1.0
+
+Doc Link (PDF)
+ http://
+
+Doc Link (WADL)
+ http://
+
+Short Description
+ This extension enables associated DNS entries with floating IPs.
+
+Sample Query Responses
+----------------------
+
+As shown below, responses to an EXTENSION query in XML or JSON provide basic information about the extension.
+
+Extension Query Response: XML::
+
+ None
+
+Extension Query Response: JSON::
+
+ {'extensions':
+ [{'updated': '2011-12-23T00:00:00+00:00',
+ 'name': 'Floating_ip_dns',
+ 'links': [],
+ 'namespace': 'http://docs.openstack.org/ext/floating_ip_dns/api/v1.1',
+ 'alias': 'os-floating-ip_dns',
+ 'description': 'Floating IP DNS support'}]}
+
+Document Change History
+-----------------------
+
+============= =====================================
+Revision Date Summary of Changes
+2011-12-23 Initial draft
+============= =====================================
+
+
+Summary of Changes
+==================
+This extension to the Compute API enables management of DNS entries for floating IP addresses.
+
+New Action
+----------
+None
+
+New Faults
+----------
+None
+
+New Headers
+-----------
+None
+
+New Resources
+-------------
+Get a list of DNS Domains (aka 'zones') published by the DNS driver:
+
+ GET /v1.1/<tenant_id>/os-floating-ip-dns/
+
+ # Sample Response:
+ { 'zones' : [
+ {'zone' : 'example.org'}
+ {'zone' : 'example.net'}]}
+
+
+Create a DNS entry:
+
+ POST /v1.1/<tenant_id>/os-floating-ip-dns/
+
+ # Sample body:
+ { 'dns_entry' :
+ { 'name': 'instance1',
+ 'ip': '192.168.53.11',
+ 'dns_type': 'A',
+ 'zone': 'example.org'}}
+
+ # Sample Response (success):
+ { 'dns_entry' :
+ { 'ip' : '192.168.53.11',
+ 'type' : 'A',
+ 'zone' : 'example.org',
+ 'name' : 'instance1' }}
+
+ Failure Response Code: 409 (indicates an entry with name & zone already exists.)
+
+Find DNS entries for a given domain and name:
+
+ GET /v1.1/<tenant_id>/os-floating-ip-dns/<domain>?name=<name>
+
+ # Sample Response:
+ { 'dns_entries' : [
+ { 'ip' : '192.168.53.11',
+ 'type' : 'A',
+ 'zone' : <domain>,
+ 'name' : <name> }]}
+
+
+Find DNS entries for a given domain and ip:
+
+ GET /v1.1/<tenant_id>/os-floating-ip-dns/<domain>/?ip=<ip>
+
+ # Sample Response:
+ { 'dns_entries' : [
+ { 'ip' : <ip>,
+ 'type' : 'A',
+ 'zone' : <domain>,
+ 'name' : 'example1' }
+ { 'ip' : <ip>,
+ 'type' : 'A',
+ 'zone' : <domain>,
+ 'name' : 'example2' }]}
+
+
+Delete a DNS entry:
+
+DELETE /v1.1/<tenant_id>/os-floating-ip-dns/<domain>?name=<name>
+
+ Normal Response Code: 200
+ Failure Response Code: 404 (Entry to be deleted not found)
+
+New States
+----------
+None
+
+Changes to the Cloud Servers Specification
+------------------------------------------
+None
+
diff --git a/nova/api/openstack/v2/contrib/floating_ip_dns.py b/nova/api/openstack/v2/contrib/floating_ip_dns.py
new file mode 100644
index 000000000..cc33241b3
--- /dev/null
+++ b/nova/api/openstack/v2/contrib/floating_ip_dns.py
@@ -0,0 +1,220 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# 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 urllib
+
+import webob
+
+from nova.api.openstack import wsgi
+from nova.api.openstack import xmlutil
+from nova.api.openstack.v2 import extensions
+from nova import exception
+from nova import log as logging
+from nova import network
+
+
+LOG = logging.getLogger('nova.api.openstack.v2.contrib.floating_ip_dns')
+
+
+def _translate_dns_entry_view(dns_entry):
+ result = {}
+ result['ip'] = dns_entry.get('ip')
+ result['id'] = dns_entry.get('id')
+ result['type'] = dns_entry.get('type')
+ result['zone'] = dns_entry.get('zone')
+ result['name'] = dns_entry.get('name')
+ return {'dns_entry': result}
+
+
+def _translate_dns_entries_view(dns_entries):
+ return {'dns_entries': [_translate_dns_entry_view(entry)['dns_entry']
+ for entry in dns_entries]}
+
+
+def _translate_zone_entries_view(zonelist):
+ return {'zones': [{'zone': zone} for zone in zonelist]}
+
+
+def _unquote_zone(zone):
+ """Unquoting function for receiving a zone name in a URL.
+
+ Zone names tend to have .'s in them. Urllib doesn't quote dots,
+ but Routes tends to choke on them, so we need an extra level of
+ by-hand quoting here.
+ """
+ return urllib.unquote(zone).replace('%2E', '.')
+
+
+def _create_dns_entry(ip, name, zone):
+ return {'ip': ip, 'name': name, 'zone': zone}
+
+
+class FloatingIPDNSController(object):
+ """DNS Entry controller for OpenStack API"""
+
+ def __init__(self):
+ self.network_api = network.API()
+ super(FloatingIPDNSController, self).__init__()
+
+ def show(self, req, id):
+ """Return a list of dns entries. If ip is specified, query for
+ names. if name is specified, query for ips.
+ Quoted domain (aka 'zone') specified as id."""
+ context = req.environ['nova.context']
+ params = req.str_GET
+ floating_ip = params['ip'] if 'ip' in params else ""
+ name = params['name'] if 'name' in params else ""
+ zone = _unquote_zone(id)
+
+ if floating_ip:
+ entries = self.network_api.get_dns_entries_by_address(context,
+ floating_ip,
+ zone)
+ entrylist = [_create_dns_entry(floating_ip, entry, zone)
+ for entry in entries]
+ elif name:
+ entries = self.network_api.get_dns_entries_by_name(context,
+ name, zone)
+ entrylist = [_create_dns_entry(entry, name, zone)
+ for entry in entries]
+ else:
+ entrylist = []
+
+ return _translate_dns_entries_view(entrylist)
+
+ def index(self, req):
+ """Return a list of available DNS zones."""
+
+ context = req.environ['nova.context']
+ zones = self.network_api.get_dns_zones(context)
+
+ return _translate_zone_entries_view(zones)
+
+ def create(self, req, body):
+ """Add dns entry for name and address"""
+ context = req.environ['nova.context']
+
+ try:
+ entry = body['dns_entry']
+ address = entry['ip']
+ name = entry['name']
+ dns_type = entry['dns_type']
+ zone = entry['zone']
+ except (TypeError, KeyError):
+ raise webob.exc.HTTPUnprocessableEntity()
+
+ try:
+ self.network_api.add_dns_entry(context, address, name,
+ dns_type, zone)
+ except exception.FloatingIpDNSExists:
+ return webob.Response(status_int=409)
+
+ return _translate_dns_entry_view({'ip': address,
+ 'name': name,
+ 'type': dns_type,
+ 'zone': zone})
+
+ def delete(self, req, id):
+ """Delete the entry identified by req and id. """
+ context = req.environ['nova.context']
+ params = req.str_GET
+ name = params['name'] if 'name' in params else ""
+ zone = _unquote_zone(id)
+
+ try:
+ self.network_api.delete_dns_entry(context, name, zone)
+ except exception.NotFound:
+ return webob.Response(status_int=404)
+
+ return webob.Response(status_int=200)
+
+
+def make_dns_entry(elem):
+ elem.set('id')
+ elem.set('ip')
+ elem.set('type')
+ elem.set('zone')
+ elem.set('name')
+
+
+def make_zone_entry(elem):
+ elem.set('zone')
+
+
+class FloatingIPDNSTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('dns_entry',
+ selector='dns_entry')
+ make_dns_entry(root)
+ return xmlutil.MasterTemplate(root, 1)
+
+
+class FloatingIPDNSsTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('dns_entries')
+ elem = xmlutil.SubTemplateElement(root, 'dns_entry',
+ selector='dns_entries')
+ make_dns_entry(elem)
+ return xmlutil.MasterTemplate(root, 1)
+
+
+class ZonesTemplate(xmlutil.TemplateBuilder):
+ def construct(self):
+ root = xmlutil.TemplateElement('zones')
+ elem = xmlutil.SubTemplateElement(root, 'zone',
+ selector='zones')
+ make_zone_entry(elem)
+ return xmlutil.MasterTemplate(root, 1)
+
+
+class FloatingIPDNSSerializer(xmlutil.XMLTemplateSerializer):
+ def index(self):
+ return ZonesTemplate()
+
+ def show(self):
+ return FloatingIPDNSsTemplate()
+
+ def default(self):
+ return FloatingIPDNSTemplate()
+
+
+class Floating_ip_dns(extensions.ExtensionDescriptor):
+ """Floating IP DNS support"""
+
+ name = "Floating_ip_dns"
+ alias = "os-floating-ip-dns"
+ namespace = "http://docs.openstack.org/ext/floating_ip_dns/api/v1.1"
+ updated = "2011-12-23:00:00+00:00"
+
+ def __init__(self, ext_mgr):
+ self.network_api = network.API()
+ super(Floating_ip_dns, self).__init__(ext_mgr)
+
+ def get_resources(self):
+ resources = []
+
+ body_serializers = {
+ 'application/xml': FloatingIPDNSSerializer(),
+ }
+
+ serializer = wsgi.ResponseSerializer(body_serializers)
+
+ res = extensions.ResourceExtension('os-floating-ip-dns',
+ FloatingIPDNSController(),
+ serializer=serializer)
+ resources.append(res)
+
+ return resources
diff --git a/nova/exception.py b/nova/exception.py
index 043322607..95e34afe2 100644
--- a/nova/exception.py
+++ b/nova/exception.py
@@ -555,6 +555,10 @@ class FloatingIpNotFound(NotFound):
message = _("Floating ip not found for id %(id)s.")
+class FloatingIpDNSExists(Invalid):
+ message = _("The DNS entry %(name)s already exists in zone %(zone)s.")
+
+
class FloatingIpNotFoundForAddress(FloatingIpNotFound):
message = _("Floating ip not found for address %(address)s.")
diff --git a/nova/flags.py b/nova/flags.py
index 599dcb0f7..9c4aeffeb 100644
--- a/nova/flags.py
+++ b/nova/flags.py
@@ -370,6 +370,12 @@ DEFINE_string('instance_dns_manager',
'DNS Manager for instance IPs')
DEFINE_string('instance_dns_zone', '',
'DNS Zone for instance IPs')
+DEFINE_string('floating_ip_dns_manager',
+ 'nova.network.dns_driver.DNSDriver',
+ 'DNS Manager for floating IPs')
+DEFINE_multistring('floating_ip_dns_zones', '',
+ 'DNS zones for floating IPs.'
+ 'e.g. "example.org"')
DEFINE_string('network_manager', 'nova.network.manager.VlanManager',
'Manager for network')
DEFINE_string('volume_manager', 'nova.volume.manager.VolumeManager',
diff --git a/nova/network/api.py b/nova/network/api.py
index 89a746359..e0a5afdd9 100644
--- a/nova/network/api.py
+++ b/nova/network/api.py
@@ -192,3 +192,42 @@ class API(base.Base):
return rpc.call(context, FLAGS.network_topic,
{'method': 'get_instance_uuids_by_ip_filter',
'args': args})
+
+ def get_dns_zones(self, context):
+ """Returns a list of available dns zones.
+ These can be used to create DNS entries for floating ips.
+ """
+ return rpc.call(context,
+ FLAGS.network_topic,
+ {'method': 'get_dns_zones'})
+
+ def add_dns_entry(self, context, address, name, dns_type, zone):
+ """Create specified DNS entry for address"""
+ args = {'address': address,
+ 'dns_name': name,
+ 'dns_type': dns_type,
+ 'dns_zone': zone}
+ return rpc.call(context, FLAGS.network_topic,
+ {'method': 'add_dns_entry',
+ 'args': args})
+
+ def delete_dns_entry(self, context, name, zone):
+ """Delete the specified dns entry."""
+ args = {'dns_name': name, 'dns_zone': zone}
+ return rpc.call(context, FLAGS.network_topic,
+ {'method': 'delete_dns_entry',
+ 'args': args})
+
+ def get_dns_entries_by_address(self, context, address, zone):
+ """Get entries for address and zone"""
+ args = {'address': address, 'dns_zone': zone}
+ return rpc.call(context, FLAGS.network_topic,
+ {'method': 'get_dns_entries_by_address',
+ 'args': args})
+
+ def get_dns_entries_by_name(self, context, name, zone):
+ """Get entries for name and zone"""
+ args = {'name': name, 'dns_zone': zone}
+ return rpc.call(context, FLAGS.network_topic,
+ {'method': 'get_dns_entries_by_name',
+ 'args': args})
diff --git a/nova/network/manager.py b/nova/network/manager.py
index 2d62581e0..a13505009 100644
--- a/nova/network/manager.py
+++ b/nova/network/manager.py
@@ -440,6 +440,24 @@ class FloatingIP(object):
fixed_address)
return [floating_ip['address'] for floating_ip in floating_ips]
+ def get_dns_zones(self, context):
+ return self.floating_dns_manager.get_zones()
+
+ def add_dns_entry(self, context, address, dns_name, dns_type, dns_zone):
+ self.floating_dns_manager.create_entry(dns_name, address,
+ dns_type, dns_zone)
+
+ def delete_dns_entry(self, context, dns_name, dns_zone):
+ self.floating_dns_manager.delete_entry(dns_name, dns_zone)
+
+ def get_dns_entries_by_address(self, context, address, dns_zone):
+ return self.floating_dns_manager.get_entries_by_address(address,
+ dns_zone)
+
+ def get_dns_entries_by_name(self, context, name, dns_zone):
+ return self.floating_dns_manager.get_entries_by_name(name,
+ dns_zone)
+
class NetworkManager(manager.SchedulerDependentManager):
"""Implements common network manager functionality.
@@ -468,6 +486,8 @@ class NetworkManager(manager.SchedulerDependentManager):
self.driver = utils.import_object(network_driver)
temp = utils.import_object(FLAGS.instance_dns_manager)
self.instance_dns_manager = temp
+ temp = utils.import_object(FLAGS.floating_ip_dns_manager)
+ self.floating_dns_manager = temp
self.network_api = network_api.API()
self.compute_api = compute_api.API()
super(NetworkManager, self).__init__(service_name='network',
diff --git a/nova/network/minidns.py b/nova/network/minidns.py
index e955ae7bd..b26ea9aeb 100644
--- a/nova/network/minidns.py
+++ b/nova/network/minidns.py
@@ -16,6 +16,7 @@ import os
import shutil
import tempfile
+from nova import exception
from nova import flags
@@ -41,7 +42,7 @@ class MiniDNS(object):
f.close()
def get_zones(self):
- return ["example.org", "example.com", "example.gov"]
+ return flags.FLAGS.floating_ip_dns_zones
def qualify(self, name, zone):
if zone:
@@ -52,6 +53,10 @@ class MiniDNS(object):
return qualified
def create_entry(self, name, address, type, dnszone):
+
+ if self.get_entries_by_name(name, dnszone):
+ raise exception.FloatingIpDNSExists(name=name, zone=dnszone)
+
outfile = open(self.filename, 'a+')
outfile.write("%s %s %s\n" %
(address, self.qualify(name, dnszone), type))
@@ -69,6 +74,7 @@ class MiniDNS(object):
return entry
def delete_entry(self, name, dnszone=""):
+ deleted = False
infile = open(self.filename, 'r')
outfile = tempfile.NamedTemporaryFile('w', delete=False)
for line in infile:
@@ -76,9 +82,13 @@ class MiniDNS(object):
if ((not entry) or
entry['name'] != self.qualify(name, dnszone).lower()):
outfile.write(line)
+ else:
+ deleted = True
infile.close()
outfile.close()
shutil.move(outfile.name, self.filename)
+ if not deleted:
+ raise exception.NotFound
def rename_entry(self, address, name, dnszone):
infile = open(self.filename, 'r')
diff --git a/nova/tests/api/openstack/v2/contrib/test_floating_ip_dns.py b/nova/tests/api/openstack/v2/contrib/test_floating_ip_dns.py
new file mode 100644
index 000000000..d658fa542
--- /dev/null
+++ b/nova/tests/api/openstack/v2/contrib/test_floating_ip_dns.py
@@ -0,0 +1,242 @@
+# Copyright 2011 Andrew Bogott for the Wikimedia Foundation
+# 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.
+
+import sys
+
+from lxml import etree
+import webob
+import urllib
+
+from nova.api.openstack.v2.contrib import floating_ips
+from nova.api.openstack.v2.contrib import floating_ip_dns
+from nova import context
+from nova import db
+from nova import network
+from nova import rpc
+from nova import test
+from nova.tests.api.openstack import fakes
+from nova import utils
+
+
+name = "arbitraryname"
+name2 = "anotherarbitraryname"
+
+testaddress = '10.0.0.66'
+testaddress2 = '10.0.0.67'
+
+zone = "example.org"
+zone2 = "example.net"
+floating_ip_id = '1'
+
+
+def _quote_zone(zone):
+ """
+ Zone names tend to have .'s in them. Urllib doesn't quote dots,
+ but Routes tends to choke on them, so we need an extra level of
+ by-hand quoting here. This function needs to duplicate the one in
+ python-novaclient/novaclient/v1_1/floating_ip_dns.py
+ """
+ return urllib.quote(zone.replace('.', '%2E'))
+
+
+def network_api_get_floating_ip(self, context, id):
+ return {'id': floating_ip_id, 'address': testaddress,
+ 'fixed_ip': None}
+
+
+def network_get_dns_zones(self, context):
+ return ['foo', 'bar', 'baz', 'quux']
+
+
+def network_get_dns_entries_by_address(self, context, address, zone):
+ return [name, name2]
+
+
+def network_get_dns_entries_by_name(self, context, address, zone):
+ return [testaddress, testaddress2]
+
+
+def network_add_dns_entry(self, context, address, name, dns_type, zone):
+ return {'dns_entry': {'ip': testaddress,
+ 'name': name,
+ 'type': dns_type,
+ 'zone': zone}}
+
+
+class FloatingIpDNSTest(test.TestCase):
+ def _create_floating_ip(self):
+ """Create a floating ip object."""
+ host = "fake_host"
+ return db.floating_ip_create(self.context,
+ {'address': testaddress,
+ 'host': host})
+
+ def _delete_floating_ip(self):
+ db.floating_ip_destroy(self.context, testaddress)
+
+ def setUp(self):
+ super(FloatingIpDNSTest, self).setUp()
+ self.stubs.Set(network.api.API, "get_dns_zones",
+ network_get_dns_zones)
+ self.stubs.Set(network.api.API, "get_dns_entries_by_address",
+ network_get_dns_entries_by_address)
+ self.stubs.Set(network.api.API, "get_dns_entries_by_name",
+ network_get_dns_entries_by_name)
+ self.stubs.Set(network.api.API, "get_floating_ip",
+ network_api_get_floating_ip)
+ self.stubs.Set(network.api.API, "add_dns_entry",
+ network_add_dns_entry)
+
+ self.context = context.get_admin_context()
+
+ self._create_floating_ip()
+ self.dns_controller = floating_ip_dns.FloatingIPDNSController()
+
+ def tearDown(self):
+ self._delete_floating_ip()
+ super(FloatingIpDNSTest, self).tearDown()
+
+ def test_dns_zones_list(self):
+ req = fakes.HTTPRequest.blank('/v2/123/os-floating-ip-dns')
+ res_dict = self.dns_controller.index(req)
+ entries = res_dict['zones']
+ self.assertTrue(entries)
+ self.assertEqual(entries[0]['zone'], "foo")
+ self.assertEqual(entries[1]['zone'], "bar")
+ self.assertEqual(entries[2]['zone'], "baz")
+ self.assertEqual(entries[3]['zone'], "quux")
+
+ def test_get_dns_entries_by_address(self):
+ qparams = {'ip': testaddress}
+ params = "?%s" % urllib.urlencode(qparams) if qparams else ""
+
+ req = fakes.HTTPRequest.blank('/v2/123/os-floating-ip-dns/%s%s' %
+ (_quote_zone(zone), params))
+ entries = self.dns_controller.show(req, _quote_zone(zone))
+
+ self.assertEqual(len(entries['dns_entries']), 2)
+ self.assertEqual(entries['dns_entries'][0]['name'],
+ name)
+ self.assertEqual(entries['dns_entries'][1]['name'],
+ name2)
+ self.assertEqual(entries['dns_entries'][0]['zone'],
+ zone)
+
+ def test_get_dns_entries_by_name(self):
+ qparams = {'name': name}
+ params = "?%s" % urllib.urlencode(qparams) if qparams else ""
+
+ req = fakes.HTTPRequest.blank('/v2/123/os-floating-ip-dns/%s%s' %
+ (_quote_zone(zone), params))
+ entries = self.dns_controller.show(req, _quote_zone(zone))
+
+ self.assertEqual(len(entries['dns_entries']), 2)
+ self.assertEqual(entries['dns_entries'][0]['ip'],
+ testaddress)
+ self.assertEqual(entries['dns_entries'][1]['ip'],
+ testaddress2)
+ self.assertEqual(entries['dns_entries'][0]['zone'],
+ zone)
+
+ def test_create(self):
+ body = {'dns_entry':
+ {'name': name,
+ 'ip': testaddress,
+ 'dns_type': 'A',
+ 'zone': zone}}
+ req = fakes.HTTPRequest.blank('/v2/123/os-floating-ip-dns')
+ entry = self.dns_controller.create(req, body)
+
+ self.assertEqual(entry['dns_entry']['ip'], testaddress)
+
+ def test_delete(self):
+ self.called = False
+ self.deleted_zone = ""
+ self.deleted_name = ""
+
+ def network_delete_dns_entry(fakeself, context, req, id):
+ self.called = True
+ self.deleted_zone = id
+
+ self.stubs.Set(network.api.API, "delete_dns_entry",
+ network_delete_dns_entry)
+
+ qparams = {'name': name}
+ params = "?%s" % urllib.urlencode(qparams) if qparams else ""
+
+ req = fakes.HTTPRequest.blank('/v2/123/os-floating-ip-dns/%s%s' %
+ (_quote_zone(zone), params))
+ entries = self.dns_controller.delete(req, _quote_zone(zone))
+
+ self.assertTrue(self.called)
+ self.assertEquals(self.deleted_zone, zone)
+
+
+class FloatingIpDNSSerializerTest(test.TestCase):
+ def test_default_serializer(self):
+ serializer = floating_ip_dns.FloatingIPDNSSerializer()
+ text = serializer.serialize(dict(
+ dns_entry=dict(
+ ip=testaddress,
+ type='A',
+ zone=zone,
+ name=name)))
+
+ tree = etree.fromstring(text)
+
+ self.assertEqual('dns_entry', tree.tag)
+ self.assertEqual(testaddress, tree.get('ip'))
+ self.assertEqual(zone, tree.get('zone'))
+ self.assertEqual(name, tree.get('name'))
+
+ def test_index_serializer(self):
+ serializer = floating_ip_dns.FloatingIPDNSSerializer()
+ text = serializer.serialize(dict(
+ zones=[
+ dict(zone=zone),
+ dict(zone=zone2)]), 'index')
+
+ tree = etree.fromstring(text)
+ self.assertEqual('zones', tree.tag)
+ self.assertEqual(2, len(tree))
+ self.assertEqual(zone, tree[0].get('zone'))
+ self.assertEqual(zone2, tree[1].get('zone'))
+
+ def test_show_serializer(self):
+ serializer = floating_ip_dns.FloatingIPDNSSerializer()
+ text = serializer.serialize(dict(
+ dns_entries=[
+ dict(ip=testaddress,
+ type='A',
+ zone=zone,
+ name=name),
+ dict(ip=testaddress2,
+ type='C',
+ zone=zone,
+ name=name2)]), 'show')
+
+ tree = etree.fromstring(text)
+ self.assertEqual('dns_entries', tree.tag)
+ self.assertEqual(2, len(tree))
+ self.assertEqual('dns_entry', tree[0].tag)
+ self.assertEqual('dns_entry', tree[1].tag)
+ self.assertEqual(testaddress, tree[0].get('ip'))
+ self.assertEqual('A', tree[0].get('type'))
+ self.assertEqual(zone, tree[0].get('zone'))
+ self.assertEqual(name, tree[0].get('name'))
+ self.assertEqual(testaddress2, tree[1].get('ip'))
+ self.assertEqual('C', tree[1].get('type'))
+ self.assertEqual(zone, tree[1].get('zone'))
+ self.assertEqual(name2, tree[1].get('name'))
diff --git a/nova/tests/api/openstack/v2/test_extensions.py b/nova/tests/api/openstack/v2/test_extensions.py
index ad017e665..302095fe8 100644
--- a/nova/tests/api/openstack/v2/test_extensions.py
+++ b/nova/tests/api/openstack/v2/test_extensions.py
@@ -108,6 +108,7 @@ class ExtensionControllerTest(ExtensionTestCase):
"FlavorExtraSpecs",
"FlavorExtraData",
"Floating_ips",
+ "Floating_ip_dns",
"Fox In Socks",
"Hosts",
"Keypairs",
diff --git a/nova/tests/test_network.py b/nova/tests/test_network.py
index dc26b0298..9ed779f4e 100644
--- a/nova/tests/test_network.py
+++ b/nova/tests/test_network.py
@@ -19,11 +19,13 @@ import mox
from nova import context
from nova import db
from nova import exception
+from nova import flags
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.network import api as network_api
from nova.tests import fake_network
@@ -1123,11 +1125,17 @@ class FloatingIPTestCase(test.TestCase):
def setUp(self):
super(FloatingIPTestCase, self).setUp()
self.network = TestFloatingIPManager()
+ temp = utils.import_object('nova.network.minidns.MiniDNS')
+ self.network.floating_dns_manager = temp
self.network.db = db
self.project_id = 'testproject'
self.context = context.RequestContext('testuser', self.project_id,
is_admin=False)
+ def tearDown(self):
+ super(FloatingIPTestCase, self).tearDown()
+ self.network.floating_dns_manager.delete_dns_file()
+
def test_double_deallocation(self):
instance_ref = db.api.instance_create(self.context,
{"project_id": self.project_id})
@@ -1138,3 +1146,66 @@ class FloatingIPTestCase(test.TestCase):
instance_id=instance_ref['id'])
self.network.deallocate_for_instance(self.context,
instance_id=instance_ref['id'])
+
+ def test_floating_dns_zones(self):
+ zone1 = "example.org"
+ zone2 = "example.com"
+ flags.FLAGS.floating_ip_dns_zones = [zone1, zone2]
+
+ zones = self.network.get_dns_zones(self.context)
+ self.assertEqual(len(zones), 2)
+ self.assertEqual(zones[0], zone1)
+ self.assertEqual(zones[1], zone2)
+
+ def test_floating_dns_create_conflict(self):
+ zone = "example.org"
+ address1 = "10.10.10.11"
+ name1 = "foo"
+ name2 = "bar"
+
+ self.network.add_dns_entry(self.context, address1, name1, "A", zone)
+
+ self.assertRaises(exception.FloatingIpDNSExists,
+ self.network.add_dns_entry, self.context,
+ address1, name1, "A", zone)
+
+ def test_floating_create_and_get(self):
+ zone = "example.org"
+ address1 = "10.10.10.11"
+ name1 = "foo"
+ name2 = "bar"
+ entries = self.network.get_dns_entries_by_address(self.context,
+ address1, zone)
+ self.assertFalse(entries)
+
+ self.network.add_dns_entry(self.context, address1, name1, "A", zone)
+ self.network.add_dns_entry(self.context, address1, name2, "A", zone)
+ entries = self.network.get_dns_entries_by_address(self.context,
+ address1, zone)
+ self.assertEquals(len(entries), 2)
+ self.assertEquals(entries[0], name1)
+ self.assertEquals(entries[1], name2)
+
+ entries = self.network.get_dns_entries_by_name(self.context,
+ name1, zone)
+ self.assertEquals(len(entries), 1)
+ self.assertEquals(entries[0], address1)
+
+ def test_floating_dns_delete(self):
+ zone = "example.org"
+ address1 = "10.10.10.11"
+ name1 = "foo"
+ name2 = "bar"
+
+ self.network.add_dns_entry(self.context, address1, name1, "A", zone)
+ self.network.add_dns_entry(self.context, address1, name2, "A", zone)
+ self.network.delete_dns_entry(self.context, name1, zone)
+
+ entries = self.network.get_dns_entries_by_address(self.context,
+ address1, zone)
+ self.assertEquals(len(entries), 1)
+ self.assertEquals(entries[0], name2)
+
+ self.assertRaises(exception.NotFound,
+ self.network.delete_dns_entry, self.context,
+ name1, zone)