Add dns domain manipulation to nova.

Adding this functionality required the existing DNS api
to be rearranged considerably.

Nova needs to track some information about domains that is
outside the scope of the DNS driver, specifically the availability
zone of a private domain and the project of a public domain.
In order to track those attributes, this patch adds a new table
to the Nova database, dns_domains.

This patch perpetuates some naming ambiguities (e.g. zone vs. domain).
A future renaming patch will sort all this out.

For blueprint public-and-private-dns.

Change-Id: I80865207d34ab7c6e2afc5638863a299b3913f8e
This commit is contained in:
Andrew Bogott 2012-01-13 17:52:58 -06:00
parent 885b9aa70d
commit 1e318af4d5
15 changed files with 810 additions and 205 deletions

View File

@ -85,70 +85,90 @@ None
New Resources New Resources
------------- -------------
Get a list of DNS Domains (aka 'zones') published by the DNS driver: Get a list of registered DNS Domains published by the DNS drivers:
GET /v1.1/<tenant_id>/os-floating-ip-dns/ GET /v1.1/<tenant_id>/os-floating-ip-dns/
# Sample Response: # Sample Response:
{ 'zones' : [ {'zone_entries' : [
{'zone' : 'example.org'} {'domain': 'domain1.example.org', 'scope': 'public', 'project': 'proj1'}
{'zone' : 'example.net'}]} {'domain': 'domain2.example.net', 'scope': 'public', 'project': 'proj2'}
{'domain': 'example.net', 'scope': 'public', 'project': ''}
{'domain': 'example.internal', 'scope': 'private', 'zone': 'zone1'}]}
Create a DNS entry: Create or modify a DNS domain:
POST /v1.1/<tenant_id>/os-floating-ip-dns/ PUT /v1.1/<tenant_id>/os-floating-ip-dns/<zone>
# Sample body, public domain:
{'zone_entry' :
{'scope': 'public',
'project' : 'project1'}}
# Sample body, public (projectless) domain:
{'zone_entry' :
{'scope': 'public'}}
# Sample Response, public domain (success):
{'zone_entry' :
{'zone': 'domain1.example.org',
'scope': 'public',
'project': 'project1'}}
# Sample body, private domain:
{'zone_entry' :
{'scope': 'private',
'availability_zone': 'zone1'}}
# Sample Response, private domain (success):
{'zone_entry' :
{'zone': 'domain1.private',
'scope': 'private',
'availability_zone': 'zone1'}}
Failure Response Code: 403 (Insufficient permissions.)
Delete a DNS domain and all associated host entries:
DELETE /v1.1/<tenant_id>/os-floating-ip-dns/<domain>
Normal Response Code: 200
Failure Response Code: 404 (Domain to be deleted not found.)
Failure Response Code: 403 (Insufficient permissions to delete.)
Create or modify a DNS entry:
PUT /v1.1/<tenant_id>/os-floating-ip-dns/<zone>/entries/<name>
# Sample body: # Sample body:
{ 'dns_entry' : { 'dns_entry' :
{ 'name': 'instance1', { 'ip': '192.168.53.11',
'ip': '192.168.53.11', 'dns_type': 'A' }}
'dns_type': 'A',
'zone': 'example.org'}}
# Sample Response (success): # Sample Response (success):
{ 'dns_entry' : { 'dns_entry' :
{ 'ip' : '192.168.53.11', { 'type' : 'A',
'type' : 'A',
'zone' : 'example.org',
'name' : 'instance1' }} 'name' : 'instance1' }}
Failure Response Code: 409 (indicates an entry with name & zone already exists.)
Find unique DNS entry for a given domain and name:
Change the ip address of an existing DNS entry: GET /v1.1/<tenant_id>/os-floating-ip-dns/<domain>/entries/<name>
PUT /v1.1/<tenant_id>/os-floating-ip-dns/<domain>
# Sample body:
{ 'dns_entry' :
{ 'name': 'instance1',
'ip': '192.168.53.99'}}
# Sample Response (success):
{ 'dns_entry' :
{ 'ip' : '192.168.53.99',
'name' : 'instance1',
'zone' : 'example.org'}}
Failure Response Code: 404 (Entry to be modified not found)
Find DNS entries for a given domain and name:
GET /v1.1/<tenant_id>/os-floating-ip-dns/<domain>?name=<name>
# Sample Response: # Sample Response:
{ 'dns_entries' : [ { 'dns_entry' :
{ 'ip' : '192.168.53.11', { 'ip' : '192.168.53.11',
'type' : 'A', 'type' : 'A',
'zone' : <domain>, 'zone' : <domain>,
'name' : <name> }]} 'name' : <name> }}
Find DNS entries for a given domain and ip: Find DNS entries for a given domain and ip:
GET /v1.1/<tenant_id>/os-floating-ip-dns/<domain>/?ip=<ip> GET /v1.1/<tenant_id>/os-floating-ip-dns/<domain>/entries?ip=<ip>
# Sample Response: # Sample Response:
{ 'dns_entries' : [ { 'dns_entries' : [
@ -164,11 +184,12 @@ Find DNS entries for a given domain and ip:
Delete a DNS entry: Delete a DNS entry:
DELETE /v1.1/<tenant_id>/os-floating-ip-dns/<domain>?name=<name> DELETE /v1.1/<tenant_id>/os-floating-ip-dns/<domain>/entries/<name>
Normal Response Code: 200 Normal Response Code: 200
Failure Response Code: 404 (Entry to be deleted not found) Failure Response Code: 404 (Entry to be deleted not found)
New States New States
---------- ----------
None None
@ -176,4 +197,3 @@ None
Changes to the Cloud Servers Specification Changes to the Cloud Servers Specification
------------------------------------------ ------------------------------------------
None None

View File

@ -6,7 +6,7 @@
"compute:create": [], "compute:create": [],
"compute:create:attach_network": [], "compute:create:attach_network": [],
"compute:create:attach_volume": [], "compute:create:attach_volume": [],
"compute:get_all" :[], "compute:get_all": [],
"volume:create": [], "volume:create": [],
@ -47,6 +47,8 @@
"network:modify_dns_entry": [], "network:modify_dns_entry": [],
"network:delete_dns_entry": [], "network:delete_dns_entry": [],
"network:get_dns_entries_by_address": [], "network:get_dns_entries_by_address": [],
"network:get_dns_entries_by_name": [] "network:get_dns_entries_by_name": [],
"network:create_private_dns_domain": [],
"network:create_public_dns_domain": [],
"network:delete_dns_domain": []
} }

View File

@ -39,6 +39,9 @@ def make_dns_entry(elem):
def make_zone_entry(elem): def make_zone_entry(elem):
elem.set('zone') elem.set('zone')
elem.set('scope')
elem.set('project')
elem.set('availability_zone')
class FloatingIPDNSTemplate(xmlutil.TemplateBuilder): class FloatingIPDNSTemplate(xmlutil.TemplateBuilder):
@ -58,11 +61,19 @@ class FloatingIPDNSsTemplate(xmlutil.TemplateBuilder):
return xmlutil.MasterTemplate(root, 1) return xmlutil.MasterTemplate(root, 1)
class ZoneTemplate(xmlutil.TemplateBuilder):
def construct(self):
root = xmlutil.TemplateElement('zone_entry',
selector='zone_entry')
make_zone_entry(root)
return xmlutil.MasterTemplate(root, 1)
class ZonesTemplate(xmlutil.TemplateBuilder): class ZonesTemplate(xmlutil.TemplateBuilder):
def construct(self): def construct(self):
root = xmlutil.TemplateElement('zones') root = xmlutil.TemplateElement('zone_entries')
elem = xmlutil.SubTemplateElement(root, 'zone', elem = xmlutil.SubTemplateElement(root, 'zone_entry',
selector='zones') selector='zone_entries')
make_zone_entry(elem) make_zone_entry(elem)
return xmlutil.MasterTemplate(root, 1) return xmlutil.MasterTemplate(root, 1)
@ -82,8 +93,18 @@ def _translate_dns_entries_view(dns_entries):
for entry in dns_entries]} for entry in dns_entries]}
def _translate_zone_entries_view(zonelist): def _translate_zone_entry_view(zone_entry):
return {'zones': [{'zone': zone} for zone in zonelist]} result = {}
result['domain'] = zone_entry.get('domain')
result['scope'] = zone_entry.get('scope')
result['project'] = zone_entry.get('project')
result['availability_zone'] = zone_entry.get('availability_zone')
return {'zone_entry': result}
def _translate_zone_entries_view(zone_entries):
return {'zone_entries': [_translate_zone_entry_view(entry)['zone_entry']
for entry in zone_entries]}
def _unquote_zone(zone): def _unquote_zone(zone):
@ -100,102 +121,153 @@ def _create_dns_entry(ip, name, zone):
return {'ip': ip, 'name': name, 'zone': zone} return {'ip': ip, 'name': name, 'zone': zone}
class FloatingIPDNSController(object): def _create_domain_entry(domain, scope=None, project=None, av_zone=None):
"""DNS Entry controller for OpenStack API""" return {'domain': domain, 'scope': scope, 'project': project,
'availability_zone': av_zone}
class FloatingIPDNSDomainController(object):
"""DNS domain controller for OpenStack API"""
def __init__(self): def __init__(self):
self.network_api = network.API() self.network_api = network.API()
super(FloatingIPDNSController, self).__init__() super(FloatingIPDNSDomainController, self).__init__()
@wsgi.serializers(xml=FloatingIPDNSsTemplate)
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.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)
@wsgi.serializers(xml=ZonesTemplate) @wsgi.serializers(xml=ZonesTemplate)
def index(self, req): def index(self, req):
"""Return a list of available DNS zones.""" """Return a list of available DNS zones."""
context = req.environ['nova.context'] context = req.environ['nova.context']
zones = self.network_api.get_dns_zones(context) zones = self.network_api.get_dns_zones(context)
zonelist = [_create_domain_entry(zone['domain'],
zone.get('scope'),
zone.get('project'),
zone.get('availability_zone'))
for zone in zones]
return _translate_zone_entries_view(zones) return _translate_zone_entries_view(zonelist)
@wsgi.serializers(xml=ZoneTemplate)
def update(self, req, id, body):
"""Add or modify domain entry"""
context = req.environ['nova.context']
fqdomain = _unquote_zone(id)
try:
entry = body['zone_entry']
scope = entry['scope']
except (TypeError, KeyError):
raise webob.exc.HTTPUnprocessableEntity()
project = entry.get('project', None)
av_zone = entry.get('availability_zone', None)
if (not scope or
project and av_zone or
scope == 'private' and project or
scope == 'public' and av_zone):
raise webob.exc.HTTPUnprocessableEntity()
try:
if scope == 'private':
self.network_api.create_private_dns_domain(context,
fqdomain,
av_zone)
return _translate_zone_entry_view({'domain': fqdomain,
'scope': scope,
'availability_zone': av_zone})
else:
self.network_api.create_public_dns_domain(context,
fqdomain,
project)
return _translate_zone_entry_view({'domain': fqdomain,
'scope': 'public',
'project': project})
except exception.NotAuthorized or exception.AdminRequired:
return webob.Response(status_int=403)
def delete(self, req, id):
"""Delete the domain identified by id. """
context = req.environ['nova.context']
params = req.str_GET
zone = _unquote_zone(id)
# Delete the whole domain
try:
self.network_api.delete_dns_domain(context, zone)
except exception.NotAuthorized or exception.AdminRequired:
return webob.Response(status_int=403)
except exception.NotFound:
return webob.Response(status_int=404)
return webob.Response(status_int=200)
class FloatingIPDNSEntryController(object):
"""DNS Entry controller for OpenStack API"""
def __init__(self):
self.network_api = network.API()
super(FloatingIPDNSEntryController, self).__init__()
@wsgi.serializers(xml=FloatingIPDNSTemplate) @wsgi.serializers(xml=FloatingIPDNSTemplate)
def create(self, req, body): def show(self, req, zone_id, id):
"""Add dns entry for name and address""" """Return the DNS entry that corresponds to zone_id and id."""
context = req.environ['nova.context'] context = req.environ['nova.context']
zone = _unquote_zone(zone_id)
name = id
entries = self.network_api.get_dns_entries_by_name(context,
name, zone)
entry = _create_dns_entry(entries[0], name, zone)
return _translate_dns_entry_view(entry)
@wsgi.serializers(xml=FloatingIPDNSsTemplate)
def index(self, req, zone_id):
"""Return a list of dns entries for the specified zone and ip."""
context = req.environ['nova.context']
params = req.GET
floating_ip = params.get('ip')
zone = _unquote_zone(zone_id)
if not floating_ip:
raise webob.exc.HTTPUnprocessableEntity()
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]
return _translate_dns_entries_view(entrylist)
@wsgi.serializers(xml=FloatingIPDNSTemplate)
def update(self, req, zone_id, id, body):
"""Add or modify dns entry"""
context = req.environ['nova.context']
zone = _unquote_zone(zone_id)
name = id
try: try:
entry = body['dns_entry'] entry = body['dns_entry']
address = entry['ip'] address = entry['ip']
name = entry['name']
dns_type = entry['dns_type'] dns_type = entry['dns_type']
zone = entry['zone']
except (TypeError, KeyError): except (TypeError, KeyError):
raise webob.exc.HTTPUnprocessableEntity() raise webob.exc.HTTPUnprocessableEntity()
try: entries = self.network_api.get_dns_entries_by_name(context, name, zone)
if not entries:
# create!
self.network_api.add_dns_entry(context, address, name, self.network_api.add_dns_entry(context, address, name,
dns_type, zone) dns_type, zone)
except exception.FloatingIpDNSExists: else:
return webob.Response(status_int=409) # modify!
self.network_api.modify_dns_entry(context, name, address, zone)
return _translate_dns_entry_view({'ip': address, return _translate_dns_entry_view({'ip': address,
'name': name, 'name': name,
'type': dns_type, 'type': dns_type,
'zone': zone}) 'zone': zone})
def update(self, req, id, body): def delete(self, req, zone_id, id):
"""Modify a dns entry."""
context = req.environ['nova.context']
zone = _unquote_zone(id)
try:
entry = body['dns_entry']
name = entry['name']
new_ip = entry['ip']
except (TypeError, KeyError):
raise webob.exc.HTTPUnprocessableEntity()
try:
self.network_api.modify_dns_entry(context, name,
new_ip, zone)
except exception.NotFound:
return webob.Response(status_int=404)
return _translate_dns_entry_view({'ip': new_ip,
'name': name,
'zone': zone})
def delete(self, req, id):
"""Delete the entry identified by req and id. """ """Delete the entry identified by req and id. """
context = req.environ['nova.context'] context = req.environ['nova.context']
params = req.GET zone = _unquote_zone(zone_id)
name = params['name'] if 'name' in params else "" name = id
zone = _unquote_zone(id)
try: try:
self.network_api.delete_dns_entry(context, name, zone) self.network_api.delete_dns_entry(context, name, zone)
@ -221,7 +293,13 @@ class Floating_ip_dns(extensions.ExtensionDescriptor):
resources = [] resources = []
res = extensions.ResourceExtension('os-floating-ip-dns', res = extensions.ResourceExtension('os-floating-ip-dns',
FloatingIPDNSController()) FloatingIPDNSDomainController())
resources.append(res)
res = extensions.ResourceExtension('entries',
FloatingIPDNSEntryController(),
parent={'member_name': 'zone',
'collection_name': 'os-floating-ip-dns'})
resources.append(res) resources.append(res)
return resources return resources

View File

@ -324,6 +324,32 @@ def floating_ip_set_auto_assigned(context, address):
"""Set auto_assigned flag to floating ip""" """Set auto_assigned flag to floating ip"""
return IMPL.floating_ip_set_auto_assigned(context, address) return IMPL.floating_ip_set_auto_assigned(context, address)
def dnsdomain_list(context):
"""Get a list of all zones in our database, public and private."""
return IMPL.dnsdomain_list(context)
def dnsdomain_register_for_zone(context, fqdomain, zone):
"""Associated a DNS domain with an availability zone"""
return IMPL.dnsdomain_register_for_zone(context, fqdomain, zone)
def dnsdomain_register_for_project(context, fqdomain, project):
"""Associated a DNS domain with a project id"""
return IMPL.dnsdomain_register_for_project(context, fqdomain, project)
def dnsdomain_unregister(context, fqdomain):
"""Purge associations for the specified DNS zone"""
return IMPL.dnsdomain_unregister(context, fqdomain)
def dnsdomain_get(context, fqdomain):
"""Get the db record for the specified domain."""
return IMPL.dnsdomain_get(context, fqdomain)
#################### ####################

View File

@ -695,6 +695,77 @@ def floating_ip_update(context, address, values):
floating_ip_ref.save(session=session) floating_ip_ref.save(session=session)
@require_context
def _dnsdomain_get(context, session, fqdomain):
return model_query(context, models.DNSDomain,
session=session, read_deleted="no").\
filter_by(domain=fqdomain).\
with_lockmode('update').\
first()
@require_context
def dnsdomain_get(context, fqdomain):
session = get_session()
with session.begin():
return _dnsdomain_get(context, session, fqdomain)
@require_admin_context
def _dnsdomain_get_or_create(context, session, fqdomain):
domain_ref = _dnsdomain_get(context, session, fqdomain)
if not domain_ref:
dns_ref = models.DNSDomain()
dns_ref.update({'domain': fqdomain,
'availability_zone': None,
'project_id': None})
return dns_ref
return domain_ref
@require_admin_context
def dnsdomain_register_for_zone(context, fqdomain, zone):
session = get_session()
with session.begin():
domain_ref = _dnsdomain_get_or_create(context, session, fqdomain)
domain_ref.scope = 'private'
domain_ref.availability_zone = zone
domain_ref.save(session=session)
@require_admin_context
def dnsdomain_register_for_project(context, fqdomain, project):
session = get_session()
with session.begin():
domain_ref = _dnsdomain_get_or_create(context, session, fqdomain)
domain_ref.scope = 'public'
domain_ref.project_id = project
domain_ref.save(session=session)
@require_admin_context
def dnsdomain_unregister(context, fqdomain):
session = get_session()
with session.begin():
domain_ref = _dnsdomain_get(context, session, fqdomain)
if domain_ref:
domain_ref.delete(session=session)
@require_context
def dnsdomain_list(context):
session = get_session()
records = model_query(context, models.DNSDomain,
session=session, read_deleted="no").\
with_lockmode('update').all()
domains = []
for record in records:
domains.append(record.domain)
return domains
################### ###################

View File

@ -0,0 +1,66 @@
# Copyright 2012 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.
from sqlalchemy import Boolean, Column, DateTime, ForeignKey
from sqlalchemy import MetaData, String, Table
from nova import log as logging
meta = MetaData()
#
# New Tables
#
dns_domains = Table('dns_domains', meta,
Column('created_at', DateTime(timezone=False)),
Column('updated_at', DateTime(timezone=False)),
Column('deleted_at', DateTime(timezone=False)),
Column('deleted', Boolean(create_constraint=True, name=None)),
Column('domain',
String(length=512, convert_unicode=False, assert_unicode=None,
unicode_error=None, _warn_on_bytestring=False),
primary_key=True, nullable=False),
Column('scope',
String(length=255, convert_unicode=False, assert_unicode=None,
unicode_error=None, _warn_on_bytestring=False)),
Column('availability_zone',
String(length=255, convert_unicode=False, assert_unicode=None,
unicode_error=None, _warn_on_bytestring=False)),
Column('project_id',
String(length=255, convert_unicode=False, assert_unicode=None,
unicode_error=None, _warn_on_bytestring=False),
ForeignKey('projects.id'))
)
def upgrade(migrate_engine):
meta.bind = migrate_engine
# load instances for fk
instances = Table('projects', meta, autoload=True)
# create dns_domains table
try:
dns_domains.create()
except Exception:
logging.error(_("Table |%s| not created!"), repr(dns_domains))
raise
def downgrade(migrate_engine):
try:
dns_domains.drop()
except Exception:
logging.error(_("dns_domains table not dropped"))
raise

View File

@ -749,6 +749,19 @@ class Project(BASE, NovaBase):
backref='projects') backref='projects')
class DNSDomain(BASE, NovaBase):
"""Represents a DNS domain with availability zone or project info."""
__tablename__ = 'dns_domains'
domain = Column(String(512), primary_key=True)
scope = Column(String(255))
availability_zone = Column(String(255))
project_id = Column(String(255))
project = relationship(Project,
primaryjoin=project_id == Project.id,
foreign_keys=[Project.id],
uselist=False)
class UserProjectRoleAssociation(BASE, NovaBase): class UserProjectRoleAssociation(BASE, NovaBase):
__tablename__ = 'user_project_role_association' __tablename__ = 'user_project_role_association'
user_id = Column(String(255), primary_key=True) user_id = Column(String(255), primary_key=True)

View File

@ -264,6 +264,13 @@ class API(base.Base):
{'method': 'delete_dns_entry', {'method': 'delete_dns_entry',
'args': args}) 'args': args})
def delete_dns_domain(self, context, fqdomain):
"""Delete the specified dns domain."""
args = {'fqdomain': fqdomain}
return rpc.call(context, FLAGS.network_topic,
{'method': 'delete_dns_domain',
'args': args})
def get_dns_entries_by_address(self, context, address, zone): def get_dns_entries_by_address(self, context, address, zone):
"""Get entries for address and zone""" """Get entries for address and zone"""
args = {'address': address, 'dns_zone': zone} args = {'address': address, 'dns_zone': zone}
@ -277,3 +284,17 @@ class API(base.Base):
return rpc.call(context, FLAGS.network_topic, return rpc.call(context, FLAGS.network_topic,
{'method': 'get_dns_entries_by_name', {'method': 'get_dns_entries_by_name',
'args': args}) 'args': args})
def create_private_dns_domain(self, context, fqdomain, availability_zone):
"""Create a private DNS domain with nova availability zone."""
args = {'fqdomain': fqdomain, 'zone': availability_zone}
return rpc.call(context, FLAGS.network_topic,
{'method': 'create_private_dns_domain',
'args': args})
def create_public_dns_domain(self, context, fqdomain, project=None):
"""Create a private DNS domain with optional nova project."""
args = {'fqdomain': fqdomain, 'project': project}
return rpc.call(context, FLAGS.network_topic,
{'method': 'create_public_dns_domain',
'args': args})

View File

@ -36,3 +36,9 @@ class DNSDriver(object):
def get_entries_by_name(self, _name, _dnszone=""): def get_entries_by_name(self, _name, _dnszone=""):
return [] return []
def create_domain(self, _fqdomain):
pass
def delete_domain(self, _fqdomain):
pass

View File

@ -497,9 +497,42 @@ class FloatingIP(object):
fixed_address) fixed_address)
return [floating_ip['address'] for floating_ip in floating_ips] return [floating_ip['address'] for floating_ip in floating_ips]
def _prepare_domain_entry(self, context, domain):
domainref = self.db.dnsdomain_get(context, domain)
scope = domainref.scope
if scope == 'private':
av_zone = domainref.availability_zone
this_zone = {'domain': domain,
'scope': scope,
'availability_zone': av_zone}
else:
project = domainref.project_id
this_zone = {'domain': domain,
'scope': scope,
'project': project}
return this_zone
@wrap_check_policy @wrap_check_policy
def get_dns_zones(self, context): def get_dns_zones(self, context):
return self.floating_dns_manager.get_zones() zones = []
db_zone_list = self.db.dnsdomain_list(context)
floating_driver_zone_list = self.floating_dns_manager.get_zones()
instance_driver_zone_list = self.instance_dns_manager.get_zones()
for db_zone in db_zone_list:
if (db_zone in floating_driver_zone_list or
db_zone in instance_driver_zone_list):
zone_entry = self._prepare_domain_entry(context, db_zone)
if zone_entry:
zones.append(zone_entry)
else:
LOG.warn(_('Database inconsistency: DNS domain |%s| is '
'registered in the Nova db but not visible to '
'either the floating or instance DNS driver. It '
'will be ignored.'), db_zone)
return zones
@wrap_check_policy @wrap_check_policy
def add_dns_entry(self, context, address, dns_name, dns_type, dns_zone): def add_dns_entry(self, context, address, dns_name, dns_type, dns_zone):
@ -525,6 +558,34 @@ class FloatingIP(object):
return self.floating_dns_manager.get_entries_by_name(name, return self.floating_dns_manager.get_entries_by_name(name,
dns_zone) dns_zone)
@wrap_check_policy
def create_private_dns_domain(self, context, fqdomain, zone):
self.db.dnsdomain_register_for_zone(context, fqdomain, zone)
try:
self.instance_dns_manager.create_domain(fqdomain)
except exception.FloatingIpDNSExists:
LOG.warn(_('Domain |%(domain)s| already exists, '
'changing zone to |%(zone)s|.'),
{'domain': fqdomain, 'zone': zone})
@wrap_check_policy
def create_public_dns_domain(self, context, fqdomain, project):
self.db.dnsdomain_register_for_project(context, fqdomain, project)
try:
self.floating_dns_manager.create_domain(fqdomain)
except exception.FloatingIpDNSExists:
LOG.warn(_('Domain |%(domain)s| already exists, '
'changing project to |%(project)s|.'),
{'domain': fqdomain, 'project': project})
@wrap_check_policy
def delete_dns_domain(self, context, fqdomain):
self.db.dnsdomain_unregister(context, fqdomain)
self.floating_dns_manager.delete_domain(fqdomain)
def _get_project_for_domain(self, context, fqdomain):
return self.db.dnsdomain_project(context, fqdomain)
class NetworkManager(manager.SchedulerDependentManager): class NetworkManager(manager.SchedulerDependentManager):
"""Implements common network manager functionality. """Implements common network manager functionality.
@ -553,6 +614,7 @@ class NetworkManager(manager.SchedulerDependentManager):
self.driver = utils.import_object(network_driver) self.driver = utils.import_object(network_driver)
temp = utils.import_object(FLAGS.instance_dns_manager) temp = utils.import_object(FLAGS.instance_dns_manager)
self.instance_dns_manager = temp self.instance_dns_manager = temp
self.instance_dns_domain = FLAGS.instance_dns_zone
temp = utils.import_object(FLAGS.floating_ip_dns_manager) temp = utils.import_object(FLAGS.floating_ip_dns_manager)
self.floating_dns_manager = temp self.floating_dns_manager = temp
self.network_api = network_api.API() self.network_api = network_api.API()
@ -1038,6 +1100,25 @@ class NetworkManager(manager.SchedulerDependentManager):
raise exception.FixedIpNotFoundForSpecificInstance( raise exception.FixedIpNotFoundForSpecificInstance(
instance_id=instance_id, ip=address) instance_id=instance_id, ip=address)
def _validate_instance_zone_for_dns_domain(self, context, instance_id):
instance = self.db.instance_get(context, instance_id)
instance_zone = instance.get('availability_zone')
if not self.instance_dns_domain:
return True
instance_domain = self.instance_dns_domain
domainref = self.db.dnsdomain_get(context, instance_zone)
dns_zone = domainref.availability_zone
if dns_zone and (dns_zone != instance_zone):
LOG.warn(_('instance-dns-zone is |%(domain)s|, '
'which is in availability zone |%(zone)s|. '
'Instance |%(instance)s| is in zone |%(zone2)s|. '
'No DNS record will be created.'),
{'domain': instance_domain, 'zone': dns_zone,
'instance': instance_id, 'zone2': instance_zone})
return False
else:
return True
def allocate_fixed_ip(self, context, instance_id, network, **kwargs): def allocate_fixed_ip(self, context, instance_id, network, **kwargs):
"""Gets a fixed ip from the pool.""" """Gets a fixed ip from the pool."""
# TODO(vish): when this is called by compute, we can associate compute # TODO(vish): when this is called by compute, we can associate compute
@ -1065,12 +1146,15 @@ class NetworkManager(manager.SchedulerDependentManager):
instance_ref = self.db.instance_get(context, instance_id) instance_ref = self.db.instance_get(context, instance_id)
name = instance_ref['display_name'] name = instance_ref['display_name']
uuid = instance_ref['uuid']
self.instance_dns_manager.create_entry(name, address,
"A", FLAGS.instance_dns_zone)
self.instance_dns_manager.create_entry(uuid, address,
"A", FLAGS.instance_dns_zone)
if self._validate_instance_zone_for_dns_domain(context, instance_id):
uuid = instance_ref['uuid']
self.instance_dns_manager.create_entry(name, address,
"A",
self.instance_dns_domain)
self.instance_dns_manager.create_entry(uuid, address,
"A",
self.instance_dns_domain)
self._setup_network(context, network) self._setup_network(context, network)
return address return address
@ -1084,8 +1168,9 @@ class NetworkManager(manager.SchedulerDependentManager):
self._do_trigger_security_group_members_refresh_for_instance( self._do_trigger_security_group_members_refresh_for_instance(
instance_id) instance_id)
for name in self.instance_dns_manager.get_entries_by_address(address): if self._validate_instance_zone_for_dns_domain(context, instance_id):
self.instance_dns_manager.delete_entry(name) for n in self.instance_dns_manager.get_entries_by_address(address):
self.instance_dns_manager.delete_entry(n)
if FLAGS.force_dhcp_release: if FLAGS.force_dhcp_release:
network = self._get_network_by_id(context, network = self._get_network_by_id(context,

View File

@ -42,7 +42,14 @@ class MiniDNS(object):
f.close() f.close()
def get_zones(self): def get_zones(self):
return flags.FLAGS.floating_ip_dns_zones entries = []
infile = open(self.filename, 'r')
for line in infile:
entry = self.parse_line(line)
if entry and entry['address'].lower() == 'domain'.lower():
entries.append(entry['name'])
infile.close()
return entries
def qualify(self, name, zone): def qualify(self, name, zone):
if zone: if zone:
@ -75,6 +82,10 @@ class MiniDNS(object):
entry['address'] = vals[0] entry['address'] = vals[0]
entry['name'] = vals[1] entry['name'] = vals[1]
entry['type'] = vals[2] entry['type'] = vals[2]
if entry['address'] == 'domain':
entry['domain'] = entry['name']
else:
entry['domain'] = entry['name'].partition('.')[2]
return entry return entry
def delete_entry(self, name, dnszone=""): def delete_entry(self, name, dnszone=""):
@ -138,3 +149,30 @@ class MiniDNS(object):
def delete_dns_file(self): def delete_dns_file(self):
os.remove(self.filename) os.remove(self.filename)
def create_domain(self, fqdomain):
if self.get_entries_by_name(fqdomain, ''):
raise exception.FloatingIpDNSExists(name=fqdomain, zone='')
outfile = open(self.filename, 'a+')
outfile.write("%s %s %s\n" %
('domain', fqdomain, 'domain'))
outfile.close()
def delete_domain(self, fqdomain):
deleted = False
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['domain'] != fqdomain):
outfile.write(line)
else:
print "deleted %s" % entry
deleted = True
infile.close()
outfile.close()
shutil.move(outfile.name, self.filename)
if not deleted:
raise exception.NotFound

View File

@ -15,6 +15,7 @@
from lxml import etree from lxml import etree
import urllib import urllib
import webob
from nova.api.openstack.compute.contrib import floating_ip_dns from nova.api.openstack.compute.contrib import floating_ip_dns
from nova import context from nova import context
@ -51,7 +52,11 @@ def network_api_get_floating_ip(self, context, id):
def network_get_dns_zones(self, context): def network_get_dns_zones(self, context):
return ['foo', 'bar', 'baz', 'quux'] return [{'domain': 'example.org', 'scope': 'public'},
{'domain': 'example.com', 'scope': 'public',
'project': 'project1'},
{'domain': 'private.example.com', 'scope': 'private',
'availability_zone': 'avzone'}]
def network_get_dns_entries_by_address(self, context, address, zone): def network_get_dns_entries_by_address(self, context, address, zone):
@ -59,7 +64,7 @@ def network_get_dns_entries_by_address(self, context, address, zone):
def network_get_dns_entries_by_name(self, context, address, zone): def network_get_dns_entries_by_name(self, context, address, zone):
return [testaddress, testaddress2] return [testaddress]
def network_add_dns_entry(self, context, address, name, dns_type, zone): def network_add_dns_entry(self, context, address, name, dns_type, zone):
@ -104,7 +109,9 @@ class FloatingIpDNSTest(test.TestCase):
self.context = context.get_admin_context() self.context = context.get_admin_context()
self._create_floating_ip() self._create_floating_ip()
self.dns_controller = floating_ip_dns.FloatingIPDNSController() temp = floating_ip_dns.FloatingIPDNSDomainController()
self.domain_controller = temp
self.entry_controller = floating_ip_dns.FloatingIPDNSEntryController()
def tearDown(self): def tearDown(self):
self._delete_floating_ip() self._delete_floating_ip()
@ -112,21 +119,26 @@ class FloatingIpDNSTest(test.TestCase):
def test_dns_zones_list(self): def test_dns_zones_list(self):
req = fakes.HTTPRequest.blank('/v2/123/os-floating-ip-dns') req = fakes.HTTPRequest.blank('/v2/123/os-floating-ip-dns')
res_dict = self.dns_controller.index(req) res_dict = self.domain_controller.index(req)
entries = res_dict['zones'] entries = res_dict['zone_entries']
self.assertTrue(entries) self.assertTrue(entries)
self.assertEqual(entries[0]['zone'], "foo") self.assertEqual(entries[0]['domain'], "example.org")
self.assertEqual(entries[1]['zone'], "bar") self.assertFalse(entries[0]['project'])
self.assertEqual(entries[2]['zone'], "baz") self.assertFalse(entries[0]['availability_zone'])
self.assertEqual(entries[3]['zone'], "quux") self.assertEqual(entries[1]['domain'], "example.com")
self.assertEqual(entries[1]['project'], "project1")
self.assertFalse(entries[1]['availability_zone'])
self.assertEqual(entries[2]['domain'], "private.example.com")
self.assertFalse(entries[2]['project'])
self.assertEqual(entries[2]['availability_zone'], "avzone")
def test_get_dns_entries_by_address(self): def test_get_dns_entries_by_address(self):
qparams = {'ip': testaddress} qparams = {'ip': testaddress}
params = "?%s" % urllib.urlencode(qparams) if qparams else "" params = "?%s" % urllib.urlencode(qparams) if qparams else ""
req = fakes.HTTPRequest.blank('/v2/123/os-floating-ip-dns/%s%s' % req = fakes.HTTPRequest.blank('/v2/123/os-floating-ip-dns/%s/entries%s'
(_quote_zone(zone), params)) % (_quote_zone(zone), params))
entries = self.dns_controller.show(req, _quote_zone(zone)) entries = self.entry_controller.index(req, _quote_zone(zone))
self.assertEqual(len(entries['dns_entries']), 2) self.assertEqual(len(entries['dns_entries']), 2)
self.assertEqual(entries['dns_entries'][0]['name'], self.assertEqual(entries['dns_entries'][0]['name'],
@ -137,96 +149,141 @@ class FloatingIpDNSTest(test.TestCase):
zone) zone)
def test_get_dns_entries_by_name(self): def test_get_dns_entries_by_name(self):
qparams = {'name': name} req = fakes.HTTPRequest.blank(
params = "?%s" % urllib.urlencode(qparams) if qparams else "" '/v2/123/os-floating-ip-dns/%s/entries/%s' %
(_quote_zone(zone), name))
entry = self.entry_controller.show(req, _quote_zone(zone), name)
req = fakes.HTTPRequest.blank('/v2/123/os-floating-ip-dns/%s%s' % self.assertEqual(entry['dns_entry']['ip'],
(_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) testaddress)
self.assertEqual(entries['dns_entries'][1]['ip'], self.assertEqual(entry['dns_entry']['zone'],
testaddress2)
self.assertEqual(entries['dns_entries'][0]['zone'],
zone) zone)
def test_create(self): def test_create_entry(self):
body = {'dns_entry': body = {'dns_entry':
{'name': name, {'ip': testaddress,
'ip': testaddress, 'dns_type': 'A'}}
'dns_type': 'A', req = fakes.HTTPRequest.blank(
'zone': zone}} '/v2/123/os-floating-ip-dns/%s/entries/%s' %
req = fakes.HTTPRequest.blank('/v2/123/os-floating-ip-dns') (_quote_zone(zone), name))
entry = self.dns_controller.create(req, body) entry = self.entry_controller.update(req, _quote_zone(zone),
name, body)
self.assertEqual(entry['dns_entry']['ip'], testaddress) self.assertEqual(entry['dns_entry']['ip'], testaddress)
def test_delete(self): def test_create_domain(self):
req = fakes.HTTPRequest.blank('/v2/123/os-floating-ip-dns/%s' %
_quote_zone(zone))
body = {'zone_entry':
{'scope': 'private',
'project': 'testproject'}}
self.assertRaises(webob.exc.HTTPUnprocessableEntity,
self.domain_controller.update,
req, _quote_zone(zone), body)
body = {'zone_entry':
{'scope': 'public',
'availability_zone': 'zone1'}}
self.assertRaises(webob.exc.HTTPUnprocessableEntity,
self.domain_controller.update,
req, _quote_zone(zone), body)
body = {'zone_entry':
{'scope': 'public',
'project': 'testproject'}}
entry = self.domain_controller.update(req, _quote_zone(zone), body)
self.assertEqual(entry['zone_entry']['domain'], zone)
self.assertEqual(entry['zone_entry']['scope'], 'public')
self.assertEqual(entry['zone_entry']['project'], 'testproject')
body = {'zone_entry':
{'scope': 'private',
'availability_zone': 'zone1'}}
entry = self.domain_controller.update(req, _quote_zone(zone), body)
self.assertEqual(entry['zone_entry']['domain'], zone)
self.assertEqual(entry['zone_entry']['scope'], 'private')
self.assertEqual(entry['zone_entry']['availability_zone'], 'zone1')
def test_delete_entry(self):
self.called = False self.called = False
self.deleted_zone = "" self.deleted_domain = ""
self.deleted_name = "" self.deleted_name = ""
def network_delete_dns_entry(fakeself, context, req, id): def network_delete_dns_entry(fakeself, context, name, domain):
self.called = True self.called = True
self.deleted_zone = id self.deleted_domain = domain
self.deleted_name = name
self.stubs.Set(network.api.API, "delete_dns_entry", self.stubs.Set(network.api.API, "delete_dns_entry",
network_delete_dns_entry) network_delete_dns_entry)
qparams = {'name': name} req = fakes.HTTPRequest.blank(
params = "?%s" % urllib.urlencode(qparams) if qparams else "" '/v2/123/os-floating-ip-dns/%s/entries/%s' %
(_quote_zone(zone), name))
req = fakes.HTTPRequest.blank('/v2/123/os-floating-ip-dns/%s%s' % entries = self.entry_controller.delete(req, _quote_zone(zone), name)
(_quote_zone(zone), params))
entries = self.dns_controller.delete(req, _quote_zone(zone))
self.assertTrue(self.called) self.assertTrue(self.called)
self.assertEquals(self.deleted_zone, zone) self.assertEquals(self.deleted_domain, zone)
self.assertEquals(self.deleted_name, name)
def test_delete_domain(self):
self.called = False
self.deleted_domain = ""
self.deleted_name = ""
def network_delete_dns_domain(fakeself, context, fqdomain):
self.called = True
self.deleted_domain = fqdomain
self.stubs.Set(network.api.API, "delete_dns_domain",
network_delete_dns_domain)
req = fakes.HTTPRequest.blank('/v2/123/os-floating-ip-dns/%s' %
_quote_zone(zone))
entries = self.domain_controller.delete(req, _quote_zone(zone))
self.assertTrue(self.called)
self.assertEquals(self.deleted_domain, zone)
def test_modify(self): def test_modify(self):
body = {'dns_entry': body = {'dns_entry':
{'name': name, {'ip': testaddress2,
'ip': testaddress2}} 'dns_type': 'A'}}
req = fakes.HTTPRequest.blank('/v2/123/os-floating-ip-dns/%s' % req = fakes.HTTPRequest.blank(
zone) '/v2/123/os-floating-ip-dns/%s/entries/%s' % (zone, name))
entry = self.dns_controller.update(req, zone, body) entry = self.entry_controller.update(req, zone, name, body)
self.assertEqual(entry['dns_entry']['ip'], testaddress2) self.assertEqual(entry['dns_entry']['ip'], testaddress2)
class FloatingIpDNSSerializerTest(test.TestCase): class FloatingIpDNSSerializerTest(test.TestCase):
def test_default_serializer(self): def test_zones_serializer(self):
serializer = floating_ip_dns.FloatingIPDNSTemplate()
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.ZonesTemplate() serializer = floating_ip_dns.ZonesTemplate()
text = serializer.serialize(dict( text = serializer.serialize(dict(
zones=[ zone_entries=[
dict(zone=zone), dict(zone=zone, scope='public', project='testproject'),
dict(zone=zone2)])) dict(zone=zone2, scope='private',
availability_zone='avzone')]))
tree = etree.fromstring(text) tree = etree.fromstring(text)
self.assertEqual('zones', tree.tag) self.assertEqual('zone_entries', tree.tag)
self.assertEqual(2, len(tree)) self.assertEqual(2, len(tree))
self.assertEqual(zone, tree[0].get('zone')) self.assertEqual(zone, tree[0].get('zone'))
self.assertEqual(zone2, tree[1].get('zone')) self.assertEqual(zone2, tree[1].get('zone'))
self.assertEqual('avzone', tree[1].get('availability_zone'))
def test_show_serializer(self): def test_zone_serializer(self):
serializer = floating_ip_dns.ZoneTemplate()
text = serializer.serialize(dict(
zone_entry=dict(zone=zone,
scope='public',
project='testproject')))
tree = etree.fromstring(text)
self.assertEqual('zone_entry', tree.tag)
self.assertEqual(zone, tree.get('zone'))
self.assertEqual('testproject', tree.get('project'))
def test_entries_serializer(self):
serializer = floating_ip_dns.FloatingIPDNSsTemplate() serializer = floating_ip_dns.FloatingIPDNSsTemplate()
text = serializer.serialize(dict( text = serializer.serialize(dict(
dns_entries=[ dns_entries=[
@ -252,3 +309,19 @@ class FloatingIpDNSSerializerTest(test.TestCase):
self.assertEqual('C', tree[1].get('type')) self.assertEqual('C', tree[1].get('type'))
self.assertEqual(zone, tree[1].get('zone')) self.assertEqual(zone, tree[1].get('zone'))
self.assertEqual(name2, tree[1].get('name')) self.assertEqual(name2, tree[1].get('name'))
def test_entry_serializer(self):
serializer = floating_ip_dns.FloatingIPDNSTemplate()
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'))

View File

@ -4,7 +4,7 @@
"compute:create:attach_volume": [], "compute:create:attach_volume": [],
"compute:get": [], "compute:get": [],
"compute:get_all" :[], "compute:get_all": [],
"compute:update": [], "compute:update": [],
@ -122,5 +122,8 @@
"network:modify_dns_entry": [], "network:modify_dns_entry": [],
"network:delete_dns_entry": [], "network:delete_dns_entry": [],
"network:get_dns_entries_by_address": [], "network:get_dns_entries_by_address": [],
"network:get_dns_entries_by_name": [] "network:get_dns_entries_by_name": [],
"network:create_private_dns_domain": [],
"network:create_public_dns_domain": [],
"network:delete_dns_domain": []
} }

View File

@ -521,3 +521,27 @@ class AggregateDBApiTestCase(test.TestCase):
self.assertRaises(exception.AggregateHostNotFound, self.assertRaises(exception.AggregateHostNotFound,
db.aggregate_host_delete, db.aggregate_host_delete,
ctxt, result.id, _get_fake_aggr_hosts()[0]) ctxt, result.id, _get_fake_aggr_hosts()[0])
def test_dns_registration(self):
domain1 = 'test.domain.one'
domain2 = 'test.domain.two'
testzone = 'testzone'
ctxt = context.get_admin_context()
db.dnsdomain_register_for_zone(ctxt, domain1, testzone)
domain_ref = db.dnsdomain_get(ctxt, domain1)
zone = domain_ref.availability_zone
scope = domain_ref.scope
self.assertEqual(scope, 'private')
self.assertEqual(zone, testzone)
db.dnsdomain_register_for_project(ctxt, domain2,
self.project_id)
domain_ref = db.dnsdomain_get(ctxt, domain2)
project = domain_ref.project_id
scope = domain_ref.scope
self.assertEqual(project, self.project_id)
self.assertEqual(scope, 'public')
db.dnsdomain_unregister(ctxt, domain1)
db.dnsdomain_unregister(ctxt, domain2)

View File

@ -277,6 +277,8 @@ class FlatNetworkTestCase(test.TestCase):
db.instance_get(self.context, db.instance_get(self.context,
1).AndReturn({'display_name': HOST, 1).AndReturn({'display_name': HOST,
'uuid': 'test-00001'}) 'uuid': 'test-00001'})
db.instance_get(mox.IgnoreArg(),
mox.IgnoreArg()).AndReturn({'availability_zone': ''})
db.fixed_ip_associate_pool(mox.IgnoreArg(), db.fixed_ip_associate_pool(mox.IgnoreArg(),
mox.IgnoreArg(), mox.IgnoreArg(),
mox.IgnoreArg()).AndReturn('192.168.0.101') mox.IgnoreArg()).AndReturn('192.168.0.101')
@ -343,6 +345,8 @@ class FlatNetworkTestCase(test.TestCase):
db.instance_get(self.context, db.instance_get(self.context,
1).AndReturn({'display_name': HOST, 1).AndReturn({'display_name': HOST,
'uuid': 'test-00001'}) 'uuid': 'test-00001'})
db.instance_get(mox.IgnoreArg(),
mox.IgnoreArg()).AndReturn({'availability_zone': ''})
db.fixed_ip_associate_pool(mox.IgnoreArg(), db.fixed_ip_associate_pool(mox.IgnoreArg(),
mox.IgnoreArg(), mox.IgnoreArg(),
mox.IgnoreArg()).AndReturn(fixedip) mox.IgnoreArg()).AndReturn(fixedip)
@ -750,7 +754,8 @@ class VlanNetworkTestCase(test.TestCase):
db.instance_get(mox.IgnoreArg(), db.instance_get(mox.IgnoreArg(),
mox.IgnoreArg()).AndReturn({'security_groups': mox.IgnoreArg()).AndReturn({'security_groups':
[{'id': 0}]}) [{'id': 0}],
'availability_zone': ''})
db.fixed_ip_associate_pool(mox.IgnoreArg(), db.fixed_ip_associate_pool(mox.IgnoreArg(),
mox.IgnoreArg(), mox.IgnoreArg(),
mox.IgnoreArg()).AndReturn('192.168.0.101') mox.IgnoreArg()).AndReturn('192.168.0.101')
@ -1264,16 +1269,6 @@ class FloatingIPTestCase(test.TestCase):
self.network.deallocate_for_instance(self.context, self.network.deallocate_for_instance(self.context,
instance_id=instance_ref['id']) 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): def test_floating_dns_create_conflict(self):
zone = "example.org" zone = "example.org"
address1 = "10.10.10.11" address1 = "10.10.10.11"
@ -1327,6 +1322,49 @@ class FloatingIPTestCase(test.TestCase):
self.network.delete_dns_entry, self.context, self.network.delete_dns_entry, self.context,
name1, zone) name1, zone)
def test_floating_dns_domains_public(self):
zone1 = "testzone"
domain1 = "example.org"
domain2 = "example.com"
address1 = '10.10.10.10'
entryname = 'testentry'
context_admin = context.RequestContext('testuser', 'testproject',
is_admin=True)
self.assertRaises(exception.AdminRequired,
self.network.create_public_dns_domain, self.context,
domain1, zone1)
self.network.create_public_dns_domain(context_admin, domain1,
'testproject')
self.network.create_public_dns_domain(context_admin, domain2,
'fakeproject')
domains = self.network.get_dns_zones(self.context)
self.assertEquals(len(domains), 2)
self.assertEquals(domains[0]['domain'], domain1)
self.assertEquals(domains[1]['domain'], domain2)
self.assertEquals(domains[0]['project'], 'testproject')
self.assertEquals(domains[1]['project'], 'fakeproject')
self.network.add_dns_entry(self.context, address1, entryname,
'A', domain1)
entries = self.network.get_dns_entries_by_name(self.context,
entryname, domain1)
self.assertEquals(len(entries), 1)
self.assertEquals(entries[0], address1)
self.assertRaises(exception.AdminRequired,
self.network.delete_dns_domain, self.context,
domain1)
self.network.delete_dns_domain(context_admin, domain1)
self.network.delete_dns_domain(context_admin, domain2)
# Verify that deleting the domain deleted the associated entry
entries = self.network.get_dns_entries_by_name(self.context,
entryname, domain1)
self.assertFalse(entries)
class NetworkPolicyTestCase(test.TestCase): class NetworkPolicyTestCase(test.TestCase):
def setUp(self): def setUp(self):
@ -1355,3 +1393,44 @@ class NetworkPolicyTestCase(test.TestCase):
network_manager.check_policy(self.context, 'get_all') network_manager.check_policy(self.context, 'get_all')
self.mox.UnsetStubs() self.mox.UnsetStubs()
self.mox.VerifyAll() self.mox.VerifyAll()
class InstanceDNSTestCase(test.TestCase):
"""Tests nova.network.manager instance DNS"""
def setUp(self):
super(InstanceDNSTestCase, self).setUp()
self.network = TestFloatingIPManager()
temp = utils.import_object('nova.network.minidns.MiniDNS')
self.network.instance_dns_manager = temp
temp = utils.import_object('nova.network.dns_driver.DNSDriver')
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(InstanceDNSTestCase, self).tearDown()
self.network.instance_dns_manager.delete_dns_file()
def test_dns_domains_private(self):
zone1 = 'testzone'
domain1 = 'example.org'
context_admin = context.RequestContext('testuser', 'testproject',
is_admin=True)
self.assertRaises(exception.AdminRequired,
self.network.create_private_dns_domain, self.context,
domain1, zone1)
self.network.create_private_dns_domain(context_admin, domain1, zone1)
domains = self.network.get_dns_zones(self.context)
self.assertEquals(len(domains), 1)
self.assertEquals(domains[0]['domain'], domain1)
self.assertEquals(domains[0]['availability_zone'], zone1)
self.assertRaises(exception.AdminRequired,
self.network.delete_dns_domain, self.context,
domain1)
self.network.delete_dns_domain(context_admin, domain1)