Remove v1 API

This completes the long awaited removal of the V1 API.

Change-Id: I30c8a5e8569b1b86286c5e3cb07856c06ebe5803
This commit is contained in:
Graham Hayes 2017-11-17 17:17:38 +00:00 committed by Dai Dang Van
parent 11ab86e320
commit c318106c01
64 changed files with 25 additions and 6682 deletions

View File

@ -1,16 +1,6 @@
{
"versions": {
"values": [
{
"id": "v1",
"links": [
{
"href": "http://127.0.0.1:9001/v1",
"rel": "self"
}
],
"status": "DEPRECATED"
},
{
"id": "v2",
"links": [

View File

@ -1,362 +0,0 @@
# Copyright (C) 2014 Red Hat, Inc.
#
# Author: Rich Megginson <rmeggins@redhat.com>
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import sys
import logging
import pprint
import json
import copy
import requests
from oslo_config import cfg
from designate.backend import impl_ipa
from designate.i18n import _LI
from designate.i18n import _LW
from designate.i18n import _LE
from designate import utils
logging.basicConfig()
LOG = logging.getLogger(__name__)
cfg.CONF.import_opt('api_base_uri', 'designate.api', 'service:api')
cfg.CONF.import_opt('backend_driver', 'designate.central', 'service:central')
class NoNameServers(Exception):
pass
class AddServerError(Exception):
pass
class DeleteServerError(Exception):
pass
class AddDomainError(Exception):
pass
class DeleteDomainError(Exception):
pass
class AddRecordError(Exception):
pass
cuiberrorstr = """ERROR: You cannot have Designate configured
to use the IPA backend when running this script. It will wipe
out your IPA DNS data. Please follow these steps:
* shutdown designate-central
* edit designate.conf
[service:central]
backend_driver = fake # or something other than ipa
* restart designate-central and other designate services
"""
class CannotUseIPABackend(Exception):
pass
# create mapping of ipa record types to designate types
iparectype2designate = {}
for rectype, tup in list(impl_ipa.rectype2iparectype.items()):
iparectype = tup[0]
iparectype2designate[iparectype] = rectype
# using the all: True flag returns fields we can't use
# strip these keys from zones
zoneskips = ['dn', 'nsrecord', 'idnszoneactive', 'objectclass']
def rec2des(rec, zonename):
"""Convert an IPA record to Designate format. A single IPA record
returned from the search may translate into multiple Designate.
IPA dnsrecord_find returns a "name". Each DNS name may contain
multiple record types. Each record type may contain multiple
values. Each one of these values must be added separately to
Designate. This function returns all of those as a list of
dict designate records.
"""
# convert record name
if rec['idnsname'][0] == '@':
name = zonename
else:
name = rec['idnsname'][0] + "." + zonename
# find all record types
rectypes = []
for k in rec:
if k.endswith("record"):
if k in iparectype2designate:
rectypes.append(k)
else:
LOG.info(_LI("Skipping unknown record type "
"%(type)s in %(name)s"),
{'type': k, 'name': name})
desrecs = []
for rectype in rectypes:
dtype = iparectype2designate[rectype]
for ddata in rec[rectype]:
desreq = {'name': name, 'type': dtype}
if dtype == 'SRV' or dtype == 'MX':
# split off the priority and send in a separate field
idx = ddata.find(' ')
desreq['priority'] = int(ddata[:idx])
if dtype == 'SRV' and not ddata.endswith("."):
# if server is specified as relative, add zonename
desreq['data'] = ddata[(idx + 1):] + "." + zonename
else:
desreq['data'] = ddata[(idx + 1):]
else:
desreq['data'] = ddata
if rec.get('description', [None])[0]:
desreq['description'] = rec.get('description')[0]
if rec.get('ttl', [None])[0]:
desreq['ttl'] = int(rec['dnsttl'][0])
desrecs.append(desreq)
return desrecs
def zone2des(ipazone):
# next, try to add the fake domain to Designate
zonename = ipazone['idnsname'][0].rstrip(".") + "."
email = ipazone['idnssoarname'][0].rstrip(".").replace(".", "@", 1)
desreq = {"name": zonename,
"ttl": int(ipazone['idnssoarefresh'][0]),
"email": email}
return desreq
def getipadomains(ipabackend, version):
# get the list of domains/zones from IPA
ipareq = {'method': 'dnszone_find',
'params': [[], {'version': version,
'all': True}]}
iparesp = ipabackend._call_and_handle_error(ipareq)
LOG.debug("Response: %s" % pprint.pformat(iparesp))
return iparesp['result']['result']
def getiparecords(ipabackend, zonename, version):
ipareq = {'method': 'dnsrecord_find',
'params': [[zonename], {"version": version,
"all": True}]}
iparesp = ipabackend._call_and_handle_error(ipareq)
return iparesp['result']['result']
def syncipaservers2des(servers, designatereq, designateurl):
# get existing servers from designate
dservers = {}
srvurl = designateurl + "/servers"
resp = designatereq.get(srvurl)
LOG.debug("Response: %s" % pprint.pformat(resp.json()))
if resp and resp.status_code == 200 and resp.json() and \
'servers' in resp.json():
for srec in resp.json()['servers']:
dservers[srec['name']] = srec['id']
else:
LOG.warning(_LW("No servers in designate"))
# first - add servers from ipa not already in designate
for server in servers:
if server in dservers:
LOG.info(_LI("Skipping ipa server %s already in designate"),
server)
else:
desreq = {"name": server}
resp = designatereq.post(srvurl, data=json.dumps(desreq))
LOG.debug("Response: %s" % pprint.pformat(resp.json()))
if resp.status_code == 200:
LOG.info(_LI("Added server %s to designate"), server)
else:
raise AddServerError("Unable to add %s: %s" %
(server, pprint.pformat(resp.json())))
# next - delete servers in designate not in ipa
for server, sid in list(dservers.items()):
if server not in servers:
delresp = designatereq.delete(srvurl + "/" + sid)
if delresp.status_code == 200:
LOG.info(_LI("Deleted server %s"), server)
else:
raise DeleteServerError("Unable to delete %s: %s" %
(server,
pprint.pformat(delresp.json())))
def main():
# HACK HACK HACK - allow required config params to be passed
# via the command line
cfg.CONF['service:api']._group._opts['api_base_uri']['cli'] = True
for optdict in cfg.CONF['backend:ipa']._group._opts.values():
if 'cli' in optdict:
optdict['cli'] = True
# HACK HACK HACK - allow api url to be passed in the usual way
utils.read_config('designate', sys.argv)
if cfg.CONF['service:central'].backend_driver == 'ipa':
raise CannotUseIPABackend(cuiberrorstr)
if cfg.CONF.debug:
LOG.setLevel(logging.DEBUG)
else:
LOG.setLevel(logging.INFO)
ipabackend = impl_ipa.IPABackend(None)
ipabackend.start()
version = cfg.CONF['backend:ipa'].ipa_version
designateurl = cfg.CONF['service:api'].api_base_uri + "v1"
# get the list of domains/zones from IPA
ipazones = getipadomains(ipabackend, version)
# get unique list of name servers
servers = {}
for zonerec in ipazones:
for nsrec in zonerec['nsrecord']:
servers[nsrec] = nsrec
if not servers:
raise NoNameServers("Error: no name servers found in IPA")
# let's see if designate is using the IPA backend
# create a fake domain in IPA
# create a fake server in Designate
# try to create the same fake domain in Designate
# if we get a DuplicateZone error from Designate, then
# raise the CannotUseIPABackend error, after deleting
# the fake server and fake domain
# find the first non-reverse zone
zone = {}
for zrec in ipazones:
if not zrec['idnsname'][0].endswith("in-addr.arpa.") and \
zrec['idnszoneactive'][0] == 'TRUE':
# ipa returns every data field as a list
# convert the list to a scalar
for n, v in list(zrec.items()):
if n in zoneskips:
continue
if isinstance(v, list):
zone[n] = v[0]
else:
zone[n] = v
break
assert(zone)
# create a fake subdomain of this zone
domname = "%s.%s" % (utils.generate_uuid(), zone['idnsname'])
args = copy.copy(zone)
del args['idnsname']
args['version'] = version
ipareq = {'method': 'dnszone_add',
'params': [[domname], args]}
iparesp = ipabackend._call_and_handle_error(ipareq)
LOG.debug("Response: %s" % pprint.pformat(iparesp))
if iparesp['error']:
raise AddDomainError(pprint.pformat(iparesp))
# set up designate connection
designatereq = requests.Session()
xtra_hdrs = {'Content-Type': 'application/json'}
designatereq.headers.update(xtra_hdrs)
# sync ipa name servers to designate
syncipaservers2des(servers, designatereq, designateurl)
domainurl = designateurl + "/domains"
# next, try to add the fake domain to Designate
email = zone['idnssoarname'].rstrip(".").replace(".", "@", 1)
desreq = {"name": domname,
"ttl": int(zone['idnssoarefresh'][0]),
"email": email}
resp = designatereq.post(domainurl, data=json.dumps(desreq))
exc = None
fakezoneid = None
if resp.status_code == 200:
LOG.info(_LI("Added domain %s"), domname)
fakezoneid = resp.json()['id']
delresp = designatereq.delete(domainurl + "/" + fakezoneid)
if delresp.status_code != 200:
LOG.error(_LE("Unable to delete %(name)s: %(response)s") %
{'name': domname, 'response': pprint.pformat(
delresp.json())})
else:
exc = CannotUseIPABackend(cuiberrorstr)
# cleanup fake stuff
ipareq = {'method': 'dnszone_del',
'params': [[domname], {'version': version}]}
iparesp = ipabackend._call_and_handle_error(ipareq)
LOG.debug("Response: %s" % pprint.pformat(iparesp))
if iparesp['error']:
LOG.error(_LE("%s") % pprint.pformat(iparesp))
if exc:
raise exc
# get and delete existing domains
resp = designatereq.get(domainurl)
LOG.debug("Response: %s" % pprint.pformat(resp.json()))
if resp and resp.status_code == 200 and resp.json() and \
'domains' in resp.json():
# domains must be deleted in child/parent order i.e. delete
# sub-domains before parent domains - simple way to get this
# order is to sort the domains in reverse order of name len
dreclist = sorted(resp.json()['domains'],
key=lambda drec: len(drec['name']),
reverse=True)
for drec in dreclist:
delresp = designatereq.delete(domainurl + "/" + drec['id'])
if delresp.status_code != 200:
raise DeleteDomainError("Unable to delete %s: %s" %
(drec['name'],
pprint.pformat(delresp.json())))
# key is zonename, val is designate rec id
zonerecs = {}
for zonerec in ipazones:
desreq = zone2des(zonerec)
resp = designatereq.post(domainurl, data=json.dumps(desreq))
if resp.status_code == 200:
LOG.info(_LI("Added domain %s"), desreq['name'])
else:
raise AddDomainError("Unable to add domain %s: %s" %
(desreq['name'], pprint.pformat(resp.json())))
zonerecs[desreq['name']] = resp.json()['id']
# get the records for each zone
for zonename, domainid in list(zonerecs.items()):
recurl = designateurl + "/domains/" + domainid + "/records"
iparecs = getiparecords(ipabackend, zonename, version)
for rec in iparecs:
desreqs = rec2des(rec, zonename)
for desreq in desreqs:
resp = designatereq.post(recurl, data=json.dumps(desreq))
if resp.status_code == 200:
LOG.info(_LI("Added record %(record)s "
"for domain %(domain)s"),
{'record': desreq['name'], 'domain': zonename})
else:
raise AddRecordError("Could not add record %s: %s" %
(desreq['name'],
pprint.pformat(resp.json())))
if __name__ == '__main__':
sys.exit(main())

View File

@ -49,11 +49,6 @@ api_opts = [
cfg.StrOpt('auth_strategy', default='keystone',
help='The strategy to use for auth. Supports noauth or '
'keystone'),
cfg.BoolOpt('enable-api-v1', default=False,
deprecated_for_removal=True,
deprecated_reason="V1 API is being removed in a future"
"release",
help='enable-api-v1 which removed in a future'),
cfg.BoolOpt('enable-api-v2', default=True,
help='enable-api-v2 which enable in a future'),
cfg.BoolOpt('enable-api-admin', default=False,
@ -65,11 +60,6 @@ api_opts = [
"Keystone v3 API with big service catalogs)."),
]
api_v1_opts = [
cfg.ListOpt('enabled-extensions-v1', default=[],
help='Enabled API Extensions'),
]
api_v2_opts = [
cfg.ListOpt('enabled-extensions-v2', default=[],
help='Enabled API Extensions for the V2 API'),
@ -109,7 +99,6 @@ api_middleware_opts = [
cfg.CONF.register_group(api_group)
cfg.CONF.register_opts(api_opts, group=api_group)
cfg.CONF.register_opts(api_v1_opts, group=api_group)
cfg.CONF.register_opts(api_v2_opts, group=api_group)
cfg.CONF.register_opts(api_admin_opts, group=api_group)
cfg.CONF.register_opts(api_middleware_opts, group=api_group)
@ -117,7 +106,6 @@ cfg.CONF.register_opts(api_middleware_opts, group=api_group)
def list_opts():
yield api_group, api_opts
yield api_group, api_v1_opts
yield api_group, api_v2_opts
yield api_group, api_admin_opts
yield api_group, api_middleware_opts

View File

@ -308,17 +308,6 @@ class FaultWrapperMiddleware(base.Middleware):
response=json.dumps(response))
class FaultWrapperMiddlewareV1(FaultWrapperMiddleware):
def _format_error(self, data):
replace_map = [
("zone", "domain",)
]
for i in replace_map:
data["type"] = data["type"].replace(i[0], i[1])
print(data)
class ValidationErrorMiddleware(base.Middleware):
def __init__(self, application):
@ -370,12 +359,6 @@ class ValidationErrorMiddleware(base.Middleware):
response=json.dumps(response))
class APIv1ValidationErrorMiddleware(ValidationErrorMiddleware):
def __init__(self, application):
super(APIv1ValidationErrorMiddleware, self).__init__(application)
self.api_version = 'API_v1'
class APIv2ValidationErrorMiddleware(ValidationErrorMiddleware):
def __init__(self, application):
super(APIv2ValidationErrorMiddleware, self).__init__(application)

View File

@ -1,149 +0,0 @@
# Copyright 2012 Managed I.T.
#
# Author: Kiall Mac Innes <kiall@managedit.ie>
#
# 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 six
import flask
from stevedore import extension
from stevedore import named
from werkzeug import exceptions as wexceptions
from werkzeug import wrappers
from werkzeug.routing import BaseConverter
from werkzeug.routing import ValidationError
from oslo_config import cfg
from oslo_log import log as logging
from oslo_serialization import jsonutils
from designate import exceptions
from designate import utils
LOG = logging.getLogger(__name__)
class DesignateRequest(flask.Request, wrappers.AcceptMixin,
wrappers.CommonRequestDescriptorsMixin):
def __init__(self, *args, **kwargs):
super(DesignateRequest, self).__init__(*args, **kwargs)
self._validate_content_type()
self._validate_accept()
def _validate_content_type(self):
if (self.method in ['POST', 'PUT', 'PATCH']
and self.mimetype != 'application/json'):
msg = 'Unsupported Content-Type: %s' % self.mimetype
raise exceptions.UnsupportedContentType(msg)
def _validate_accept(self):
if 'accept' in self.headers and not self.accept_mimetypes.accept_json:
msg = 'Unsupported Accept: %s' % self.accept_mimetypes
raise exceptions.UnsupportedAccept(msg)
class JSONEncoder(flask.json.JSONEncoder):
@staticmethod
def default(o):
return jsonutils.to_primitive(o)
def factory(global_config, **local_conf):
if not cfg.CONF['service:api'].enable_api_v1:
def disabled_app(environ, start_response):
status = '404 Not Found'
start_response(status, [])
return []
return disabled_app
app = flask.Flask('designate.api.v1')
app.request_class = DesignateRequest
app.json_encoder = JSONEncoder
app.config.update(
PROPAGATE_EXCEPTIONS=True
)
# Install custom converters (URL param varidators)
app.url_map.converters['uuid'] = UUIDConverter
# Ensure all error responses are JSON
def _json_error(ex):
code = ex.code if isinstance(ex, wexceptions.HTTPException) else 500
response = {
'code': code
}
if code == 405:
response['type'] = 'invalid_method'
response = flask.jsonify(**response)
response.status_code = code
return response
for code in six.iterkeys(wexceptions.default_exceptions):
app.register_error_handler(code, _json_error)
# TODO(kiall): Ideally, we want to make use of the Plugin class here.
# This works for the moment though.
def _register_blueprint(ext):
app.register_blueprint(ext.plugin)
# Add all in-built APIs
mgr = extension.ExtensionManager('designate.api.v1')
mgr.map(_register_blueprint)
# Add any (enabled) optional extensions
extensions = cfg.CONF['service:api'].enabled_extensions_v1
if len(extensions) > 0:
extmgr = named.NamedExtensionManager('designate.api.v1.extensions',
names=extensions)
extmgr.map(_register_blueprint)
return app
class UUIDConverter(BaseConverter):
"""Validates UUID URL parameters"""
def to_python(self, value):
if not utils.is_uuid_like(value):
raise ValidationError()
return value
def to_url(self, value):
return str(value)
def load_values(request, valid_keys):
"""Load valid attributes from request"""
result = {}
error_keys = []
values = request.json
for k in values:
if k in valid_keys:
result[k] = values[k]
else:
error_keys.append(k)
if error_keys:
error_msg = 'Provided object does not match schema. Keys {0} are not \
valid in the request body', error_keys
raise exceptions.InvalidObject(error_msg)
return result

View File

@ -1,173 +0,0 @@
# Copyright 2012 Managed I.T.
#
# Author: Kiall Mac Innes <kiall@managedit.ie>
#
# 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 flask
from oslo_log import log as logging
from designate import schema
from designate.api.v1 import load_values
from designate.central import rpcapi as central_rpcapi
from designate.i18n import _LI
from designate import objects
LOG = logging.getLogger(__name__)
blueprint = flask.Blueprint('domains', __name__)
domain_schema = schema.Schema('v1', 'domain')
domains_schema = schema.Schema('v1', 'domains')
servers_schema = schema.Schema('v1', 'servers')
def _pool_ns_record_to_server(pool_ns_record):
server_values = {
'id': pool_ns_record.id,
'created_at': pool_ns_record.created_at,
'updated_at': pool_ns_record.updated_at,
'version': pool_ns_record.version,
'name': pool_ns_record.hostname
}
return objects.Server.from_dict(server_values)
@blueprint.route('/schemas/domain', methods=['GET'])
def get_domain_schema():
return flask.jsonify(domain_schema.raw)
@blueprint.route('/schemas/domains', methods=['GET'])
def get_domains_schema():
return flask.jsonify(domains_schema.raw)
@blueprint.route('/domains', methods=['POST'])
def create_domain():
valid_attributes = ['name', 'email', 'ttl', 'description']
context = flask.request.environ.get('context')
values = load_values(flask.request, valid_attributes)
domain_schema.validate(values)
central_api = central_rpcapi.CentralAPI.get_instance()
# A V1 zone only supports being a primary (No notion of a type)
values['type'] = 'PRIMARY'
domain = central_api.create_zone(context, objects.Zone(**values))
LOG.info(_LI("Created %(zone)s"), {'zone': domain})
response = flask.jsonify(domain_schema.filter(domain))
response.status_int = 201
response.location = flask.url_for('.get_domain', domain_id=domain['id'])
return response
@blueprint.route('/domains', methods=['GET'])
def get_domains():
"""List existing zones except those flagged for deletion
"""
context = flask.request.environ.get('context')
central_api = central_rpcapi.CentralAPI.get_instance()
domains = central_api.find_zones(context, criterion={"type": "PRIMARY",
"action": "!DELETE"})
LOG.info(_LI("Retrieved %(zones)s"), {'zones': domains})
return flask.jsonify(domains_schema.filter({'domains': domains}))
@blueprint.route('/domains/<uuid:domain_id>', methods=['GET'])
def get_domain(domain_id):
"""Return zone data unless the zone is flagged for purging
"""
context = flask.request.environ.get('context')
central_api = central_rpcapi.CentralAPI.get_instance()
criterion = {"id": domain_id, "type": "PRIMARY", "action": "!DELETE"}
domain = central_api.find_zone(context, criterion=criterion)
LOG.info(_LI("Retrieved %(zone)s"), {'zone': domain})
return flask.jsonify(domain_schema.filter(domain))
@blueprint.route('/domains/<uuid:domain_id>', methods=['PUT'])
def update_domain(domain_id):
context = flask.request.environ.get('context')
values = flask.request.json
central_api = central_rpcapi.CentralAPI.get_instance()
# Fetch the existing resource
criterion = {"id": domain_id, "type": "PRIMARY", "action": "!DELETE"}
domain = central_api.find_zone(context, criterion=criterion)
# Prepare a dict of fields for validation
domain_data = domain_schema.filter(domain)
domain_data.update(values)
# Validate the new set of data
domain_schema.validate(domain_data)
# Update and persist the resource
domain.update(values)
domain = central_api.update_zone(context, domain)
LOG.info(_LI("Updated %(zone)s"), {'zone': domain})
return flask.jsonify(domain_schema.filter(domain))
@blueprint.route('/domains/<uuid:domain_id>', methods=['DELETE'])
def delete_domain(domain_id):
context = flask.request.environ.get('context')
central_api = central_rpcapi.CentralAPI.get_instance()
# TODO(ekarlso): Fix this to something better.
criterion = {"id": domain_id, "type": "PRIMARY", "action": "!DELETE"}
central_api.find_zone(context, criterion=criterion)
domain = central_api.delete_zone(context, domain_id)
LOG.info(_LI("Deleted %(zone)s"), {'zone': domain})
return flask.Response(status=200)
@blueprint.route('/domains/<uuid:domain_id>/servers', methods=['GET'])
def get_domain_servers(domain_id):
context = flask.request.environ.get('context')
central_api = central_rpcapi.CentralAPI.get_instance()
# TODO(ekarlso): Fix this to something better.
criterion = {"id": domain_id, "type": "PRIMARY", "action": "!DELETE"}
central_api.find_zone(context, criterion=criterion)
nameservers = central_api.get_zone_ns_records(context, domain_id)
servers = objects.ServerList()
for ns in nameservers:
servers.append(_pool_ns_record_to_server(ns))
return flask.jsonify(servers_schema.filter({'servers': servers}))

View File

@ -1,34 +0,0 @@
# Copyright 2012 Hewlett-Packard Development Company, L.P. All Rights Reserved.
#
# Author: Kiall Mac Innes <kiall@hpe.com>
#
# 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 flask
import oslo_messaging as messaging
from designate import rpc
blueprint = flask.Blueprint('diagnostics', __name__)
@blueprint.route('/diagnostics/ping/<topic>/<host>', methods=['GET'])
def ping_host(topic, host):
context = flask.request.environ.get('context')
client = rpc.get_client(messaging.Target(topic=topic))
cctxt = client.prepare(server=host, timeout=10)
pong = cctxt.call(context, 'ping')
return flask.jsonify(pong)

View File

@ -1,83 +0,0 @@
# Copyright 2012 Hewlett-Packard Development Company, L.P.
#
# Author: Kiall Mac Innes <kiall@hpe.com>
#
# 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 flask
from designate.central import rpcapi as central_rpcapi
central_api = central_rpcapi.CentralAPI()
blueprint = flask.Blueprint('quotas', __name__)
KEYS_TO_SWAP = {
'zones': 'domains',
'zone_records': 'domain_records',
'zone_recordsets': 'domain_recordsets',
'recordset_records': 'recordset_records',
'api_export_size': 'api_export_size',
}
KEYS_TO_SWAP_REVERSE = {
'domains': 'zones',
'domain_records': 'zone_records',
'domain_recordsets': 'zone_recordsets',
'recordset_records': 'recordset_records',
'api_export_size': 'api_export_size',
}
def swap_keys(quotas, reverse=False):
if reverse:
quotas = {KEYS_TO_SWAP_REVERSE[k]: quotas[k] for k in quotas}
else:
quotas = {KEYS_TO_SWAP[k]: quotas[k] for k in quotas}
return quotas
@blueprint.route('/quotas/<tenant_id>', methods=['GET'])
def get_quotas(tenant_id):
context = flask.request.environ.get('context')
quotas = central_api.get_quotas(context, tenant_id)
quotas = swap_keys(quotas)
return flask.jsonify(quotas)
@blueprint.route('/quotas/<tenant_id>', methods=['PUT', 'POST'])
def set_quota(tenant_id):
context = flask.request.environ.get('context')
values = flask.request.json
values = swap_keys(values, reverse=True)
for resource, hard_limit in values.items():
central_api.set_quota(context, tenant_id, resource, hard_limit)
quotas = central_api.get_quotas(context, tenant_id)
quotas = swap_keys(quotas)
return flask.jsonify(quotas)
@blueprint.route('/quotas/<tenant_id>', methods=['DELETE'])
def reset_quotas(tenant_id):
context = flask.request.environ.get('context')
central_api.reset_quotas(context, tenant_id)
return flask.Response(status=200)

View File

@ -1,78 +0,0 @@
# Copyright 2012 Hewlett-Packard Development Company, L.P. All Rights Reserved.
#
# Author: Simon McCartney <simon.mccartney@hpe.com>
#
# 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 flask
from designate.central import rpcapi as central_rpcapi
central_api = central_rpcapi.CentralAPI()
blueprint = flask.Blueprint('reports', __name__)
@blueprint.route('/reports/tenants', methods=['GET'])
def reports_tenants():
context = flask.request.environ.get('context')
tenants = central_api.find_tenants(context)
return flask.jsonify(tenants=tenants)
@blueprint.route('/reports/tenants/<tenant_id>', methods=['GET'])
def reports_tenant(tenant_id):
context = flask.request.environ.get('context')
tenant = central_api.get_tenant(context, tenant_id)
return flask.jsonify(tenant)
@blueprint.route('/reports/counts', methods=['GET'])
def reports_counts():
context = flask.request.environ.get('context')
tenants = central_api.count_tenants(context)
domains = central_api.count_zones(context)
records = central_api.count_records(context)
return flask.jsonify(tenants=tenants, domains=domains, records=records)
@blueprint.route('/reports/counts/tenants', methods=['GET'])
def reports_counts_tenants():
context = flask.request.environ.get('context')
count = central_api.count_tenants(context)
return flask.jsonify(tenants=count)
@blueprint.route('/reports/counts/domains', methods=['GET'])
def reports_counts_domains():
context = flask.request.environ.get('context')
count = central_api.count_zones(context)
return flask.jsonify(domains=count)
@blueprint.route('/reports/counts/records', methods=['GET'])
def reports_counts_records():
context = flask.request.environ.get('context')
count = central_api.count_records(context)
return flask.jsonify(records=count)

View File

@ -1,52 +0,0 @@
# Copyright 2012 Hewlett-Packard Development Company, L.P. All Rights Reserved.
#
# Author: Kiall Mac Innes <kiall@hpe.com>
#
# 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 flask
from designate.central import rpcapi as central_rpcapi
central_api = central_rpcapi.CentralAPI()
blueprint = flask.Blueprint('sync', __name__)
@blueprint.route('/domains/sync', methods=['POST'])
def sync_domains():
context = flask.request.environ.get('context')
central_api.sync_zones(context)
return flask.Response(status=200)
@blueprint.route('/domains/<uuid:domain_id>/sync', methods=['POST'])
def sync_domain(domain_id):
context = flask.request.environ.get('context')
central_api.sync_zone(context, domain_id)
return flask.Response(status=200)
@blueprint.route('/domains/<uuid:domain_id>/records/<uuid:record_id>/sync',
methods=['POST'])
def sync_record(domain_id, record_id):
context = flask.request.environ.get('context')
record = central_api.find_record(context, {'id': record_id})
central_api.sync_record(context, domain_id, record['recordset_id'],
record_id)
return flask.Response(status=200)

View File

@ -1,31 +0,0 @@
# Copyright 2013 Hewlett-Packard Development Company, L.P.
#
# Author: Kiall Mac Innes <kiall@hpe.com>
#
# 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 flask
from designate.central import rpcapi as central_rpcapi
central_api = central_rpcapi.CentralAPI()
blueprint = flask.Blueprint('touch', __name__)
@blueprint.route('/domains/<uuid:domain_id>/touch', methods=['POST'])
def touch_domain(domain_id):
context = flask.request.environ.get('context')
central_api.touch_zone(context, domain_id)
return flask.Response(status=200)

View File

@ -1,46 +0,0 @@
# Copyright 2012 Managed I.T.
#
# Author: Kiall Mac Innes <kiall@managedit.ie>
#
# 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 flask
from designate import schema
from designate.central import rpcapi as central_rpcapi
blueprint = flask.Blueprint('limits', __name__)
limits_schema = schema.Schema('v1', 'limits')
@blueprint.route('/schemas/limits', methods=['GET'])
def get_limits_schema():
return flask.jsonify(limits_schema.raw)
@blueprint.route('/limits', methods=['GET'])
def get_limits():
context = flask.request.environ.get('context')
central_api = central_rpcapi.CentralAPI.get_instance()
absolute_limits = central_api.get_absolute_limits(context)
return flask.jsonify(limits_schema.filter({
"limits": {
"absolute": {
"maxDomains": absolute_limits['zones'],
"maxDomainRecords": absolute_limits['zone_records']
}
}
}))

View File

@ -1,277 +0,0 @@
# Copyright 2012 Managed I.T.
#
# Author: Kiall Mac Innes <kiall@managedit.ie>
#
# 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 flask
from oslo_log import log as logging
from designate.central import rpcapi as central_rpcapi
from designate import exceptions
from designate import objects
from designate import schema
from designate import utils
from designate.i18n import _LI
LOG = logging.getLogger(__name__)
blueprint = flask.Blueprint('records', __name__)
record_schema = schema.Schema('v1', 'record')
records_schema = schema.Schema('v1', 'records')
def _find_recordset(context, domain_id, name, type):
central_api = central_rpcapi.CentralAPI.get_instance()
return central_api.find_recordset(context, {
'zone_id': domain_id,
'name': name,
'type': type,
})
def _find_or_create_recordset(context, domain_id, name, type, ttl):
central_api = central_rpcapi.CentralAPI.get_instance()
criterion = {"id": domain_id, "type": "PRIMARY", "action": "!DELETE"}
central_api.find_zone(context, criterion=criterion)
try:
# Attempt to create an empty recordset
values = {
'name': name,
'type': type,
'ttl': ttl,
}
recordset = central_api.create_recordset(
context, domain_id, objects.RecordSet(**values))
except exceptions.DuplicateRecordSet:
# Fetch the existing recordset
recordset = _find_recordset(context, domain_id, name, type)
return recordset
def _extract_record_values(values):
record_values = dict((k, values[k]) for k in ('data', 'description',)
if k in values)
if values.get('priority', None) is not None:
record_values['data'] = '%d %s' % (
values['priority'], record_values['data'])
return record_values
def _extract_recordset_values(values):
recordset_values = ('name', 'type', 'ttl',)
return dict((k, values[k]) for k in recordset_values if k in values)
def _format_record_v1(record, recordset):
record = dict(record)
record['priority'], record['data'] = utils.extract_priority_from_data(
recordset.type, record)
record['domain_id'] = record['zone_id']
del record['zone_id']
record.update({
'name': recordset['name'],
'type': recordset['type'],
'ttl': recordset['ttl'],
})
return record
@blueprint.route('/schemas/record', methods=['GET'])
def get_record_schema():
return flask.jsonify(record_schema.raw)
@blueprint.route('/schemas/records', methods=['GET'])
def get_records_schema():
return flask.jsonify(records_schema.raw)
@blueprint.route('/domains/<uuid:domain_id>/records', methods=['POST'])
def create_record(domain_id):
context = flask.request.environ.get('context')
values = flask.request.json
record_schema.validate(values)
if values['type'] == 'SOA':
raise exceptions.BadRequest('SOA records cannot be manually created.')
recordset = _find_or_create_recordset(context,
domain_id,
values['name'],
values['type'],
values.get('ttl', None))
record = objects.Record(**_extract_record_values(values))
central_api = central_rpcapi.CentralAPI.get_instance()
record = central_api.create_record(context, domain_id,
recordset['id'],
record)
LOG.info(_LI("Created %(record)s"), {'record': record})
record = _format_record_v1(record, recordset)
response = flask.jsonify(record_schema.filter(record))
response.status_int = 201
response.location = flask.url_for('.get_record', domain_id=domain_id,
record_id=record['id'])
return response
@blueprint.route('/domains/<uuid:domain_id>/records', methods=['GET'])
def get_records(domain_id):
context = flask.request.environ.get('context')
central_api = central_rpcapi.CentralAPI.get_instance()
# NOTE: We need to ensure the domain actually exists, otherwise we may
# return an empty records array instead of a domain not found
central_api.get_zone(context, domain_id)
recordsets = central_api.find_recordsets(context, {'zone_id': domain_id})
LOG.info(_LI("Retrieved %(recordsets)s"), {'recordsets': recordsets})
records = []
for rrset in recordsets:
records.extend([_format_record_v1(r, rrset) for r in rrset.records])
return flask.jsonify(records_schema.filter({'records': records}))
@blueprint.route('/domains/<uuid:domain_id>/records/<uuid:record_id>',
methods=['GET'])
def get_record(domain_id, record_id):
context = flask.request.environ.get('context')
central_api = central_rpcapi.CentralAPI.get_instance()
# NOTE: We need to ensure the domain actually exists, otherwise we may
# return an record not found instead of a domain not found
central_api.get_zone(context, domain_id)
criterion = {'zone_id': domain_id, 'id': record_id}
record = central_api.find_record(context, criterion)
recordset = central_api.get_recordset(
context, domain_id, record['recordset_id'])
LOG.info(_LI("Retrieved %(recordset)s"), {'recordset': recordset})
record = _format_record_v1(record, recordset)
return flask.jsonify(record_schema.filter(record))
@blueprint.route('/domains/<uuid:domain_id>/records/<uuid:record_id>',
methods=['PUT'])
def update_record(domain_id, record_id):
context = flask.request.environ.get('context')
values = flask.request.json
central_api = central_rpcapi.CentralAPI.get_instance()
# NOTE: We need to ensure the domain actually exists, otherwise we may
# return a record not found instead of a domain not found
criterion = {"id": domain_id, "type": "PRIMARY", "action": "!DELETE"}
central_api.find_zone(context, criterion)
# Fetch the existing resource
# NOTE(kiall): We use "find_record" rather than "get_record" as we do not
# have the recordset_id.
criterion = {'zone_id': domain_id, 'id': record_id}
record = central_api.find_record(context, criterion)
# TODO(graham): Move this further down the stack
if record.managed and not context.edit_managed_records:
raise exceptions.BadRequest('Managed records may not be updated')
# Find the associated recordset
recordset = central_api.get_recordset(
context, domain_id, record.recordset_id)
# Prepare a dict of fields for validation
record_data = record_schema.filter(_format_record_v1(record, recordset))
record_data.update(values)
# Validate the new set of data
record_schema.validate(record_data)
# Update and persist the resource
record.update(_extract_record_values(values))
record = central_api.update_record(context, record)
# Update the recordset resource (if necessary)
recordset.update(_extract_recordset_values(values))
if len(recordset.obj_what_changed()) > 0:
recordset = central_api.update_recordset(context, recordset)
LOG.info(_LI("Updated %(recordset)s"), {'recordset': recordset})
# Format and return the response
record = _format_record_v1(record, recordset)
return flask.jsonify(record_schema.filter(record))
def _delete_recordset_if_empty(context, domain_id, recordset_id):
central_api = central_rpcapi.CentralAPI.get_instance()
recordset = central_api.find_recordset(context, {
'id': recordset_id
})
# Make sure it's the right recordset
if len(recordset.records) == 0:
recordset = central_api.delete_recordset(
context, domain_id, recordset_id)
LOG.info(_LI("Deleted %(recordset)s"), {'recordset': recordset})
@blueprint.route('/domains/<uuid:domain_id>/records/<uuid:record_id>',
methods=['DELETE'])
def delete_record(domain_id, record_id):
context = flask.request.environ.get('context')
central_api = central_rpcapi.CentralAPI.get_instance()
# NOTE: We need to ensure the domain actually exists, otherwise we may
# return a record not found instead of a domain not found
criterion = {"id": domain_id, "type": "PRIMARY", "action": "!DELETE"}
central_api.find_zone(context, criterion=criterion)
# Find the record
criterion = {'zone_id': domain_id, 'id': record_id}
record = central_api.find_record(context, criterion)
# Cannot delete a managed record via the API.
if record['managed'] is True:
raise exceptions.BadRequest('Managed records may not be deleted')
record = central_api.delete_record(
context, domain_id, record['recordset_id'], record_id)
LOG.info(_LI("Deleted %(record)s"), {'record': record})
_delete_recordset_if_empty(context, domain_id, record['recordset_id'])
return flask.Response(status=200)

View File

@ -1,226 +0,0 @@
# Copyright 2012 Managed I.T.
#
# Author: Kiall Mac Innes <kiall@managedit.ie>
#
# 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 flask
from oslo_config import cfg
from oslo_log import log as logging
from designate import exceptions
from designate import schema
from designate import objects
from designate.i18n import _LI
from designate.central import rpcapi as central_rpcapi
LOG = logging.getLogger(__name__)
blueprint = flask.Blueprint('servers', __name__)
server_schema = schema.Schema('v1', 'server')
servers_schema = schema.Schema('v1', 'servers')
default_pool_id = cfg.CONF['service:central'].default_pool_id
# Servers are no longer used. They have been replaced by nameservers, which
# is stored as a PoolAttribute. However, the v1 server API calls still need
# to work
def _pool_ns_record_to_server(pool_ns_record):
server_values = {
'id': pool_ns_record.id,
'created_at': pool_ns_record.created_at,
'updated_at': pool_ns_record.updated_at,
'version': pool_ns_record.version,
'name': pool_ns_record.hostname
}
return objects.Server.from_dict(server_values)
@blueprint.route('/schemas/server', methods=['GET'])
def get_server_schema():
return flask.jsonify(server_schema.raw)
@blueprint.route('/schemas/servers', methods=['GET'])
def get_servers_schema():
return flask.jsonify(servers_schema.raw)
@blueprint.route('/servers', methods=['POST'])
def create_server():
context = flask.request.environ.get('context')
values = flask.request.json
central_api = central_rpcapi.CentralAPI.get_instance()
# Validate against the original server schema
server_schema.validate(values)
# Create a PoolNsRecord object
pns_values = {
'priority': 10,
'hostname': values['name']
}
ns_record = objects.PoolNsRecord.from_dict(pns_values)
# Get the default pool
pool = central_api.get_pool(context, default_pool_id)
# Add the new PoolAttribute to the pool as a nameserver
pool.ns_records.append(ns_record)
try:
# Update the pool
updated_pool = central_api.update_pool(context, pool)
LOG.info(_LI("Updated %(pool)s"), {'pool': pool})
except exceptions.DuplicatePoolAttribute:
raise exceptions.DuplicateServer()
# Go through the pool.ns_records to find the right one to get the ID
for ns in updated_pool.ns_records:
if ns.hostname == pns_values['hostname']:
created_ns_record = ns
break
# Convert the PoolAttribute to a Server so we can validate with the
# original schema and display
server = _pool_ns_record_to_server(created_ns_record)
response = flask.jsonify(server_schema.filter(server))
response.status_int = 201
response.location = flask.url_for('.get_server', server_id=server['id'])
return response
@blueprint.route('/servers', methods=['GET'])
def get_servers():
context = flask.request.environ.get('context')
central_api = central_rpcapi.CentralAPI.get_instance()
# Get the default pool
pool = central_api.get_pool(context, default_pool_id)
LOG.info(_LI("Retrieved %(pool)s"), {'pool': pool})
servers = objects.ServerList()
for ns in pool.ns_records:
servers.append(_pool_ns_record_to_server(ns))
LOG.info(_LI("Retrieved %(servers)s"), {'servers': servers})
return flask.jsonify(servers_schema.filter({'servers': servers}))
@blueprint.route('/servers/<uuid:server_id>', methods=['GET'])
def get_server(server_id):
context = flask.request.environ.get('context')
central_api = central_rpcapi.CentralAPI.get_instance()
# Get the default pool
pool = central_api.get_pool(context, default_pool_id)
LOG.info(_LI("Retrieved %(pool)s"), {'pool': pool})
# Create an empty PoolNsRecord object
nameserver = objects.PoolNsRecord()
# Get the desired nameserver from the pool
for ns in pool.ns_records:
if ns.id == server_id:
nameserver = ns
break
# If the nameserver wasn't found, raise an exception
if nameserver.id != server_id:
raise exceptions.ServerNotFound
LOG.info(_LI("Retrieved %(server)s"), {'server': nameserver})
server = _pool_ns_record_to_server(nameserver)
return flask.jsonify(server_schema.filter(server))
@blueprint.route('/servers/<uuid:server_id>', methods=['PUT'])
def update_server(server_id):
context = flask.request.environ.get('context')
values = flask.request.json
central_api = central_rpcapi.CentralAPI.get_instance()
# Get the default pool
pool = central_api.get_pool(context, default_pool_id)
# Get the Nameserver from the pool
index = -1
ns_records = pool.ns_records
for ns in ns_records:
if ns.id == server_id:
index = ns_records.index(ns)
break
if index == -1:
raise exceptions.ServerNotFound
# Get the ns_record from the pool so we can update it
nameserver = ns_records.pop(index)
# Update it with the new values
nameserver.update({'hostname': values['name']})
# Change it to a server, so we can use the original validation. We want
# to make sure we don't change anything in v1
server = _pool_ns_record_to_server(nameserver)
server_data = server_schema.filter(server)
server_data.update(values)
# Validate the new set of data
server_schema.validate(server_data)
# Now that it's been validated, add it back to the pool and persist it
pool.ns_records.append(nameserver)
pool = central_api.update_pool(context, pool)
LOG.info(_LI("Updated %(pool)s"), {'pool': pool})
return flask.jsonify(server_schema.filter(server))
@blueprint.route('/servers/<uuid:server_id>', methods=['DELETE'])
def delete_server(server_id):
context = flask.request.environ.get('context')
central_api = central_rpcapi.CentralAPI.get_instance()
# Get the default pool
pool = central_api.get_pool(context, default_pool_id)
# Get the Nameserver from the pool
index = -1
ns_records = pool.ns_records
for ns in ns_records:
if ns.id == server_id:
index = ns_records.index(ns)
break
if index == -1:
raise exceptions.ServerNotFound
# Remove the nameserver from the pool so it will be deleted
ns_records.pop(index)
# Update the pool without the deleted server
pool = central_api.update_pool(context, pool)
LOG.info(_LI("Updated %(pool)s"), {'pool': pool})
return flask.Response(status=200)

View File

@ -1,155 +0,0 @@
# Copyright 2012 Managed I.T.
#
# Author: Kiall Mac Innes <kiall@managedit.ie>
#
# 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 flask
from oslo_log import log as logging
from oslo_config import cfg
from designate import schema
from designate.central import rpcapi as central_rpcapi
from designate.objects import TsigKey
from designate.i18n import _LI
LOG = logging.getLogger(__name__)
blueprint = flask.Blueprint('tsigkeys', __name__)
tsigkey_schema = schema.Schema('v1', 'tsigkey')
tsigkeys_schema = schema.Schema('v1', 'tsigkeys')
default_pool_id = cfg.CONF['service:central'].default_pool_id
@blueprint.route('/schemas/tsigkey', methods=['GET'])
def get_tsigkey_schema():
return flask.jsonify(tsigkey_schema.raw)
@blueprint.route('/schemas/tsigkeys', methods=['GET'])
def get_tsigkeys_schema():
return flask.jsonify(tsigkeys_schema.raw)
@blueprint.route('/tsigkeys', methods=['POST'])
def create_tsigkey():
context = flask.request.environ.get('context')
values = flask.request.json
central_api = central_rpcapi.CentralAPI.get_instance()
tsigkey_schema.validate(values)
tsigkey = TsigKey.from_dict(values)
# The V1 API only deals with the default pool, so we restrict the view
# of TSIG Keys to those scoped to the default pool.
tsigkey.scope = 'POOL'
tsigkey.resource_id = default_pool_id
tsigkey = central_api.create_tsigkey(context, tsigkey)
LOG.info(_LI("Created %(tsigkey)s"), {'tsigkey': tsigkey})
response = flask.jsonify(tsigkey_schema.filter(tsigkey))
response.status_int = 201
response.location = flask.url_for('.get_tsigkey', tsigkey_id=tsigkey['id'])
return response
@blueprint.route('/tsigkeys', methods=['GET'])
def get_tsigkeys():
context = flask.request.environ.get('context')
central_api = central_rpcapi.CentralAPI.get_instance()
criterion = {'scope': 'POOL', 'resource_id': default_pool_id}
tsigkeys = central_api.find_tsigkeys(context, criterion)
LOG.info(_LI("Retrieved %(tsigkeys)s"), {'tsigkeys': tsigkeys})
return flask.jsonify(tsigkeys_schema.filter({'tsigkeys': tsigkeys}))
@blueprint.route('/tsigkeys/<uuid:tsigkey_id>', methods=['GET'])
def get_tsigkey(tsigkey_id):
context = flask.request.environ.get('context')
central_api = central_rpcapi.CentralAPI.get_instance()
criterion = {
'scope': 'POOL',
'resource_id': default_pool_id,
'id': tsigkey_id,
}
tsigkey = central_api.find_tsigkeys(context, criterion)
LOG.info(_LI("Retrieved %(tsigkey)s"), {'tsigkey': tsigkey})
return flask.jsonify(tsigkey_schema.filter(tsigkey))
@blueprint.route('/tsigkeys/<uuid:tsigkey_id>', methods=['PUT'])
def update_tsigkey(tsigkey_id):
context = flask.request.environ.get('context')
values = flask.request.json
central_api = central_rpcapi.CentralAPI.get_instance()
# Fetch the existing tsigkey
criterion = {
'scope': 'POOL',
'resource_id': default_pool_id,
'id': tsigkey_id,
}
tsigkey = central_api.find_tsigkey(context, criterion)
# Prepare a dict of fields for validation
tsigkey_data = tsigkey_schema.filter(tsigkey)
tsigkey_data.update(values)
# Validate the new set of data
tsigkey_schema.validate(tsigkey_data)
# Update and persist the resource
tsigkey.update(values)
tsigkey = central_api.update_tsigkey(context, tsigkey)
LOG.info(_LI("Updated %(tsigkey)s"), {'tsigkey': tsigkey})
return flask.jsonify(tsigkey_schema.filter(tsigkey))
@blueprint.route('/tsigkeys/<uuid:tsigkey_id>', methods=['DELETE'])
def delete_tsigkey(tsigkey_id):
context = flask.request.environ.get('context')
central_api = central_rpcapi.CentralAPI.get_instance()
# Fetch the existing resource, this ensures the key to be deleted has the
# correct scope/resource_id values, otherwise it will trigger a 404.
criterion = {
'scope': 'POOL',
'resource_id': default_pool_id,
'id': tsigkey_id,
}
central_api.find_tsigkeys(context, criterion)
# Delete the TSIG Key
tsigkey = central_api.delete_tsigkey(context, tsigkey_id)
LOG.info(_LI("Deleted %(tsigkey)s"), {'tsigkey': tsigkey})
return flask.Response(status=200)

View File

@ -29,7 +29,6 @@ def factory(global_config, **local_conf):
def _host_header_links():
del versions[:]
host_url = flask.request.host_url.rstrip('/')
_version('v1', 'DEPRECATED', host_url)
_version('v2', 'CURRENT', host_url)
def _version(version, status, base_uri):
@ -42,9 +41,6 @@ def factory(global_config, **local_conf):
}]
})
if cfg.CONF['service:api'].enable_api_v1:
_version('v1', 'DEPRECATED', base)
if cfg.CONF['service:api'].enable_api_v2:
_version('v2', 'CURRENT', base)

View File

@ -18,39 +18,6 @@ from oslo_policy import policy
from designate.common.policies import base
rules = [
policy.DocumentedRuleDefault(
name="create_record",
check_str=base.RULE_ADMIN_OR_OWNER,
description='Create record.',
operations=[
{
'path': '/v1/domains/<uuid:domain_id>/records',
'method': 'POST'
}
]
),
policy.DocumentedRuleDefault(
name="get_records",
check_str=base.RULE_ADMIN_OR_OWNER,
description='Get records.',
operations=[
{
'path': '/v1/domains/<uuid:domain_id>/records',
'method': 'GET'
}
]
),
policy.DocumentedRuleDefault(
name="get_record",
check_str=base.RULE_ADMIN_OR_OWNER,
description='Get record.',
operations=[
{
'path': '/v1/domains/<uuid:domain_id>/records/<uuid:record_id>', # noqa
'method': 'GET'
}
]
),
policy.DocumentedRuleDefault(
name="find_records",
check_str=base.RULE_ADMIN_OR_OWNER,
@ -65,45 +32,6 @@ rules = [
}
]
),
policy.DocumentedRuleDefault(
name="find_record",
check_str=base.RULE_ADMIN_OR_OWNER,
description='Find record.',
operations=[
{
'path': '/v1/domains/<uuid:domain_id>/records/<uuid:record_id>', # noqa
'method': 'GET'
}, {
'path': '/v1/domains/<uuid:domain_id>/records/<uuid:record_id>', # noqa
'method': 'DELETE'
}, {
'path': '/v1/domains/<uuid:domain_id>/records/<uuid:record_id>', # noqa
'method': 'PUT'
}
]
),
policy.DocumentedRuleDefault(
name="update_record",
check_str=base.RULE_ADMIN_OR_OWNER,
description='Update record.',
operations=[
{
'path': '/v1/domains/<uuid:domain_id>/records/<uuid:record_id>', # noqa
'method': 'PUT'
}
]
),
policy.DocumentedRuleDefault(
name="delete_record",
check_str=base.RULE_ADMIN_OR_OWNER,
description='Delete record.',
operations=[
{
'path': '/v1/domains/<uuid:domain_id>/records/<uuid:record_id>', # noqa
'method': 'DELETE'
}
]
),
policy.RuleDefault(
name="count_records",
check_str=base.RULE_ADMIN_OR_OWNER)

View File

@ -42,12 +42,6 @@ rules = [
description="Get recordset",
operations=[
{
'path': '/v1/domains/<uuid:domain_id>/records/<uuid:record_id>', # noqa
'method': 'GET'
}, {
'path': '/v1/domains/<uuid:domain_id>/records/<uuid:record_id>', # noqa
'method': 'PUT'
}, {
'path': '/v2/zones/{zone_id}/recordsets/{recordset_id}',
'method': 'GET'
}, {
@ -59,40 +53,12 @@ rules = [
}
]
),
policy.DocumentedRuleDefault(
name="find_recordsets",
check_str=base.RULE_ADMIN_OR_OWNER,
description="Find recordsets",
operations=[
{
'path': '/v1/domains/<uuid:domain_id>/records',
'method': 'GET'
}
]
),
policy.DocumentedRuleDefault(
name="find_recordset",
check_str=base.RULE_ADMIN_OR_OWNER,
description="Find recordset",
operations=[
{
'path': '/v1/domains/<uuid:domain_id>/records',
'method': 'POST'
}, {
'path': '/v1/domains/<uuid:domain_id>/records/<uuid:record_id>', # noqa
'method': 'DELETE'
}
]
),
policy.DocumentedRuleDefault(
name="update_recordset",
check_str=base.RULE_ZONE_PRIMARY_OR_ADMIN,
description="Update recordset",
operations=[
{
'path': '/v1/domains/<uuid:domain_id>/records/<uuid:record_id>', # noqa
'method': 'PUT'
}, {
'path': '/v2/zones/{zone_id}/recordsets/{recordset_id}',
'method': 'PUT'
}, {
@ -107,9 +73,6 @@ rules = [
description="Delete RecordSet",
operations=[
{
'path': '/v1/domains/<uuid:domain_id>/records/<uuid:record_id>', # noqa
'method': 'DELETE'
}, {
'path': '/v2/zones/{zone_id}/recordsets/{recordset_id}',
'method': 'DELETE'
}

View File

@ -24,9 +24,6 @@ rules = [
description="Create Tsigkey",
operations=[
{
'path': '/v1/tsigkeys',
'method': 'POST'
}, {
'path': '/v2/tsigkeys',
'method': 'POST'
}
@ -38,15 +35,6 @@ rules = [
description="List Tsigkeys",
operations=[
{
'path': '/v1/tsigkeys',
'method': 'GET'
}, {
'path': '/v1/tsigkeys/<uuid:tsigkey_id>',
'method': 'GET'
}, {
'path': '/v1/tsigkeys/<uuid:tsigkey_id>',
'method': 'DELETE'
}, {
'path': '/v2/tsigkeys',
'method': 'GET'
}
@ -72,9 +60,6 @@ rules = [
description="Update Tsigkey",
operations=[
{
'path': '/v1/tsigkeys/{tsigkey_id}',
'method': 'PATCH'
}, {
'path': '/v2/tsigkeys/{tsigkey_id}',
'method': 'PATCH'
}
@ -86,9 +71,6 @@ rules = [
description="Delete a Tsigkey",
operations=[
{
'path': '/v1/tsigkeys/{tsigkey_id}',
'method': 'DELETE'
}, {
'path': '/v2/tsigkeys/{tsigkey_id}',
'method': 'DELETE'
}

View File

@ -24,9 +24,6 @@ rules = [
description="Create Zone",
operations=[
{
'path': '/v1//domains',
'method': 'POST'
}, {
'path': '/v2/zones',
'method': 'POST'
}
@ -42,12 +39,6 @@ rules = [
description="Get Zone",
operations=[
{
'path': '/v1/domains/<uuid:domain_id>/records/<uuid:record_id>', # noqa
'method': 'GET'
}, {
'path': '/v1/domains/<uuid:domain_id>/records',
'method': 'GET'
}, {
'path': '/v2/zones/{zone_id}',
'method': 'GET'
}, {
@ -69,43 +60,17 @@ rules = [
description="List existing zones",
operations=[
{
'path': '/v1/domains',
'method': 'GET'
}, {
'path': '/v2/zones',
'method': 'GET'
}
]
),
policy.DocumentedRuleDefault(
name="find_zone",
check_str=base.RULE_ADMIN_OR_OWNER,
description="Find Zone",
operations=[
{
'path': '/v1/domains/<uuid:domain_id>',
'method': 'GET'
}, {
'path': '/v1/domains/<uuid:domain_id>/servers',
'method': 'GET'
}, {
'path': '/v1/domains/<uuid:domain_id>',
'method': 'PUT'
}, {
'path': '/v1/domains/<uuid:domain_id>',
'method': 'DELETE'
}
]
),
policy.DocumentedRuleDefault(
name="update_zone",
check_str=base.RULE_ADMIN_OR_OWNER,
description="Update Zone",
operations=[
{
'path': '/v1/domains/<uuid:domain_id>',
'method': 'PUT'
}, {
'path': '/v2/zones/{zone_id}',
'method': 'PATCH'
}
@ -117,9 +82,6 @@ rules = [
description="Delete Zone",
operations=[
{
'path': '/v1/domains/<uuid:domain_id>',
'method': 'DELETE'
}, {
'path': '/v2/zones/{zone_id}',
'method': 'DELETE'
}

View File

@ -1,20 +0,0 @@
# Copyright 2014 Hewlett-Packard Development Company, L.P.
#
# 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.objects.adapters import base
class APIv1Adapter(base.DesignateAdapter):
ADAPTER_FORMAT = 'API_v1'

View File

@ -1,76 +0,0 @@
{
"id": "domain",
"$schema": "http://json-schema.org/draft-03/hyper-schema",
"title": "domain",
"description": "Domain",
"additionalProperties": false,
"properties": {
"id": {
"type": "string",
"description": "Domain Identifier",
"format": "uuid",
"readonly": true
},
"name": {
"type": "string",
"description": "Domain name",
"format": "domain-name",
"maxLength": 255,
"required": true,
"readonly": true
},
"email": {
"type": "string",
"description": "Hostmaster email address",
"format": "email",
"maxLength": 255,
"required": true
},
"ttl": {
"type": "integer",
"description": "Time to live",
"minimum": 1,
"maximum": 2147483647
},
"serial": {
"type": "integer",
"description": "Serial Number",
"minimum": 1,
"maximum": 4294967295,
"readonly": true
},
"description": {
"type": ["string", "null"],
"description": "Description for the Domain",
"maxLength": 160
},
"created_at": {
"type": "string",
"description": "Date and time of domain creation",
"format": "date-time",
"readonly": true
},
"updated_at": {
"type": ["string", "null"],
"description": "Date and time of last domain update",
"format": "date-time",
"readonly": true
}
},
"links": [{
"rel": "self",
"href": "/domains/{id}"
}, {
"rel": "records",
"href": "/domains/{id}/records"
}, {
"rel": "servers",
"href": "/domains/{id}/servers"
}, {
"rel": "collection",
"href": "/domains"
}]
}

View File

@ -1,17 +0,0 @@
{
"id": "domains",
"$schema": "http://json-schema.org/draft-03/hyper-schema",
"title": "domains",
"description": "Domains",
"additionalProperties": false,
"properties": {
"domains": {
"type": "array",
"description": "Domains",
"items": {"$ref": "domain#"}
}
}
}

View File

@ -1,53 +0,0 @@
{
"id": "fault",
"$schema": "http://json-schema.org/draft-03/hyper-schema",
"title": "fault",
"description": "Fault",
"additionalProperties": false,
"properties": {
"code": {
"type": "integer",
"description": "Fault Code",
"required": true,
"readonly": true
},
"type": {
"type": "string",
"description": "Fault Type",
"readonly": true
},
"message": {
"type": "string",
"description": "Fault Message",
"readonly": true
},
"errors": {
"type": "array",
"description": "List of Errors",
"readonly": true,
"items": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Error Path",
"readonly": true
},
"type": {
"type": "string",
"description": "Error Type",
"readonly": true
}
}
}
},
"request_id": {
"type": "string",
"description": "Request ID",
"readonly": true
}
}
}

View File

@ -1,29 +0,0 @@
{
"id": "limits",
"$schema": "http://json-schema.org/draft-03/hyper-schema",
"title": "limits",
"description": "Limits",
"additionalProperties": false,
"properties": {
"limits": {
"type": "object",
"description": "Limits",
"properties": {
"absolute": {
"type": "object",
"properties": {
"maxDomains": {
"type": "integer"
},
"maxDomainRecords": {
"type": "integer"
}
}
}
}
}
}
}

View File

@ -1,246 +0,0 @@
{
"id": "record",
"$schema": "http://json-schema.org/draft-03/hyper-schema",
"title": "record",
"description": "Record",
"additionalProperties": false,
"properties": {
"id": {
"type": "string",
"description": "Record Identifier",
"format": "uuid",
"readonly": true
},
"domain_id": {
"type": "string",
"description": "Domain Identifier",
"format": "uuid",
"readonly": true
},
"name": {
"type": "string",
"description": "DNS Record Name",
"format": "host-name",
"maxLength": 255,
"required": true
},
"type": {
"type": "string",
"description": "DNS Record Type",
"enum": ["A", "AAAA", "CNAME", "MX", "SRV", "TXT", "SPF", "NS", "PTR", "SSHFP", "SOA"],
"required": true
},
"data": {
"type": "string",
"description": "DNS Record Value",
"maxLength": 255,
"required": true
},
"priority": {
"type": ["integer", "null"],
"description": "DNS Record Priority",
"minimum": 0,
"maximum": 65535
},
"ttl": {
"type": ["integer", "null"],
"description": "Time to live",
"minimum": 1,
"maximum": 2147483647
},
"description": {
"type": ["string", "null"],
"description": "Description for the record",
"maxLength": 160
},
"created_at": {
"type": "string",
"description": "Date and time of record creation",
"format": "date-time",
"readonly": true
},
"updated_at": {
"type": ["string", "null"],
"description": "Date and time of last record update",
"format": "date-time",
"readonly": true
}
},
"oneOf": [{
"description": "An A Record",
"properties": {
"type": {
"type": "string",
"enum": ["A"]
},
"data": {
"format": "ip-address",
"required": true
},
"priority": {
"type": "null"
}
}
}, {
"description": "An AAAA Record",
"properties": {
"type": {
"type": "string",
"enum": ["AAAA"]
},
"data": {
"format": "ipv6",
"required": true
},
"priority": {
"type": "null"
}
}
}, {
"description": "A CNAME Record",
"properties": {
"type": {
"type": "string",
"enum": ["CNAME"]
},
"data": {
"format": "host-name",
"required": true
},
"priority": {
"type": "null"
}
}
}, {
"description": "A MX Record",
"properties": {
"type": {
"type": "string",
"enum": ["MX"]
},
"data": {
"format": "host-name",
"required": true
},
"priority": {
"type": "integer",
"required": true
}
}
}, {
"description": "A SRV Record",
"properties": {
"type": {
"type": "string",
"enum": ["SRV"]
},
"name": {
"type": "string",
"pattern": "^(?:_[A-Za-z0-9_\\-]{1,62}\\.){2}"
},
"data": {
"type": "string",
"pattern": "^(?:(?:6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[1-5][0-9]{4}|[1-9][0-9]{1,3}|[0-9])\\s){2}(?!.{255,})((?!\\-)[A-Za-z0-9_\\-]{1,63}(?<!\\-)\\.)+$"
},
"priority": {
"type": "integer",
"required": true
}
}
}, {
"description": "A TXT Record",
"properties": {
"type": {
"type": "string",
"enum": ["TXT"]
},
"priority": {
"type": "null"
}
}
}, {
"description": "A SPF Record",
"properties": {
"type": {
"type": "string",
"enum": ["SPF"]
},
"priority": {
"type": "null"
}
}
}, {
"description": "A NS Record",
"properties": {
"type": {
"type": "string",
"enum": ["NS"]
},
"data": {
"format": "host-name",
"required": true
},
"priority": {
"type": "null"
}
}
}, {
"description": "A PTR Record",
"properties": {
"type": {
"type": "string",
"enum": ["PTR"]
},
"name": {
"type": "string",
"pattern": "^(?:(?:\\d{1,3}\\.){4}in-addr\\.arpa\\.|(?:[a-f|\\d]\\.){32}ip6\\.arpa\\.)$"
},
"data": {
"format": "host-name",
"required": true
},
"priority": {
"type": "null"
}
}
}, {
"description": "A SSHFP Record",
"properties": {
"type": {
"type": "string",
"enum": ["SSHFP"]
},
"data": {
"pattern": "^[1-2] 1 [0-9A-Fa-f]{40}$",
"required": true
},
"priority": {
"type": "null"
}
}
}, {
"description": "A SOA Record",
"properties": {
"type": {
"type": "string",
"enum": ["SOA"]
},
"priority": {
"type": "null"
}
}
}],
"links": [{
"rel": "self",
"href": "/domains/{domain_id}/records/{id}"
}, {
"rel": "domain",
"href": "/domains/{domain_id}"
}, {
"rel": "collection",
"href": "/domains/{domain_id}/records"
}]
}

View File

@ -1,17 +0,0 @@
{
"id": "records",
"$schema": "http://json-schema.org/draft-03/hyper-schema",
"title": "records",
"description": "Records",
"additionalProperties": false,
"properties": {
"records": {
"type": "array",
"description": "Records",
"items": {"$ref": "record#"}
}
}
}

View File

@ -1,44 +0,0 @@
{
"id": "server",
"$schema": "http://json-schema.org/draft-03/hyper-schema",
"title": "server",
"description": "Server",
"additionalProperties": false,
"properties": {
"id": {
"type": "string",
"description": "Server Identifier",
"format": "uuid",
"readonly": true
},
"name": {
"type": "string",
"description": "Server DNS name",
"format": "host-name",
"maxLength": 255,
"required": true
},
"created_at": {
"type": "string",
"description": "Date and time of server creation",
"format": "date-time",
"readonly": true
},
"updated_at": {
"type": ["string", "null"],
"description": "Date and time of last server update",
"format": "date-time",
"readonly": true
}
},
"links": [{
"rel": "self",
"href": "/servers/{id}"
}, {
"rel": "collection",
"href": "/servers"
}]
}

View File

@ -1,17 +0,0 @@
{
"id": "servers",
"$schema": "http://json-schema.org/draft-03/hyper-schema",
"title": "servers",
"description": "Servers",
"additionalProperties": false,
"properties": {
"servers": {
"type": "array",
"description": "Servers",
"items": {"$ref": "server#"}
}
}
}

View File

@ -1,43 +0,0 @@
{
"id": "tsigkey",
"$schema": "http://json-schema.org/draft-03/hyper-schema",
"title": "tsigkey",
"description": "TSIG Key",
"additionalProperties": false,
"properties": {
"id": {
"type": "string",
"description": "TSIG Key Identifier",
"format": "uuid",
"readonly": true
},
"name": {
"type": "string",
"description": "TSIG Key Name",
"maxLength": 255,
"required": true
},
"algorithm": {
"type": "string",
"description": "TSIG Algorithm",
"enum": ["hmac-md5", "hmac-sha1", "hmac-sha224", "hmac-sha256", "hmac-sha384", "hmac-sha512"],
"required": true
},
"secret": {
"type": "string",
"description": "TSIG Secret",
"maxLength": 255,
"required": true
}
},
"links": [{
"rel": "self",
"href": "/tsigkeys/{id}"
}, {
"rel": "collection",
"href": "/tsigkeys"
}]
}

View File

@ -1,17 +0,0 @@
{
"id": "tsigkeys",
"$schema": "http://json-schema.org/draft-03/hyper-schema",
"title": "tsigkeys",
"description": "TSIG Keys",
"additionalProperties": false,
"properties": {
"tsigkeys": {
"type": "array",
"description": "TSIG Keys",
"items": {"$ref": "tsigkey#"}
}
}
}

View File

@ -30,11 +30,7 @@ class Schema(object):
self.resolver = resolvers.LocalResolver.from_schema(
version, self.raw_schema)
if version == 'v1':
self.validator = validators.Draft3Validator(
self.raw_schema, resolver=self.resolver,
format_checker=format.draft3_format_checker)
elif version in ['v2', 'admin']:
if version in ['v2', 'admin']:
self.validator = validators.Draft4Validator(
self.raw_schema, resolver=self.resolver,
format_checker=format.draft4_format_checker)

View File

@ -1,118 +0,0 @@
# Copyright 2012 Managed I.T.
#
# Author: Kiall Mac Innes <kiall@managedit.ie>
#
# 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 oslo_log import log as logging
from oslo_serialization import jsonutils as json
from designate.api import v1 as api_v1
from designate.api import middleware
from designate.tests.test_api import ApiTestCase
LOG = logging.getLogger(__name__)
class ApiV1Test(ApiTestCase):
def setUp(self):
super(ApiV1Test, self).setUp()
# Ensure the v1 API is enabled
self.config(enable_api_v1=True, group='service:api')
# Create the application
self.app = api_v1.factory({})
# Inject the NormalizeURIMiddleware middleware
self.app.wsgi_app = middleware.NormalizeURIMiddleware(
self.app.wsgi_app)
# Inject the FaultWrapper middleware
self.app.wsgi_app = middleware.FaultWrapperMiddleware(
self.app.wsgi_app)
# Inject the ValidationError middleware
self.app.wsgi_app = middleware.APIv1ValidationErrorMiddleware(
self.app.wsgi_app)
# Inject the TestAuth middleware
self.app.wsgi_app = middleware.TestContextMiddleware(
self.app.wsgi_app, self.admin_context.tenant,
self.admin_context.user)
# Obtain a test client
self.client = self.app.test_client()
def get(self, path, **kw):
expected_status_code = kw.pop('status_code', 200)
resp = self.client.get(path=path)
LOG.debug('Response Body: %r' % resp.data)
self.assertEqual(expected_status_code, resp.status_code)
try:
resp.json = json.loads(resp.data)
except ValueError:
resp.json = None
return resp
def post(self, path, data, content_type="application/json", **kw):
expected_status_code = kw.pop('status_code', 200)
content = json.dumps(data)
resp = self.client.post(path=path, content_type=content_type,
data=content)
LOG.debug('Response Body: %r' % resp.data)
self.assertEqual(expected_status_code, resp.status_code)
try:
resp.json = json.loads(resp.data)
except ValueError:
resp.json = None
return resp
def put(self, path, data, content_type="application/json", **kw):
expected_status_code = kw.pop('status_code', 200)
content = json.dumps(data)
resp = self.client.put(path=path, content_type=content_type,
data=content)
LOG.debug('Response Body: %r' % resp.data)
self.assertEqual(expected_status_code, resp.status_code)
try:
resp.json = json.loads(resp.data)
except ValueError:
resp.json = None
return resp
def delete(self, path, **kw):
expected_status_code = kw.pop('status_code', 200)
resp = self.client.delete(path=path)
LOG.debug('Response Body: %r' % resp.data)
self.assertEqual(expected_status_code, resp.status_code)
return resp

View File

@ -1,522 +0,0 @@
# coding=utf-8
# Copyright 2012 Managed I.T.
#
# Author: Kiall Mac Innes <kiall@managedit.ie>
#
# 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 datetime
import testtools
from mock import patch
import oslo_messaging as messaging
from oslo_config import cfg
from oslo_log import log as logging
from designate import exceptions
from designate.central import service as central_service
from designate.tests.test_api.test_v1 import ApiV1Test
LOG = logging.getLogger(__name__)
class ApiV1zonesTest(ApiV1Test):
def test_get_zone_schema(self):
response = self.get('schemas/domain')
self.assertIn('description', response.json)
self.assertIn('links', response.json)
self.assertIn('title', response.json)
self.assertIn('id', response.json)
self.assertIn('additionalProperties', response.json)
self.assertIn('properties', response.json)
self.assertIn('description', response.json['properties'])
self.assertIn('created_at', response.json['properties'])
self.assertIn('updated_at', response.json['properties'])
self.assertIn('name', response.json['properties'])
self.assertIn('email', response.json['properties'])
self.assertIn('ttl', response.json['properties'])
self.assertIn('serial', response.json['properties'])
def test_get_zones_schema(self):
response = self.get('schemas/domains')
self.assertIn('description', response.json)
self.assertIn('additionalProperties', response.json)
self.assertIn('properties', response.json)
self.assertIn('title', response.json)
self.assertIn('id', response.json)
def test_create_zone(self):
# Create a zone
fixture = self.get_zone_fixture(0)
# V1 doesn't have these
del fixture['type']
response = self.post('domains', data=fixture)
self.assertIn('id', response.json)
self.assertIn('name', response.json)
self.assertEqual(response.json['name'], fixture['name'])
def test_create_zone_junk(self):
# Create a zone
fixture = self.get_zone_fixture(0)
# Add a junk property
fixture['junk'] = 'Junk Field'
# Ensure it fails with a 400
self.post('domains', data=fixture, status_code=400)
@patch.object(central_service.Service, 'create_zone',
side_effect=messaging.MessagingTimeout())
def test_create_zone_timeout(self, _):
# Create a zone
fixture = self.get_zone_fixture(0)
# V1 doesn't have these
del fixture['type']
self.post('domains', data=fixture, status_code=504)
@patch.object(central_service.Service, 'create_zone',
side_effect=exceptions.DuplicateZone())
def test_create_zone_duplicate(self, _):
# Create a zone
fixture = self.get_zone_fixture(0)
# V1 doesn't have these
del fixture['type']
self.post('domains', data=fixture, status_code=409)
def test_create_zone_null_ttl(self):
# Create a zone
fixture = self.get_zone_fixture(0)
fixture['ttl'] = None
self.post('domains', data=fixture, status_code=400)
def test_create_zone_negative_ttl(self):
# Create a zone
fixture = self.get_zone_fixture(0)
fixture['ttl'] = -1
self.post('domains', data=fixture, status_code=400)
def test_create_zone_zero_ttl(self):
# Create a zone
fixture = self.get_zone_fixture(0)
fixture['ttl'] = 0
self.post('domains', data=fixture, status_code=400)
def test_create_zone_invalid_ttl(self):
# Create a zone
fixture = self.get_zone_fixture(0)
fixture['ttl'] = "$?>&"
self.post('domains', data=fixture, status_code=400)
def test_create_zone_ttl_greater_than_max(self):
fixture = self.get_zone_fixture(0)
fixture['ttl'] = 2147483648
self.post('domains', data=fixture, status_code=400)
def test_create_zone_utf_description(self):
# Create a zone
fixture = self.get_zone_fixture(0)
# V1 doesn't have type
del fixture['type']
# Give it a UTF-8 filled description
fixture['description'] = "utf-8:2H₂+O₂⇌2H₂O,R=4.7kΩ,⌀200mm∮E⋅da=Q,n" \
",∑f(i)=∏g(i),∀x∈:⌈x⌉"
# Create the zone, ensuring it succeeds, thus UTF-8 is supported
self.post('domains', data=fixture)
def test_create_zone_description_too_long(self):
# Create a zone
fixture = self.get_zone_fixture(0)
fixture['description'] = "x" * 161
# Create the zone, ensuring it fails with a 400
self.post('domains', data=fixture, status_code=400)
def test_create_zone_with_unwanted_attributes(self):
zone_id = "2d1d1d1d-1324-4a80-aa32-1f69a91bf2c8"
created_at = datetime.datetime(2014, 6, 22, 21, 50, 0)
updated_at = datetime.datetime(2014, 6, 22, 21, 50, 0)
serial = 1234567
# Create a zone
fixture = self.get_zone_fixture(0)
fixture['id'] = zone_id
fixture['created_at'] = created_at
fixture['updated_at'] = updated_at
fixture['serial'] = serial
self.post('domains', data=fixture, status_code=400)
def test_create_invalid_name(self):
# Prepare a zone
fixture = self.get_zone_fixture(0)
invalid_names = [
'org',
'example.org',
'example.321',
]
for invalid_name in invalid_names:
fixture['name'] = invalid_name
# Create a record
response = self.post('domains', data=fixture, status_code=400)
self.assertNotIn('id', response.json)
def test_create_zone_name_too_long(self):
fixture = self.get_zone_fixture(0)
long_name = 'a' * 255 + ".org."
fixture['name'] = long_name
response = self.post('domains', data=fixture, status_code=400)
self.assertNotIn('id', response.json)
def test_create_zone_name_is_not_present(self):
fixture = self.get_zone_fixture(0)
del fixture['name']
self.post('domains', data=fixture, status_code=400)
def test_create_invalid_email(self):
# Prepare a zone
fixture = self.get_zone_fixture(0)
invalid_emails = [
'org',
'example.org',
'bla.example.org',
'org.',
'example.org.',
'bla.example.org.',
'bla.example.org.',
]
for invalid_email in invalid_emails:
fixture['email'] = invalid_email
# Create a record
response = self.post('domains', data=fixture, status_code=400)
self.assertNotIn('id', response.json)
def test_create_zone_email_too_long(self):
fixture = self.get_zone_fixture(0)
long_email = 'a' * 255 + "@org.com"
fixture['email'] = long_email
response = self.post('domains', data=fixture, status_code=400)
self.assertNotIn('id', response.json)
def test_create_zone_email_not_present(self):
fixture = self.get_zone_fixture(0)
del fixture['email']
self.post('domains', data=fixture, status_code=400)
def test_create_zone_twice(self):
self.create_zone()
with testtools.ExpectedException(exceptions.DuplicateZone):
self.create_zone()
def test_create_zone_pending_deletion(self):
zone = self.create_zone()
self.delete('domains/%s' % zone['id'])
with testtools.ExpectedException(exceptions.DuplicateZone):
self.create_zone()
def test_get_zones(self):
response = self.get('domains')
self.assertIn('domains', response.json)
self.assertEqual(0, len(response.json['domains']))
# Create a zone
self.create_zone()
response = self.get('domains')
self.assertIn('domains', response.json)
self.assertEqual(1, len(response.json['domains']))
# Create a second zone
self.create_zone(fixture=1)
response = self.get('domains')
self.assertIn('domains', response.json)
self.assertEqual(2, len(response.json['domains']))
def test_get_zone_servers(self):
# Create a zone
zone = self.create_zone()
response = self.get('domains/%s/servers' % zone['id'])
# Verify length of zone servers
self.assertEqual(1, len(response.json['servers']))
@patch.object(central_service.Service, 'find_zones',
side_effect=messaging.MessagingTimeout())
def test_get_zones_timeout(self, _):
self.get('domains', status_code=504)
def test_get_zone(self):
# Create a zone
zone = self.create_zone()
response = self.get('domains/%s' % zone['id'])
self.assertIn('id', response.json)
self.assertEqual(response.json['id'], zone['id'])
@patch.object(central_service.Service, 'find_zone',
side_effect=messaging.MessagingTimeout())
def test_get_zone_timeout(self, _):
# Create a zone
zone = self.create_zone()
self.get('domains/%s' % zone['id'], status_code=504)
def test_get_zone_missing(self):
self.get('domains/2fdadfb1-cf96-4259-ac6b-bb7b6d2ff980',
status_code=404)
def test_get_zone_invalid_id(self):
# The letter "G" is not valid in a UUID
self.get('domains/2fdadfb1-cf96-4259-ac6b-bb7b6d2ff9GG',
status_code=404)
self.get('domains/2fdadfb1cf964259ac6bbb7b6d2ff980', status_code=404)
def test_update_zone(self):
# Create a zone
zone = self.create_zone()
data = {'email': 'prefix-%s' % zone['email']}
response = self.put('domains/%s' % zone['id'], data=data)
self.assertIn('id', response.json)
self.assertEqual(response.json['id'], zone['id'])
self.assertIn('email', response.json)
self.assertEqual('prefix-%s' % zone['email'], response.json['email'])
def test_update_zone_junk(self):
# Create a zone
zone = self.create_zone()
data = {'email': 'prefix-%s' % zone['email'], 'junk': 'Junk Field'}
self.put('domains/%s' % zone['id'], data=data, status_code=400)
def test_update_zone_name_fail(self):
# Create a zone
zone = self.create_zone()
data = {'name': 'renamed.com.'}
self.put('domains/%s' % zone['id'], data=data, status_code=400)
def test_update_zone_null_ttl(self):
# Create a zone
zone = self.create_zone()
data = {'ttl': None}
self.put('domains/%s' % zone['id'], data=data, status_code=400)
def test_update_zone_negative_ttl(self):
# Create a zone
zone = self.create_zone()
data = {'ttl': -1}
self.put('domains/%s' % zone['id'], data=data, status_code=400)
def test_update_zone_zero_ttl(self):
# Create a zone
zone = self.create_zone()
data = {'ttl': 0}
self.put('domains/%s' % zone['id'], data=data, status_code=400)
@patch.object(central_service.Service, 'update_zone',
side_effect=messaging.MessagingTimeout())
def test_update_zone_timeout(self, _):
# Create a zone
zone = self.create_zone()
data = {'email': 'prefix-%s' % zone['email']}
self.put('domains/%s' % zone['id'], data=data, status_code=504)
@patch.object(central_service.Service, 'update_zone',
side_effect=exceptions.DuplicateZone())
def test_update_zone_duplicate(self, _):
# Create a zone
zone = self.create_zone()
data = {'email': 'prefix-%s' % zone['email']}
self.put('domains/%s' % zone['id'], data=data, status_code=409)
def test_update_zone_missing(self):
data = {'email': 'bla@bla.com'}
self.put('domains/2fdadfb1-cf96-4259-ac6b-bb7b6d2ff980', data=data,
status_code=404)
def test_update_zone_invalid_id(self):
data = {'email': 'bla@bla.com'}
# The letter "G" is not valid in a UUID
self.put('domains/2fdadfb1-cf96-4259-ac6b-bb7b6d2ff9GG', data=data,
status_code=404)
self.put('domains/2fdadfb1cf964259ac6bbb7b6d2ff980', data=data,
status_code=404)
def test_update_zone_ttl_greter_than_max(self):
# Create a zone
zone = self.create_zone()
data = {'ttl': 2147483648}
self.put('domains/%s' % zone['id'], data=data, status_code=400)
def test_update_zone_invalid_email(self):
# Create a zone
zone = self.create_zone()
invalid_emails = [
'org',
'example.org',
'bla.example.org',
'org.',
'example.org.',
'bla.example.org.',
'bla.example.org.',
'a' * 255 + "@com",
''
]
for invalid_email in invalid_emails:
data = {'email': invalid_email}
self.put('domains/%s' % zone['id'], data=data, status_code=400)
def test_update_zone_description_too_long(self):
# Create a zone
zone = self.create_zone()
invalid_des = 'a' * 165
data = {'description': invalid_des}
self.put('domains/%s' % zone['id'], data=data, status_code=400)
def test_update_zone_in_pending_deletion(self):
zone = self.create_zone()
self.delete('domains/%s' % zone['id'])
self.put('domains/%s' % zone['id'], data={}, status_code=404)
def test_delete_zone(self):
# Create a zone
zone = self.create_zone()
self.delete('domains/%s' % zone['id'])
# Simulate the zone having been deleted on the backend
zone_serial = self.central_service.get_zone(
self.admin_context, zone['id']).serial
self.central_service.update_status(
self.admin_context, zone['id'], "SUCCESS", zone_serial)
# Ensure we can no longer fetch the zone
self.get('domains/%s' % zone['id'], status_code=404)
def test_zone_in_pending_deletion(self):
zone1 = self.create_zone()
self.create_zone(fixture=1)
response = self.get('domains')
self.assertEqual(2, len(response.json['domains']))
# Delete zone1
self.delete('domains/%s' % zone1['id'])
# Ensure we can no longer list nor fetch the deleted zone
response = self.get('domains')
self.assertEqual(1, len(response.json['domains']))
self.get('domains/%s' % zone1['id'], status_code=404)
@patch.object(central_service.Service, 'delete_zone',
side_effect=messaging.MessagingTimeout())
def test_delete_zone_timeout(self, _):
# Create a zone
zone = self.create_zone()
self.delete('domains/%s' % zone['id'], status_code=504)
def test_delete_zone_missing(self):
self.delete('domains/2fdadfb1-cf96-4259-ac6b-bb7b6d2ff980',
status_code=404)
def test_delete_zone_invalid_id(self):
# The letter "G" is not valid in a UUID
self.delete('domains/2fdadfb1-cf96-4259-ac6b-bb7b6d2ff9GG',
status_code=404)
self.delete('domains/2fdadfb1cf964259ac6bbb7b6d2ff980',
status_code=404)
def test_get_secondary_missing(self):
fixture = self.get_zone_fixture('SECONDARY', 0)
fixture['email'] = cfg.CONF['service:central'].managed_resource_email
zone = self.create_zone(**fixture)
self.get('domains/%s' % zone.id, status_code=404)
def test_update_secondary_missing(self):
fixture = self.get_zone_fixture('SECONDARY', 0)
fixture['email'] = cfg.CONF['service:central'].managed_resource_email
zone = self.create_zone(**fixture)
self.put('domains/%s' % zone.id, {}, status_code=404)
def test_delete_secondary_missing(self):
fixture = self.get_zone_fixture('SECONDARY', 0)
fixture['email'] = cfg.CONF['service:central'].managed_resource_email
zone = self.create_zone(**fixture)
self.delete('domains/%s' % zone.id, status_code=404)
def test_get_zone_servers_from_secondary(self):
fixture = self.get_zone_fixture('SECONDARY', 0)
fixture['email'] = cfg.CONF['service:central'].managed_resource_email
zone = self.create_zone(**fixture)
self.get('domains/%s/servers' % zone.id, status_code=404)

View File

@ -1,39 +0,0 @@
# coding=utf-8
# Copyright 2012 Managed I.T.
#
# Author: Kiall Mac Innes <kiall@managedit.ie>
#
# 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 oslo_log import log as logging
from designate.tests.test_api.test_v1 import ApiV1Test
LOG = logging.getLogger(__name__)
class ApiV1LimitsTest(ApiV1Test):
def test_get_limits_schema(self):
response = self.get('/schemas/limits')
self.assertIn('id', response.json)
self.assertIn('description', response.json)
self.assertIn('title', response.json)
self.assertIn('additionalProperties', response.json)
self.assertIn('properties', response.json)
def test_get_limits(self):
response = self.get('/limits')
self.assertIn('limits', response.json)
self.assertIn('absolute', response.json['limits'])
self.assertIn('maxDomains', response.json['limits']['absolute'])
self.assertIn('maxDomainRecords', response.json['limits']['absolute'])

View File

@ -1,862 +0,0 @@
# coding=utf-8
# Copyright 2012 Managed I.T.
#
# Author: Kiall Mac Innes <kiall@managedit.ie>
#
# 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 mock import patch
import oslo_messaging as messaging
from oslo_log import log as logging
from designate.central import service as central_service
from designate.tests.test_api.test_v1 import ApiV1Test
LOG = logging.getLogger(__name__)
class ApiV1RecordsTest(ApiV1Test):
def setUp(self):
super(ApiV1RecordsTest, self).setUp()
self.zone = self.create_zone()
self.recordset = self.create_recordset(self.zone, 'A')
def test_get_record_schema(self):
response = self.get('schemas/record')
self.assertIn('description', response.json)
self.assertIn('links', response.json)
self.assertIn('title', response.json)
self.assertIn('id', response.json)
self.assertIn('additionalProperties', response.json)
self.assertIn('properties', response.json)
self.assertIn('id', response.json['properties'])
self.assertIn('domain_id', response.json['properties'])
self.assertIn('type', response.json['properties'])
self.assertIn('data', response.json['properties'])
self.assertIn('priority', response.json['properties'])
self.assertIn('description', response.json['properties'])
self.assertIn('created_at', response.json['properties'])
self.assertIn('updated_at', response.json['properties'])
self.assertIn('name', response.json['properties'])
self.assertIn('ttl', response.json['properties'])
self.assertIn('oneOf', response.json)
def test_get_records_schema(self):
response = self.get('schemas/records')
self.assertIn('description', response.json)
self.assertIn('additionalProperties', response.json)
self.assertIn('properties', response.json)
self.assertIn('title', response.json)
self.assertIn('id', response.json)
def test_create_record(self):
recordset_fixture = self.get_recordset_fixture(
self.zone['name'])
fixture = self.get_record_fixture(recordset_fixture['type'])
fixture.update({
'name': recordset_fixture['name'],
'type': recordset_fixture['type'],
})
# Create a record
response = self.post('domains/%s/records' % self.zone['id'],
data=fixture)
self.assertIn('id', response.json)
self.assertIn('name', response.json)
self.assertEqual(response.json['name'], fixture['name'])
def test_create_record_existing_recordset(self):
fixture = self.get_record_fixture(self.recordset['type'])
fixture.update({
'name': self.recordset['name'],
'type': self.recordset['type'],
})
# Create a record
response = self.post('domains/%s/records' % self.zone['id'],
data=fixture)
self.assertIn('id', response.json)
self.assertIn('name', response.json)
self.assertEqual(response.json['name'], fixture['name'])
def test_create_record_name_reuse(self):
fixture_1 = self.get_record_fixture(self.recordset['type'])
fixture_1.update({
'name': self.recordset['name'],
'type': self.recordset['type'],
})
fixture_2 = self.get_record_fixture(self.recordset['type'], fixture=1)
fixture_2.update({
'name': self.recordset['name'],
'type': self.recordset['type'],
})
# Create 2 records
record_1 = self.post('domains/%s/records' % self.zone['id'],
data=fixture_1)
record_2 = self.post('domains/%s/records' % self.zone['id'],
data=fixture_2)
# Delete record 1, this should not have any side effects
self.delete('domains/%s/records/%s' % (self.zone['id'],
record_1.json['id']))
# Simulate the record 1 having been deleted on the backend
zone_serial = self.central_service.get_zone(
self.admin_context, self.zone['id']).serial
self.central_service.update_status(
self.admin_context, self.zone['id'], "SUCCESS", zone_serial)
# Get the record 2 to ensure recordset did not get deleted
rec_2_get_response = self.get('domains/%s/records/%s' %
(self.zone['id'], record_2.json['id']))
self.assertIn('id', rec_2_get_response.json)
self.assertIn('name', rec_2_get_response.json)
self.assertEqual(rec_2_get_response.json['name'], fixture_1['name'])
# Delete record 2, this should delete the null recordset too
self.delete('domains/%s/records/%s' % (self.zone['id'],
record_2.json['id']))
# Simulate the record 2 having been deleted on the backend
zone_serial = self.central_service.get_zone(
self.admin_context, self.zone['id']).serial
self.central_service.update_status(
self.admin_context, self.zone['id'], "SUCCESS", zone_serial)
# Re-create as a different type, but use the same name
fixture = self.get_record_fixture('CNAME')
fixture.update({
'name': self.recordset['name'],
'type': 'CNAME'
})
response = self.post('domains/%s/records' % self.zone['id'],
data=fixture)
self.assertIn('id', response.json)
self.assertIn('name', response.json)
self.assertEqual(response.json['name'], fixture['name'])
def test_create_record_junk(self):
fixture = self.get_record_fixture(self.recordset['type'])
fixture.update({
'name': self.recordset['name'],
'type': self.recordset['type'],
})
# Add a junk property
fixture['junk'] = 'Junk Field'
# Create a record, ensuring it fails with a 400
self.post('domains/%s/records' % self.zone['id'], data=fixture,
status_code=400)
def test_create_wildcard_record_after_named(self):
# We want to test that a wildcard record rs does not use the
# previous one
# https://bugs.launchpad.net/designate/+bug/1391426
name = "foo.%s" % self.zone.name
fixture = {
"name": name,
"type": "A",
"data": "10.0.0.1"
}
self.post('domains/%s/records' % self.zone['id'],
data=fixture)
wildcard_name = '*.%s' % self.zone["name"]
fixture['name'] = wildcard_name
self.post('domains/%s/records' % self.zone['id'],
data=fixture)
named_rs = self.central_service.find_recordset(
self.admin_context, {"name": name})
wildcard_rs = self.central_service.find_recordset(
self.admin_context, {"name": wildcard_name})
self.assertNotEqual(named_rs.name, wildcard_rs.name)
self.assertNotEqual(named_rs.id, wildcard_rs.id)
def test_create_record_utf_description(self):
fixture = self.get_record_fixture(self.recordset['type'])
fixture.update({
'name': self.recordset['name'],
'type': self.recordset['type'],
})
# Add a UTF-8 riddled description
fixture['description'] = "utf-8:2H₂+O₂⇌2H₂O,R=4.7kΩ,⌀200mm∮E⋅da=Q,n" \
",∑f(i)=∏g(i),∀x∈:⌈x⌉"
# Create a record, ensuring it succeeds
self.post('domains/%s/records' % self.zone['id'], data=fixture)
def test_create_record_description_too_long(self):
fixture = self.get_record_fixture(self.recordset['type'])
fixture.update({
'name': self.recordset['name'],
'type': self.recordset['type'],
})
# Add a description that is too long
fixture['description'] = "x" * 161
# Create a record, ensuring it fails with a 400
self.post('domains/%s/records' % self.zone['id'], data=fixture,
status_code=400)
def test_create_record_name_too_long(self):
fixture = self.get_record_fixture(self.recordset['type'])
fixture.update({'type': self.recordset['type']})
fixture['name'] = 'w' * 255 + ".%s" % self.zone.name
self.post('domains/%s/records' % self.zone['id'], data=fixture,
status_code=400)
def test_create_record_name_is_missing(self):
fixture = self.get_record_fixture(self.recordset['type'])
fixture.update({'type': self.recordset['type']})
self.post('domains/%s/records' % self.zone['id'], data=fixture,
status_code=400)
def test_create_record_type_is_missing(self):
fixture = self.get_record_fixture(self.recordset['type'])
fixture['name'] = "www.%s" % self.zone.name
self.post('domains/%s/records' % self.zone['id'], data=fixture,
status_code=400)
def test_create_record_invalid_type(self):
fixture = self.get_record_fixture(self.recordset['type'])
fixture.update({'type': "ABC", 'name': self.recordset['name']})
self.post('domains/%s/records' % self.zone['id'], data=fixture,
status_code=400)
def test_create_record_data_is_missing(self):
fixture = self.get_record_fixture(self.recordset['type'])
fixture.update({'type': self.recordset['type'],
'name': self.recordset['name']})
del fixture['data']
self.post('domains/%s/records' % self.zone['id'], data=fixture,
status_code=400)
def test_create_record_ttl_greater_than_max(self):
fixture = self.get_record_fixture(self.recordset['type'])
fixture.update({
'name': self.recordset['name'],
'type': self.recordset['type'],
})
fixture['ttl'] = 2174483648
self.post('domains/%s/records' % self.zone['id'], data=fixture,
status_code=400)
def test_create_record_negative_ttl(self):
fixture = self.get_record_fixture(self.recordset['type'])
fixture.update({
'name': self.recordset['name'],
'type': self.recordset['type'],
})
# Set the TTL to a negative value
fixture['ttl'] = -1
# Create a record, ensuring it fails with a 400
self.post('domains/%s/records' % self.zone['id'], data=fixture,
status_code=400)
def test_create_record_zero_ttl(self):
fixture = self.get_record_fixture(self.recordset['type'])
fixture.update({
'name': self.recordset['name'],
'type': self.recordset['type'],
})
# Set the TTL to a value zero
fixture['ttl'] = 0
# Create a record, ensuring it fails with a 400
self.post('domains/%s/records' % self.zone['id'], data=fixture,
status_code=400)
def test_create_record_invalid_ttl(self):
fixture = self.get_record_fixture(self.recordset['type'])
fixture.update({
'name': self.recordset['name'],
'type': self.recordset['type'],
})
# Set the TTL to an invalid value
fixture['ttl'] = "$?!."
# Create a record, ensuring it fails with a 400
self.post('domains/%s/records' % self.zone['id'], data=fixture,
status_code=400)
def test_create_record_invalid_priority(self):
fixture = self.get_record_fixture(self.recordset['type'])
fixture.update({
'name': self.recordset['name'],
'type': self.recordset['type'],
})
fixture['priority'] = "$?!."
self.post('domains/%s/records' % self.zone['id'], data=fixture,
status_code=400)
def test_create_record_negative_priority(self):
fixture = self.get_record_fixture(self.recordset['type'])
fixture.update({
'name': self.recordset['name'],
'type': self.recordset['type'],
})
fixture['priority'] = -1
self.post('domains/%s/records' % self.zone['id'], data=fixture,
status_code=400)
def test_create_record_priority_greater_than_max(self):
fixture = self.get_record_fixture(self.recordset['type'])
fixture.update({
'name': self.recordset['name'],
'type': self.recordset['type'],
})
fixture['priority'] = 65536
self.post('domains/%s/records' % self.zone['id'], data=fixture,
status_code=400)
@patch.object(central_service.Service, 'create_record',
side_effect=messaging.MessagingTimeout())
def test_create_record_timeout(self, _):
fixture = self.get_record_fixture(self.recordset['type'])
fixture.update({
'name': self.recordset['name'],
'type': self.recordset['type'],
})
# Create a record
self.post('domains/%s/records' % self.zone['id'], data=fixture,
status_code=504)
def test_create_wildcard_record(self):
# Prepare a record
fixture = self.get_record_fixture(self.recordset['type'])
fixture.update({
'name': '*.%s' % self.recordset['name'],
'type': self.recordset['type'],
})
# Create a record
response = self.post('domains/%s/records' % self.zone['id'],
data=fixture)
self.assertIn('id', response.json)
self.assertIn('name', response.json)
self.assertEqual(response.json['name'], fixture['name'])
def test_create_srv_record(self):
recordset_fixture = self.get_recordset_fixture(
self.zone['name'], 'SRV')
fixture = self.get_record_fixture(recordset_fixture['type'])
priority, _, data = fixture['data'].partition(" ")
fixture.update({
'data': data,
'priority': int(priority),
'name': recordset_fixture['name'],
'type': recordset_fixture['type'],
})
# Create a record
response = self.post('domains/%s/records' % self.zone['id'],
data=fixture)
self.assertIn('id', response.json)
self.assertEqual(fixture['type'], response.json['type'])
self.assertEqual(fixture['name'], response.json['name'])
self.assertEqual(fixture['priority'], response.json['priority'])
self.assertEqual(fixture['data'], response.json['data'])
def test_create_invalid_data_srv_record(self):
recordset_fixture = self.get_recordset_fixture(
self.zone['name'], 'SRV')
fixture = self.get_record_fixture(recordset_fixture['type'])
fixture.update({
'name': recordset_fixture['name'],
'type': recordset_fixture['type'],
})
invalid_datas = [
'I 5060 sip.%s' % self.zone['name'],
'5060 sip.%s' % self.zone['name'],
'5060 I sip.%s' % self.zone['name'],
'0 5060 sip',
'sip',
'sip.%s' % self.zone['name'],
]
for invalid_data in invalid_datas:
fixture['data'] = invalid_data
# Attempt to create the record
self.post('domains/%s/records' % self.zone['id'], data=fixture,
status_code=400)
def test_create_invalid_name_srv_record(self):
recordset_fixture = self.get_recordset_fixture(
self.zone['name'], 'SRV')
fixture = self.get_record_fixture(recordset_fixture['type'])
fixture.update({
'name': recordset_fixture['name'],
'type': recordset_fixture['type'],
})
invalid_names = [
'%s' % self.zone['name'],
'_udp.%s' % self.zone['name'],
'sip._udp.%s' % self.zone['name'],
'_sip.udp.%s' % self.zone['name'],
]
for invalid_name in invalid_names:
fixture['name'] = invalid_name
# Attempt to create the record
self.post('domains/%s/records' % self.zone['id'], data=fixture,
status_code=400)
def test_create_invalid_name(self):
# Prepare a record
fixture = self.get_record_fixture(self.recordset['type'])
fixture.update({
'name': self.recordset['name'],
'type': self.recordset['type'],
})
invalid_names = [
'org',
'example.org',
'$$.example.org',
'*example.org.',
'*.*.example.org.',
'abc.*.example.org.',
]
for invalid_name in invalid_names:
fixture['name'] = invalid_name
# Create a record
response = self.post('domains/%s/records' % self.zone['id'],
data=fixture, status_code=400)
self.assertNotIn('id', response.json)
def test_get_records(self):
response = self.get('domains/%s/records' % self.zone['id'])
# Verify that the SOA & NS records are already created
self.assertIn('records', response.json)
self.assertEqual(2, len(response.json['records']))
# Create a record
self.create_record(self.zone, self.recordset)
response = self.get('domains/%s/records' % self.zone['id'])
# Verify that one more record has been added
self.assertIn('records', response.json)
self.assertEqual(3, len(response.json['records']))
# Create a second record
self.create_record(self.zone, self.recordset, fixture=1)
response = self.get('domains/%s/records' % self.zone['id'])
# Verfiy that all 4 records are there
self.assertIn('records', response.json)
self.assertEqual(4, len(response.json['records']))
@patch.object(central_service.Service, 'get_zone',
side_effect=messaging.MessagingTimeout())
def test_get_records_timeout(self, _):
self.get('domains/%s/records' % self.zone['id'],
status_code=504)
def test_get_records_missing_zone(self):
self.get('domains/2fdadfb1-cf96-4259-ac6b-bb7b6d2ff980/records',
status_code=404)
def test_get_records_invalid_zone_id(self):
self.get('domains/2fdadfb1cf964259ac6bbb7b6d2ff980/records',
status_code=404)
def test_get_record_missing(self):
self.get('domains/%s/records/2fdadfb1-cf96-4259-ac6b-'
'bb7b6d2ff980' % self.zone['id'],
status_code=404)
def test_get_record_with_invalid_id(self):
self.get('domains/%s/records/2fdadfb1-cf96-4259-ac6b-'
'bb7b6d2ff980GH' % self.zone['id'],
status_code=404)
def test_get_record(self):
# Create a record
record = self.create_record(self.zone, self.recordset)
response = self.get('domains/%s/records/%s' % (self.zone['id'],
record['id']))
self.assertIn('id', response.json)
self.assertEqual(response.json['id'], record['id'])
self.assertEqual(response.json['name'], self.recordset['name'])
self.assertEqual(response.json['type'], self.recordset['type'])
def test_update_record(self):
# Create a record
record = self.create_record(self.zone, self.recordset)
# Fetch another fixture to use in the update
fixture = self.get_record_fixture(self.recordset['type'], fixture=1)
# Update the record
data = {'data': fixture['data']}
response = self.put('domains/%s/records/%s' % (self.zone['id'],
record['id']),
data=data)
self.assertIn('id', response.json)
self.assertEqual(response.json['id'], record['id'])
self.assertEqual(response.json['data'], fixture['data'])
self.assertEqual(response.json['type'], self.recordset['type'])
def test_update_record_ttl(self):
# Create a record
record = self.create_record(self.zone, self.recordset)
# Update the record
data = {'ttl': 100}
response = self.put('domains/%s/records/%s' % (self.zone['id'],
record['id']),
data=data)
self.assertIn('id', response.json)
self.assertEqual(record['id'], response.json['id'])
self.assertEqual(record['data'], response.json['data'])
self.assertEqual(self.recordset['type'], response.json['type'])
self.assertEqual(100, response.json['ttl'])
def test_update_record_junk(self):
# Create a record
record = self.create_record(self.zone, self.recordset)
data = {'ttl': 100, 'junk': 'Junk Field'}
self.put('domains/%s/records/%s' % (self.zone['id'], record['id']),
data=data, status_code=400)
def test_update_record_negative_ttl(self):
# Create a record
record = self.create_record(self.zone, self.recordset)
data = {'ttl': -1}
self.put('domains/%s/records/%s' % (self.zone['id'], record['id']),
data=data, status_code=400)
def test_update_record_ttl_greater_than_max(self):
record = self.create_record(self.zone, self.recordset)
data = {'ttl': 2174483648}
self.put('domains/%s/records/%s' % (self.zone['id'], record['id']),
data=data, status_code=400)
def test_update_record_zero_ttl(self):
# Create a record
record = self.create_record(self.zone, self.recordset)
data = {'ttl': 0}
self.put('domains/%s/records/%s' % (self.zone['id'], record['id']),
data=data, status_code=400)
def test_update_record_invalid_ttl(self):
# Create a record
record = self.create_record(self.zone, self.recordset)
data = {'ttl': "$?>%"}
self.put('domains/%s/records/%s' % (self.zone['id'], record['id']),
data=data, status_code=400)
def test_update_record_description_too_long(self):
record = self.create_record(self.zone, self.recordset)
data = {'description': 'x' * 165}
self.put('domains/%s/records/%s' % (self.zone['id'], record['id']),
data=data, status_code=400)
def test_update_record_negative_priority(self):
record = self.create_record(self.zone, self.recordset)
data = {'priority': -1}
self.put('domains/%s/records/%s' % (self.zone['id'], record['id']),
data=data, status_code=400)
def test_update_record_invalid_priority(self):
record = self.create_record(self.zone, self.recordset)
data = {'priority': "?!:>"}
self.put('domains/%s/records/%s' % (self.zone['id'], record['id']),
data=data, status_code=400)
def test_update_record_priority_greater_than_max(self):
record = self.create_record(self.zone, self.recordset)
data = {'priority': 65536}
self.put('domains/%s/records/%s' % (self.zone['id'], record['id']),
data=data, status_code=400)
def test_update_record_name_too_long(self):
record = self.create_record(self.zone, self.recordset)
data = {'name': 'w' * 256 + ".%s" % self.zone.name}
self.put('domains/%s/records/%s' % (self.zone['id'], record['id']),
data=data, status_code=400)
def test_update_record_invalid_type(self):
record = self.create_record(self.zone, self.recordset)
data = {'type': 'ABC'}
self.put('domains/%s/records/%s' % (self.zone['id'], record['id']),
data=data, status_code=400)
def test_update_record_data_too_long(self):
record = self.create_record(self.zone, self.recordset)
data = {'data': '1' * 255 + '.2.3.4'}
self.put('domains/%s/records/%s' % (self.zone['id'], record['id']),
data=data, status_code=400)
def test_update_record_outside_zone_fail(self):
# Create a record
record = self.create_record(self.zone, self.recordset)
data = {'name': 'test.someotherzone.com.'}
self.put('domains/%s/records/%s' % (self.zone['id'], record['id']),
data=data, status_code=400)
@patch.object(central_service.Service, 'find_zone',
side_effect=messaging.MessagingTimeout())
def test_update_record_timeout(self, _):
# Create a record
record = self.create_record(self.zone, self.recordset)
data = {'name': 'test.example.org.'}
self.put('domains/%s/records/%s' % (self.zone['id'], record['id']),
data=data, status_code=504)
def test_update_record_missing(self):
data = {'name': 'test.example.org.'}
self.put('domains/%s/records/2fdadfb1-cf96-4259-ac6b-'
'bb7b6d2ff980' % self.zone['id'],
data=data,
status_code=404)
def test_update_record_invalid_id(self):
data = {'name': 'test.example.org.'}
self.put('domains/%s/records/2fdadfb1cf964259ac6bbb7b6d2ff980' %
self.zone['id'],
data=data,
status_code=404)
def test_update_record_missing_zone(self):
data = {'name': 'test.example.org.'}
self.put('domains/2fdadfb1-cf96-4259-ac6b-bb7b6d2ff980/records/'
'2fdadfb1-cf96-4259-ac6b-bb7b6d2ff980',
data=data,
status_code=404)
def test_update_record_invalid_zone_id(self):
data = {'name': 'test.example.org.'}
self.put('domains/2fdadfb1cf964259ac6bbb7b6d2ff980/records/'
'2fdadfb1-cf96-4259-ac6b-bb7b6d2ff980',
data=data,
status_code=404)
def test_delete_record(self):
# Create a record
record = self.create_record(self.zone, self.recordset)
self.delete('domains/%s/records/%s' % (self.zone['id'],
record['id']))
# Simulate the record having been deleted on the backend
zone_serial = self.central_service.get_zone(
self.admin_context, self.zone['id']).serial
self.central_service.update_status(
self.admin_context, self.zone['id'], "SUCCESS", zone_serial)
# Ensure we can no longer fetch the record
self.get('domains/%s/records/%s' % (self.zone['id'],
record['id']),
status_code=404)
@patch.object(central_service.Service, 'find_zone',
side_effect=messaging.MessagingTimeout())
def test_delete_record_timeout(self, _):
# Create a record
record = self.create_record(self.zone, self.recordset)
self.delete('domains/%s/records/%s' % (self.zone['id'],
record['id']),
status_code=504)
def test_delete_record_missing(self):
self.delete('domains/%s/records/2fdadfb1-cf96-4259-ac6b-'
'bb7b6d2ff980' % self.zone['id'],
status_code=404)
def test_delete_record_missing_zone(self):
self.delete('domains/2fdadfb1-cf96-4259-ac6b-bb7b6d2ff980/records/'
'2fdadfb1-cf96-4259-ac6b-bb7b6d2ff980',
status_code=404)
def test_delete_record_invalid_zone_id(self):
self.delete('domains/2fdadfb1cf964259ac6bbb7b6d2ff980/records/'
'2fdadfb1-cf96-4259-ac6b-bb7b6d2ff980',
status_code=404)
def test_delete_record_invalid_id(self):
self.delete('domains/%s/records/2fdadfb1-cf96-4259-ac6b-'
'bb7b6d2ff980GH' % self.zone['id'],
status_code=404)
def test_get_record_in_secondary(self):
fixture = self.get_zone_fixture('SECONDARY', 1)
fixture['email'] = "root@example.com"
zone = self.create_zone(**fixture)
record = self.create_record(zone, self.recordset)
url = 'zones/%s/records/%s' % (zone.id, record.id)
self.get(url, status_code=404)
def test_create_record_in_secondary(self):
fixture = self.get_zone_fixture('SECONDARY', 1)
fixture['email'] = "root@example.com"
zone = self.create_zone(**fixture)
record = {
"name": "foo.%s" % zone.name,
"type": "A",
"data": "10.0.0.1"
}
url = 'zones/%s/records' % zone.id
self.post(url, record, status_code=404)
def test_update_record_in_secondary(self):
fixture = self.get_zone_fixture('SECONDARY', 1)
fixture['email'] = "root@example.com"
zone = self.create_zone(**fixture)
record = self.create_record(zone, self.recordset)
url = 'zones/%s/records/%s' % (zone.id, record.id)
self.put(url, {"data": "10.0.0.1"}, status_code=404)
def test_delete_record_in_secondary(self):
fixture = self.get_zone_fixture('SECONDARY', 1)
fixture['email'] = "root@example.com"
zone = self.create_zone(**fixture)
record = self.create_record(zone, self.recordset)
url = 'zones/%s/records/%s' % (zone.id, record.id)
self.delete(url, status_code=404)
def test_create_record_deleting_zone(self):
recordset_fixture = self.get_recordset_fixture(
self.zone['name'])
fixture = self.get_record_fixture(recordset_fixture['type'])
fixture.update({
'name': recordset_fixture['name'],
'type': recordset_fixture['type'],
})
self.delete('/domains/%s' % self.zone['id'])
self.post('domains/%s/records' % self.zone['id'],
data=fixture, status_code=404)
def test_update_record_deleting_zone(self):
# Create a record
record = self.create_record(self.zone, self.recordset)
# Fetch another fixture to use in the update
fixture = self.get_record_fixture(self.recordset['type'], fixture=1)
# Update the record
data = {'data': fixture['data']}
self.delete('/domains/%s' % self.zone['id'])
self.put('domains/%s/records/%s' % (self.zone['id'],
record['id']),
data=data, status_code=404)
def test_delete_record_deleting_zone(self):
# Create a record
record = self.create_record(self.zone, self.recordset)
self.delete('/domains/%s' % self.zone['id'])
self.delete('domains/%s/records/%s' % (self.zone['id'],
record['id']),
status_code=404)
class ApiV1TxtRecordsTest(ApiV1Test):
def setUp(self):
super(ApiV1TxtRecordsTest, self).setUp()
self.zone = self.create_zone()
self.recordset = self.create_recordset(self.zone, 'TXT')
def test_create_txt_record(self):
# See bug #1474012
record = self.create_record(self.zone, self.recordset)
data = {'data': 'a' * 255}
self.put(
'domains/%s/records/%s' % (self.zone['id'], record['id']),
data=data
)
def test_create_txt_record_too_long(self):
# See bug #1474012
record = self.create_record(self.zone, self.recordset)
data = {'data': 'a' * 256}
self.put(
'domains/%s/records/%s' % (self.zone['id'], record['id']),
data=data,
status_code=400
)

View File

@ -1,244 +0,0 @@
# Copyright 2012 Managed I.T.
#
# Author: Kiall Mac Innes <kiall@managedit.ie>
#
# 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 mock import patch
import oslo_messaging as messaging
from oslo_config import cfg
from oslo_log import log as logging
from designate import exceptions
from designate import objects
from designate.central import service as central_service
from designate.tests.test_api.test_v1 import ApiV1Test
cfg.CONF.import_opt('default_pool_id',
'designate.central',
group='service:central')
default_pool_id = cfg.CONF['service:central'].default_pool_id
LOG = logging.getLogger(__name__)
class ApiV1ServersTest(ApiV1Test):
def setUp(self):
super(ApiV1ServersTest, self).setUp()
# All Server Checks should be performed as an admin, so..
# Override to policy to make everyone an admin.
self.policy({'admin': '@'})
def test_get_server_schema(self):
response = self.get('schemas/server')
self.assertIn('description', response.json)
self.assertIn('id', response.json)
self.assertIn('title', response.json)
self.assertIn('additionalProperties', response.json)
self.assertIn('properties', response.json)
self.assertIn('name', response.json['properties'])
self.assertIn('links', response.json)
self.assertIn('created_at', response.json['properties'])
self.assertIn('updated_at', response.json['properties'])
def test_get_servers_schema(self):
response = self.get('schemas/servers')
self.assertIn('description', response.json)
self.assertIn('id', response.json)
self.assertIn('title', response.json)
self.assertIn('additionalProperties', response.json)
self.assertIn('properties', response.json)
def test_create_server(self):
# Create a server
# In a base somewhere, we create the default / 0 server fixture
# automatically, so this would trigger a duplicate otherwise.
fixture = self.get_server_fixture(1)
response = self.post('servers', data=fixture)
self.assertIn('id', response.json)
self.assertIn('name', response.json)
self.assertEqual(response.json['name'], fixture['name'])
def test_create_server_junk(self):
# Create a server
fixture = self.get_server_fixture(0)
# Add a junk property
fixture['junk'] = 'Junk Field'
# Ensure it fails with a 400
self.post('servers', data=fixture, status_code=400)
def test_create_server_with_invalid_name(self):
# Create a server
fixture = self.get_server_fixture(0)
# Add an invalid name
fixture['name'] = '$#$%^^'
# Ensure it fails with a 400
self.post('servers', data=fixture, status_code=400)
def test_create_server_name_missing(self):
fixture = self.get_server_fixture(0)
del fixture['name']
self.post('servers', data=fixture, status_code=400)
def test_create_server_name_too_long(self):
fixture = self.get_server_fixture(0)
fixture['name'] = 'a' * 255 + '.example.org.'
self.post('servers', data=fixture, status_code=400)
@patch.object(central_service.Service, 'update_pool',
side_effect=messaging.MessagingTimeout())
def test_create_server_timeout(self, _):
# Create a server
fixture = self.get_server_fixture(0)
self.post('servers', data=fixture, status_code=504)
@patch.object(central_service.Service, 'update_pool',
side_effect=exceptions.DuplicateServer())
def test_create_server_duplicate(self, _):
# Create a server
fixture = self.get_server_fixture(0)
self.post('servers', data=fixture, status_code=409)
def test_get_servers(self):
# Fetch the default pool
pool = self.storage.get_pool(self.admin_context, default_pool_id)
# Fetch the list of servers
response = self.get('servers')
self.assertIn('servers', response.json)
self.assertEqual(len(pool.ns_records), len(response.json['servers']))
# Add a new NS record to the pool
pool.ns_records.append(
objects.PoolNsRecord(priority=1, hostname='new-ns1.example.org.'))
# Save the pool to add a new nameserver
self.storage.update_pool(self.admin_context, pool)
# Fetch the list of servers
response = self.get('servers')
self.assertIn('servers', response.json)
self.assertEqual(len(pool.ns_records), len(response.json['servers']))
# Add a new NS record to the pool
pool.ns_records.append(
objects.PoolNsRecord(priority=1, hostname='new-ns2.example.org.'))
# Save the pool to add a new nameserver
self.storage.update_pool(self.admin_context, pool)
response = self.get('servers')
self.assertIn('servers', response.json)
self.assertEqual(len(pool.ns_records), len(response.json['servers']))
@patch.object(central_service.Service, 'get_pool',
side_effect=messaging.MessagingTimeout())
def test_get_servers_timeout(self, _):
self.get('servers', status_code=504)
def test_get_server(self):
# Fetch the default pool
pool = self.storage.get_pool(self.admin_context, default_pool_id)
# Fetch the Server from the pool
response = self.get('servers/%s' % pool.ns_records[0].id)
self.assertIn('id', response.json)
self.assertEqual(response.json['id'], pool.ns_records[0]['id'])
@patch.object(central_service.Service, 'get_pool',
side_effect=messaging.MessagingTimeout())
def test_get_server_timeout(self, _):
# Fetch the default pool
pool = self.storage.get_pool(self.admin_context, default_pool_id)
self.get('servers/%s' % pool.ns_records[0].id, status_code=504)
def test_get_server_with_invalid_id(self):
self.get('servers/2fdadfb1-cf96-4259-ac6b-bb7b6d2ff98GH',
status_code=404)
def test_get_server_missing(self):
self.get('servers/2fdadfb1-cf96-4259-ac6b-bb7b6d2ff980',
status_code=404)
def test_update_server(self):
# Fetch the default pool
pool = self.storage.get_pool(self.admin_context, default_pool_id)
data = {'name': 'new-ns1.example.org.'}
response = self.put('servers/%s' % pool.ns_records[0].id,
data=data)
self.assertIn('id', response.json)
self.assertEqual(response.json['id'], pool.ns_records[0].id)
self.assertIn('name', response.json)
self.assertEqual(response.json['name'], 'new-ns1.example.org.')
def test_update_server_missing(self):
data = {'name': 'test.example.org.'}
self.put('servers/2fdadfb1-cf96-4259-ac6b-bb7b6d2ff980', data=data,
status_code=404)
def test_update_server_junk(self):
# Fetch the default pool
pool = self.storage.get_pool(self.admin_context, default_pool_id)
data = {'name': 'test.example.org.', 'junk': 'Junk Field'}
self.put('servers/%s' % pool.ns_records[0].id, data=data,
status_code=400)
def test_delete_server(self):
# Fetch the default pool
pool = self.storage.get_pool(self.admin_context, default_pool_id)
# Create a second server so that we can delete the first
# because the last remaining server is not allowed to be deleted
# Add a new NS record to the pool
pool.ns_records.append(
objects.PoolNsRecord(priority=1, hostname='new-ns2.example.org.'))
# Save the pool to add a new nameserver
self.storage.update_pool(self.admin_context, pool)
# Now delete the server
self.delete('servers/%s' % pool.ns_records[1].id)
# Ensure we can no longer fetch the deleted server
self.get('servers/%s' % pool.ns_records[1].id, status_code=404)
# Also, verify we cannot delete last remaining server
self.delete('servers/%s' % pool.ns_records[0].id, status_code=400)
def test_delete_server_with_invalid_id(self):
self.delete('servers/9fdadfb1-cf96-4259-ac6b-bb7b6d2ff98GH',
status_code=404)
def test_delete_server_missing(self):
self.delete('servers/9fdadfb1-cf96-4259-ac6b-bb7b6d2ff980',
status_code=404)

View File

@ -1,136 +0,0 @@
# Copyright 2015 NEC Corporation. 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 mock import patch
import oslo_messaging as messaging
from oslo_log import log as logging
from designate.central import service as central_service
from designate.tests.test_api.test_v1 import ApiV1Test
LOG = logging.getLogger(__name__)
class ApiV1TsigkeysTest(ApiV1Test):
def setUp(self):
super(ApiV1TsigkeysTest, self).setUp()
# Set the policy to accept everyone as an admin, as this is an
# admin-only API
self.policy({'admin': '@'})
def test_get_tsigkey_schema(self):
response = self.get('schemas/tsigkey')
self.assertIn('description', response.json)
self.assertIn('links', response.json)
self.assertIn('title', response.json)
self.assertIn('id', response.json)
self.assertIn('additionalProperties', response.json)
self.assertIn('properties', response.json)
self.assertIn('name', response.json['properties'])
self.assertIn('algorithm', response.json['properties'])
self.assertIn('secret', response.json['properties'])
def test_get_tsigkeys_schema(self):
response = self.get('schemas/tsigkeys')
self.assertIn('description', response.json)
self.assertIn('title', response.json)
self.assertIn('id', response.json)
self.assertIn('additionalProperties', response.json)
self.assertIn('properties', response.json)
self.assertIn('tsigkeys', response.json['properties'])
def test_create_tsigkeys(self):
# Create a Tsigkey
fixture = self.get_tsigkey_fixture(0)
# V1 doesn't have these
del fixture['scope']
del fixture['resource_id']
response = self.post('tsigkeys', data=fixture)
self.assertIn('id', response.json)
self.assertIn('name', response.json)
self.assertEqual(response.json['name'], fixture['name'])
def test_create_tsigkeys_junk(self):
# Create a tsigkey
fixture = self.get_tsigkey_fixture(0)
# Add a junk property
fixture['junk'] = 'Junk Field'
# Ensure it fails with a 400
self.post('tsigkeys', data=fixture, status_code=400)
def test_create_tsigkey_name_missing(self):
# Create tsigkey
fixture = self.get_tsigkey_fixture(0)
del fixture['name']
self.post('tsigkeys', data=fixture, status_code=400)
def test_create_tsigkey_algorithm_missing(self):
# Create tsigkey
fixture = self.get_tsigkey_fixture(0)
del fixture['algorithm']
self.post('tsigkeys', data=fixture, status_code=400)
def test_create_tsigkey_secret_missing(self):
# Create tsigkey
fixture = self.get_tsigkey_fixture(0)
del fixture['secret']
self.post('tsigkeys', data=fixture, status_code=400)
def test_create_tsigkey_name_too_long(self):
# Create a tsigkey
fixture = self.get_tsigkey_fixture(0)
fixture['name'] = 'x' * 300
self.post('tsigkeys', data=fixture, status_code=400)
def test_create_tsigkey_secret_too_long(self):
# Create a tsigkey
fixture = self.get_tsigkey_fixture(0)
fixture['secret'] = 'x' * 300
self.post('tsigkeys', data=fixture, status_code=400)
def test_delete_tsigkey(self):
# Delete a tsigkey
tsigkey = self.create_tsigkey()
self.delete('/tsigkeys/%s' % tsigkey['id'], status=200)
@patch.object(central_service.Service, 'find_tsigkeys',
side_effect=messaging.MessagingTimeout())
def test_get_tsigkeys_timeout(self, _):
self.get('tsigkeys', status_code=504)
@patch.object(central_service.Service, 'find_tsigkeys',
side_effect=messaging.MessagingTimeout())
def test_get_tsigkey_timeout(self, _):
# Create a tsigkey
tsigkey = self.create_tsigkey()
self.get('tsigkeys/%s' % tsigkey['id'], status_code=504)

View File

@ -19,6 +19,6 @@ from designate import schema
class TestSchema(TestCase):
def test_constructor(self):
zone = schema.Schema('v1', 'domain')
quota = schema.Schema('admin', 'quota')
self.assertIsInstance(zone, schema.Schema)
self.assertIsInstance(quota, schema.Schema)

View File

@ -47,11 +47,6 @@ class TestUtils(TestCase):
with testtools.ExpectedException(ValueError):
utils.resource_string()
def test_load_schema(self):
schema = utils.load_schema('v1', 'domain')
self.assertIsInstance(schema, dict)
def test_load_schema_missing(self):
with testtools.ExpectedException(exceptions.ResourceNotFound):
utils.load_schema('v1', 'missing')

View File

@ -15,7 +15,7 @@
# How many seconds to wait for the API to be responding before giving up
API_RESPONDING_TIMEOUT=20
if ! timeout ${API_RESPONDING_TIMEOUT} sh -c "while ! curl -s http://127.0.0.1:9001/ 2>/dev/null | grep -q 'v1' ; do sleep 1; done"; then
if ! timeout ${API_RESPONDING_TIMEOUT} sh -c "while ! curl -s http://127.0.0.1:9001/ 2>/dev/null | grep -q 'v2' ; do sleep 1; done"; then
echo "The Designate API failed to respond within ${API_RESPONDING_TIMEOUT} seconds"
exit 1
fi

View File

@ -83,11 +83,9 @@ function configure_designate {
# API Configuration
sudo cp $DESIGNATE_DIR/etc/designate/api-paste.ini $DESIGNATE_APIPASTE_CONF
iniset $DESIGNATE_CONF service:api enabled_extensions_v1 $DESIGNATE_ENABLED_EXTENSIONS_V1
iniset $DESIGNATE_CONF service:api enabled_extensions_v2 $DESIGNATE_ENABLED_EXTENSIONS_V2
iniset $DESIGNATE_CONF service:api enabled_extensions_admin $DESIGNATE_ENABLED_EXTENSIONS_ADMIN
iniset $DESIGNATE_CONF service:api api_base_uri $DESIGNATE_SERVICE_PROTOCOL://$DESIGNATE_SERVICE_HOST:$DESIGNATE_SERVICE_PORT/
iniset $DESIGNATE_CONF service:api enable_api_v1 $DESIGNATE_ENABLE_API_V1
iniset $DESIGNATE_CONF service:api enable_api_v2 $DESIGNATE_ENABLE_API_V2
iniset $DESIGNATE_CONF service:api enable_api_admin $DESIGNATE_ENABLE_API_ADMIN
@ -163,7 +161,6 @@ function configure_designate_tempest() {
iniset $TEMPEST_CONFIG service_available designate True
# Tell tempest which APIs are available
iniset $TEMPEST_CONFIG dns_feature_enabled api_v1 $DESIGNATE_ENABLE_API_V1
iniset $TEMPEST_CONFIG dns_feature_enabled api_v2 $DESIGNATE_ENABLE_API_V2
iniset $TEMPEST_CONFIG dns_feature_enabled api_admin $DESIGNATE_ENABLE_API_ADMIN
iniset $TEMPEST_CONFIG dns_feature_enabled api_v2_root_recordsets True

View File

@ -20,10 +20,8 @@ DESIGNATE_QUOTA_RECORDSET_RECORDS=${DESIGNATE_QUOTA_RECORDSET_RECORDS:-20}
DESIGNATE_QUOTA_API_EXPORT_SIZE=${DESIGNATE_QUOTA_API_EXPORT_SIZE:-1000}
# Default APIs and Extensions
DESIGNATE_ENABLE_API_V1=${DESIGNATE_ENABLE_API_V1:-"True"}
DESIGNATE_ENABLE_API_V2=${DESIGNATE_ENABLE_API_V2:-"True"}
DESIGNATE_ENABLE_API_ADMIN=${DESIGNATE_ENABLE_API_ADMIN:-"True"}
DESIGNATE_ENABLED_EXTENSIONS_V1=${DESIGNATE_ENABLED_EXTENSIONS_V1:-"quotas"}
DESIGNATE_ENABLED_EXTENSIONS_V2=${DESIGNATE_ENABLED_EXTENSIONS_V2:-""}
DESIGNATE_ENABLED_EXTENSIONS_ADMIN=${DESIGNATE_ENABLED_EXTENSIONS_ADMIN:-"quotas"}

View File

@ -28,17 +28,6 @@ directory.
.. index::
double: configure; designate
#. Create the designate.conf file
::
$ editor designate.conf
#. Copy or mirror the configuration from this sample file here:
.. literalinclude:: ../examples/basic-config-sample.conf
:language: ini
#. You can generate full sample *designate.conf* (if it does not already exist)::
$ oslo-config-generator --config-file etc/designate/designate-config-generator.conf --output-file /etc/designate/designate.conf

View File

@ -1,130 +0,0 @@
[DEFAULT]
########################
## General Configuration
########################
# Show debugging output in logs (sets DEBUG log level output)
debug = True
# Top-level directory for maintaining designate's state.
state_path = $pybasedir/state
# Use "sudo designate-rootwrap /etc/designate/rootwrap.conf" to use the real
# root filter facility.
# Change to "sudo" to skip the filtering and just run the command directly
# root_helper = sudo
# Supported record types
#supported_record_type = A, AAAA, CNAME, MX, SRV, TXT, SPF, NS, PTR, SSHFP, SOA
# RabbitMQ Config
transport_url = rabbit://designate:designate@127.0.0.1//
[oslo_messaging_notifications]
# Driver used for issuing notifications
driver = messaging
########################
## Service Configuration
########################
#-----------------------
# Central Service
#-----------------------
[service:central]
# Maximum domain name length
#max_domain_name_len = 255
# Maximum record name length
#max_record_name_len = 255
#-----------------------
# API Service
#-----------------------
[service:api]
# API host:port pairs to listen on
listen = 0.0.0.0:9001
# Authentication strategy to use - can be either "noauth" or "keystone"
auth_strategy = noauth
# Enable API Version 1
enable_api_v1 = True
# Enabled API Version 1 extensions
enabled_extensions_v1 = diagnostics, quotas, reports, sync, touch
# Enable API Version 2
enable_api_v2 = True
# Enabled API Version 2 extensions
enabled_extensions_v2 = quotas, reports
#-----------------------
# mDNS Service
#-----------------------
[service:mdns]
#workers = None
#host = 0.0.0.0
#port = 5354
#tcp_backlog = 100
#-----------------------
# Worker Service
#-----------------------
[service:worker]
# Whether to send events to worker instead of Pool Manager
enabled = True
#workers = None
#threads = 1000
#threshold_percentage = 100
#poll_timeout = 30
#poll_retry_interval = 15
#poll_max_retries = 10
#poll_delay = 5
#notify = True
#-----------------------
# Producer Service
#-----------------------
[service:producer]
#workers = None
#threads = 1000
#enabled_tasks = None
#export_synchronous = True
#------------------------
# Deleted domains purging
#------------------------
[producer_task:domain_purge]
#interval = 3600 # 1h
#batch_size = 100
#time_threshold = 604800 # 7 days
#------------------------
# Delayed zones NOTIFY
#------------------------
[producer_task:delayed_notify]
#interval = 5
#------------------------
# Worker Periodic Recovery
#------------------------
[producer_task:worker_periodic_recovery]
#interval = 120
########################
## Storage Configuration
########################
#-----------------------
# SQLAlchemy Storage
#-----------------------
[storage:sqlalchemy]
# Database connection string - to configure options for a given implementation
# like sqlalchemy or other see below
connection = mysql+pymysql://root:password@127.0.0.1/designate?charset=utf8
#connection_debug = 100
#connection_trace = True
#sqlite_synchronous = True
#idle_timeout = 3600
#max_retries = 10
#retry_interval = 10

View File

@ -93,9 +93,7 @@ Install and configure components
[service:api]
listen = 0.0.0.0:9001
auth_strategy = keystone
enable_api_v1 = True
api_base_uri = http://controller:9001/
enabled_extensions_v1 = quotas, reports
enable_api_v2 = True
enabled_extensions_v2 = quotas, reports

View File

@ -93,9 +93,7 @@ Install and configure components
[service:api]
listen = 0.0.0.0:9001
auth_strategy = keystone
enable_api_v1 = True
api_base_uri = http://controller:9001/
enabled_extensions_v1 = quotas, reports
enable_api_v2 = True
enabled_extensions_v2 = quotas, reports

View File

@ -86,9 +86,7 @@ Install and configure components
[service:api]
listen = 0.0.0.0:9001
auth_strategy = keystone
enable_api_v1 = True
api_base_uri = http://controller:9001/
enabled_extensions_v1 = quotas, reports
enable_api_v2 = True
enabled_extensions_v2 = quotas, reports

View File

@ -336,138 +336,3 @@ for more information.
Designate, the name of a record MUST be a complete host name.
.. _RFC 2317: https://tools.ietf.org/html/rfc2317
Using the V1 API
----------------
Using the V1 REST interface let's start by creating a domain.
.. code-block:: http
POST /v1/domains HTTP/1.1
Content-Type: application/json
{
"name": "example.com.",
"ttl": 3600,
"email": "admin@example.com"
}
This should return the JSON document describing the new domain.
.. code-block:: http
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 238
Location: http://127.0.0.1:9001/v1/domains/77c4f4aa-b8c9-4df5-af8e-b54e5fcadef7
X-Openstack-Request-Id: req-c3f8478d-1665-4b40-9545-9a856fac17ea
Date: Fri, 20 Feb 2015 19:35:37 GMT
Connection: keep-alive
{
"updated_at": null,
"ttl": 3600,
"serial": 1424460937,
"name": "example.com.",
"id": "77c4f4aa-b8c9-4df5-af8e-b54e5fcadef7",
"email": "admin@example.com",
"description": null,
"created_at": "2015-02-20T19:35:37.000000"
}
Now that we have a domain we want to return when we use our `PTR`
record, we'll create the `in-addr.arpa.` domain that will be used when
looking up the IP address.
Let's configure `192.0.2.10` to return our `example.com.` domain
name when we do a reverse look up.
.. code-block:: http
POST /v1/domains HTTP/1.1
Content-Type: application/json
{
"name": "10.2.0.192.in-addr.arpa.",
"ttl": 1200,
"email": "admin@thedns.com"
}
We should get a response like
.. code-block:: http
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 252
Location: http://127.0.0.1:9001/v1/domains/d098abaa-37e3-40e5-b7c5-3794b5a0ec32
X-Openstack-Request-Id: req-bc2b1796-bd11-47a9-bb06-fd6a870a4bc2
Date: Fri, 20 Feb 2015 19:43:15 GMT
Connection: keep-alive
{
"updated_at": null,
"ttl": 1200,
"serial": 1424461395,
"name": "10.2.0.192.in-addr.arpa.",
"id": "d098abaa-37e3-40e5-b7c5-3794b5a0ec32",
"email": "admin@thedns.com",
"description": null,
"created_at": "2015-02-20T19:43:15.000000"
}
We will use this `in-addr.arpa.` domain to create the actual `PTR`
record.
.. code-block:: http
POST /v1/domains/d098abaa-37e3-40e5-b7c5-3794b5a0ec32/records HTTP/1.1
Content-Type: application/json
{
"name": "10.2.0.192.in-addr.arpa.",
"type": "PTR",
"data": "example.com."
}
Here is the response.
.. code-block:: http
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 315
Location: http://127.0.0.1:9001/v1/domains/d098abaa-37e3-40e5-b7c5-3794b5a0ec32/records/0476ed89-9823-4f8e-a991-79422bc2e490
X-Openstack-Request-Id: req-36588ba6-e91a-4456-9706-8d156ea7cfd2
Date: Fri, 20 Feb 2015 19:48:01 GMT
Connection: keep-alive
{
"updated_at": null,
"type": "PTR",
"ttl": null,
"priority": null,
"name": "11.2.0.192.in-addr.arpa.",
"id": "0476ed89-9823-4f8e-a991-79422bc2e490",
"domain_id": "d098abaa-37e3-40e5-b7c5-3794b5a0ec32",
"description": null,
"data": "example.com.",
"created_at": "2015-02-20T19:48:01.000000"
}
We should now have a correct `PTR` record assigned in our nameserver
that we can test.
We'll use dig to make sure our reverse lookup is resolving correctly.
.. code-block:: bash
$ dig @localhost -x 192.0.2.10 +short
example.com.
It worked!

View File

@ -12,18 +12,13 @@ example:
.. code-block:: http
POST /v2/pools HTTP/1.1
POST /v2/zones HTTP/1.1
Accept: application/json
Content-Type: application/json
{
"name": "Example Pool",
"ns_records": [
{
"hostname": "ns1.example.org.",
"priority": 1
}
]
"name": "example.org.",
"email": "hostmaster@example.org"
}
With this info we can make this request using the cURL_ tool. We'll
@ -34,8 +29,8 @@ assume we are running Designate on `localhost`.
curl -X POST -i \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{"name": "ns1.example.org."}' \
http://localhost:9001/v1/servers
-d '{"name": "example.org.", "email": "hostmaster@example.org"}' \
http://localhost:9001/v2/zones
The `-i` flag is used to dump the response headers as well as the
response body.
@ -64,24 +59,6 @@ These headers work for all APIs
API Versions
============
The API has 2 versions - V1 and V2.
.. note:: V1 has been deprecated since the Kilo release.
V1 API
------
.. toctree::
:maxdepth: 2
:glob:
rest/v1/servers
rest/v1/domains
rest/v1/records
rest/v1/diagnostics
rest/v1/quotas
rest/v1/reports
rest/v1/sync
V2 API
------

View File

@ -1,66 +0,0 @@
..
Copyright 2014 Hewlett-Packard Development Company, L.P.
Author: Endre Karlson <endre.karlson@hp.com>
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.
Diagnostics
===========
Overview
--------
*Note*: Diagnostics is an extension and needs to be enabled before it can be
used. If Designate returns a 404 error, ensure that the following line has been
added to the designate.conf file::
enabled_extensions_v1 = diagnostic, ...
Diagnose parts of the system.
Ping a host on a RPC topic
--------------------------
.. http:get:: /diagnostics/ping/(topic)/(host)
Ping a host on a RPC topic
**Example request**:
.. sourcecode:: http
GET /diagnostics/ping/agents/msdns-1 HTTP/1.1
Host: example.com
Accept: application/json
Content-Type: application/json
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"host": "rpc-hostname",
"status": true,
"backend": "msdns",
"storage": {"status": true, "message": "..."}
}
:statuscode 200: Success
:statuscode 401: Access Denied

View File

@ -1,293 +0,0 @@
Domains
=======
Domain entries are used to generate zones containing RR
TODO: More detail.
Create Domain
-------------
.. http:post:: /domains
Create a domain
**Example request**:
.. sourcecode:: http
POST /domains HTTP/1.1
Host: example.com
Accept: application/json
Content-Type: application/json
{
"name": "domain1.com.",
"ttl": 3600,
"email": "nsadmin@example.org"
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"id": "89acac79-38e7-497d-807c-a011e1310438",
"name": "domain1.com.",
"ttl": 3600,
"serial": 1351800588,
"email": "nsadmin@example.org",
"created_at": "2012-11-01T20:09:48.094457",
"updated_at": null,
"description": null
}
:form created_at: timestamp
:form updated_at: timestamp
:form name: domain name
:form id: uuid
:form ttl: time-to-live numeric value in seconds
:form serial: numeric seconds
:form email: email address
:form description: UTF-8 text field
:statuscode 200: Success
:statuscode 401: Access Denied
:statuscode 400: Invalid Object
:statuscode 409: Duplicate Domain
Get a Domain
-------------
.. http:get:: /domains/(uuid:id)
Lists a particular domain
**Example request**:
.. sourcecode:: http
GET /domains/09494b72-b65b-4297-9efb-187f65a0553e HTTP/1.1
Host: example.com
Accept: application/json
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"id": "09494b72-b65b-4297-9efb-187f65a0553e",
"name": "domain1.com.",
"ttl": 3600,
"serial": 1351800668,
"email": "nsadmin@example.org",
"created_at": "2012-11-01T20:11:08.000000",
"updated_at": null,
"description": null
}
:form created_at: timestamp
:form updated_at: timestamp
:form name: domain name
:form id: uuid
:form ttl: time-to-live numeric value in seconds
:form serial: numeric seconds
:form email: email address
:form description: UTF-8 text field
:statuscode 200: Success
:statuscode 401: Access Denied
Update a Domain
---------------
.. http:put:: /domains/(uuid:id)
updates a domain
**Example request**:
.. sourcecode:: http
PUT /domains/09494b72-b65b-4297-9efb-187f65a0553e HTTP/1.1
Host: example.com
Accept: application/json
Content-Type: application/json
{
"name": "domainnamex.com",
"ttl": 7200,
"email": "nsadmin@example.org"
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
Content-Length: 422
Date: Fri, 02 Nov 2012 01:06:19 GMT
{
"id": "09494b72-b65b-4297-9efb-187f65a0553e",
"name": "domain1.com.",
"email": "nsadmin@example.org",
"ttl": 7200,
"serial": 1351818367,
"created_at": "2012-11-02T00:58:42.000000",
"updated_at": "2012-11-02T01:06:07.000000",
"description": null
}
:form created_at: timestamp
:form updated_at: timestamp
:form name: domain name
:form id: uuid
:form ttl: time-to-live numeric value in seconds
:form serial: numeric seconds
:form email: email address
:form description: UTF-8 text field
:statuscode 200: Success
:statuscode 401: Access Denied
:statuscode 400: Invalid Object
:statuscode 400: Domain not found
:statuscode 409: Duplicate Domain
Delete a Domain
---------------
.. http:delete:: /domains/(uuid:id)
delete a domain
**Example request**:
.. sourcecode:: http
DELETE /domains/09494b72-b65b-4297-9efb-187f65a0553e HTTP/1.1
Host: example.com
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 0
Date: Fri, 02 Nov 2012 01:26:06 GMT
:statuscode 200: Success
:statuscode 401: Access Denied
:statuscode 400: Invalid Object
:statuscode 404: Domain not found
Get Servers Hosting a Domain
----------------------------
.. http:get:: /domains/(uuid:id)/servers
Lists the nameservers hosting a particular domain
**Example request**:
.. sourcecode:: http
GET /domains/09494b72-b65b-4297-9efb-187f65a0553e/servers HTTP/1.1
Host: example.com
Accept: application/json
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
[
{
"id": "384a9b20-239c-11e2-81c1-0800200c9a66",
"name": "ns1.provider.com.",
"created_at": "2011-01-21T11:33:21Z",
"updated_at": null
},
{
"id": "cf661142-e577-40b5-b3eb-75795cdc0cd7",
"name": "ns2.provider.com.",
"created_at": "2011-01-21T11:33:21Z",
"updated_at": "2011-01-21T11:33:21Z"
}
]
:form id: UUID server_id
:form name: Server hostname
:form created_at: timestamp
:form updated_at: timestamp
:statuscode 200: Success
:statuscode 401: Access Denied
:statuscode 404: Domain Not Found
List Domains
------------
.. http:get:: /domains
Lists all domains
**Example request**:
.. sourcecode:: http
GET /domains HTTP/1.1
Host: example.com
Accept: application/json
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"domains": [
{
"name": "domain1.com.",
"created_at": "2012-11-01T20:11:08.000000",
"email": "nsadmin@example.org",
"ttl": 3600,
"serial": 1351800668,
"id": "09494b72-b65b-4297-9efb-187f65a0553e"
},
{
"name": "domain2.com.",
"created_at": "2012-11-01T20:09:48.000000",
"email": "nsadmin@example.org",
"ttl": 3600,
"serial": 1351800588,
"id": "89acac79-38e7-497d-807c-a011e1310438"
}
]
}
:form name: domain name
:form created_at: timestamp
:form email: email address
:form ttl: time-to-live numeric value in seconds
:form serial: numeric seconds
:param id: Domain ID
:type id: uuid
:statuscode 200: Success
:statuscode 401: Access Denied

View File

@ -1,141 +0,0 @@
..
Copyright 2014 Hewlett-Packard Development Company, L.P.
Author: Endre Karlson <endre.karlson@hp.com>
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.
Quotas
======
Overview
--------
The quotas extension can be used to retrieve a tenant's absolute limits.
*Note*: Quotas is an extension and needs to be enabled before it can be used.
If Designate returns a 404 error, ensure that the following line has been
added to the designate.conf file::
enabled_extensions_v1 = quotas, ...
Once this line has been added, restart the designate-central and designate-api
services.
Get Quotas
----------
.. http:get:: /quotas/TENANT_ID
Retrieves quotas for tenant with the specified TENANT_ID. The
following example retrieves the quotas for tenant 12345.
**Example request:**
.. sourcecode:: http
GET /v1/quotas/12345 HTTP/1.1
Host: 127.0.0.1:9001
Accept: application/json
Content-Type: application/json
**Example response:**
.. sourcecode:: http
HTTP/1.1 201 Created
Content-Type: application/json
{
"api_export_size": 1000,
"domains": 10,
"recordset_records": 20,
"domain_records": 500,
"domain_recordsets": 500
}
:from api_export_size: Number of recordsets allowed in a zone export
:form domains: Number of domains the tenant is allowed to own
:form recordset_records: Number of records allowed per recordset
:form domain_records: Number of records allowed per domain
:form domain_recordsets: Number of recordsets allowed per domain
:statuscode 200: Success
:statuscode 401: Access Denied
Update Quotas
-------------
.. http:put:: /quotas/TENANT_ID
Updates the specified quota(s) to their new values.
**Example request:**
.. sourcecode:: http
PUT /v1/quotas/12345 HTTP/1.1
Host: 127.0.0.1:9001
Accept: application/json
Content-Type: application/json
{
"domains": 1000,
"domain_records": 50
}
**Example response:**
.. sourcecode:: http
HTTP/1.1 200 OK
Content-Type: application/json
{
"api_export_size": 1000,
"domains": 1000,
"recordset_records": 20,
"domain_records": 50,
"domain_recordsets": 500
}
:statuscode 200: Success
:statuscode 401: Access Denied
Reset Quotas to Default
-----------------------
.. http:delete:: /quotas/TENANT_ID
Restores the tenant's quotas back to their default values.
**Example request:**
.. sourcecode:: http
DELETE /v1/quotas/12345 HTTP/1.1
Host: 127.0.0.1:9001
Accept: application/json
Content-Type: application/json
**Example response:**
.. sourcecode:: http
HTTP/1.1 200 Success
:statuscode 200: Success
:statuscode 401: Access Denied

View File

@ -1,832 +0,0 @@
Records
=======
Resource record entries are used to generate records within a zone
TODO: More detail.
.. note:: V1 API has been deprecated since the Kilo release.
.. note:: The "description" field on Records cannot be accessed from the V2
API. Likewise, the "description" field on Record Sets cannot be accessed
from the V1 API.
Create Record
-------------
.. http:post:: /domains/(uuid:domain_id)/records
Create an A record for a domain
**Example request**:
.. sourcecode:: http
POST /domains/89acac79-38e7-497d-807c-a011e1310438/records HTTP/1.1
Host: example.com
Accept: application/json
Content-Type: application/json
{
"name": "www.example.com.",
"type": "A",
"data": "192.0.2.3"
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 399
Location: http://localhost:9001/v1/domains/89acac79-38e7-497d-807c-a011e1310438/records/2e32e609-3a4f-45ba-bdef-e50eacd345ad
Date: Fri, 02 Nov 2012 19:56:26 GMT
{
"id": "2e32e609-3a4f-45ba-bdef-e50eacd345ad",
"name": "www.example.com.",
"type": "A",
"created_at": "2012-11-02T19:56:26.366792",
"updated_at": null,
"domain_id": "89acac79-38e7-497d-807c-a011e1310438",
"ttl": null,
"priority": null,
"data": "192.0.2.3",
"description": null
}
:param domain_id: domain ID
:form id: record ID
:form name: name of record FQDN
:form type: type of record
:form created_at: timestamp
:form updated_at: timestamp
:form ttl: time-to-live numeric value in seconds
:form data: IPv4 address
:form domain_id: domain ID
:form priority: must be null for 'A' record
:form description: UTF-8 text field
:statuscode 200: Success
:statuscode 401: Access Denied
:statuscode 400: Invalid Object
:statuscode 404: Not Found
:statuscode 409: Duplicate Record
Create a AAAA record for a domain
**Example request**:
.. sourcecode:: http
POST /domains/89acac79-38e7-497d-807c-a011e1310438/records HTTP/1.1
Host: example.com
Accept: application/json
Content-Type: application/json
{
"name": "www.example.com.",
"type": "AAAA",
"data": "2001:db8:0:1234:0:5678:9:12"
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 303
Location: http://localhost:9001/v1/domains/89acac79-38e7-497d-807c-a011e1310438/records/11112222-3333-4444-5555-666677778888
Date: Fri, 02 Nov 2012 19:56:26 GMT
{
"id": "11112222-3333-4444-5555-666677778888",
"name": "www.example.com.",
"type": "AAAA",
"created_at": "2013-01-07T00:00:00.000000",
"updated_at": null,
"domain_id": "89acac79-38e7-497d-807c-a011e1310438",
"priority": null,
"ttl": null,
"data": "2001:db8:0:1234:0:5678:9:12",
"description": null
}
:param domain_id: domain ID
:form id: record ID
:form name: name of record FQDN
:form type: type of record
:form created_at: timestamp
:form updated_at: timestamp
:form ttl: time-to-live numeric value in seconds
:form data: IPv6 address
:form domain_id: domain ID
:form priority: must be null for 'AAAA' records
:form description: UTF-8 text field
:statuscode 200: Success
:statuscode 401: Access Denied
:statuscode 400: Invalid Object
:statuscode 404: Not Found
:statuscode 409: Duplicate Record
Create an MX record for a domain
**Example request**:
.. sourcecode:: http
POST /domains/89acac79-38e7-497d-807c-a011e1310438/records HTTP/1.1
Host: example.com
Accept: application/json
Content-Type: application/json
{
"name": "example.com.",
"type": "MX",
"data": "mail.example.com.",
"priority": 10
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 420
Location: http://localhost:9001/v1/domains/89acac79-38e7-497d-807c-a011e1310438/records/11112222-3333-4444-5555-666677778888
Date: Fri, 02 Nov 2012 19:56:26 GMT
{
"id": "11112222-3333-4444-5555-666677778888",
"name": "www.example.com.",
"type": "MX",
"created_at": "2013-01-07T00:00:00.000000",
"updated_at": null,
"domain_id": "89acac79-38e7-497d-807c-a011e1310438",
"priority": 10,
"ttl": null,
"data": "mail.example.com.",
"description": null
}
:param domain_id: domain ID
:form id: record ID
:form name: name of record FQDN
:form type: type of record
:form created_at: timestamp
:form ttl: time-to-live numeric value in seconds
:form data: value of record
:form domain_id: domain ID
:form priority: priority of MX record
:form description: UTF-8 text field
:statuscode 200: Success
:statuscode 401: Access Denied
:statuscode 400: Invalid Object
:statuscode 404: Not Found
:statuscode 409: Duplicate Record
Create a CNAME record for a domain
**Example request**:
.. sourcecode:: http
POST /domains/89acac79-38e7-497d-807c-a011e1310438/records HTTP/1.1
Host: example.com
Accept: application/json
Content-Type: application/json
{
"name": "www.example.com.",
"type": "CNAME",
"data": "example.com."
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 303
Location: http://localhost:9001/v1/domains/89acac79-38e7-497d-807c-a011e1310438/records/11112222-3333-4444-5555-666677778889
Date: Fri, 02 Nov 2012 19:56:26 GMT
{
"id": "11112222-3333-4444-5555-666677778889",
"name": "www.example.com.",
"type": "CNAME",
"created_at": "2013-01-07T00:00:00.000000",
"updated_at": null,
"domain_id": "89acac79-38e7-497d-807c-a011e1310438",
"priority": null,
"ttl": null,
"data": "example.com.",
"description": null
}
:param domain_id: domain ID
:form id: record ID
:form name: alias for the CNAME
:form type: type of record
:form created_at: timestamp
:form updated_at: timestamp
:form ttl: time-to-live numeric value in seconds
:form data: CNAME
:form domain_id: domain ID
:form priority: must be null for 'CNAME' records
:form description: UTF-8 text field
:statuscode 200: Success
:statuscode 401: Access Denied
:statuscode 400: Invalid Object
:statuscode 404: Not Found
:statuscode 409: Duplicate Record
Create a TXT record for a domain
**Example request**:
.. sourcecode:: http
POST /domains/89acac79-38e7-497d-807c-a011e1310438/records HTTP/1.1
Host: example.com
Accept: application/json
Content-Type: application/json
{
"name": "www.example.com.",
"type": "TXT",
"data": "This is a TXT record"
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 303
Location: http://localhost:9001/v1/domains/89acac79-38e7-497d-807c-a011e1310438/records/11112222-3333-4444-5555-666677778899
Date: Fri, 02 Nov 2012 19:56:26 GMT
{
"id": "11112222-3333-4444-5555-666677778899",
"name": "www.example.com.",
"type": "TXT",
"created_at": "2013-01-07T00:00:00.000000",
"updated_at": null,
"domain_id": "89acac79-38e7-497d-807c-a011e1310438",
"priority": null,
"ttl": null,
"data": "This is a TXT record",
"description": null
}
:param domain_id: domain ID
:form id: record ID
:form name: name of record FQDN
:form type: type of record
:form created_at: timestamp
:form updated_at: timestamp
:form ttl: time-to-live numeric value in seconds
:form data: Text associated with record.
:form domain_id: domain ID
:form priority: must be null for 'TXT' records
:form description: UTF-8 text field
:statuscode 200: Success
:statuscode 401: Access Denied
:statuscode 400: Invalid Object
:statuscode 404: Not Found
:statuscode 409: Duplicate Record
Create an SRV record for a domain
**Example request**:
.. sourcecode:: http
POST /domains/89acac79-38e7-497d-807c-a011e1310438/records HTTP/1.1
Host: example.com
Accept: application/json
Content-Type: application/json
{
"name": "_sip._tcp.example.com.",
"type": "SRV",
"data": "0 5060 sip.example.com.",
"priority": 30
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 399
Location: http://localhost:9001/v1/domains/89acac79-38e7-497d-807c-a011e1310438/records/11112222-3333-4444-5555-666677778999
Date: Fri, 02 Nov 2012 19:56:26 GMT
{
"id": "11112222-3333-4444-5555-66667777899",
"name": "_sip._tcp.example.com.",
"type": "SRV",
"created_at": "2012-11-02T19:56:26.366792",
"updated_at": null,
"domain_id": "89acac79-38e7-497d-807c-a011e1310438",
"ttl": null,
"priority" : 30,
"data": "0 5060 sip.example.com.",
"description": null
}
:param domain_id: domain ID
:form id: record ID
:form name: name of service
:form type: type of record
:form created_at: timestamp
:form updated_at: timestamp
:form ttl: time-to-live numeric value in seconds
:form data: weight port target
:form domain_id: domain ID
:form priority: priority of SRV record
:form description: UTF-8 text field
:statuscode 200: Success
:statuscode 401: Access Denied
:statuscode 400: Invalid Object
:statuscode 404: Not Found
:statuscode 409: Duplicate Record
Create an NS record for a domain
**Example request**:
.. sourcecode:: http
POST /domains/89acac79-38e7-497d-807c-a011e1310438/records HTTP/1.1
Host: example.com
Accept: application/json
Content-Type: application/json
{
"name": ".example.com.",
"type": "NS",
"data": "ns1.example.com."
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 399
Location: http://localhost:9001/v1/domains/89acac79-38e7-497d-807c-a011e1310438/records/11112222-3333-4444-5555-666677789999
Date: Fri, 02 Nov 2012 19:56:26 GMT
{
"id": "11112222-3333-4444-5555-666677789999",
"name": ".example.com.",
"type": "NS",
"created_at": "2012-11-02T19:56:26.366792",
"updated_at": null,
"domain_id": "89acac79-38e7-497d-807c-a011e1310438",
"ttl": null,
"priority" : null,
"data": "ns1.example.com",
"description": null
}
:param domain_id: domain ID
:form id: record ID
:form name: record name
:form type: type of record
:form created_at: timestamp
:form updated_at: timestamps
:form ttl: time-to-live numeric value in seconds
:form data: record value
:form domain_id: domain ID
:form priority: must be null for 'NS' record
:form description: UTF-8 text field
:statuscode 200: Success
:statuscode 401: Access Denied
:statuscode 400: Invalid Object
:statuscode 404: Not Found
:statuscode 409: Duplicate Record
Create a PTR record for a domain
**Example request**:
.. sourcecode:: http
POST /domains/89acac79-38e7-497d-807c-a011e1310438/records HTTP/1.1
Host: 2.3.192.in-addr.arpa.
Accept: application/json
Content-Type: application/json
{
"name": "1.2.3.192.in-addr.arpa.",
"type": "PTR",
"data": "www.example.com."
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 399
Location: http://localhost:9001/v1/domains/89acac79-38e7-497d-807c-a011e1310438/records/11112222-3333-4444-5555-666677889999
Date: Fri, 02 Nov 2012 19:56:26 GMT
{
"id": "11112222-3333-4444-5555-666677889999",
"name": "1.2.3.192.in-addr.arpa.",
"type": "PTR",
"created_at": "2012-11-02T19:56:26.366792",
"updated_at": null,
"domain_id": "89acac79-38e7-497d-807c-a011e1310438",
"ttl": null,
"priority" : null,
"data": "www.example.com",
"description": null
}
:param domain_id: domain ID
:form id: record ID
:form name: PTR record name
:form type: type of record
:form created_at: timestamp
:form updated_at: timestamp
:form ttl: time-to-live numeric value in seconds
:form data: DNS record value
:form domain_id: domain ID
:form priority: must be null for 'PTR' record
:form description: UTF-8 text field
:statuscode 200: Success
:statuscode 401: Access Denied
:statuscode 400: Invalid Object
:statuscode 404: Not Found
:statuscode 409: Duplicate Record
Create an SPF record for a domain
**Example request**:
.. sourcecode:: http
POST /domains/89acac79-38e7-497d-807c-a011e1310438/records HTTP/1.1
Host: example.com
Accept: application/json
Content-Type: application/json
{
"name": ".example.com.",
"type": "SPF",
"data": "v=spf1 +mx a:colo.example.com/28 -all"
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 399
Location: http://localhost:9001/v1/domains/89acac79-38e7-497d-807c-a011e1310438/records/11112222-3333-4444-5555-666678889999
Date: Fri, 02 Nov 2012 19:56:26 GMT
{
"id": "11112222-3333-4444-5555-666678889999",
"name": ".example.com.",
"type": "SPF",
"created_at": "2012-11-02T19:56:26.366792",
"updated_at": null,
"domain_id": "89acac79-38e7-497d-807c-a011e1310438",
"ttl": null,
"priority" : null,
"data": "v=spf1 +mx a:colo.example.com/28 -all",
"description": null
}
:param domain_id: domain ID
:form id: record ID
:form name: name of record
:form type: type of record
:form created_at: timestamp
:form updated_at: timestamp
:form ttl: time-to-live numeric value in seconds
:form data: record value
:form domain_id: domain ID
:form priority: must be null for 'SPF' record
:form description: UTF-8 text field
:statuscode 200: Success
:statuscode 401: Access Denied
:statuscode 400: Invalid Object
:statuscode 404: Not Found
:statuscode 409: Duplicate Record
Create an SSHFP record for a domain
**Example request**:
.. sourcecode:: http
POST /domains/89acac79-38e7-497d-807c-a011e1310438/records HTTP/1.1
Host: example.com
Accept: application/json
Content-Type: application/json
{
"name": "www.example.com.",
"type": "SSHFP",
"data": "2 1 6c3c958af43d953f91f40e0d84157f4fe7b4a898"
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 399
Location: http://localhost:9001/v1/domains/89acac79-38e7-497d-807c-a011e1310438/records/11112222-3333-4444-5555-666778889999
Date: Fri, 02 Nov 2012 19:56:26 GMT
{
"id": "11112222-3333-4444-5555-666778889999",
"name": "www.example.com.",
"type": "SSHFP",
"created_at": "2012-11-02T19:56:26.366792",
"updated_at": null,
"domain_id": "89acac79-38e7-497d-807c-a011e1310438",
"ttl": null,
"priority" : null,
"data": "2 1 6c3c958af43d953f91f40e0d84157f4fe7b4a898",
"description": null
}
:param domain_id: domain ID
:form id: record ID
:form name: name of record
:form type: type of record
:form created_at: timestamp
:form updated_at: timestamp
:form ttl: time-to-live numeric value in seconds
:form data: algorithm number, fingerprint type, fingerprint
:form domain_id: domain ID
:form priority: must be null for 'SSHFP' record
:form description: UTF-8 text field
:statuscode 200: Success
:statuscode 401: Access Denied
:statuscode 400: Invalid Object
:statuscode 404: Not Found
:statuscode 409: Duplicate Record
Get a Record
-------------
.. http:get:: /domains/(uuid:domain_id)/records/(uuid:id)
Get a particular record
**Example request**:
.. sourcecode:: http
GET /domains/09494b72b65b42979efb187f65a0553e/records/2e32e609-3a4f-45ba-bdef-e50eacd345ad HTTP/1.1
Host: example.com
Accept: application/json
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"id": "2e32e609-3a4f-45ba-bdef-e50eacd345ad",
"name": "www.example.com.",
"type": "A",
"created_at": "2012-11-02T19:56:26.366792",
"updated_at": "2012-11-04T13:22:36.859786",
"priority": null,
"ttl": 3600,
"data": "15.185.172.153",
"domain_id": "89acac79-38e7-497d-807c-a011e1310438",
"description": null
}
:param domain_id: Domain ID
:param id: Record ID
:form id: record ID
:form name: name of record FQDN
:form type: type of record
:form created_at: timestamp
:form updated_at: timestamp
:form priority: priority of record
:form ttl: time-to-live numeric value in seconds
:form data: value of record
:form description: UTF-8 text field
:form domain_id: domain ID
:statuscode 200: Success
:statuscode 401: Access Denied
:statuscode 404: Record Not Found
Update a record
---------------
.. http:put:: /domains/(uuid:domain_id)/records/(uuid:id)
Updates a record
**Example request**:
.. sourcecode:: http
PUT /domains/89acac79-38e7-497d-807c-a011e1310438/records/2e32e609-3a4f-45ba-bdef-e50eacd345ad HTTP/1.1
Host: example.com
Accept: application/json
Content-Type: application/json
{
"name": "www.example.com.",
"type": "A",
"data": "192.0.2.5"
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 446
Date: Sun, 04 Nov 2012 13:22:36 GMT
{
"id": "2e32e609-3a4f-45ba-bdef-e50eacd345ad",
"name": "www.example.com.",
"type": "A",
"created_at": "2012-11-02T19:56:26.366792",
"updated_at": "2012-11-04T13:22:36.859786",
"priority": null,
"ttl": 3600,
"data": "192.0.2.5",
"domain_id": "89acac79-38e7-497d-807c-a011e1310438",
"description": null
}
:param domain_id: domain ID
:param id: record ID
:form id: record ID
:form name: name of record FQDN
:form type: type of record
:form created_at: timestamp
:form updated_at: timestamp
:form priority: priority of record
:form ttl: time-to-live numeric value in seconds
:form data: value of record
:form description: UTF-8 text field
:form domain_id: domain ID
:statuscode 200: Success
:statuscode 401: Access Denied
:statuscode 400: Invalid Object
:statuscode 409: Duplicate Record
Delete a record
---------------
.. http:delete:: /domains/(uuid:domain_id)/records/(uuid:id)
Delete a DNS resource record
**Example request**:
.. sourcecode:: http
DELETE /domains/89acac79-38e7-497d-807c-a011e1310438/records/4ad19089-3e62-40f8-9482-17cc8ccb92cb HTTP/1.1
:param domain_id: domain ID
:param id: record ID
**Example response**:
Content-Type: text/html; charset=utf-8
Content-Length: 0
Date: Sun, 04 Nov 2012 14:35:57 GMT
List Records in a Domain
------------------------
.. http:get:: /domains/(uuid:domain_id)/records
Lists records of a domain
**Example request**:
.. sourcecode:: http
GET /domains/89acac79-38e7-497d-807c-a011e1310438/records HTTP/1.1
Host: example.com
Accept: application/json
**Example response**:
.. sourcecode:: guess
Content-Type: application/json
Content-Length: 1209
Date: Sun, 04 Nov 2012 13:58:21 GMT
{
"records": [
{
"id": "2e32e609-3a4f-45ba-bdef-e50eacd345ad",
"name": "www.example.com.",
"type": "A",
"ttl": 3600,
"created_at": "2012-11-02T19:56:26.000000",
"updated_at": "2012-11-04T13:22:36.000000",
"data": "15.185.172.153",
"domain_id": "89acac79-38e7-497d-807c-a011e1310438",
"tenant_id": null,
"priority": null,
"description": null,
"version": 1
},
{
"id": "8e9ecf3e-fb92-4a3a-a8ae-7596f167bea3",
"name": "host1.example.com.",
"type": "A",
"ttl": 3600,
"created_at": "2012-11-04T13:57:50.000000",
"updated_at": null,
"data": "15.185.172.154",
"domain_id": "89acac79-38e7-497d-807c-a011e1310438",
"tenant_id": null,
"priority": null,
"description": null,
"version": 1
},
{
"id": "4ad19089-3e62-40f8-9482-17cc8ccb92cb",
"name": "web.example.com.",
"type": "CNAME",
"ttl": 3600,
"created_at": "2012-11-04T13:58:16.393735",
"updated_at": null,
"data": "www.example.com.",
"domain_id": "89acac79-38e7-497d-807c-a011e1310438",
"tenant_id": null,
"priority": null,
"description": null,
"version": 1
}
]
}
:param domain_id: domain ID
:form id: record id
:form name: name of record FQDN
:form type: type of record
:form created_at: timestamp
:form updated_at: timestamp
:form priority: priority of record
:form ttl: time-to-live numeric value in seconds
:form data: value of record
:form description: UTF-8 text field
:form domain_id: domain ID
:statuscode 200: Success
:statuscode 401: Access Denied

View File

@ -1,238 +0,0 @@
..
Copyright 2014 Hewlett-Packard Development Company, L.P.
Author: Endre Karlson <endre.karlson@hp.com>
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.
Reports
=======
Overview
--------
*Note*: Reports is an extension and needs to be enabled before it can be
used. If Designate returns a 404 error, ensure that the following line has been
added to the designate.conf file::
enabled_extensions_v1 = reports, ...
Reports about things in the system
Get all tenants
---------------
.. http:get:: /reports/tenants
Fetch all tenants
**Example request**:
.. sourcecode:: http
GET /reports/tenants HTTP/1.1
Host: example.com
Accept: application/json
Content-Type: application/json
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"tenants": [{
"domain_count": 2,
"id": "71ee6d049a49435c8f7dd002cfe08d96"
}]
}
:form tenants: List of tenants
:statuscode 200: Success
:statuscode 401: Access Denied
Report tenant resources
-----------------------
.. http:get:: /reports/tenants/(tenant_id)
Report tenant resources
**Example request**:
.. sourcecode:: http
GET /reports/tenants/3d8391080d4a4ec4b3eadf18e6b1539a HTTP/1.1
Host: example.com
Accept: application/json
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"domain_count": 0,
"domains": [],
"id": "3d8391080d4a4ec4b3eadf18e6b1539a"
}
:param tenant_id: Tenant Id to get reports for
:type tenant_id: string
:form domain_count: integer
:form domains: Server hostname
:form id: Tenant Id
:statuscode 200: Success
:statuscode 401: Access Denied
Report resource counts
----------------------
.. http:get:: /reports/counts
Report resource counts
**Example request**:
.. sourcecode:: http
GET /reports/counts HTTP/1.1
Host: example.com
Accept: application/json
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"domains": 0,
"records": 0,
"tenants": 0
}
:form domains: Domains count
:form records: Records count
:form tenants: Tenants count
:statuscode 200: Success
:statuscode 401: Access Denied
Report tenant counts
----------------------
.. http:get:: /reports/counts/tenants
Report tenant counts
**Example request**:
.. sourcecode:: http
GET /reports/counts/tenants HTTP/1.1
Host: example.com
Accept: application/json
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"tenants": 0
}
:form tenants: Tenants count
:statuscode 200: Success
:statuscode 401: Access Denied
Report domain counts
----------------------
.. http:get:: /reports/counts/domains
Report domain counts
**Example request**:
.. sourcecode:: http
GET /reports/counts/domains HTTP/1.1
Host: example.com
Accept: application/json
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"domains": 0
}
:form domains: Domains count
:statuscode 200: Success
:statuscode 401: Access Denied
Report record counts
----------------------
.. http:get:: /reports/counts/records
Report record counts
**Example request**:
.. sourcecode:: http
GET /reports/counts/records HTTP/1.1
Host: example.com
Accept: application/json
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"records": 0
}
:form records: Records count
:statuscode 200: Success
:statuscode 401: Access Denied

View File

@ -1,205 +0,0 @@
Servers
=======
Server entries are used to generate NS records for zones..
TODO: More detail.
TODO: Server Groups Concept.
Create Server
-------------
.. http:post:: /servers
Create a DNS server
**Example request**:
.. sourcecode:: http
POST /servers HTTP/1.1
Host: example.com
Accept: application/json
Content-Type: application/json
{
"name": "ns1.example.org."
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"id": "384a9b20-239c-11e2-81c1-0800200c9a66",
"name": "ns1.example.org.",
"created_at": "2011-01-21T11:33:21Z",
"updated_at": null
}
:form name: Server hostname
:statuscode 200: Success
:statuscode 401: Access Denied
:statuscode 409: Conflict
Get Server
----------
.. http:get:: /servers/(uuid:server_id)
Lists all configured DNS servers
**Example request**:
.. sourcecode:: http
GET /servers/384a9b20-239c-11e2-81c1-0800200c9a66 HTTP/1.1
Host: example.com
Accept: application/json
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"id": "384a9b20-239c-11e2-81c1-0800200c9a66",
"name": "ns1.example.org.",
"created_at": "2011-01-21T11:33:21Z",
"updated_at": null
}
:param server_id: The server's unique id
:type server_id: uuid
:form name: Server hostname
:form created_at: timestamp
:form updated_at: timestamp
:statuscode 200: Success
:statuscode 401: Access Denied
:statuscode 404: Not Found
Update Server
-------------
.. http:put:: /servers/(uuid:server_id)
Create a DNS server
**Example request**:
.. sourcecode:: http
PUT /servers/879c1100-9c92-4244-bc83-9535ee6534d0 HTTP/1.1
Content-Type: application/json
Accept: application/json
Content-Type: application/json
{
"name": "ns1.example.org."
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"id": "879c1100-9c92-4244-bc83-9535ee6534d0",
"name": "ns1.example.org.",
"created_at": "2012-11-02T02:55:44.000000",
"updated_at": "2012-11-02T02:58:41.993556"
}
:form id: UUID server_id
:form name: Server hostname
:form created_at: timestamp
:form updated_at: timestamp
:statuscode 200: Success
:statuscode 401: Access Denied
:statuscode 404: Server Not Found
:statuscode 409: Duplicate Server
List Servers
------------
.. http:get:: /servers
Lists all configured DNS servers
**Example request**:
.. sourcecode:: http
GET /servers HTTP/1.1
Host: example.com
Accept: application/json
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
[
{
"id": "384a9b20-239c-11e2-81c1-0800200c9a66",
"name": "ns1.example.org.",
"created_at": "2011-01-21T11:33:21Z",
"updated_at": null
},
{
"id": "cf661142-e577-40b5-b3eb-75795cdc0cd7",
"name": "ns2.example.org.",
"created_at": "2011-01-21T11:33:21Z",
"updated_at": "2011-01-21T11:33:21Z"
}
]
:form id: UUID server_id
:form name: Server hostname
:form created_at: timestamp
:form updated_at: timestamp
:statuscode 200: Success
:statuscode 401: Access Denied
Delete Server
-------------
.. http:delete:: /servers/(uuid:server_id)
Deletes a specified server
**Example request**:
.. sourcecode:: http
DELETE /servers/5d1d7879-b778-4f77-bb95-02f4a5a224d8 HTTP/1.1
Host: example.com
**Example response**
.. sourcecode:: guess
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 0
Date: Thu, 01 Nov 2012 10:00:00 GMT
:statuscode 200: Success
:statuscode 401: Access Denied
:statuscode 404: Not Found

View File

@ -1,112 +0,0 @@
..
Copyright 2014 Hewlett-Packard Development Company, L.P.
Author: Endre Karlson <endre.karlson@hp.com>
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.
Synchronize
===========
Overview
--------
*Note*: Synchronize is an extension and needs to be enabled before it can be
used. If Designate returns a 404 error, ensure that the following line has been
added to the designate.conf file::
enabled_extensions_v1 = sync, ...
Trigger a synchronization of one or more resource(s) in the system.
Synchronize all domains
-----------------------
.. http:post:: /domains/sync
Synchronize all domains
**Example request**:
.. sourcecode:: http
POST /domains/sync HTTP/1.1
Host: example.com
Content-Type: application/json
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
:statuscode 200: Success
:statuscode 401: Access Denied
Synchronize one domain
----------------------
.. http:post:: /domains/(uuid:domain_id)/sync
Synchronize one domain
**Example request**:
.. sourcecode:: http
POST /domains/1dd7851a-74e7-4ddb-b6e8-38a610956bd5/sync HTTP/1.1
Host: example.com
Content-Type: application/json
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
:statuscode 200: Success
:statuscode 401: Access Denied
Synchronize one record
----------------------
.. http:post:: /domains/(uuid:domain_id)/records/(uuid:record_id)/sync
Synchronize one record
**Example request**:
.. sourcecode:: http
POST /domains/1dd7851a-74e7-4ddb-b6e8-38a610956bd5/records/1dd7851a-74e7-4ddb-b6e8-38a610956bd5/sync HTTP/1.1
Host: example.com
Content-Type: application/json
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
:statuscode 200: Success
:statuscode 401: Access Denied

View File

@ -1,7 +1,6 @@
[composite:osapi_dns]
use = egg:Paste#urlmap
/: osapi_dns_versions
/v1: osapi_dns_v1
/v2: osapi_dns_v2
/admin: osapi_dns_admin
@ -13,14 +12,6 @@ keystone = http_proxy_to_wsgi cors maintenance faultwrapper osapi_dns_app_versio
[app:osapi_dns_app_versions]
paste.app_factory = designate.api.versions:factory
[composite:osapi_dns_v1]
use = call:designate.api.middleware:auth_pipeline_factory
noauth = http_proxy_to_wsgi cors request_id noauthcontext maintenance validation_API_v1 faultwrapper_v1 normalizeuri osapi_dns_app_v1
keystone = http_proxy_to_wsgi cors request_id authtoken keystonecontext maintenance validation_API_v1 faultwrapper_v1 normalizeuri osapi_dns_app_v1
[app:osapi_dns_app_v1]
paste.app_factory = designate.api.v1:factory
[composite:osapi_dns_v2]
use = call:designate.api.middleware:auth_pipeline_factory
@ -66,11 +57,5 @@ paste.filter_factory = designate.api.middleware:NormalizeURIMiddleware.factory
[filter:faultwrapper]
paste.filter_factory = designate.api.middleware:FaultWrapperMiddleware.factory
[filter:faultwrapper_v1]
paste.filter_factory = designate.api.middleware:FaultWrapperMiddlewareV1.factory
[filter:validation_API_v1]
paste.filter_factory = designate.api.middleware:APIv1ValidationErrorMiddleware.factory
[filter:validation_API_v2]
paste.filter_factory = designate.api.middleware:APIv2ValidationErrorMiddleware.factory

View File

@ -26,7 +26,7 @@
#"admin_or_target": "rule:admin or rule:target"
#
#"zone_primary_or_admin": "('PRIMARY':%(zone_type)s and rule:admin_or_owner) OR ('SECONDARY':%(zone_type)s AND is_admin:True)"
#"zone_primary_or_admin": "('PRIMARY':%(zone_type)s and rule:admin_or_owner) OR ('SECONDARY':%(zone_type)s AND is_admin:True)"
# Create blacklist.
# POST /v2/blacklists
@ -120,75 +120,36 @@
# DELETE /v2/quotas/{project_id}
#"reset_quotas": "rule:admin"
# Create record.
# POST /v1/domains/<uuid:domain_id>/records
#"create_record": "rule:admin_or_owner"
# Get records.
# GET /v1/domains/<uuid:domain_id>/records
#"get_records": "rule:admin_or_owner"
# Get record.
# GET /v1/domains/<uuid:domain_id>/records/<uuid:record_id>
#"get_record": "rule:admin_or_owner"
# Find records.
# GET /v2/reverse/floatingips/{region}:{floatingip_id}
# GET /v2/reverse/floatingips
#"find_records": "rule:admin_or_owner"
# Find record.
# GET /v1/domains/<uuid:domain_id>/records/<uuid:record_id>
# DELETE /v1/domains/<uuid:domain_id>/records/<uuid:record_id>
# PUT /v1/domains/<uuid:domain_id>/records/<uuid:record_id>
#"find_record": "rule:admin_or_owner"
# Update record.
# PUT /v1/domains/<uuid:domain_id>/records/<uuid:record_id>
#"update_record": "rule:admin_or_owner"
# Delete record.
# DELETE /v1/domains/<uuid:domain_id>/records/<uuid:record_id>
#"delete_record": "rule:admin_or_owner"
#
#"count_records": "rule:admin_or_owner"
# Create Recordset
# POST /v2/zones/{zone_id}/recordsets
# PATCH /v2/reverse/floatingips/{region}:{floatingip_id}
#"create_recordset": "('PRIMARY':%(zone_type)s and rule:admin_or_owner) OR ('SECONDARY':%(zone_type)s AND is_admin:True)"
#"create_recordset": "('PRIMARY':%(zone_type)s and rule:admin_or_owner) OR ('SECONDARY':%(zone_type)s AND is_admin:True)"
#
#"get_recordsets": "rule:admin_or_owner"
# Get recordset
# GET /v1/domains/<uuid:domain_id>/records/<uuid:record_id>
# PUT /v1/domains/<uuid:domain_id>/records/<uuid:record_id>
# GET /v2/zones/{zone_id}/recordsets/{recordset_id}
# DELETE /v2/zones/{zone_id}/recordsets/{recordset_id}
# PUT /v2/zones/{zone_id}/recordsets/{recordset_id}
#"get_recordset": "rule:admin_or_owner"
# Find recordsets
# GET /v1/domains/<uuid:domain_id>/records
#"find_recordsets": "rule:admin_or_owner"
# Find recordset
# POST /v1/domains/<uuid:domain_id>/records
# DELETE /v1/domains/<uuid:domain_id>/records/<uuid:record_id>
#"find_recordset": "rule:admin_or_owner"
# Update recordset
# PUT /v1/domains/<uuid:domain_id>/records/<uuid:record_id>
# PUT /v2/zones/{zone_id}/recordsets/{recordset_id}
# PATCH /v2/reverse/floatingips/{region}:{floatingip_id}
#"update_recordset": "('PRIMARY':%(zone_type)s and rule:admin_or_owner) OR ('SECONDARY':%(zone_type)s AND is_admin:True)"
#"update_recordset": "('PRIMARY':%(zone_type)s and rule:admin_or_owner) OR ('SECONDARY':%(zone_type)s AND is_admin:True)"
# Delete RecordSet
# DELETE /v1/domains/<uuid:domain_id>/records/<uuid:record_id>
# DELETE /v2/zones/{zone_id}/recordsets/{recordset_id}
#"delete_recordset": "('PRIMARY':%(zone_type)s and rule:admin_or_owner) OR ('SECONDARY':%(zone_type)s AND is_admin:True)"
#"delete_recordset": "('PRIMARY':%(zone_type)s and rule:admin_or_owner) OR ('SECONDARY':%(zone_type)s AND is_admin:True)"
# Count recordsets
#"count_recordset": "rule:admin_or_owner"
@ -234,14 +195,10 @@
#"delete_tld": "rule:admin"
# Create Tsigkey
# POST /v1/tsigkeys
# POST /v2/tsigkeys
#"create_tsigkey": "rule:admin"
# List Tsigkeys
# GET /v1/tsigkeys
# GET /v1/tsigkeys/<uuid:tsigkey_id>
# DELETE /v1/tsigkeys/<uuid:tsigkey_id>
# GET /v2/tsigkeys
#"find_tsigkeys": "rule:admin"
@ -251,17 +208,14 @@
#"get_tsigkey": "rule:admin"
# Update Tsigkey
# PATCH /v1/tsigkeys/{tsigkey_id}
# PATCH /v2/tsigkeys/{tsigkey_id}
#"update_tsigkey": "rule:admin"
# Delete a Tsigkey
# DELETE /v1/tsigkeys/{tsigkey_id}
# DELETE /v2/tsigkeys/{tsigkey_id}
#"delete_tsigkey": "rule:admin"
# Create Zone
# POST /v1//domains
# POST /v2/zones
#"create_zone": "rule:admin_or_owner"
@ -269,8 +223,6 @@
#"get_zones": "rule:admin_or_owner"
# Get Zone
# GET /v1/domains/<uuid:domain_id>/records/<uuid:record_id>
# GET /v1/domains/<uuid:domain_id>/records
# GET /v2/zones/{zone_id}
# PATCH /v2/zones/{zone_id}
# PUT /v2/zones/{zone_id}/recordsets/{recordset_id}
@ -280,24 +232,14 @@
#"get_zone_servers": "rule:admin_or_owner"
# List existing zones
# GET /v1/domains
# GET /v2/zones
#"find_zones": "rule:admin_or_owner"
# Find Zone
# GET /v1/domains/<uuid:domain_id>
# GET /v1/domains/<uuid:domain_id>/servers
# PUT /v1/domains/<uuid:domain_id>
# DELETE /v1/domains/<uuid:domain_id>
#"find_zone": "rule:admin_or_owner"
# Update Zone
# PUT /v1/domains/<uuid:domain_id>
# PATCH /v2/zones/{zone_id}
#"update_zone": "rule:admin_or_owner"
# Delete Zone
# DELETE /v1/domains/<uuid:domain_id>
# DELETE /v2/zones/{zone_id}
#"delete_zone": "rule:admin_or_owner"
@ -364,7 +306,7 @@
# Create Zone Transfer Accept
# POST /v2/zones/tasks/transfer_accepts
#"create_zone_transfer_accept": "rule:admin_or_owner or tenant:%(target_tenant_id)s or None:%(target_tenant_id)s"
#"create_zone_transfer_accept": "rule:admin_or_owner OR tenant:%(target_tenant_id)s OR None:%(target_tenant_id)s"
# Get Zone Transfer Accept
# GET /v2/zones/tasks/transfer_requests/{zone_transfer_accept_id}
@ -391,7 +333,7 @@
# Show a Zone Transfer Request
# GET /v2/zones/tasks/transfer_requests/{zone_transfer_request_id}
# PATCH /v2/zones/tasks/transfer_requests/{zone_transfer_request_id}
#"get_zone_transfer_request": "rule:admin_or_owner or tenant:%(target_tenant_id)s or None:%(target_tenant_id)s"
#"get_zone_transfer_request": "rule:admin_or_owner OR tenant:%(target_tenant_id)s OR None:%(target_tenant_id)s"
#
#"get_zone_transfer_request_detailed": "rule:admin_or_owner"

View File

@ -46,7 +46,7 @@
export DEVSTACK_GATE_TEMPEST=1
export DEVSTACK_GATE_TEMPEST_ALL_PLUGINS=1
export DEVSTACK_GATE_GRENADE=pullup
export DEVSTACK_GATE_TEMPEST_REGEX=designate
export DEVSTACK_GATE_TEMPEST_REGEX="designate_tempest_plugin(?!\.tests.api.v1).*"
export DEVSTACK_GATE_HORIZON=1
export PROJECTS="openstack/designate $PROJECTS"

View File

@ -0,0 +1,9 @@
---
prelude: >
V1 API removal is complete in this version of designate.
upgrade:
- |
Any tooling using the V1 API needs to be reworked to use the v2 API
critical:
- |
V1 API has been removed

View File

@ -64,19 +64,6 @@ console_scripts =
designate-worker = designate.cmd.worker:main
designate-producer = designate.cmd.producer:main
designate.api.v1 =
domains = designate.api.v1.domains:blueprint
limits = designate.api.v1.limits:blueprint
records = designate.api.v1.records:blueprint
servers = designate.api.v1.servers:blueprint
tsigkeys = designate.api.v1.tsigkeys:blueprint
designate.api.v1.extensions =
diagnostics = designate.api.v1.extensions.diagnostics:blueprint
quotas = designate.api.v1.extensions.quotas:blueprint
sync = designate.api.v1.extensions.sync:blueprint
reports = designate.api.v1.extensions.reports:blueprint
touch = designate.api.v1.extensions.touch:blueprint
designate.api.admin.extensions =
reports = designate.api.admin.controllers.extensions.reports:ReportsController