Merge "Add APIs for managing TLDs"
This commit is contained in:
commit
3677ea7e7e
@ -17,6 +17,7 @@ from designate.openstack.common import log as logging
|
||||
from designate.api.v2.controllers import limits
|
||||
from designate.api.v2.controllers import reverse
|
||||
from designate.api.v2.controllers import schemas
|
||||
from designate.api.v2.controllers import tlds
|
||||
from designate.api.v2.controllers import zones
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -30,4 +31,5 @@ class RootController(object):
|
||||
limits = limits.LimitsController()
|
||||
schemas = schemas.SchemasController()
|
||||
reverse = reverse.ReverseController()
|
||||
tlds = tlds.TldsController()
|
||||
zones = zones.ZonesController()
|
||||
|
126
designate/api/v2/controllers/tlds.py
Normal file
126
designate/api/v2/controllers/tlds.py
Normal file
@ -0,0 +1,126 @@
|
||||
# Copyright (c) 2014 Rackspace Hosting
|
||||
# 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 pecan
|
||||
from designate.openstack.common import log as logging
|
||||
from designate import schema
|
||||
from designate import utils
|
||||
from designate.api.v2.controllers import rest
|
||||
from designate.api.v2.views import tlds as tlds_view
|
||||
from designate.central import rpcapi as central_rpcapi
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
central_api = central_rpcapi.CentralAPI()
|
||||
|
||||
|
||||
class TldsController(rest.RestController):
|
||||
_view = tlds_view.TldsView()
|
||||
_resource_schema = schema.Schema('v2', 'tld')
|
||||
_collection_schema = schema.Schema('v2', 'tlds')
|
||||
|
||||
@pecan.expose(template='json:', content_type='application/json')
|
||||
def get_one(self, tld_id):
|
||||
""" Get Tld """
|
||||
|
||||
request = pecan.request
|
||||
context = request.environ['context']
|
||||
|
||||
tld = central_api.get_tld(context, tld_id)
|
||||
return self._view.show(context, request, tld)
|
||||
|
||||
@pecan.expose(template='json:', content_type='application/json')
|
||||
def get_all(self, **params):
|
||||
""" List Tlds """
|
||||
request = pecan.request
|
||||
context = request.environ['context']
|
||||
|
||||
# Extract the pagination params
|
||||
#marker = params.pop('marker', None)
|
||||
#limit = int(params.pop('limit', 30))
|
||||
|
||||
# Extract any filter params.
|
||||
accepted_filters = ('name')
|
||||
criterion = dict((k, params[k]) for k in accepted_filters
|
||||
if k in params)
|
||||
|
||||
tlds = central_api.find_tlds(context, criterion)
|
||||
|
||||
return self._view.list(context, request, tlds)
|
||||
|
||||
@pecan.expose(template='json:', content_type='application/json')
|
||||
def post_all(self):
|
||||
""" Create Tld """
|
||||
request = pecan.request
|
||||
response = pecan.response
|
||||
context = request.environ['context']
|
||||
body = request.body_dict
|
||||
|
||||
# Validate the request conforms to the schema
|
||||
self._resource_schema.validate(body)
|
||||
|
||||
# Convert from APIv2 -> Central format
|
||||
values = self._view.load(context, request, body)
|
||||
|
||||
# Create the tld
|
||||
tld = central_api.create_tld(context, values)
|
||||
response.status_int = 201
|
||||
|
||||
response.headers['Location'] = self._view._get_resource_href(request,
|
||||
tld)
|
||||
# Prepare and return the response body
|
||||
return self._view.show(context, request, tld)
|
||||
|
||||
@pecan.expose(template='json:', content_type='application/json')
|
||||
@pecan.expose(template='json:', content_type='application/json-patch+json')
|
||||
def patch_one(self, tld_id):
|
||||
""" Update Tld """
|
||||
request = pecan.request
|
||||
context = request.environ['context']
|
||||
body = request.body_dict
|
||||
response = pecan.response
|
||||
|
||||
# Fetch the existing tld
|
||||
tld = central_api.get_tld(context, tld_id)
|
||||
|
||||
# Convert to APIv2 Format
|
||||
tld = self._view.show(context, request, tld)
|
||||
|
||||
if request.content_type == 'application/json-patch+json':
|
||||
raise NotImplemented('json-patch not implemented')
|
||||
else:
|
||||
tld = utils.deep_dict_merge(tld, body)
|
||||
|
||||
# Validate the request conforms to the schema
|
||||
self._resource_schema.validate(tld)
|
||||
|
||||
values = self._view.load(context, request, body)
|
||||
tld = central_api.update_tld(context, tld_id, values)
|
||||
|
||||
response.status_int = 200
|
||||
|
||||
return self._view.show(context, request, tld)
|
||||
|
||||
@pecan.expose(template=None, content_type='application/json')
|
||||
def delete_one(self, tld_id):
|
||||
""" Delete Tld """
|
||||
request = pecan.request
|
||||
response = pecan.response
|
||||
context = request.environ['context']
|
||||
|
||||
central_api.delete_tld(context, tld_id)
|
||||
|
||||
response.status_int = 204
|
||||
|
||||
# NOTE: This is a hack and a half.. But Pecan needs it.
|
||||
return ''
|
49
designate/api/v2/views/tlds.py
Normal file
49
designate/api/v2/views/tlds.py
Normal file
@ -0,0 +1,49 @@
|
||||
# Copyright (c) 2014 Rackspace Hosting
|
||||
# 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 designate.api.v2.views import base as base_view
|
||||
from designate.openstack.common import log as logging
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TldsView(base_view.BaseView):
|
||||
""" Model a TLD API response as a python dictionary """
|
||||
|
||||
_resource_name = 'tld'
|
||||
_collection_name = 'tlds'
|
||||
|
||||
def show_basic(self, context, request, tld):
|
||||
""" Basic view of a tld """
|
||||
return {
|
||||
"id": tld['id'],
|
||||
"name": tld['name'],
|
||||
"description": tld['description'],
|
||||
"created_at": tld['created_at'],
|
||||
"updated_at": tld['updated_at'],
|
||||
"links": self._get_resource_links(request, tld)
|
||||
}
|
||||
|
||||
def load(self, context, request, body):
|
||||
""" Extract a "central" compatible dict from an API call """
|
||||
result = {}
|
||||
item = body[self._resource_name]
|
||||
|
||||
# Copy keys which need no alterations
|
||||
for k in ('id', 'name', 'description'):
|
||||
if k in item:
|
||||
result[k] = item[k]
|
||||
|
||||
return result
|
@ -32,10 +32,6 @@ cfg.CONF.register_opts([
|
||||
default=['\\.arpa\\.$', '\\.novalocal\\.$', '\\.localhost\\.$',
|
||||
'\\.localdomain\\.$', '\\.local\\.$'],
|
||||
help='DNS domain name blacklist'),
|
||||
cfg.StrOpt('accepted-tlds-file', default='tlds-alpha-by-domain.txt',
|
||||
help='Accepted TLDs'),
|
||||
cfg.StrOpt('effective-tlds-file', default='effective_tld_names.dat',
|
||||
help='Effective TLDs'),
|
||||
cfg.IntOpt('max_domain_name_len', default=255,
|
||||
help="Maximum domain name length"),
|
||||
cfg.IntOpt('max_recordset_name_len', default=255,
|
||||
|
@ -1,179 +0,0 @@
|
||||
# Copyright (c) 2013 Rackspace Hosting
|
||||
# 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 codecs
|
||||
import re
|
||||
from designate import utils
|
||||
from designate.openstack.common import log as logging
|
||||
from oslo.config import cfg
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class EffectiveTld(object):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._load_accepted_tld_list()
|
||||
self._load_effective_tld_list()
|
||||
|
||||
def _load_accepted_tld_list(self):
|
||||
"""
|
||||
This loads the accepted TLDs from a file to a list - accepted_tld_list.
|
||||
The file is expected to have one TLD per line. TLDs need to be in the
|
||||
IDN format. Comments in the file are lines beginning with a #
|
||||
The normal source for this file is
|
||||
http://data.iana.org/TLD/tlds-alpha-by-domain.txt
|
||||
"""
|
||||
self.accepted_tld_list = []
|
||||
accepted_tld_files = utils.find_config(
|
||||
cfg.CONF['service:central'].accepted_tlds_file)
|
||||
|
||||
# We do not require the accepted_tld_files to be present to be
|
||||
# compatible with stable/havana release.
|
||||
if len(accepted_tld_files) == 0:
|
||||
LOG.info('Unable to determine appropriate accepted tlds file')
|
||||
return
|
||||
|
||||
LOG.info('Using accepted_tld_file found at: %s'
|
||||
% accepted_tld_files[0])
|
||||
|
||||
with open(accepted_tld_files[0]) as fh:
|
||||
for line in fh:
|
||||
if line.startswith('#'):
|
||||
continue
|
||||
line = line.strip()
|
||||
self.accepted_tld_list.append(line.lower())
|
||||
|
||||
LOG.info("Entries in Accepted TLD List: %d"
|
||||
% len(self.accepted_tld_list))
|
||||
# LOG.info("Accepted TLD List:\n%s" % self.accepted_tld_list)
|
||||
|
||||
def _load_effective_tld_list(self):
|
||||
"""
|
||||
This loads the effective TLDs from a file. Effective TLDs are the SLDs
|
||||
that act as TLDs - e.g. co.uk. The file is in UTF-8 format.
|
||||
The normal source for this file is at http://publicsuffix.org/list/
|
||||
The format of the file is:
|
||||
1. Lines beginning with a // or ! are ignored.
|
||||
2. The domain names are 1 per line.
|
||||
3. The wildcard character * (asterisk) may only be used to wildcard the
|
||||
topmost level in a domain name.
|
||||
|
||||
The publicsuffix.org has more rules and !'s are treated differently but
|
||||
this code ignores those until we find that we need to do otherwise.
|
||||
|
||||
The file is put into a dictionary and a list. Domain names with only 1
|
||||
label are ignored as they are already present in the accepted_tld_list.
|
||||
All the entries are converted to IDN format.
|
||||
All the effective TLDs without a wildcard are put into a dictionary -
|
||||
_effective_tld_dict.
|
||||
The entries with a wildcard are converted to a regular expression and
|
||||
put into a separate list - _effective_re_tld_list.
|
||||
The separation to a dictionary and a regular expression list is done
|
||||
to make it easier for searching.
|
||||
The maximum labels in the dictionary and list are tracked to short
|
||||
circuit checks later as needed.
|
||||
"""
|
||||
self._effective_tld_dict = {}
|
||||
|
||||
# _max_effective_tld_labels tracks the maximum labels in the
|
||||
# dictionary self._effective_tld_dict
|
||||
# This helps to determine if we need to search the dictionary while
|
||||
# creating a domain
|
||||
self._max_effective_tld_labels = 0
|
||||
|
||||
# The list _effective_re_tld_list contains domains with a *
|
||||
self._effective_re_tld_list = []
|
||||
|
||||
# _max_effective_re_tld_labels tracks the maximum labels in the
|
||||
# list self._effective_re_tld_list
|
||||
self._max_effective_re_tld_labels = 0
|
||||
|
||||
effective_tld_files = utils.find_config(
|
||||
cfg.CONF['service:central'].effective_tlds_file)
|
||||
|
||||
# We do not require the effective_tld_file to be present to be
|
||||
# compatible with stable/havana release.
|
||||
if len(effective_tld_files) == 0:
|
||||
LOG.info('Unable to determine appropriate effective tlds file')
|
||||
return
|
||||
|
||||
LOG.info('Using effective_tld_file found at: %s'
|
||||
% effective_tld_files[0])
|
||||
|
||||
with codecs.open(effective_tld_files[0], "r", "utf-8") as fh:
|
||||
for line in fh:
|
||||
line = line.strip()
|
||||
|
||||
if line.startswith('//') or line.startswith('!') or not line:
|
||||
continue
|
||||
labels_len = len(line.split('.'))
|
||||
|
||||
# skip TLDs as they are already in the accepted_tld_list
|
||||
if labels_len == 1:
|
||||
continue
|
||||
|
||||
# Convert the public suffix list to idna format
|
||||
line = line.encode('idna')
|
||||
|
||||
# Entries with wildcards go to a separate list.
|
||||
if (line.startswith('*')):
|
||||
if labels_len > self._max_effective_re_tld_labels:
|
||||
self._max_effective_re_tld_labels = labels_len
|
||||
|
||||
# Convert the wildcard entry to a regular expression
|
||||
# The ^ and $ at the beginning and end respectively are to
|
||||
# match the whole term. The [^.]* is to match anything
|
||||
# other than a "." This is so that only one label is
|
||||
# matched. The rest of the label separators "." are
|
||||
# escaped to match the "." and not any character.
|
||||
self._effective_re_tld_list.append(
|
||||
'^[^.]*' + '\.'.join(line.split('.'))[1:] + '$')
|
||||
continue
|
||||
|
||||
if labels_len > self._max_effective_tld_labels:
|
||||
self._max_effective_tld_labels = labels_len
|
||||
|
||||
# The rest of the entries go into a dictionary.
|
||||
self._effective_tld_dict[line.lower()] = 1
|
||||
|
||||
LOG.info("Entries in Effective TLD List Dict: %d"
|
||||
% len(self._effective_tld_dict))
|
||||
# LOG.info("Effective TLD Dict:\n%s" % self._effective_tld_dict)
|
||||
|
||||
LOG.info("Entries in Effective RE TLD List: %d"
|
||||
% len(self._effective_re_tld_list))
|
||||
# LOG.info("Effective RE TLD List:\n%s" % self._effective_re_tld_list)
|
||||
|
||||
def is_effective_tld(self, domain_name):
|
||||
"""
|
||||
Returns True if the domain_name is the same as an effective TLD else
|
||||
returns False.
|
||||
"""
|
||||
# Break the domain name up into its component labels
|
||||
stripped_domain_name = domain_name.strip('.').lower()
|
||||
domain_labels = stripped_domain_name.split('.')
|
||||
|
||||
if len(domain_labels) <= self._max_effective_tld_labels:
|
||||
# First search the dictionary
|
||||
if stripped_domain_name in self._effective_tld_dict.keys():
|
||||
return True
|
||||
|
||||
# Now search the list of regular expressions for effective TLDs
|
||||
if len(domain_labels) <= self._max_effective_re_tld_labels:
|
||||
for eff_re_label in self._effective_re_tld_list:
|
||||
if bool(re.search(eff_re_label, stripped_domain_name)):
|
||||
return True
|
||||
|
||||
return False
|
@ -34,6 +34,8 @@ class CentralAPI(rpc_proxy.RpcProxy):
|
||||
2.1 - Add quota methods
|
||||
3.0 - RecordSet Changes
|
||||
3.1 - Add floating ip ptr methods
|
||||
3.2 - TLD Api changes
|
||||
|
||||
"""
|
||||
def __init__(self, topic=None):
|
||||
topic = topic if topic else cfg.CONF.central_topic
|
||||
@ -214,6 +216,37 @@ class CentralAPI(rpc_proxy.RpcProxy):
|
||||
|
||||
return self.call(context, msg)
|
||||
|
||||
# TLD Methods
|
||||
def create_tld(self, context, values):
|
||||
LOG.info("create_tld: Calling central's create_tld.")
|
||||
msg = self.make_msg('create_tld', values=values)
|
||||
|
||||
return self.call(context, msg, version='3.2')
|
||||
|
||||
def find_tlds(self, context, criterion=None):
|
||||
LOG.info("find_tlds: Calling central's find_tlds.")
|
||||
msg = self.make_msg('find_tlds', criterion=criterion)
|
||||
|
||||
return self.call(context, msg, version='3.2')
|
||||
|
||||
def get_tld(self, context, tld_id):
|
||||
LOG.info("get_tld: Calling central's get_tld.")
|
||||
msg = self.make_msg('get_tld', tld_id=tld_id)
|
||||
|
||||
return self.call(context, msg, version='3.2')
|
||||
|
||||
def update_tld(self, context, tld_id, values):
|
||||
LOG.info("update_tld: Calling central's update_tld.")
|
||||
msg = self.make_msg('update_tld', tld_id=tld_id, values=values)
|
||||
|
||||
return self.call(context, msg, version='3.2')
|
||||
|
||||
def delete_tld(self, context, tld_id):
|
||||
LOG.info("delete_tld: Calling central's delete_tld.")
|
||||
msg = self.make_msg('delete_tld', tld_id=tld_id)
|
||||
|
||||
return self.call(context, msg, version='3.2')
|
||||
|
||||
# RecordSet Methods
|
||||
def create_recordset(self, context, domain_id, values):
|
||||
LOG.info("create_recordset: Calling central's create_recordset.")
|
||||
|
@ -17,7 +17,6 @@
|
||||
import re
|
||||
import contextlib
|
||||
from oslo.config import cfg
|
||||
from designate.central import effectivetld
|
||||
from designate.openstack.common import log as logging
|
||||
from designate.openstack.common.rpc import service as rpc_service
|
||||
from designate.openstack.common.notifier import proxy as notifier
|
||||
@ -46,7 +45,7 @@ def wrap_backend_call():
|
||||
|
||||
|
||||
class Service(rpc_service.Service):
|
||||
RPC_API_VERSION = '3.1'
|
||||
RPC_API_VERSION = '3.2'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
backend_driver = cfg.CONF['service:central'].backend_driver
|
||||
@ -68,11 +67,19 @@ class Service(rpc_service.Service):
|
||||
|
||||
# Get a quota manager instance
|
||||
self.quota = quota.get_quota()
|
||||
self.effective_tld = effectivetld.EffectiveTld()
|
||||
|
||||
self.network_api = network_api.get_api(cfg.CONF.network_api)
|
||||
|
||||
def start(self):
|
||||
# Check to see if there are any TLDs in the database
|
||||
tlds = self.storage_api.find_tlds({})
|
||||
if tlds:
|
||||
self.check_for_tlds = True
|
||||
LOG.info("Checking for TLDs")
|
||||
else:
|
||||
self.check_for_tlds = False
|
||||
LOG.info("NOT checking for TLDs")
|
||||
|
||||
self.backend.start()
|
||||
|
||||
super(Service, self).start()
|
||||
@ -94,21 +101,25 @@ class Service(rpc_service.Service):
|
||||
if len(domain_labels) <= 1:
|
||||
raise exceptions.InvalidDomainName('More than one label is '
|
||||
'required')
|
||||
# Check the TLD for validity
|
||||
# We cannot use the effective TLD list as the publicsuffix.org list is
|
||||
# missing some top level entries. At the time of coding, the following
|
||||
# entries were missing
|
||||
# arpa, au, bv, gb, gn, kp, lb, lr, sj, tp, tz, xn--80ao21a, xn--l1acc
|
||||
# xn--mgbx4cd0ab
|
||||
if self.effective_tld.accepted_tld_list:
|
||||
domain_tld = domain_labels[-1].lower()
|
||||
if domain_tld not in self.effective_tld.accepted_tld_list:
|
||||
raise exceptions.InvalidTLD('Unknown or invalid TLD')
|
||||
|
||||
# Check if the domain_name is the same as an effective TLD.
|
||||
if self.effective_tld.is_effective_tld(domain_name):
|
||||
raise exceptions.DomainIsSameAsAnEffectiveTLD(
|
||||
'Domain name cannot be the same as an effective TLD')
|
||||
# Check the TLD for validity if there are entries in the database
|
||||
if self.check_for_tlds:
|
||||
try:
|
||||
self.storage_api.find_tld(context, {'name': domain_labels[-1]})
|
||||
except exceptions.TLDNotFound:
|
||||
raise exceptions.InvalidDomainName('Invalid TLD')
|
||||
|
||||
# Now check that the domain name is not the same as a TLD
|
||||
try:
|
||||
stripped_domain_name = domain_name.strip('.').lower()
|
||||
self.storage_api.find_tld(
|
||||
context,
|
||||
{'name': stripped_domain_name})
|
||||
except exceptions.TLDNotFound:
|
||||
pass
|
||||
else:
|
||||
raise exceptions.InvalidDomainName(
|
||||
'Domain name cannot be the same as a TLD')
|
||||
|
||||
# Check domain name blacklist
|
||||
if self._is_blacklisted_domain_name(context, domain_name):
|
||||
@ -343,6 +354,52 @@ class Service(rpc_service.Service):
|
||||
|
||||
self.notifier.info(context, 'dns.server.delete', server)
|
||||
|
||||
# TLD Methods
|
||||
def create_tld(self, context, values):
|
||||
policy.check('create_tld', context)
|
||||
|
||||
# The TLD is only created on central's storage and not on the backend.
|
||||
with self.storage_api.create_tld(context, values) as tld:
|
||||
pass
|
||||
self.notifier.info(context, 'dns.tld.create', tld)
|
||||
|
||||
# Set check for tlds to be true
|
||||
self.check_for_tlds = True
|
||||
return tld
|
||||
|
||||
def find_tlds(self, context, criterion=None):
|
||||
policy.check('find_tlds', context)
|
||||
|
||||
return self.storage_api.find_tlds(context, criterion)
|
||||
|
||||
def get_tld(self, context, tld_id):
|
||||
policy.check('get_tld', context, {'tld_id': tld_id})
|
||||
|
||||
return self.storage_api.get_tld(context, tld_id)
|
||||
|
||||
def update_tld(self, context, tld_id, values):
|
||||
policy.check('update_tld', context, {'tld_id': tld_id})
|
||||
|
||||
with self.storage_api.update_tld(context, tld_id, values) as tld:
|
||||
pass
|
||||
|
||||
self.notifier.info(context, 'dns.tld.update', tld)
|
||||
|
||||
return tld
|
||||
|
||||
def delete_tld(self, context, tld_id):
|
||||
# Known issue - self.check_for_tld is not reset here. So if the last
|
||||
# TLD happens to be deleted, then we would incorrectly do the TLD
|
||||
# validations.
|
||||
# This decision was influenced by weighing the (ultra low) probability
|
||||
# of hitting this issue vs doing the checks for every delete.
|
||||
policy.check('delete_tld', context, {'tld_id': tld_id})
|
||||
|
||||
with self.storage_api.delete_tld(context, tld_id) as tld:
|
||||
pass
|
||||
|
||||
self.notifier.info(context, 'dns.tld.delete', tld)
|
||||
|
||||
# TSIG Key Methods
|
||||
def create_tsigkey(self, context, values):
|
||||
policy.check('create_tsigkey', context)
|
||||
|
@ -107,16 +107,6 @@ class InvalidDomainName(Base):
|
||||
error_type = 'invalid_domain_name'
|
||||
|
||||
|
||||
class DomainIsSameAsAnEffectiveTLD(Base):
|
||||
error_code = 400
|
||||
error_type = 'domain_is_same_as_an_effective_tld'
|
||||
|
||||
|
||||
class InvalidTLD(Base):
|
||||
error_code = 400
|
||||
error_type = 'invalid_tld'
|
||||
|
||||
|
||||
class InvalidRecordSetName(Base):
|
||||
error_code = 400
|
||||
error_type = 'invalid_recordset_name'
|
||||
@ -158,6 +148,10 @@ class DuplicateDomain(Duplicate):
|
||||
error_type = 'duplicate_domain'
|
||||
|
||||
|
||||
class DuplicateTLD(Duplicate):
|
||||
error_type = 'duplicate_tld'
|
||||
|
||||
|
||||
class DuplicateRecordSet(Duplicate):
|
||||
error_type = 'duplicate_recordset'
|
||||
|
||||
@ -187,6 +181,10 @@ class DomainNotFound(NotFound):
|
||||
error_type = 'domain_not_found'
|
||||
|
||||
|
||||
class TLDNotFound(NotFound):
|
||||
error_type = 'tld_not_found'
|
||||
|
||||
|
||||
class RecordSetNotFound(NotFound):
|
||||
error_type = 'recordset_not_found'
|
||||
|
||||
|
63
designate/resources/schemas/v2/tld.json
Normal file
63
designate/resources/schemas/v2/tld.json
Normal file
@ -0,0 +1,63 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/hyper-schema",
|
||||
|
||||
"id": "tld",
|
||||
|
||||
"title": "tld",
|
||||
"description": "Tld",
|
||||
"additionalProperties": false,
|
||||
|
||||
"required": ["tld"],
|
||||
|
||||
"properties": {
|
||||
"tld": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["name"],
|
||||
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Tld identifier",
|
||||
"pattern": "^([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}$",
|
||||
"readOnly": true
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Tld name",
|
||||
"format": "tldname",
|
||||
"maxLength": 255,
|
||||
"immutable": true
|
||||
},
|
||||
"description": {
|
||||
"type": ["string", "null"],
|
||||
"description": "Description for the tld",
|
||||
"maxLength": 160
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string",
|
||||
"description": "Date and time of tld creation",
|
||||
"format": "date-time",
|
||||
"readOnly": true
|
||||
},
|
||||
"updated_at": {
|
||||
"type": ["string", "null"],
|
||||
"description": "Date and time of last tld modification",
|
||||
"format": "date-time",
|
||||
"readOnly": true
|
||||
},
|
||||
"links": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
|
||||
"properties": {
|
||||
"self": {
|
||||
"type": "string",
|
||||
"format": "url"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
38
designate/resources/schemas/v2/tlds.json
Normal file
38
designate/resources/schemas/v2/tlds.json
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/hyper-schema",
|
||||
|
||||
"id": "tlds",
|
||||
|
||||
"title": "tlds",
|
||||
"description": "Tlds",
|
||||
"additionalProperties": false,
|
||||
|
||||
"required": ["tlds"],
|
||||
|
||||
"properties": {
|
||||
"tlds": {
|
||||
"type": "array",
|
||||
"description": "Tlds",
|
||||
"items": {"$ref": "tld#/properties/tld"}
|
||||
},
|
||||
"links": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
|
||||
"properties": {
|
||||
"self": {
|
||||
"type": "string",
|
||||
"format": "url"
|
||||
},
|
||||
"next": {
|
||||
"type": ["string", "null"],
|
||||
"format": "url"
|
||||
},
|
||||
"previous": {
|
||||
"type": ["string", "null"],
|
||||
"format": "url"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -24,6 +24,10 @@ LOG = logging.getLogger(__name__)
|
||||
RE_DOMAINNAME = r'^(?!.{255,})((?!\-)[A-Za-z0-9_\-]{1,63}(?<!\-)\.)+$'
|
||||
RE_HOSTNAME = r'^(?!.{255,})((^\*|(?!\-)[A-Za-z0-9_\-]{1,63})(?<!\-)\.)+$'
|
||||
|
||||
# The TLD name will not end in a period.
|
||||
RE_TLDNAME = r'^(?!.{255,})((?!\-)[A-Za-z0-9_\-]{1,63}(?<!\-))' \
|
||||
r'(\.((?!\-)[A-Za-z0-9_\-]{1,63}(?<!\-)))*$'
|
||||
|
||||
draft3_format_checker = jsonschema.draft3_format_checker
|
||||
draft4_format_checker = jsonschema.draft4_format_checker
|
||||
|
||||
@ -86,6 +90,18 @@ def is_domainname(instance):
|
||||
return True
|
||||
|
||||
|
||||
@draft3_format_checker.checks("tld-name")
|
||||
@draft4_format_checker.checks("tldname")
|
||||
def is_tldname(instance):
|
||||
if not isinstance(instance, compat.str_types):
|
||||
return True
|
||||
|
||||
if not re.match(RE_TLDNAME, instance):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@draft3_format_checker.checks("email")
|
||||
@draft4_format_checker.checks("email")
|
||||
def is_email(instance):
|
||||
|
@ -173,6 +173,79 @@ class StorageAPI(object):
|
||||
yield self.storage.get_server(context, server_id)
|
||||
self.storage.delete_server(context, server_id)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def create_tld(self, context, values):
|
||||
"""
|
||||
Create a TLD.
|
||||
|
||||
:param context: RPC Context.
|
||||
:param values: Values to create the new TLD from.
|
||||
"""
|
||||
tld = self.storage.create_tld(context, values)
|
||||
|
||||
try:
|
||||
yield tld
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
self.storage.delete_tld(context, tld['id'])
|
||||
|
||||
def get_tld(self, context, tld_id):
|
||||
"""
|
||||
Get a TLD via ID.
|
||||
|
||||
:param context: RPC Context.
|
||||
:param tld_id: TLD ID to get.
|
||||
"""
|
||||
return self.storage.get_tld(context, tld_id)
|
||||
|
||||
def find_tlds(self, context, criterion=None):
|
||||
"""
|
||||
Find TLDs
|
||||
|
||||
:param context: RPC Context.
|
||||
:param criterion: Criteria to filter by.
|
||||
"""
|
||||
return self.storage.find_tlds(context, criterion)
|
||||
|
||||
def find_tld(self, context, criterion):
|
||||
"""
|
||||
Find a single TLD.
|
||||
|
||||
:param context: RPC Context.
|
||||
:param criterion: Criteria to filter by.
|
||||
"""
|
||||
return self.storage.find_tld(context, criterion)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def update_tld(self, context, tld_id, values):
|
||||
"""
|
||||
Update a TLD via ID
|
||||
|
||||
:param context: RPC Context.
|
||||
:param tld_id: TLD ID to update.
|
||||
:param values: Values to update the TLD from
|
||||
"""
|
||||
backup = self.storage.get_tld(context, tld_id)
|
||||
tld = self.storage.update_tld(context, tld_id, values)
|
||||
|
||||
try:
|
||||
yield tld
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
restore = self._extract_dict_subset(backup, values.keys())
|
||||
self.storage.update_tld(context, tld_id, restore)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def delete_tld(self, context, tld_id):
|
||||
"""
|
||||
Delete a TLD via ID.
|
||||
|
||||
:param context: RPC Context.
|
||||
:param tld_id: Delete a TLD via ID
|
||||
"""
|
||||
yield self.storage.get_tld(context, tld_id)
|
||||
self.storage.delete_tld(context, tld_id)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def create_tsigkey(self, context, values):
|
||||
"""
|
||||
|
@ -124,6 +124,61 @@ class Storage(Plugin):
|
||||
:param server_id: Delete a Server via ID
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_tld(self, context, values):
|
||||
"""
|
||||
Create a TLD.
|
||||
|
||||
:param context: RPC Context.
|
||||
:param values: Values to create the new TLD from.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_tld(self, context, tld_id):
|
||||
"""
|
||||
Get a TLD via ID.
|
||||
|
||||
:param context: RPC Context.
|
||||
:param tld_id: TLD ID to get.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def find_tlds(self, context, criterion=None):
|
||||
"""
|
||||
Find TLDs
|
||||
|
||||
:param context: RPC Context.
|
||||
:param criterion: Criteria to filter by.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def find_tld(self, context, criterion):
|
||||
"""
|
||||
Find a single TLD.
|
||||
|
||||
:param context: RPC Context.
|
||||
:param criterion: Criteria to filter by.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def update_tld(self, context, tld_id, values):
|
||||
"""
|
||||
Update a TLD via ID
|
||||
|
||||
:param context: RPC Context.
|
||||
:param tld_id: TLD ID to update.
|
||||
:param values: Values to update the TLD from
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def delete_tld(self, context, tld_id):
|
||||
"""
|
||||
Delete a TLD via ID.
|
||||
|
||||
:param context: RPC Context.
|
||||
:param tld_id: Delete a TLD via ID
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_tsigkey(self, context, values):
|
||||
"""
|
||||
|
@ -212,6 +212,51 @@ class SQLAlchemyStorage(base.Storage):
|
||||
|
||||
server.delete(self.session)
|
||||
|
||||
# TLD Methods
|
||||
def _find_tlds(self, context, criterion, one=False):
|
||||
try:
|
||||
return self._find(models.Tld, context, criterion, one)
|
||||
except exceptions.NotFound:
|
||||
raise exceptions.TLDNotFound()
|
||||
|
||||
def create_tld(self, context, values):
|
||||
tld = models.Tld()
|
||||
tld.update(values)
|
||||
|
||||
try:
|
||||
tld.save(self.session)
|
||||
except exceptions.Duplicate:
|
||||
raise exceptions.DuplicateTLD()
|
||||
|
||||
return dict(tld)
|
||||
|
||||
def find_tlds(self, context, criterion=None):
|
||||
tlds = self._find_tlds(context, criterion)
|
||||
return [dict(s) for s in tlds]
|
||||
|
||||
def find_tld(self, context, criterion=None):
|
||||
tld = self._find_tlds(context, criterion, one=True)
|
||||
return dict(tld)
|
||||
|
||||
def get_tld(self, context, tld_id):
|
||||
tld = self._find_tlds(context, {'id': tld_id}, one=True)
|
||||
return dict(tld)
|
||||
|
||||
def update_tld(self, context, tld_id, values):
|
||||
tld = self._find_tlds(context, {'id': tld_id}, one=True)
|
||||
tld.update(values)
|
||||
|
||||
try:
|
||||
tld.save(self.session)
|
||||
except exceptions.Duplicate:
|
||||
raise exceptions.DuplicateTLD()
|
||||
|
||||
return dict(tld)
|
||||
|
||||
def delete_tld(self, context, tld_id):
|
||||
tld = self._find_tlds(context, {'id': tld_id}, one=True)
|
||||
tld.delete(self.session)
|
||||
|
||||
# TSIG Key Methods
|
||||
def _find_tsigkeys(self, context, criterion, one=False):
|
||||
try:
|
||||
|
@ -0,0 +1,49 @@
|
||||
# Copyright (c) 2014 Rackspace Hosting
|
||||
# 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 Integer, String, DateTime, Unicode
|
||||
from sqlalchemy.schema import Table, Column, MetaData
|
||||
from designate.openstack.common import timeutils
|
||||
from designate.openstack.common.uuidutils import generate_uuid
|
||||
from designate.sqlalchemy.types import UUID
|
||||
from designate.openstack.common import log as logging
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
meta = MetaData()
|
||||
|
||||
tlds_table = Table(
|
||||
'tlds',
|
||||
meta,
|
||||
|
||||
Column('id', UUID(), default=generate_uuid, primary_key=True),
|
||||
Column('created_at', DateTime(), default=timeutils.utcnow),
|
||||
Column('updated_at', DateTime(), onupdate=timeutils.utcnow),
|
||||
Column('version', Integer(), default=1, nullable=False),
|
||||
|
||||
Column('name', String(255), nullable=False, unique=True),
|
||||
Column('description', Unicode(160), nullable=True),
|
||||
|
||||
mysql_engine='INNODB',
|
||||
mysql_charset='utf8')
|
||||
|
||||
|
||||
def upgrade(migrate_engine):
|
||||
meta.bind = migrate_engine
|
||||
|
||||
tlds_table.create()
|
||||
|
||||
|
||||
def downgrade(migrate_engine):
|
||||
meta.bind = migrate_engine
|
||||
tlds_table.drop()
|
@ -72,6 +72,13 @@ class Server(Base):
|
||||
name = Column(String(255), nullable=False, unique=True)
|
||||
|
||||
|
||||
class Tld(Base):
|
||||
__tablename__ = 'tlds'
|
||||
|
||||
name = Column(String(255), nullable=False, unique=True)
|
||||
description = Column(Unicode(160), nullable=True)
|
||||
|
||||
|
||||
class Domain(SoftDeleteMixin, Base):
|
||||
__tablename__ = 'domains'
|
||||
__table_args__ = (
|
||||
|
@ -144,6 +144,23 @@ class TestCase(test.BaseTestCase):
|
||||
'name': 'ns2.example.org.',
|
||||
}]
|
||||
|
||||
# The last tld is invalid
|
||||
tld_fixtures = [{
|
||||
'name': 'com',
|
||||
}, {
|
||||
'name': 'co.uk',
|
||||
}, {
|
||||
'name': 'com.',
|
||||
}]
|
||||
|
||||
default_tld_fixtures = [{
|
||||
'name': 'com',
|
||||
}, {
|
||||
'name': 'org',
|
||||
}, {
|
||||
'name': 'net',
|
||||
}]
|
||||
|
||||
tsigkey_fixtures = [{
|
||||
'name': 'test-key-one',
|
||||
'algorithm': 'hmac-md5',
|
||||
@ -312,6 +329,16 @@ class TestCase(test.BaseTestCase):
|
||||
_values.update(values)
|
||||
return _values
|
||||
|
||||
def get_tld_fixture(self, fixture=0, values={}):
|
||||
_values = copy.copy(self.tld_fixtures[fixture])
|
||||
_values.update(values)
|
||||
return _values
|
||||
|
||||
def get_default_tld_fixture(self, fixture=0, values={}):
|
||||
_values = copy.copy(self.default_tld_fixtures[fixture])
|
||||
_values.update(values)
|
||||
return _values
|
||||
|
||||
def get_tsigkey_fixture(self, fixture=0, values={}):
|
||||
_values = copy.copy(self.tsigkey_fixtures[fixture])
|
||||
_values.update(values)
|
||||
@ -367,6 +394,27 @@ class TestCase(test.BaseTestCase):
|
||||
values = self.get_server_fixture(fixture=fixture, values=kwargs)
|
||||
return self.central_service.create_server(context, values=values)
|
||||
|
||||
def create_tld(self, **kwargs):
|
||||
context = kwargs.pop('context', self.admin_context)
|
||||
fixture = kwargs.pop('fixture', 0)
|
||||
|
||||
values = self.get_tld_fixture(fixture=fixture, values=kwargs)
|
||||
return self.central_service.create_tld(context, values=values)
|
||||
|
||||
def create_default_tld(self, **kwargs):
|
||||
context = kwargs.pop('context', self.admin_context)
|
||||
fixture = kwargs.pop('fixture', 0)
|
||||
|
||||
values = self.get_default_tld_fixture(fixture=fixture, values=kwargs)
|
||||
return self.central_service.create_tld(context, values=values)
|
||||
|
||||
def create_default_tlds(self):
|
||||
for index in range(len(self.default_tld_fixtures)):
|
||||
try:
|
||||
self.create_default_tld(fixture=index)
|
||||
except exceptions.DuplicateTLD:
|
||||
pass
|
||||
|
||||
def create_tsigkey(self, **kwargs):
|
||||
context = kwargs.pop('context', self.admin_context)
|
||||
fixture = kwargs.pop('fixture', 0)
|
||||
|
135
designate/tests/test_api/test_v2/test_tlds.py
Normal file
135
designate/tests/test_api/test_v2/test_tlds.py
Normal file
@ -0,0 +1,135 @@
|
||||
# Copyright (c) 2014 Rackspace Hosting
|
||||
# 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 designate.tests.test_api.test_v2 import ApiV2TestCase
|
||||
|
||||
|
||||
class ApiV2ZTldsTest(ApiV2TestCase):
|
||||
def setUp(self):
|
||||
super(ApiV2ZTldsTest, self).setUp()
|
||||
|
||||
def test_create_tld(self):
|
||||
self.policy({'create_tld': '@'})
|
||||
fixture = self.get_tld_fixture(0)
|
||||
response = self.client.post_json('/tlds/', {'tld': fixture})
|
||||
|
||||
# Check the headers are what we expect
|
||||
self.assertEqual(201, response.status_int)
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
|
||||
# Check the body structure is what we expect
|
||||
self.assertIn('tld', response.json)
|
||||
self.assertIn('links', response.json['tld'])
|
||||
self.assertIn('self', response.json['tld']['links'])
|
||||
|
||||
# Check the values returned are what we expect
|
||||
self.assertIn('id', response.json['tld'])
|
||||
self.assertIn('created_at', response.json['tld'])
|
||||
self.assertIsNone(response.json['tld']['updated_at'])
|
||||
self.assertEqual(fixture['name'], response.json['tld']['name'])
|
||||
|
||||
def test_create_tld_validation(self):
|
||||
self.policy({'create_tld': '@'})
|
||||
invalid_fixture = self.get_tld_fixture(-1)
|
||||
|
||||
# Ensure it fails with a 400
|
||||
response = self.client.post_json('/tlds/', {'tld': invalid_fixture},
|
||||
status=400)
|
||||
self.assertEqual(400, response.status_int)
|
||||
|
||||
def test_get_tlds(self):
|
||||
self.policy({'find_tlds': '@'})
|
||||
response = self.client.get('/tlds/')
|
||||
|
||||
# Check the headers are what we expect
|
||||
self.assertEqual(200, response.status_int)
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
|
||||
# Check the body structure is what we expect
|
||||
self.assertIn('tlds', response.json)
|
||||
self.assertIn('links', response.json)
|
||||
self.assertIn('self', response.json['links'])
|
||||
|
||||
# We should start with 0 tlds
|
||||
self.assertEqual(0, len(response.json['tlds']))
|
||||
|
||||
# Test with 1 tld
|
||||
self.create_tld(fixture=0)
|
||||
|
||||
response = self.client.get('/tlds/')
|
||||
|
||||
self.assertIn('tlds', response.json)
|
||||
self.assertEqual(1, len(response.json['tlds']))
|
||||
|
||||
# test with 2 tlds
|
||||
self.create_tld(fixture=1)
|
||||
|
||||
response = self.client.get('/tlds/')
|
||||
|
||||
self.assertIn('tlds', response.json)
|
||||
self.assertEqual(2, len(response.json['tlds']))
|
||||
|
||||
def test_get_tld(self):
|
||||
tld = self.create_tld(fixture=0)
|
||||
self.policy({'get_tld': '@'})
|
||||
|
||||
response = self.client.get('/tlds/%s' % tld['id'],
|
||||
headers=[('Accept', 'application/json')])
|
||||
|
||||
# Check the headers are what we expect
|
||||
self.assertEqual(200, response.status_int)
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
|
||||
# Check the body structure is what we expect
|
||||
self.assertIn('tld', response.json)
|
||||
self.assertIn('links', response.json['tld'])
|
||||
self.assertIn('self', response.json['tld']['links'])
|
||||
|
||||
# Check the values returned are what we expect
|
||||
self.assertIn('id', response.json['tld'])
|
||||
self.assertIn('created_at', response.json['tld'])
|
||||
self.assertIsNone(response.json['tld']['updated_at'])
|
||||
self.assertEqual(self.get_tld_fixture(0)['name'],
|
||||
response.json['tld']['name'])
|
||||
|
||||
def test_delete_tld(self):
|
||||
tld = self.create_tld(fixture=0)
|
||||
self.policy({'delete_tld': '@'})
|
||||
|
||||
self.client.delete('/tlds/%s' % tld['id'], status=204)
|
||||
|
||||
def test_update_tld(self):
|
||||
tld = self.create_tld(fixture=0)
|
||||
self.policy({'update_tld': '@'})
|
||||
|
||||
# Prepare an update body
|
||||
body = {'tld': {'description': 'prefix-%s' % tld['description']}}
|
||||
|
||||
response = self.client.patch_json('/tlds/%s' % tld['id'], body,
|
||||
status=200)
|
||||
|
||||
# Check the headers are what we expect
|
||||
self.assertEqual(200, response.status_int)
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
|
||||
# Check the body structure is what we expect
|
||||
self.assertIn('tld', response.json)
|
||||
self.assertIn('links', response.json['tld'])
|
||||
self.assertIn('self', response.json['tld']['links'])
|
||||
|
||||
# Check the values returned are what we expect
|
||||
self.assertIn('id', response.json['tld'])
|
||||
self.assertIsNotNone(response.json['tld']['updated_at'])
|
||||
self.assertEqual('prefix-%s' % tld['description'],
|
||||
response.json['tld']['description'])
|
@ -28,6 +28,9 @@ class ApiV2ZonesTest(ApiV2TestCase):
|
||||
# Create a server
|
||||
self.create_server()
|
||||
|
||||
# Create the default TLDs
|
||||
self.create_default_tlds()
|
||||
|
||||
def test_missing_accept(self):
|
||||
self.client.get('/zones/123', status=400)
|
||||
|
||||
|
@ -34,7 +34,6 @@ class CentralServiceTest(CentralTestCase):
|
||||
|
||||
def test_is_valid_domain_name(self):
|
||||
self.config(max_domain_name_len=10,
|
||||
accepted_tlds_file='tlds-alpha-by-domain.txt.sample',
|
||||
group='service:central')
|
||||
|
||||
context = self.get_context()
|
||||
@ -221,6 +220,86 @@ class CentralServiceTest(CentralTestCase):
|
||||
self.central_service.delete_server, self.admin_context,
|
||||
server2['id'])
|
||||
|
||||
# TLD Tests
|
||||
def test_create_tld(self):
|
||||
# Create a TLD with one label
|
||||
tld = self.create_tld(fixture=0)
|
||||
|
||||
# Ensure all values have been set correctly
|
||||
self.assertIsNotNone(tld['id'])
|
||||
self.assertEqual(tld['name'], self.get_tld_fixture(fixture=0)['name'])
|
||||
|
||||
# Create a TLD with more than one label
|
||||
tld = self.create_tld(fixture=1)
|
||||
|
||||
# Ensure all values have been set correctly
|
||||
self.assertIsNotNone(tld['id'])
|
||||
self.assertEqual(tld['name'], self.get_tld_fixture(fixture=1)['name'])
|
||||
|
||||
def test_find_tlds(self):
|
||||
# Ensure we have no tlds to start with.
|
||||
tlds = self.central_service.find_tlds(self.admin_context)
|
||||
self.assertEqual(len(tlds), 0)
|
||||
|
||||
# Create a single tld
|
||||
self.create_tld(fixture=0)
|
||||
# Ensure we can retrieve the newly created tld
|
||||
tlds = self.central_service.find_tlds(self.admin_context)
|
||||
self.assertEqual(len(tlds), 1)
|
||||
self.assertEqual(tlds[0]['name'],
|
||||
self.get_tld_fixture(fixture=0)['name'])
|
||||
|
||||
# Create a second tld
|
||||
self.create_tld(fixture=1)
|
||||
|
||||
# Ensure we can retrieve both tlds
|
||||
tlds = self.central_service.find_tlds(self.admin_context)
|
||||
self.assertEqual(len(tlds), 2)
|
||||
self.assertEqual(tlds[0]['name'],
|
||||
self.get_tld_fixture(fixture=0)['name'])
|
||||
self.assertEqual(tlds[1]['name'],
|
||||
self.get_tld_fixture(fixture=1)['name'])
|
||||
|
||||
def test_get_tld(self):
|
||||
# Create a tld
|
||||
tld_name = 'ns%d.co.uk' % random.randint(10, 1000)
|
||||
expected_tld = self.create_tld(name=tld_name)
|
||||
|
||||
# Retrieve it, and ensure it's the same
|
||||
tld = self.central_service.get_tld(
|
||||
self.admin_context, expected_tld['id'])
|
||||
|
||||
self.assertEqual(tld['id'], expected_tld['id'])
|
||||
self.assertEqual(tld['name'], expected_tld['name'])
|
||||
|
||||
def test_update_tld(self):
|
||||
# Create a tld
|
||||
expected_tld = self.create_tld(fixture=0)
|
||||
|
||||
# Update the tld
|
||||
values = dict(name='prefix.%s' % expected_tld['name'])
|
||||
self.central_service.update_tld(
|
||||
self.admin_context, expected_tld['id'], values=values)
|
||||
|
||||
# Fetch the tld again
|
||||
tld = self.central_service.get_tld(
|
||||
self.admin_context, expected_tld['id'])
|
||||
|
||||
# Ensure the tld was updated correctly
|
||||
self.assertEqual(tld['name'], 'prefix.%s' % expected_tld['name'])
|
||||
|
||||
def test_delete_tld(self):
|
||||
# Create a tld
|
||||
tld = self.create_tld(fixture=0)
|
||||
# Delete the tld
|
||||
self.central_service.delete_tld(self.admin_context, tld['id'])
|
||||
|
||||
# Fetch the tld again, ensuring an exception is raised
|
||||
self.assertRaises(
|
||||
exceptions.TLDNotFound,
|
||||
self.central_service.get_tld,
|
||||
self.admin_context, tld['id'])
|
||||
|
||||
# TsigKey Tests
|
||||
def test_create_tsigkey(self):
|
||||
values = self.get_tsigkey_fixture(fixture=0)
|
||||
@ -368,6 +447,13 @@ class CentralServiceTest(CentralTestCase):
|
||||
self._test_create_domain(values)
|
||||
|
||||
def test_idn_create_domain_over_tld(self):
|
||||
values = dict(
|
||||
name='xn--3e0b707e'
|
||||
)
|
||||
|
||||
# Create the appropriate TLD
|
||||
self.central_service.create_tld(self.admin_context, values=values)
|
||||
|
||||
# Test creation of a domain in 한국 (kr)
|
||||
values = dict(
|
||||
name='example.xn--3e0b707e.',
|
||||
@ -375,28 +461,6 @@ class CentralServiceTest(CentralTestCase):
|
||||
)
|
||||
self._test_create_domain(values)
|
||||
|
||||
def test_create_domain_over_re_effective_tld(self):
|
||||
values = dict(
|
||||
name='example.co.uk.',
|
||||
email='info@example.co.uk'
|
||||
)
|
||||
self._test_create_domain(values)
|
||||
|
||||
def test_create_domain_over_effective_tld(self):
|
||||
values = dict(
|
||||
name='example.com.ac.',
|
||||
email='info@example.com.ac'
|
||||
)
|
||||
self._test_create_domain(values)
|
||||
|
||||
def test_idn_create_domain_over_effective_tld(self):
|
||||
# Test creation of a domain in 公司.cn
|
||||
values = dict(
|
||||
name='example.xn--55qx5d.cn.',
|
||||
email='info@example.xn--55qx5d.cn'
|
||||
)
|
||||
self._test_create_domain(values)
|
||||
|
||||
def test_create_domain_over_quota(self):
|
||||
self.config(quota_domains=1)
|
||||
|
||||
@ -485,16 +549,6 @@ class CentralServiceTest(CentralTestCase):
|
||||
self.admin_context, values=values)
|
||||
|
||||
def _test_create_domain_fail(self, values, exception):
|
||||
self.config(accepted_tlds_file='tlds-alpha-by-domain.txt.sample',
|
||||
effective_tlds_file='effective_tld_names.dat.sample',
|
||||
group='service:central')
|
||||
|
||||
# The above configuration values are not overriden at the time when
|
||||
# the initializer is called to load the accepted and effective tld
|
||||
# lists. So I need to call them again explicitly to load the correct
|
||||
# values
|
||||
self.central_service.effective_tld._load_accepted_tld_list()
|
||||
self.central_service.effective_tld._load_effective_tld_list()
|
||||
|
||||
with testtools.ExpectedException(exception):
|
||||
# Create an invalid domain
|
||||
@ -502,67 +556,31 @@ class CentralServiceTest(CentralTestCase):
|
||||
self.admin_context, values=values)
|
||||
|
||||
def test_create_domain_invalid_tld_fail(self):
|
||||
self.config(accepted_tlds_file='tlds-alpha-by-domain.txt.sample',
|
||||
effective_tlds_file='effective_tld_names.dat.sample',
|
||||
group='service:central')
|
||||
|
||||
# The above configuration values are not overriden at the time when
|
||||
# the initializer is called to load the accepted and effective tld
|
||||
# lists. So I need to call them again explicitly to load the correct
|
||||
# values
|
||||
self.central_service.effective_tld._load_accepted_tld_list()
|
||||
self.central_service.effective_tld._load_effective_tld_list()
|
||||
|
||||
# Create a server
|
||||
self.create_server()
|
||||
|
||||
# add a tld for com
|
||||
self.create_tld(fixture=0)
|
||||
|
||||
values = dict(
|
||||
name='invalid.cOM.',
|
||||
email='info@invalid.com'
|
||||
name='example.com.',
|
||||
email='info@example.com'
|
||||
)
|
||||
|
||||
# Create a valid domain
|
||||
self.central_service.create_domain(self.admin_context, values=values)
|
||||
|
||||
values = dict(
|
||||
name='invalid.NeT1.',
|
||||
email='info@invalid.com'
|
||||
name='example.net.',
|
||||
email='info@example.net'
|
||||
)
|
||||
|
||||
with testtools.ExpectedException(exceptions.InvalidTLD):
|
||||
# There is no TLD for net so it should fail
|
||||
with testtools.ExpectedException(exceptions.InvalidDomainName):
|
||||
# Create an invalid domain
|
||||
self.central_service.create_domain(
|
||||
self.admin_context, values=values)
|
||||
|
||||
def test_create_domain_effective_tld_fail(self):
|
||||
values = dict(
|
||||
name='co.ug',
|
||||
email='info@invalid.com'
|
||||
)
|
||||
|
||||
self._test_create_domain_fail(
|
||||
values, exceptions.DomainIsSameAsAnEffectiveTLD)
|
||||
|
||||
def test_idn_create_domain_effective_tld_fail(self):
|
||||
# Test creation of the effective TLD - brønnøysund.no
|
||||
values = dict(
|
||||
name='xn--brnnysund-m8ac.no',
|
||||
email='info@invalid.com'
|
||||
)
|
||||
|
||||
self._test_create_domain_fail(
|
||||
values, exceptions.DomainIsSameAsAnEffectiveTLD)
|
||||
|
||||
def test_create_domain_re_effective_tld_fail(self):
|
||||
# co.uk is in the regular expression list for effective_tlds
|
||||
values = dict(
|
||||
name='co.uk',
|
||||
email='info@invalid.com'
|
||||
)
|
||||
|
||||
self._test_create_domain_fail(
|
||||
values, exceptions.DomainIsSameAsAnEffectiveTLD)
|
||||
|
||||
def test_find_domains(self):
|
||||
# Ensure we have no domains to start with.
|
||||
domains = self.central_service.find_domains(self.admin_context)
|
||||
|
@ -35,9 +35,6 @@ backend_driver = powerdns
|
||||
# List of blacklist domain name regexes
|
||||
#domain_name_blacklist = \.arpa\.$, \.novalocal\.$, \.localhost\.$, \.localdomain\.$, \.local\.$
|
||||
|
||||
# Accepted TLD list - http://data.iana.org/TLD/tlds-alpha-by-domain.txt
|
||||
#accepted_tld_list = COM, NET, ORG, IE, UK, ...
|
||||
|
||||
# Maximum domain name length
|
||||
max_domain_name_len = 255
|
||||
|
||||
|
@ -38,16 +38,6 @@ root_helper = sudo
|
||||
# List of blacklist domain name regexes
|
||||
#domain_name_blacklist = \.arpa\.$, \.novalocal\.$, \.localhost\.$, \.localdomain\.$, \.local\.$
|
||||
|
||||
# Accepted TLDs
|
||||
# This is a local copy of the list at
|
||||
# http://data.iana.org/TLD/tlds-alpha-by-domain.txt
|
||||
#accepted_tlds_file = tlds-alpha-by-domain.txt
|
||||
|
||||
# Effective TLDs
|
||||
# This is a local copy of the list at http://publicsuffix.org/list/
|
||||
# This contains domain names that effectively act like TLDs e.g. co.uk or tx.us
|
||||
#effective_tlds_file = effective_tld_names.dat
|
||||
|
||||
# Maximum domain name length
|
||||
#max_domain_name_len = 255
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -16,6 +16,12 @@
|
||||
"update_server": "rule:admin",
|
||||
"delete_server": "rule:admin",
|
||||
|
||||
"create_tld": "rule:admin",
|
||||
"find_tlds": "rule:admin",
|
||||
"get_tld": "rule:admin",
|
||||
"update_tld": "rule:admin",
|
||||
"delete_tld": "rule:admin",
|
||||
|
||||
"create_tsigkey": "rule:admin",
|
||||
"find_tsigkeys": "rule:admin",
|
||||
"get_tsigkey": "rule:admin",
|
||||
|
@ -1,338 +0,0 @@
|
||||
# The latest version of the file can be obtained from
|
||||
# http://data.iana.org/TLD/tlds-alpha-by-domain.txt
|
||||
# Commented lines in this file begin with a #.
|
||||
# There is one entry per line and the entries are in the IDNA format.
|
||||
#
|
||||
# Version 2013111800, Last Updated Mon Nov 18 07:07:01 2013 UTC
|
||||
AC
|
||||
AD
|
||||
AE
|
||||
AERO
|
||||
AF
|
||||
AG
|
||||
AI
|
||||
AL
|
||||
AM
|
||||
AN
|
||||
AO
|
||||
AQ
|
||||
AR
|
||||
ARPA
|
||||
AS
|
||||
ASIA
|
||||
AT
|
||||
AU
|
||||
AW
|
||||
AX
|
||||
AZ
|
||||
BA
|
||||
BB
|
||||
BD
|
||||
BE
|
||||
BF
|
||||
BG
|
||||
BH
|
||||
BI
|
||||
BIKE
|
||||
BIZ
|
||||
BJ
|
||||
BM
|
||||
BN
|
||||
BO
|
||||
BR
|
||||
BS
|
||||
BT
|
||||
BV
|
||||
BW
|
||||
BY
|
||||
BZ
|
||||
CA
|
||||
CAMERA
|
||||
CAT
|
||||
CC
|
||||
CD
|
||||
CF
|
||||
CG
|
||||
CH
|
||||
CI
|
||||
CK
|
||||
CL
|
||||
CLOTHING
|
||||
CM
|
||||
CN
|
||||
CO
|
||||
COM
|
||||
CONSTRUCTION
|
||||
CONTRACTORS
|
||||
COOP
|
||||
CR
|
||||
CU
|
||||
CV
|
||||
CW
|
||||
CX
|
||||
CY
|
||||
CZ
|
||||
DE
|
||||
DJ
|
||||
DK
|
||||
DM
|
||||
DO
|
||||
DZ
|
||||
EC
|
||||
EDU
|
||||
EE
|
||||
EG
|
||||
EQUIPMENT
|
||||
ER
|
||||
ES
|
||||
ESTATE
|
||||
ET
|
||||
EU
|
||||
FI
|
||||
FJ
|
||||
FK
|
||||
FM
|
||||
FO
|
||||
FR
|
||||
GA
|
||||
GALLERY
|
||||
GB
|
||||
GD
|
||||
GE
|
||||
GF
|
||||
GG
|
||||
GH
|
||||
GI
|
||||
GL
|
||||
GM
|
||||
GN
|
||||
GOV
|
||||
GP
|
||||
GQ
|
||||
GR
|
||||
GRAPHICS
|
||||
GS
|
||||
GT
|
||||
GU
|
||||
GURU
|
||||
GW
|
||||
GY
|
||||
HK
|
||||
HM
|
||||
HN
|
||||
HOLDINGS
|
||||
HR
|
||||
HT
|
||||
HU
|
||||
ID
|
||||
IE
|
||||
IL
|
||||
IM
|
||||
IN
|
||||
INFO
|
||||
INT
|
||||
IO
|
||||
IQ
|
||||
IR
|
||||
IS
|
||||
IT
|
||||
JE
|
||||
JM
|
||||
JO
|
||||
JOBS
|
||||
JP
|
||||
KE
|
||||
KG
|
||||
KH
|
||||
KI
|
||||
KM
|
||||
KN
|
||||
KP
|
||||
KR
|
||||
KW
|
||||
KY
|
||||
KZ
|
||||
LA
|
||||
LAND
|
||||
LB
|
||||
LC
|
||||
LI
|
||||
LIGHTING
|
||||
LK
|
||||
LR
|
||||
LS
|
||||
LT
|
||||
LU
|
||||
LV
|
||||
LY
|
||||
MA
|
||||
MC
|
||||
MD
|
||||
ME
|
||||
MG
|
||||
MH
|
||||
MIL
|
||||
MK
|
||||
ML
|
||||
MM
|
||||
MN
|
||||
MO
|
||||
MOBI
|
||||
MP
|
||||
MQ
|
||||
MR
|
||||
MS
|
||||
MT
|
||||
MU
|
||||
MUSEUM
|
||||
MV
|
||||
MW
|
||||
MX
|
||||
MY
|
||||
MZ
|
||||
NA
|
||||
NAME
|
||||
NC
|
||||
NE
|
||||
NET
|
||||
NF
|
||||
NG
|
||||
NI
|
||||
NL
|
||||
NO
|
||||
NP
|
||||
NR
|
||||
NU
|
||||
NZ
|
||||
OM
|
||||
ORG
|
||||
PA
|
||||
PE
|
||||
PF
|
||||
PG
|
||||
PH
|
||||
PK
|
||||
PL
|
||||
PLUMBING
|
||||
PM
|
||||
PN
|
||||
POST
|
||||
PR
|
||||
PRO
|
||||
PS
|
||||
PT
|
||||
PW
|
||||
PY
|
||||
QA
|
||||
RE
|
||||
RO
|
||||
RS
|
||||
RU
|
||||
RW
|
||||
SA
|
||||
SB
|
||||
SC
|
||||
SD
|
||||
SE
|
||||
SEXY
|
||||
SG
|
||||
SH
|
||||
SI
|
||||
SINGLES
|
||||
SJ
|
||||
SK
|
||||
SL
|
||||
SM
|
||||
SN
|
||||
SO
|
||||
SR
|
||||
ST
|
||||
SU
|
||||
SV
|
||||
SX
|
||||
SY
|
||||
SZ
|
||||
TATTOO
|
||||
TC
|
||||
TD
|
||||
TECHNOLOGY
|
||||
TEL
|
||||
TF
|
||||
TG
|
||||
TH
|
||||
TJ
|
||||
TK
|
||||
TL
|
||||
TM
|
||||
TN
|
||||
TO
|
||||
TP
|
||||
TR
|
||||
TRAVEL
|
||||
TT
|
||||
TV
|
||||
TW
|
||||
TZ
|
||||
UA
|
||||
UG
|
||||
UK
|
||||
US
|
||||
UY
|
||||
UZ
|
||||
VA
|
||||
VC
|
||||
VE
|
||||
VENTURES
|
||||
VG
|
||||
VI
|
||||
VN
|
||||
VOYAGE
|
||||
VU
|
||||
WF
|
||||
WS
|
||||
XN--3E0B707E
|
||||
XN--45BRJ9C
|
||||
XN--80AO21A
|
||||
XN--80ASEHDB
|
||||
XN--80ASWG
|
||||
XN--90A3AC
|
||||
XN--CLCHC0EA0B2G2A9GCD
|
||||
XN--FIQS8S
|
||||
XN--FIQZ9S
|
||||
XN--FPCRJ9C3D
|
||||
XN--FZC2C9E2C
|
||||
XN--GECRJ9C
|
||||
XN--H2BRJ9C
|
||||
XN--J1AMH
|
||||
XN--J6W193G
|
||||
XN--KPRW13D
|
||||
XN--KPRY57D
|
||||
XN--L1ACC
|
||||
XN--LGBBAT1AD8J
|
||||
XN--MGB9AWBF
|
||||
XN--MGBA3A4F16A
|
||||
XN--MGBAAM7A8H
|
||||
XN--MGBAYH7GPA
|
||||
XN--MGBBH1A71E
|
||||
XN--MGBC0A9AZCG
|
||||
XN--MGBERP4A5D4AR
|
||||
XN--MGBX4CD0AB
|
||||
XN--NGBC5AZD
|
||||
XN--O3CW4H
|
||||
XN--OGBPF8FL
|
||||
XN--P1AI
|
||||
XN--PGBS0DH
|
||||
XN--S9BRJ9C
|
||||
XN--UNUP4Y
|
||||
XN--WGBH1C
|
||||
XN--WGBL6A
|
||||
XN--XKC2AL3HYE2A
|
||||
XN--XKC2DL3A5EE0H
|
||||
XN--YFRO4I67O
|
||||
XN--YGBI2AMMX
|
||||
XXX
|
||||
YE
|
||||
YT
|
||||
ZA
|
||||
ZM
|
||||
ZW
|
Loading…
x
Reference in New Issue
Block a user