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)