summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.zuul.yaml2
-rw-r--r--api-ref/source/conf.py22
-rw-r--r--designate/api/wsgi.py5
-rw-r--r--designate/backend/impl_akamai_v2.py199
-rw-r--r--designate/cmd/agent.py2
-rw-r--r--designate/cmd/api.py2
-rw-r--r--designate/cmd/central.py3
-rw-r--r--designate/cmd/mdns.py2
-rw-r--r--designate/cmd/producer.py2
-rw-r--r--designate/cmd/sink.py2
-rw-r--r--designate/cmd/worker.py2
-rw-r--r--designate/service.py5
-rw-r--r--designate/service_status.py28
-rw-r--r--designate/tests/unit/backend/test_akamai_v2.py494
-rw-r--r--designate/tests/unit/test_heartbeat.py18
-rw-r--r--designate/tests/unit/test_service_status.py48
-rw-r--r--devstack/designate_plugins/backend-akamai-v2161
-rwxr-xr-xdevstack/plugin.sh20
-rw-r--r--doc/requirements.txt4
-rw-r--r--doc/source/admin/backends/akamai_v2.rst40
-rw-r--r--doc/source/admin/backends/sample_yaml_snippets/akamai-v2.yaml40
-rw-r--r--doc/source/admin/support-matrix.ini7
-rw-r--r--doc/source/conf.py15
-rw-r--r--etc/designate/pools.yaml.sample-akamai_v240
-rw-r--r--lower-constraints.txt3
-rw-r--r--playbooks/legacy/designate-devstack-agent-base/post.yaml15
-rw-r--r--playbooks/legacy/designate-devstack-agent-base/run.yaml67
-rw-r--r--playbooks/legacy/designate-devstack-base/post.yaml15
-rw-r--r--playbooks/legacy/designate-devstack-base/run.yaml81
-rw-r--r--playbooks/legacy/grenade-devstack-designate-pdns4/run.yaml2
-rw-r--r--releasenotes/notes/akamai-v2-5a3edb35f59a17c2.yaml8
-rw-r--r--releasenotes/notes/drop-py-2-7-737ea2547cb7ea06.yaml3
-rw-r--r--releasenotes/source/conf.py1
-rw-r--r--releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po12
-rw-r--r--requirements.txt1
-rw-r--r--setup.cfg1
36 files changed, 1071 insertions, 301 deletions
diff --git a/.zuul.yaml b/.zuul.yaml
index 3cd1bf12..faf096bf 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -15,7 +15,7 @@
s-object: false
s-proxy: false
c-bak: false
- tox_envlist: all-plugin
+ tox_envlist: all
tempest_test_regex: |
designate_tempest_plugin.*
required-projects: &base_required_projects
diff --git a/api-ref/source/conf.py b/api-ref/source/conf.py
index 617d4ece..1cf3b6c5 100644
--- a/api-ref/source/conf.py
+++ b/api-ref/source/conf.py
@@ -23,15 +23,9 @@
# serve to show the default.
import os
-import subprocess
import sys
-from designate.version import version_info
-
-import openstackdocstheme
-
html_theme = 'openstackdocs'
-html_theme_path = [openstackdocstheme.get_html_theme_path()]
html_theme_options = {
"sidebar_mode": "toc",
"sidebar_dropdown": "api_ref",
@@ -39,6 +33,7 @@ html_theme_options = {
extensions = [
'os_api_ref',
+ 'openstackdocstheme'
]
# If extensions (or modules to document with autodoc) are in another directory,
@@ -71,14 +66,6 @@ copyright = u'OpenStack Foundation'
# |version| and |release|, also used in various other places throughout the
# built documents.
#
-# The full version, including alpha/beta/rc tags.
-release = version_info.release_string()
-# The short X.Y version.
-version = version_info.version_string()
-
-# html_context allows us to pass arbitrary values into the html template
-html_context = {'bug_tag': 'api-ref',
- 'bug_project': 'designate'}
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@@ -151,13 +138,6 @@ pygments_style = 'sphinx'
# so a file named "default.css" will overwrite the builtin "default.css".
# html_static_path = ['_static']
-# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
-# using the given strftime format.
-# html_last_updated_fmt = '%b %d, %Y'
-git_cmd = ["git", "log", "--pretty=format:'%ad, commit %h'", "--date=local",
- "-n1"]
-html_last_updated_fmt = subprocess.check_output(git_cmd).decode('utf-8')
-
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
# html_use_smartypants = True
diff --git a/designate/api/wsgi.py b/designate/api/wsgi.py
index 46b80593..0103c9aa 100644
--- a/designate/api/wsgi.py
+++ b/designate/api/wsgi.py
@@ -15,7 +15,6 @@ import os
from oslo_config import cfg
from oslo_log import log as logging
-from oslo_service import threadgroup
from paste import deploy
from designate import conf
@@ -48,9 +47,7 @@ def init_application():
if not rpc.initialized():
rpc.init(CONF)
- heartbeat = service.Heartbeat(
- 'api', threadgroup.ThreadGroup(thread_pool_size=1)
- )
+ heartbeat = service.Heartbeat('api')
heartbeat.start()
conf = conf_files[0]
diff --git a/designate/backend/impl_akamai_v2.py b/designate/backend/impl_akamai_v2.py
new file mode 100644
index 00000000..407f81f6
--- /dev/null
+++ b/designate/backend/impl_akamai_v2.py
@@ -0,0 +1,199 @@
+# Copyright 2019 Cloudification GmbH
+#
+# Author: Sergey Kraynev <contact@cloudification.io>
+#
+# 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 time
+
+import requests
+from akamai import edgegrid
+from oslo_log import log as logging
+import six.moves.urllib.parse as urlparse
+
+from designate import exceptions
+from designate.backend import base
+
+
+LOG = logging.getLogger(__name__)
+
+
+class AkamaiClient(object):
+ def __init__(self, client_token=None, client_secret=None,
+ access_token=None, host=None):
+ session = requests.Session()
+ self.baseurl = 'https://%s' % host
+ self.client_token = client_token
+ self.client_secret = client_secret
+ self.access_token = access_token
+
+ session.auth = edgegrid.EdgeGridAuth(
+ client_token=self.client_token,
+ client_secret=self.client_secret,
+ access_token=self.access_token
+ )
+
+ self.http = session
+
+ def gen_url(self, url_path):
+ return urlparse.urljoin(self.baseurl, url_path)
+
+ def post(self, payloads):
+ url_path = payloads.pop('url')
+ return self.http.post(url=self.gen_url(url_path), **payloads)
+
+ def get(self, url_path):
+ return self.http.get(url=self.gen_url(url_path))
+
+ def build_masters_field(self, masters):
+ # Akamai v2 supports only ip and hostnames. Ports could not be
+ # specified explicitly. 53 will be used by default
+ return [master.host for master in masters]
+
+ def gen_tsig_payload(self, target):
+ return {
+ 'name': target.options.get('tsig_key_name'),
+ 'algorithm': target.options.get('tsig_key_algorithm'),
+ 'secret': target.options.get('tsig_key_secret'),
+ }
+
+ def gen_create_payload(self, zone, masters, contract_id, gid, tenant_id,
+ target):
+ if contract_id is None:
+ raise exceptions.Backend(
+ 'contractId is required for zone creation')
+
+ masters = self.build_masters_field(masters)
+ body = {
+ 'zone': zone['name'],
+ 'type': 'secondary',
+ 'comment': 'Created by Designate for Tenant %s' % tenant_id,
+ 'masters': masters,
+ }
+ # Add tsigKey if it exists
+ if target.options.get('tsig_key_name'):
+ # It's not mentioned in doc, but json schema supports specification
+ # TsigKey in the same zone creation body
+ body.update({'tsigKey': self.gen_tsig_payload(target)})
+
+ params = {
+ 'contractId': contract_id,
+ 'gid': gid,
+ }
+ return {
+ 'url': 'config-dns/v2/zones',
+ 'params': params,
+ 'json': body,
+ }
+
+ def create_zone(self, payload):
+ result = self.post(payload)
+ # NOTE: ignore error about duplicate SZ in AKAMAI
+ if result.status_code == 409 and result.reason == 'Conflict':
+ LOG.info("Can't create zone %s because it already exists",
+ payload['json']['zone'])
+
+ elif not result.ok:
+ json_res = result.json()
+ raise exceptions.Backend(
+ 'Zone creation failed due to: %s' % json_res['detail'])
+
+ @staticmethod
+ def gen_delete_payload(zone_name, force):
+ return {
+ 'url': '/config-dns/v2/zones/delete-requests',
+ 'params': {'force': force},
+ 'json': {'zones': [zone_name]},
+ }
+
+ def delete_zone(self, zone_name):
+ # - try to delete with force=True
+ # - if we get Forbidden error - try to delete it with Checks logic
+
+ result = self.post(
+ self.gen_delete_payload(zone_name, force=True))
+
+ if result.status_code == 403 and result.reason == 'Forbidden':
+ result = self.post(
+ self.gen_delete_payload(zone_name, force=False))
+ if result.ok:
+ request_id = result.json().get('requestId')
+ LOG.info('Run soft delete for zone (%s) and requestId (%s)',
+ zone_name, request_id)
+
+ if request_id is None:
+ reason = 'requestId missed in response'
+ raise exceptions.Backend(
+ 'Zone deletion failed due to: %s' % reason)
+
+ self.validate_deletion_is_complete(request_id)
+
+ if not result.ok and result.status_code != 404:
+ reason = result.json().get('detail') or result.json()
+ raise exceptions.Backend(
+ 'Zone deletion failed due to: %s' % reason)
+
+ def validate_deletion_is_complete(self, request_id):
+ check_url = '/config-dns/v2/zones/delete-requests/%s' % request_id
+ deleted = False
+ attempt = 0
+ while not deleted and attempt < 10:
+ result = self.get(check_url)
+ deleted = result.json()['isComplete']
+ attempt += 1
+ time.sleep(1.0)
+
+ if not deleted:
+ raise exceptions.Backend(
+ 'Zone was not deleted after %s attempts' % attempt)
+
+
+class AkamaiBackend(base.Backend):
+ __plugin_name__ = 'akamai_v2'
+
+ __backend_status__ = 'untested'
+
+ def __init__(self, target):
+ super(AkamaiBackend, self).__init__(target)
+
+ self._host = self.options.get('host', '127.0.0.1')
+ self._port = int(self.options.get('port', 53))
+ self.client = self.init_client()
+
+ def init_client(self):
+ baseurl = self.options.get('akamai_host', '127.0.0.1')
+ client_token = self.options.get('akamai_client_token', 'admin')
+ client_secret = self.options.get('akamai_client_secret', 'admin')
+ access_token = self.options.get('akamai_access_token', 'admin')
+
+ return AkamaiClient(client_token, client_secret, access_token, baseurl)
+
+ def create_zone(self, context, zone):
+ """Create a DNS zone"""
+ LOG.debug('Create Zone')
+ contract_id = self.options.get('akamai_contract_id')
+ gid = self.options.get('akamai_gid')
+ project_id = context.project_id or zone.tenant_id
+ # Take list of masters from pools.yaml
+ payload = self.client.gen_create_payload(
+ zone, self.masters, contract_id, gid, project_id, self.target)
+ self.client.create_zone(payload)
+
+ self.mdns_api.notify_zone_changed(
+ context, zone, self._host, self._port, self.timeout,
+ self.retry_interval, self.max_retries, self.delay)
+
+ def delete_zone(self, context, zone):
+ """Delete a DNS zone"""
+ LOG.debug('Delete Zone')
+ self.client.delete_zone(zone['name'])
diff --git a/designate/cmd/agent.py b/designate/cmd/agent.py
index e96a5d59..edc7f46d 100644
--- a/designate/cmd/agent.py
+++ b/designate/cmd/agent.py
@@ -38,7 +38,7 @@ def main():
hookpoints.log_hook_setup()
server = agent_service.Service()
- heartbeat = service.Heartbeat(server.service_name, server.tg)
+ heartbeat = service.Heartbeat(server.service_name)
service.serve(server, workers=CONF['service:agent'].workers)
heartbeat.start()
service.wait()
diff --git a/designate/cmd/api.py b/designate/cmd/api.py
index 6ac6d558..61b07911 100644
--- a/designate/cmd/api.py
+++ b/designate/cmd/api.py
@@ -40,7 +40,7 @@ def main():
hookpoints.log_hook_setup()
server = api_service.Service()
- heartbeat = service.Heartbeat(server.service_name, server.tg)
+ heartbeat = service.Heartbeat(server.service_name)
service.serve(server, workers=CONF['service:api'].workers)
heartbeat.start()
service.wait()
diff --git a/designate/cmd/central.py b/designate/cmd/central.py
index 80ee8b93..6ae0524a 100644
--- a/designate/cmd/central.py
+++ b/designate/cmd/central.py
@@ -38,8 +38,7 @@ def main():
hookpoints.log_hook_setup()
server = central_service.Service()
- heartbeat = service.Heartbeat(server.service_name, server.tg,
- rpc_api=server)
+ heartbeat = service.Heartbeat(server.service_name, rpc_api=server)
service.serve(server, workers=CONF['service:central'].workers)
heartbeat.start()
service.wait()
diff --git a/designate/cmd/mdns.py b/designate/cmd/mdns.py
index e79586dc..b8993d9c 100644
--- a/designate/cmd/mdns.py
+++ b/designate/cmd/mdns.py
@@ -38,7 +38,7 @@ def main():
hookpoints.log_hook_setup()
server = mdns_service.Service()
- heartbeat = service.Heartbeat(server.service_name, server.tg)
+ heartbeat = service.Heartbeat(server.service_name)
service.serve(server, workers=CONF['service:mdns'].workers)
heartbeat.start()
service.wait()
diff --git a/designate/cmd/producer.py b/designate/cmd/producer.py
index 4bf18732..89808f27 100644
--- a/designate/cmd/producer.py
+++ b/designate/cmd/producer.py
@@ -38,7 +38,7 @@ def main():
hookpoints.log_hook_setup()
server = producer_service.Service()
- heartbeat = service.Heartbeat(server.service_name, server.tg)
+ heartbeat = service.Heartbeat(server.service_name)
service.serve(server, workers=CONF['service:producer'].workers)
heartbeat.start()
service.wait()
diff --git a/designate/cmd/sink.py b/designate/cmd/sink.py
index 7242b198..30725712 100644
--- a/designate/cmd/sink.py
+++ b/designate/cmd/sink.py
@@ -38,7 +38,7 @@ def main():
hookpoints.log_hook_setup()
server = sink_service.Service()
- heartbeat = service.Heartbeat(server.service_name, server.tg)
+ heartbeat = service.Heartbeat(server.service_name)
service.serve(server, workers=CONF['service:sink'].workers)
heartbeat.start()
service.wait()
diff --git a/designate/cmd/worker.py b/designate/cmd/worker.py
index e0046ac2..2e0f07af 100644
--- a/designate/cmd/worker.py
+++ b/designate/cmd/worker.py
@@ -38,7 +38,7 @@ def main():
hookpoints.log_hook_setup()
server = worker_service.Service()
- heartbeat = service.Heartbeat(server.service_name, server.tg)
+ heartbeat = service.Heartbeat(server.service_name)
service.serve(server, workers=CONF['service:worker'].workers)
heartbeat.start()
service.wait()
diff --git a/designate/service.py b/designate/service.py
index 3ad8e9b3..22f92532 100644
--- a/designate/service.py
+++ b/designate/service.py
@@ -69,9 +69,8 @@ class Service(service.Service):
class Heartbeat(object):
- def __init__(self, name, tg, rpc_api=None):
+ def __init__(self, name, rpc_api=None):
self.name = name
- self.tg = tg
self._status = 'UP'
self._stats = {}
@@ -81,7 +80,7 @@ class Heartbeat(object):
CONF.heartbeat_emitter.emitter_type
)
self.heartbeat_emitter = emitter_cls(
- self.name, self.tg,
+ self.name,
status_factory=self.get_status, rpc_api=rpc_api
)
diff --git a/designate/service_status.py b/designate/service_status.py
index b6f0ba24..22d4410e 100644
--- a/designate/service_status.py
+++ b/designate/service_status.py
@@ -14,6 +14,7 @@
import abc
from oslo_log import log as logging
+from oslo_service import loopingcall
from oslo_utils import timeutils
import designate.conf
@@ -31,19 +32,15 @@ class HeartBeatEmitter(plugin.DriverPlugin):
__plugin_ns__ = 'designate.heartbeat_emitter'
__plugin_type__ = 'heartbeat_emitter'
- def __init__(self, service, thread_group, status_factory=None,
- *args, **kwargs):
+ def __init__(self, service, status_factory=None, *args, **kwargs):
super(HeartBeatEmitter, self).__init__()
self._service = service
self._hostname = CONF.host
- self._running = False
- self._tg = thread_group
- self._tg.add_timer(
- CONF.heartbeat_emitter.heartbeat_interval,
- self._emit_heartbeat)
-
+ self._timer = loopingcall.FixedIntervalLoopingCall(
+ self._emit_heartbeat
+ )
self._status_factory = status_factory
def _get_status(self):
@@ -56,9 +53,6 @@ class HeartBeatEmitter(plugin.DriverPlugin):
"""
Returns Status, Stats, Capabilities
"""
- if not self._running:
- return
-
status, stats, capabilities = self._get_status()
service_status = objects.ServiceStatus(
@@ -79,10 +73,13 @@ class HeartBeatEmitter(plugin.DriverPlugin):
pass
def start(self):
- self._running = True
+ self._timer.start(
+ CONF.heartbeat_emitter.heartbeat_interval,
+ stop_on_exception=False
+ )
def stop(self):
- self._running = False
+ self._timer.stop()
class NoopEmitter(HeartBeatEmitter):
@@ -95,9 +92,8 @@ class NoopEmitter(HeartBeatEmitter):
class RpcEmitter(HeartBeatEmitter):
__plugin_name__ = 'rpc'
- def __init__(self, service, thread_group, rpc_api=None, *args, **kwargs):
- super(RpcEmitter, self).__init__(
- service, thread_group, *args, **kwargs)
+ def __init__(self, service, rpc_api=None, *args, **kwargs):
+ super(RpcEmitter, self).__init__(service, *args, **kwargs)
self.rpc_api = rpc_api
def _transmit(self, status):
diff --git a/designate/tests/unit/backend/test_akamai_v2.py b/designate/tests/unit/backend/test_akamai_v2.py
new file mode 100644
index 00000000..f3dcfa35
--- /dev/null
+++ b/designate/tests/unit/backend/test_akamai_v2.py
@@ -0,0 +1,494 @@
+# Copyright 2019 Cloudification GmbH
+#
+# Author: Sergey Kraynev <contact@cloudification.io>
+#
+# 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 json
+import mock
+import requests
+
+import designate.tests
+from designate import exceptions
+from designate import objects
+from designate.backend import impl_akamai_v2 as akamai
+from designate.tests import fixtures
+
+
+class AkamaiBackendTestCase(designate.tests.TestCase):
+ def setUp(self):
+ super(AkamaiBackendTestCase, self).setUp()
+ self.zone = objects.Zone(
+ id='cca7908b-dad4-4c50-adba-fb67d4c556e8',
+ name='example.com.',
+ email='example@example.com'
+ )
+
+ self.target = {
+ 'id': '4588652b-50e7-46b9-b688-a9bad40a873e',
+ 'type': 'akamai_v2',
+ 'masters': [
+ {'host': '192.168.1.1', 'port': 53},
+ {'host': '192.168.1.2', 'port': 35}
+ ],
+ 'options': [
+ {'key': 'host', 'value': '192.168.2.3'},
+ {'key': 'port', 'value': '53'},
+ {'key': 'akamai_client_secret', 'value': 'client_secret'},
+ {'key': 'akamai_host', 'value': 'host_value'},
+ {'key': 'akamai_access_token', 'value': 'access_token'},
+ {'key': 'akamai_client_token', 'value': 'client_token'},
+ {'key': 'akamai_contract_id', 'value': 'G-XYW'},
+ {'key': 'akamai_gid', 'value': '777'}
+ ],
+ }
+
+ def gen_response(self, status_code, reason, json_data=None):
+ response = requests.models.Response()
+ response.status_code = status_code
+ response.reason = reason
+ response._content = json.dumps(json_data or {}).encode('utf-8')
+ return response
+
+ @mock.patch.object(akamai, 'edgegrid')
+ @mock.patch.object(akamai.requests.Session, 'post')
+ def test_create_zone_missed_contract_id(self, mock_post, mock_auth):
+ self.target['options'].remove(
+ {'key': 'akamai_contract_id', 'value': 'G-XYW'})
+ backend = akamai.AkamaiBackend(
+ objects.PoolTarget.from_dict(self.target)
+ )
+ mock_auth.EdgeGridAuth.assert_called_once_with(
+ access_token='access_token',
+ client_secret='client_secret',
+ client_token='client_token'
+ )
+
+ with fixtures.random_seed(0):
+ self.assertRaisesRegex(
+ exceptions.Backend,
+ 'contractId is required for zone creation',
+ backend.create_zone, self.admin_context, self.zone)
+
+ mock_post.assert_not_called()
+
+ @mock.patch.object(akamai, 'edgegrid')
+ @mock.patch.object(akamai.requests.Session, 'post')
+ def test_create_zone(self, mock_post, mock_auth):
+ backend = akamai.AkamaiBackend(
+ objects.PoolTarget.from_dict(self.target)
+ )
+ mock_auth.EdgeGridAuth.assert_called_once_with(
+ access_token='access_token',
+ client_secret='client_secret',
+ client_token='client_token'
+ )
+
+ with fixtures.random_seed(0):
+ backend.create_zone(self.admin_context, self.zone)
+
+ project_id = self.admin_context.project_id or self.zone.tenant_id
+ mock_post.assert_called_once_with(
+ json={
+ 'comment': 'Created by Designate for Tenant %s' % project_id,
+ 'masters': ['192.168.1.1', '192.168.1.2'],
+ 'type': 'secondary', 'zone': u'example.com.'
+ },
+ params={
+ 'gid': '777',
+ 'contractId': 'G-XYW'
+ },
+ url='https://host_value/config-dns/v2/zones'
+ )
+
+ @mock.patch.object(akamai, 'edgegrid')
+ @mock.patch.object(akamai.requests.Session, 'post')
+ def test_create_zone_duplicate_zone(self, mock_post, mock_auth):
+ backend = akamai.AkamaiBackend(
+ objects.PoolTarget.from_dict(self.target)
+ )
+ mock_auth.EdgeGridAuth.assert_called_once_with(
+ access_token='access_token',
+ client_secret='client_secret',
+ client_token='client_token'
+ )
+
+ mock_post.return_value = self.gen_response(409, 'Conflict')
+
+ with fixtures.random_seed(0):
+ backend.create_zone(self.admin_context, self.zone)
+
+ project_id = self.admin_context.project_id or self.zone.tenant_id
+ mock_post.assert_called_once_with(
+ json={
+ 'comment': 'Created by Designate for Tenant %s' % project_id,
+ 'masters': ['192.168.1.1', '192.168.1.2'],
+ 'type': 'secondary', 'zone': u'example.com.'
+ },
+ params={
+ 'gid': '777',
+ 'contractId': 'G-XYW'
+ },
+ url='https://host_value/config-dns/v2/zones'
+ )
+
+ @mock.patch.object(akamai, 'edgegrid')
+ @mock.patch.object(akamai.requests.Session, 'post')
+ def test_create_zone_with_tsig_key(self, mock_post, mock_auth):
+ self.target['options'].extend([
+ {'key': 'tsig_key_name', 'value': 'test_key'},
+ {'key': 'tsig_key_algorithm', 'value': 'hmac-sha512'},
+ {'key': 'tsig_key_secret', 'value': 'aaaabbbbccc'}
+ ])
+ backend = akamai.AkamaiBackend(
+ objects.PoolTarget.from_dict(self.target)
+ )
+ mock_auth.EdgeGridAuth.assert_called_once_with(
+ access_token='access_token',
+ client_secret='client_secret',
+ client_token='client_token'
+ )
+
+ with fixtures.random_seed(0):
+ backend.create_zone(self.admin_context, self.zone)
+
+ project_id = self.admin_context.project_id or self.zone.tenant_id
+ mock_post.assert_called_once_with(
+ json={
+ 'comment': 'Created by Designate for Tenant %s' % project_id,
+ 'masters': ['192.168.1.1', '192.168.1.2'],
+ 'type': 'secondary',
+ 'zone': 'example.com.',
+ 'tsigKey': {
+ 'name': 'test_key',
+ 'algorithm': 'hmac-sha512',
+ 'secret': 'aaaabbbbccc',
+ }
+ },
+ params={
+ 'gid': '777',
+ 'contractId': 'G-XYW'
+ },
+ url='https://host_value/config-dns/v2/zones'
+ )
+
+ @mock.patch.object(akamai, 'edgegrid')
+ @mock.patch.object(akamai.requests.Session, 'post')
+ def test_create_zone_raise_error(self, mock_post, mock_auth):
+ backend = akamai.AkamaiBackend(
+ objects.PoolTarget.from_dict(self.target)
+ )
+ mock_auth.EdgeGridAuth.assert_called_once_with(
+
+ access_token='access_token',
+ client_secret='client_secret',
+ client_token='client_token'
+ )
+
+ json_data = {
+ 'title': 'Missing parameter',
+ 'detail': 'Missed A option'
+ }
+ mock_post.return_value = self.gen_response(
+ 400, 'Bad Request', json_data)
+
+ with fixtures.random_seed(0):
+ self.assertRaisesRegex(
+ exceptions.Backend,
+ 'Zone creation failed due to: Missed A option',
+ backend.create_zone, self.admin_context, self.zone)
+
+ project_id = self.admin_context.project_id or self.zone.tenant_id
+ mock_post.assert_called_once_with(
+ json={
+ 'comment': 'Created by Designate for Tenant %s' % project_id,
+ 'masters': ['192.168.1.1', '192.168.1.2'],
+ 'type': 'secondary', 'zone': 'example.com.'
+ },
+ params={
+ 'gid': '777',
+ 'contractId': 'G-XYW'
+ },
+ url='https://host_value/config-dns/v2/zones'
+ )
+
+ @mock.patch.object(akamai, 'edgegrid')
+ @mock.patch.object(akamai.requests.Session, 'post')
+ def test_force_delete_zone(self, mock_post, mock_auth):
+ backend = akamai.AkamaiBackend(
+ objects.PoolTarget.from_dict(self.target)
+ )
+ mock_auth.EdgeGridAuth.assert_called_once_with(
+
+ access_token='access_token',
+ client_secret='client_secret',
+ client_token='client_token'
+ )
+
+ mock_post.return_value = self.gen_response(200, 'Success')
+
+ with fixtures.random_seed(0):
+ backend.delete_zone(self.admin_context, self.zone)
+
+ mock_post.assert_called_once_with(
+ json={
+ 'zones': ['example.com.']
+ },
+ params={
+ 'force': True
+ },
+ url='https://host_value/config-dns/v2/zones/delete-requests'
+ )
+
+ @mock.patch.object(akamai, 'edgegrid')
+ @mock.patch.object(akamai.requests.Session, 'post')
+ def test_force_delete_zone_raise_error(self, mock_post, mock_auth):
+ backend = akamai.AkamaiBackend(
+ objects.PoolTarget.from_dict(self.target)
+ )
+ mock_auth.EdgeGridAuth.assert_called_once_with(
+
+ access_token='access_token',
+ client_secret='client_secret',
+ client_token='client_token'
+ )
+
+ mock_post.return_value = self.gen_response(
+ 403, 'Bad Request', {'detail': 'Unexpected error'})
+
+ with fixtures.random_seed(0):
+ self.assertRaisesRegex(
+ exceptions.Backend,
+ 'Zone deletion failed due to: Unexpected error',
+ backend.delete_zone, self.admin_context, self.zone)
+
+ mock_post.assert_called_once_with(
+ json={
+ 'zones': ['example.com.']
+ },
+ params={
+ 'force': True
+ },
+ url='https://host_value/config-dns/v2/zones/delete-requests'
+ )
+
+ @mock.patch.object(akamai, 'edgegrid')
+ @mock.patch.object(akamai.requests.Session, 'post')
+ def test_force_delete_zone_raise_error_404(self, mock_post, mock_auth):
+ backend = akamai.AkamaiBackend(
+ objects.PoolTarget.from_dict(self.target)
+ )
+ mock_auth.EdgeGridAuth.assert_called_once_with(
+
+ access_token='access_token',
+ client_secret='client_secret',
+ client_token='client_token'
+ )
+
+ mock_post.return_value = self.gen_response(
+ 404, 'Bad Request', {'detail': 'Unexpected error'})
+
+ with fixtures.random_seed(0):
+ backend.delete_zone(self.admin_context, self.zone)
+
+ mock_post.assert_called_once_with(
+ json={
+ 'zones': ['example.com.']
+ },
+ params={
+ 'force': True
+ },
+ url='https://host_value/config-dns/v2/zones/delete-requests'
+ )
+
+ @mock.patch.object(akamai, 'edgegrid')
+ @mock.patch.object(akamai.requests.Session, 'post')
+ @mock.patch.object(akamai.requests.Session, 'get')
+ def test_soft_delete_zone(self, mock_get, mock_post, mock_auth):
+ backend = akamai.AkamaiBackend(
+ objects.PoolTarget.from_dict(self.target)
+ )
+ mock_auth.EdgeGridAuth.assert_called_once_with(
+
+ access_token='access_token',
+ client_secret='client_secret',
+ client_token='client_token'
+ )
+
+ mock_post.side_effect = [
+ # emulate, when Force=True is forbidden
+ self.gen_response(403, 'Forbidden'),
+ # emulate request, when Force=False
+ self.gen_response(200, 'Success', {'requestId': 'nice_id'}),
+ ]
+
+ # emulate max 9 failed attempts and 1 success
+ mock_get.side_effect = 9 * [
+ self.gen_response(200, 'Success', {'isComplete': False})
+ ] + [
+ self.gen_response(200, 'Success', {'isComplete': True})
+ ]
+
+ with fixtures.random_seed(0), \
+ mock.patch.object(akamai.time, 'sleep') as mock_sleep:
+ mock_sleep.return_value = None
+ backend.delete_zone(self.admin_context, self.zone)
+
+ self.assertEqual(10, mock_sleep.call_count)
+
+ url = 'https://host_value/config-dns/v2/zones/delete-requests/nice_id'
+ mock_get.assert_has_calls(9 * [mock.call(url=url)])
+
+ mock_post.assert_has_calls([
+ mock.call(
+ json={'zones': ['example.com.']},
+ params={'force': True},
+ url='https://host_value/config-dns/v2/zones/delete-requests'
+ ),
+ mock.call(
+ json={'zones': ['example.com.']},
+ params={'force': False},
+ url='https://host_value/config-dns/v2/zones/delete-requests'
+ )
+ ])
+
+ @mock.patch.object(akamai, 'edgegrid')
+ @mock.patch.object(akamai.requests.Session, 'post')
+ @mock.patch.object(akamai.requests.Session, 'get')
+ def test_soft_delete_zone_failed_after_10_attempts(
+ self, mock_get, mock_post, mock_auth):
+ backend = akamai.AkamaiBackend(
+ objects.PoolTarget.from_dict(self.target)
+ )
+ mock_auth.EdgeGridAuth.assert_called_once_with(
+
+ access_token='access_token',
+ client_secret='client_secret',
+ client_token='client_token'
+ )
+
+ mock_post.side_effect = [
+ # emulate, when Force=True is forbidden
+ self.gen_response(403, 'Forbidden'),
+ # emulate request, when Force=False
+ self.gen_response(200, 'Success', {'requestId': 'nice_id'}),
+ ]
+
+ # emulate max 10 failed attempts
+ mock_get.side_effect = 10 * [
+ self.gen_response(200, 'Success', {'isComplete': False})
+ ]
+
+ with fixtures.random_seed(0), \
+ mock.patch.object(akamai.time, 'sleep') as mock_sleep:
+ mock_sleep.return_value = None
+ self.assertRaisesRegex(
+ exceptions.Backend,
+ 'Zone was not deleted after 10 attempts',
+ backend.delete_zone, self.admin_context, self.zone)
+
+ self.assertEqual(10, mock_sleep.call_count)
+
+ url = 'https://host_value/config-dns/v2/zones/delete-requests/nice_id'
+ mock_get.assert_has_calls(10 * [mock.call(url=url)])
+
+ mock_post.assert_has_calls([
+ mock.call(
+ json={'zones': ['example.com.']},
+ params={'force': True},
+ url='https://host_value/config-dns/v2/zones/delete-requests'
+ ),
+ mock.call(
+ json={'zones': ['example.com.']},
+ params={'force': False},
+ url='https://host_value/config-dns/v2/zones/delete-requests'
+ )
+ ])
+
+ @mock.patch.object(akamai, 'edgegrid')
+ @mock.patch.object(akamai.requests.Session, 'post')
+ def test_soft_delete_zone_raise_error(self, mock_post, mock_auth):
+ backend = akamai.AkamaiBackend(
+ objects.PoolTarget.from_dict(self.target)
+ )
+ mock_auth.EdgeGridAuth.assert_called_once_with(
+
+ access_token='access_token',
+ client_secret='client_secret',
+ client_token='client_token'
+ )
+
+ mock_post.side_effect = [
+ # emulate, when Force=True is forbidden
+ self.gen_response(403, 'Forbidden'),
+ # emulate request, when Force=False
+ self.gen_response(409, 'Conflict', {'detail': 'Intenal Error'})
+ ]
+
+ with fixtures.random_seed(0):
+ self.assertRaisesRegex(
+ exceptions.Backend,
+ 'Zone deletion failed due to: Intenal Error',
+ backend.delete_zone, self.admin_context, self.zone)
+
+ mock_post.assert_has_calls([
+ mock.call(
+ json={'zones': [u'example.com.']},
+ params={'force': True},
+ url='https://host_value/config-dns/v2/zones/delete-requests'
+ ),
+ mock.call(
+ json={'zones': [u'example.com.']},
+ params={'force': False},
+ url='https://host_value/config-dns/v2/zones/delete-requests'
+ )
+ ])
+
+ @mock.patch.object(akamai, 'edgegrid')
+ @mock.patch.object(akamai.requests.Session, 'post')
+ def test_soft_delete_zone_missed_request_id(self, mock_post, mock_auth):
+ backend = akamai.AkamaiBackend(
+ objects.PoolTarget.from_dict(self.target)
+ )
+ mock_auth.EdgeGridAuth.assert_called_once_with(
+
+ access_token='access_token',
+ client_secret='client_secret',
+ client_token='client_token'
+ )
+
+ mock_post.side_effect = [
+ # emulate, when Force=True is forbidden
+ self.gen_response(403, 'Forbidden'),
+ # emulate request, when Force=False
+ self.gen_response(200, 'Success')
+ ]
+
+ with fixtures.random_seed(0):
+ self.assertRaisesRegex(
+ exceptions.Backend,
+ 'Zone deletion failed due to: requestId missed in response',
+ backend.delete_zone, self.admin_context, self.zone)
+
+ mock_post.assert_has_calls([
+ mock.call(
+ json={'zones': [u'example.com.']},
+ params={'force': True},
+ url='https://host_value/config-dns/v2/zones/delete-requests'
+ ),
+ mock.call(
+ json={'zones': [u'example.com.']},
+ params={'force': False},
+ url='https://host_value/config-dns/v2/zones/delete-requests'
+ )
+ ])
diff --git a/designate/tests/unit/test_heartbeat.py b/designate/tests/unit/test_heartbeat.py
index fbb01862..a531d5b0 100644
--- a/designate/tests/unit/test_heartbeat.py
+++ b/designate/tests/unit/test_heartbeat.py
@@ -13,6 +13,7 @@ import mock
import oslotest.base
from oslo_config import cfg
from oslo_config import fixture as cfg_fixture
+from oslo_service import loopingcall
from designate import service
@@ -20,14 +21,18 @@ CONF = cfg.CONF
class HeartbeatTest(oslotest.base.BaseTestCase):
- def setUp(self):
+ @mock.patch.object(loopingcall, 'FixedIntervalLoopingCall')
+ def setUp(self, mock_looping):
super(HeartbeatTest, self).setUp()
+
+ self.mock_timer = mock.Mock()
+ mock_looping.return_value = self.mock_timer
+
self.useFixture(cfg_fixture.Config(CONF))
CONF.set_override('emitter_type', 'noop', 'heartbeat_emitter')
- self.mock_tg = mock.Mock()
- self.heartbeat = service.Heartbeat('test', self.mock_tg)
+ self.heartbeat = service.Heartbeat('test')
def test_get_status(self):
self.assertEqual(('UP', {}, {},), self.heartbeat.get_status())
@@ -38,16 +43,13 @@ class HeartbeatTest(oslotest.base.BaseTestCase):
)
def test_start_heartbeat(self):
- self.assertFalse(self.heartbeat.heartbeat_emitter._running)
-
self.heartbeat.start()
- self.assertTrue(self.heartbeat.heartbeat_emitter._running)
+ self.mock_timer.start.assert_called_once()
def test_stop_heartbeat(self):
- self.assertFalse(self.heartbeat.heartbeat_emitter._running)
self.heartbeat.start()
self.heartbeat.stop()
- self.assertFalse(self.heartbeat.heartbeat_emitter._running)
+ self.mock_timer.stop.assert_called_once()
diff --git a/designate/tests/unit/test_service_status.py b/designate/tests/unit/test_service_status.py
index a8fe3880..c7827289 100644
--- a/designate/tests/unit/test_service_status.py
+++ b/designate/tests/unit/test_service_status.py
@@ -14,6 +14,7 @@
import mock
import oslotest.base
from oslo_config import cfg
+from oslo_service import loopingcall
from designate import objects
from designate import service_status
@@ -22,40 +23,46 @@ from designate import service_status
class NoopEmitterTest(oslotest.base.BaseTestCase):
def setUp(self):
super(NoopEmitterTest, self).setUp()
- self.mock_tg = mock.Mock()
- def test_init(self):
- service_status.NoopEmitter("svc", self.mock_tg)
+ @mock.patch.object(loopingcall, 'FixedIntervalLoopingCall')
+ def test_start(self, mock_looping):
+ mock_timer = mock.Mock()
+ mock_looping.return_value = mock_timer
- def test_start(self):
- emitter = service_status.NoopEmitter("svc", self.mock_tg)
+ emitter = service_status.NoopEmitter("svc")
emitter.start()
- self.mock_tg.add_timer.assert_called_once_with(
- 10.0, emitter._emit_heartbeat)
+ mock_timer.start.assert_called_once()
- def test_stop(self):
- mock_pulse = mock.Mock()
- self.mock_tg.add_timer.return_value = mock_pulse
+ @mock.patch.object(loopingcall, 'FixedIntervalLoopingCall')
+ def test_stop(self, mock_looping):
+ mock_timer = mock.Mock()
+ mock_looping.return_value = mock_timer
- emitter = service_status.NoopEmitter("svc", self.mock_tg)
+ emitter = service_status.NoopEmitter("svc")
emitter.start()
emitter.stop()
- self.assertFalse(emitter._running)
+ mock_timer.stop.assert_called_once()
class RpcEmitterTest(oslotest.base.BaseTestCase):
def setUp(self):
super(RpcEmitterTest, self).setUp()
- self.mock_tg = mock.Mock()
@mock.patch.object(objects, "ServiceStatus")
@mock.patch("designate.context.DesignateContext.get_admin_context")
- def test_emit_no_status_factory(self, mock_context, mock_service_status):
- emitter = service_status.RpcEmitter("svc", self.mock_tg)
+ @mock.patch.object(loopingcall, 'FixedIntervalLoopingCall')
+ def test_emit_no_status_factory(self, mock_looping, mock_context,
+ mock_service_status):
+ mock_timer = mock.Mock()
+ mock_looping.return_value = mock_timer
+
+ emitter = service_status.RpcEmitter("svc")
emitter.start()
+ mock_timer.start.assert_called_once()
+
central = mock.Mock()
with mock.patch("designate.central.rpcapi.CentralAPI.get_instance",
return_value=central):
@@ -76,16 +83,23 @@ class RpcEmitterTest(oslotest.base.BaseTestCase):
@mock.patch.object(objects, "ServiceStatus")
@mock.patch("designate.context.DesignateContext.get_admin_context")
- def test_emit_status_factory(self, mock_context, mock_service_status):
+ @mock.patch.object(loopingcall, 'FixedIntervalLoopingCall')
+ def test_emit_status_factory(self, mock_looping, mock_context,
+ mock_service_status):
+ mock_timer = mock.Mock()
+ mock_looping.return_value = mock_timer
+
status = False
stats = {"a": 1}
capabilities = {"b": 2}
status_factory = mock.Mock(return_value=(status, stats, capabilities,))
- emitter = service_status.RpcEmitter("svc", self.mock_tg,
+ emitter = service_status.RpcEmitter("svc",
status_factory=status_factory)
emitter.start()
+ mock_timer.start.assert_called_once()
+
central = mock.Mock()
with mock.patch("designate.central.rpcapi.CentralAPI.get_instance",
return_value=central):
diff --git a/devstack/designate_plugins/backend-akamai-v2 b/devstack/designate_plugins/backend-akamai-v2
new file mode 100644
index 00000000..ce6a2198
--- /dev/null
+++ b/devstack/designate_plugins/backend-akamai-v2
@@ -0,0 +1,161 @@
+# Configure the Akamai v2 backend
+
+# Requirements:
+# An active Akamai account / contract will be requied to use this DevStack
+# plugin.
+
+# Enable with:
+# DESIGNATE_BACKEND_DRIVER=akamai_v2
+
+# Dependencies:
+# ``functions`` file
+# ``designate`` configuration
+
+# install_designate_backend - install any external requirements
+# configure_designate_backend - make configuration changes, including those to other services
+# init_designate_backend - initialize databases, etc.
+# start_designate_backend - start any external services
+# stop_designate_backend - stop any external services
+# cleanup_designate_backend - remove transient data and cache
+
+# Save trace setting
+DP_AKAMAI_XTRACE=$(set +o | grep xtrace)
+set +o xtrace
+
+# Defaults
+# --------
+
+# DESIGNATE_HOST is IP address of the one of AKAMAI_NAMESERVERS
+DESIGNATE_HOST=${DESIGNATE_HOST:-"193.108.91.197"}
+DESIGNATE_AKAMAI_CLIENT_SECRET=${DESIGNATE_AKAMAI_CLIENT_SECRET:-"client_secret_string"}
+DESIGNATE_AKAMAI_HOST=${DESIGNATE_AKAMAI_HOST:-"akamai_host_string"}
+DESIGNATE_AKAMAI_ACCESS_TOKEN=${DESIGNATE_AKAMAI_ACCESS_TOKEN:-"access_token_string"}
+DESIGNATE_AKAMAI_CLIENT_TOKEN=${DESIGNATE_AKAMAI_CLIENT_TOKEN:-"client_token_string"}
+DESIGNATE_AKAMAI_CONTRACT_ID=${DESIGNATE_AKAMAI_CONTRACT_ID:-"contract_id"}
+DESIGNATE_AKAMAI_GID=${DESIGNATE_AKAMAI_GID:-"group_id"}
+DESIGNATE_AKAMAI_MASTERS=${DESIGNATE_AKAMAI_MASTERS:-"$DESIGNATE_SERVICE_HOST:$DESIGNATE_SERVICE_PORT_MDNS"}
+DESIGNATE_AKAMAI_NAMESERVERS=${DESIGNATE_AKAMAI_NAMESERVERS:-""}
+DESIGNATE_AKAMAI_ALSO_NOTIFIES=${DESIGNATE_AKAMAI_ALSO_NOTIFIES:-"23.14.128.185,23.207.197.166,23.205.121.134,104.122.95.88,72.247.124.98"}
+
+# Sanity Checks
+# -------------
+if [ -z "$DESIGNATE_AKAMAI_NAMESERVERS" ]; then
+ die $LINENO "You must configure DESIGNATE_AKAMAI_NAMESERVERS"
+fi
+
+if [ "$DESIGNATE_SERVICE_PORT_MDNS" != "53" ]; then
+ die $LINENO "Akamai requires DESIGNATE_SERVICE_PORT_MDNS is set to '53'"
+fi
+
+# Entry Points
+# ------------
+
+# install_designate_backend - install any external requirements
+function install_designate_backend {
+ :
+}
+
+# configure_designate_backend - make configuration changes, including those to other services
+function configure_designate_backend {
+ # Generate Designate pool.yaml file
+ sudo tee $DESIGNATE_CONF_DIR/pools.yaml > /dev/null <<EOF
+---
+- name: default
+ description: DevStack Akamai Pool
+ attributes: {}
+
+ targets:
+ - type: akamai
+ description: Akamai API
+ options:
+ host: $DESIGNATE_HOST
+ port: 53
+ akamai_client_secret: $DESIGNATE_AKAMAI_CLIENT_SECRET
+ akamai_host: $DESIGNATE_AKAMAI_HOST
+ akamai_access_token: $DESIGNATE_AKAMAI_ACCESS_TOKEN
+ akamai_client_token: $DESIGNATE_AKAMAI_CLIENT_TOKEN
+ akamai_contract_id: $DESIGNATE_AKAMAI_CONTRACT_ID
+ akamai_gid: $DESIGNATE_AKAMAI_GID
+
+ # NOTE: TSIG key has to be set manully if it's necessary
+ #tsig_key_name: key_test
+ #tsig_key_algorithm: hmac-sha512
+ #tsig_key_secret: test_ley_secret
+
+
+ masters:
+EOF
+
+ # Create a Pool Master for each of the Akamai Masters
+ IFS=',' read -a masters <<< "$DESIGNATE_AKAMAI_MASTERS"
+
+ for master in "${masters[@]}"; do
+ sudo tee -a $DESIGNATE_CONF_DIR/pools.yaml > /dev/null <<EOF
+ - host: $master
+ port: 53
+EOF
+ done
+
+ # Create a Pool NS Record for each of the Akamai Nameservers
+ IFS=',' read -a nameservers <<< "$DESIGNATE_AKAMAI_NAMESERVERS"
+
+ sudo tee -a $DESIGNATE_CONF_DIR/pools.yaml > /dev/null <<EOF
+ ns_records:
+EOF
+
+ for nameserver in "${nameservers[@]}"; do
+ sudo tee -a $DESIGNATE_CONF_DIR/pools.yaml > /dev/null <<EOF
+ - hostname: $nameserver
+ priority: 1
+EOF
+ done
+
+ # Create a Pool Nameserver for each of the Akamai Nameservers
+ sudo tee -a $DESIGNATE_CONF_DIR/pools.yaml > /dev/null <<EOF
+ nameservers:
+EOF
+
+ for nameserver in "${nameservers[@]}"; do
+ sudo tee -a $DESIGNATE_CONF_DIR/pools.yaml > /dev/null <<EOF
+ - host: `dig +short A $nameserver | head -n 1`
+ port: 53
+EOF
+ done
+
+ # Create a Pool Also Notifies for each of the Akamai Also Notifies
+ IFS=',' read -a also_notifies <<< "$DESIGNATE_AKAMAI_ALSO_NOTIFIES"
+
+ sudo tee -a $DESIGNATE_CONF_DIR/pools.yaml > /dev/null <<EOF
+ also_notifies:
+EOF
+
+ for also_notify in "${also_notifies[@]}"; do
+ sudo tee -a $DESIGNATE_CONF_DIR/pools.yaml > /dev/null <<EOF
+ - host: $also_notify
+ port: 53
+EOF
+ done
+}
+
+# init_designate_backend - initialize databases, etc.
+function init_designate_backend {
+ :
+}
+
+# start_designate_backend - start any external services
+function start_designate_backend {
+ :
+}
+
+# stop_designate_backend - stop any external services
+function stop_designate_backend {
+ :
+}
+
+# cleanup_designate_backend - remove transient data and cache
+function cleanup_designate_backend {
+ :
+}
+
+# Restore xtrace
+$DP_AKAMAI_XTRACE
diff --git a/devstack/plugin.sh b/devstack/plugin.sh
index d6f9c85c..e8a635c3 100755
--- a/devstack/plugin.sh
+++ b/devstack/plugin.sh
@@ -115,14 +115,7 @@ function configure_designate {
fi
# Logging Configuration
- if [ "$USE_SYSTEMD" != "False" ]; then
- setup_systemd_logging $DESIGNATE_CONF
- fi
-
- # Format logging
- if [ "$LOG_COLOR" == "True" ] && [ "$USE_SYSTEMD" == "False" ]; then
- setup_colorized_logging $DESIGNATE_CONF DEFAULT
- fi
+ setup_systemd_logging $DESIGNATE_CONF
# Backend Plugin Configuation
configure_designate_backend
@@ -238,10 +231,6 @@ function create_designate_pool_configuration {
# init_designate - Initialize etc.
function init_designate {
- # Some Designate Backends require mdns be bound to port 53, make that
- # doable.
- sudo setcap 'cap_net_bind_service=+ep' $(readlink -f /usr/bin/python)
-
# (Re)create designate database
recreate_database designate utf8
@@ -259,12 +248,9 @@ function install_designate {
install_apache_wsgi
fi
-
- if is_ubuntu; then
- install_package libcap2-bin
- elif is_fedora; then
+ if is_fedora; then
# bind-utils package provides `dig`
- install_package libcap bind-utils
+ install_package bind-utils
fi
git_clone $DESIGNATE_REPO $DESIGNATE_DIR $DESIGNATE_BRANCH
diff --git a/doc/requirements.txt b/doc/requirements.txt
index 23e0f99c..fbade88b 100644
--- a/doc/requirements.txt
+++ b/doc/requirements.txt
@@ -1,9 +1,9 @@
# The order of packages is significant, because pip processes them in the order
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
-sphinx!=1.6.6,!=1.6.7,!=2.1.0,>=1.6.2;python_version>='3.4' # BSD
+sphinx!=1.6.6,!=1.6.7,!=2.1.0,>=1.6.2 # BSD
sphinxcontrib-httpdomain>=1.3.0 # BSD
sphinxcontrib-blockdiag>=1.5.4 # BSD
reno>=2.5.0 # Apache-2.0
os-api-ref>=1.4.0 # Apache-2.0
-openstackdocstheme>=1.18.1 # Apache-2.0
+openstackdocstheme>=1.31.2 # Apache-2.0
diff --git a/doc/source/admin/backends/akamai_v2.rst b/doc/source/admin/backends/akamai_v2.rst
new file mode 100644
index 00000000..925f9b4f
--- /dev/null
+++ b/doc/source/admin/backends/akamai_v2.rst
@@ -0,0 +1,40 @@
+..
+ Copyright 2013 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.
+
+.. _akamai_v2_backend_docs:
+
+Akamai v2 Backend
+=================
+
+This page documents using the Akamai v2 backend.
+The backend uses the FastDNS V2 API to create and delete zones remotely.
+
+Designate Configuration
+-----------------------
+
+Example configuration required:
+One section for each pool target
+
+ .. literalinclude:: sample_yaml_snippets/akamai-v2.yaml
+ :language: yaml
+
+
+Then update the pools in designate - see :ref:`designate_manage_pool`
+for further details on the ``designate-manage pool`` command
+
+.. code-block:: console
+
+ $ designate-manage pool update
+
diff --git a/doc/source/admin/backends/sample_yaml_snippets/akamai-v2.yaml b/doc/source/admin/backends/sample_yaml_snippets/akamai-v2.yaml
new file mode 100644
index 00000000..ebdcc8f8
--- /dev/null
+++ b/doc/source/admin/backends/sample_yaml_snippets/akamai-v2.yaml
@@ -0,0 +1,40 @@
+- name: default-akamai-v2
+ # The name is immutable. There will be no option to change the name after
+ # creation and the only way will to change it will be to delete it
+ # (and all zones associated with it) and recreate it.
+ description: Akamai v2
+
+ attributes: {}
+
+ # List out the NS records for zones hosted within this pool
+ ns_records:
+ - hostname: ns1-1.example.org.
+ priority: 1
+
+ # List out the nameservers for this pool. These are the actual Akamai servers.
+ # We use these to verify changes have propagated to all nameservers.
+ nameservers:
+ - host: 192.0.2.2
+ port: 53
+
+ # List out the targets for this pool. For Akamai, most often, there will be
+ # one entry for each Akamai server.
+ targets:
+ - type: akamai_v2
+ description: Akamai v2 server
+
+ # List out the designate-mdns servers from which Akamai servers should
+ # request zone transfers (AXFRs) from.
+ masters:
+ - host: 192.0.2.1
+ port: 5354
+
+ options:
+ host: 192.0.2.2
+ port: 53
+ akamai_host: 192.0.2.2
+ akamai_client_token: client_token_string
+ akamai_access_token: access_token_string
+ akamai_client_secret: client_secret_string
+ akamai_contract_id: contract_id
+ akamai_gid: group_id
diff --git a/doc/source/admin/support-matrix.ini b/doc/source/admin/support-matrix.ini
index 98393db4..1af819a2 100644
--- a/doc/source/admin/support-matrix.ini
+++ b/doc/source/admin/support-matrix.ini
@@ -49,6 +49,7 @@ backend-impl-pdns4=Power DNS 4
backend-impl-designate=Designate to Designate
backend-impl-dynect=DynECT
backend-impl-akamai=Akamai eDNS
+backend-impl-akamai_v2=Akamai DNS v2
backend-impl-infoblox-xfr=Infoblox (XFR)
backend-impl-nsd4=NSD4
backend-impl-agent=Agent
@@ -73,7 +74,11 @@ status=untested
status=untested
[backends.backend-impl-akamai]
-status=untested
+status=known-broken
+notes=Akamai has turned off the eDNS API - see https://community.akamai.com/customers/s/article/Big-Changes-Coming-to-Fast-DNS-in-2018
+
+[backends.backend-impl-akamai_v2]
+docs=akamai_v2_backend_docs
[backends.backend-impl-agent]
diff --git a/doc/source/conf.py b/doc/source/conf.py
index ffa1d137..f3cdeb01 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -13,6 +13,9 @@
import sys, os
+# oslo.config needs the following import
+import designate.conf # noqa
+
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
@@ -43,7 +46,6 @@ extensions = ['sphinx.ext.autodoc',
repository_name = 'openstack/designate'
bug_project = 'designate'
bug_tag = ''
-html_last_updated_fmt = '%Y-%m-%d %H:%M'
config_generator_config_file = '../../etc/designate/designate-config-generator.conf'
sample_config_basename = '_static/designate'
@@ -69,17 +71,6 @@ master_doc = 'index'
project = u'designate'
copyright = u'2012, Managed I.T.'
-# The version info for the project you're documenting, acts as replacement for
-# |version| and |release|, also used in various other places throughout the
-# built documents.
-#
-# The short X.Y version.
-import designate.conf # noqa
-from designate.version import version_info as designate_version
-version = designate_version.canonical_version_string()
-# The full version, including alpha/beta/rc tags.
-release = designate_version.version_string_with_vcs()
-
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
diff --git a/etc/designate/pools.yaml.sample-akamai_v2 b/etc/designate/pools.yaml.sample-akamai_v2
new file mode 100644
index 00000000..ebdcc8f8
--- /dev/null
+++ b/etc/designate/pools.yaml.sample-akamai_v2
@@ -0,0 +1,40 @@
+- name: default-akamai-v2
+ # The name is immutable. There will be no option to change the name after
+ # creation and the only way will to change it will be to delete it
+ # (and all zones associated with it) and recreate it.
+ description: Akamai v2
+
+ attributes: {}
+
+ # List out the NS records for zones hosted within this pool
+ ns_records:
+ - hostname: ns1-1.example.org.
+ priority: 1
+
+ # List out the nameservers for this pool. These are the actual Akamai servers.
+ # We use these to verify changes have propagated to all nameservers.
+ nameservers:
+ - host: 192.0.2.2
+ port: 53
+
+ # List out the targets for this pool. For Akamai, most often, there will be
+ # one entry for each Akamai server.
+ targets:
+ - type: akamai_v2
+ description: Akamai v2 server
+
+ # List out the designate-mdns servers from which Akamai servers should
+ # request zone transfers (AXFRs) from.
+ masters:
+ - host: 192.0.2.1
+ port: 5354
+
+ options:
+ host: 192.0.2.2
+ port: 53
+ akamai_host: 192.0.2.2
+ akamai_client_token: client_token_string
+ akamai_access_token: access_token_string
+ akamai_client_secret: client_secret_string
+ akamai_contract_id: contract_id
+ akamai_gid: group_id
diff --git a/lower-constraints.txt b/lower-constraints.txt
index 5952f01c..0e98199d 100644
--- a/lower-constraints.txt
+++ b/lower-constraints.txt
@@ -27,6 +27,7 @@ doc8==0.6.0
docutils==0.14
dogpile.cache==0.6.5
dulwich==0.19.0
+edgegrid-python==1.1.1
enum-compat==0.0.2
eventlet==0.18.2
extras==1.0.0
@@ -67,7 +68,7 @@ msgpack==0.5.6
munch==2.2.0
netaddr==0.7.18
netifaces==0.10.6
-openstackdocstheme==1.20.0
+openstackdocstheme==1.31.2
openstacksdk==0.12.0
os-api-ref==1.5.0
os-client-config==1.29.0
diff --git a/playbooks/legacy/designate-devstack-agent-base/post.yaml b/playbooks/legacy/designate-devstack-agent-base/post.yaml
deleted file mode 100644
index e07f5510..00000000
--- a/playbooks/legacy/designate-devstack-agent-base/post.yaml
+++ /dev/null
@@ -1,15 +0,0 @@
-- hosts: primary
- tasks:
-
- - name: Copy files from {{ ansible_user_dir }}/workspace/ on node
- synchronize:
- src: '{{ ansible_user_dir }}/workspace/'
- dest: '{{ zuul.executor.log_root }}'
- mode: pull
- copy_links: true
- verify_host: true
- rsync_opts:
- - --include=/logs/**
- - --include=*/
- - --exclude=*
- - --prune-empty-dirs
diff --git a/playbooks/legacy/designate-devstack-agent-base/run.yaml b/playbooks/legacy/designate-devstack-agent-base/run.yaml
deleted file mode 100644
index f5acb694..00000000
--- a/playbooks/legacy/designate-devstack-agent-base/run.yaml
+++ /dev/null
@@ -1,67 +0,0 @@
-- hosts: all
- name: Designate devstack agent base
- tasks:
-
- - name: Ensure legacy workspace directory
- file:
- path: '{{ ansible_user_dir }}/workspace'
- state: directory
-
- - shell:
- cmd: |
- set -e
- set -x
- cat > clonemap.yaml << EOF
- clonemap:
- - name: openstack/devstack-gate
- dest: devstack-gate
- EOF
- /usr/zuul-env/bin/zuul-cloner -m clonemap.yaml --cache-dir /opt/git \
- https://opendev.org \
- openstack/devstack-gate
- executable: /bin/bash
- chdir: '{{ ansible_user_dir }}/workspace'
- environment: '{{ zuul | zuul_legacy_vars }}'
-
- - shell:
- cmd: |
- set -e
- set -x
- cat << 'EOF' >>"/tmp/dg-local.conf"
- [[local|localrc]]
- enable_plugin designate https://opendev.org/openstack/designate
- DESIGNATE_SERVICE_PORT_DNS=5322
- DESIGNATE_BACKEND_DRIVER=agent
- DESIGNATE_AGENT_BACKEND_DRIVER={{ backend_driver }}
-
- EOF
- executable: /bin/bash
- chdir: '{{ ansible_user_dir }}/workspace'
- environment: '{{ zuul | zuul_legacy_vars }}'
-
- - shell:
- cmd: |
- set -e
- set -x
- export PYTHONUNBUFFERED=true
-
- export DEVSTACK_GATE_TEMPEST=1
- export DEVSTACK_GATE_TEMPEST_ALL_PLUGINS=1
- export DEVSTACK_GATE_TEMPEST_REGEX=designate
- export DEVSTACK_GATE_USE_PYTHON3=True
-
- export PROJECTS="openstack/designate $PROJECTS"
- export PROJECTS="openstack/python-designateclient $PROJECTS"
- export PROJECTS="openstack/designate-dashboard $PROJECTS"
- export PROJECTS="openstack/designate-tempest-plugin $PROJECTS"
-
- export BRANCH_OVERRIDE=default
- if [ "$BRANCH_OVERRIDE" != "default" ] ; then
- export OVERRIDE_ZUUL_BRANCH=$BRANCH_OVERRIDE
- fi
-
- cp devstack-gate/devstack-vm-gate-wrap.sh ./safe-devstack-vm-gate-wrap.sh
- ./safe-devstack-vm-gate-wrap.sh
- executable: /bin/bash
- chdir: '{{ ansible_user_dir }}/workspace'
- environment: '{{ zuul | zuul_legacy_vars }}'
diff --git a/playbooks/legacy/designate-devstack-base/post.yaml b/playbooks/legacy/designate-devstack-base/post.yaml
deleted file mode 100644
index e07f5510..00000000
--- a/playbooks/legacy/designate-devstack-base/post.yaml
+++ /dev/null
@@ -1,15 +0,0 @@
-- hosts: primary
- tasks:
-
- - name: Copy files from {{ ansible_user_dir }}/workspace/ on node
- synchronize:
- src: '{{ ansible_user_dir }}/workspace/'
- dest: '{{ zuul.executor.log_root }}'
- mode: pull
- copy_links: true
- verify_host: true
- rsync_opts:
- - --include=/logs/**
- - --include=*/
- - --exclude=*
- - --prune-empty-dirs
diff --git a/playbooks/legacy/designate-devstack-base/run.yaml b/playbooks/legacy/designate-devstack-base/run.yaml
deleted file mode 100644
index 3d3ee1ab..00000000
--- a/playbooks/legacy/designate-devstack-base/run.yaml
+++ /dev/null
@@ -1,81 +0,0 @@
-- hosts: all
- name: Autoconverted job legacy-tempest-dsvm-designate-pdns4 from old job gate-tempest-dsvm-designate-pdns4-ubuntu-xenial
- tasks:
-
- - name: Ensure legacy workspace directory
- file:
- path: '{{ ansible_user_dir }}/workspace'
- state: directory
-
- - shell:
- cmd: |
- set -e
- set -x
- cat > clonemap.yaml << EOF
- clonemap:
- - name: openstack/devstack-gate
- dest: devstack-gate
- EOF
- /usr/zuul-env/bin/zuul-cloner -m clonemap.yaml --cache-dir /opt/git \
- https://opendev.org \
- openstack/devstack-gate
- executable: /bin/bash
- chdir: '{{ ansible_user_dir }}/workspace'
- environment: '{{ zuul | zuul_legacy_vars }}'
-
- - shell:
- cmd: |
- set -e
- set -x
- export PYTHONUNBUFFERED=true
-
- services=rabbit,tempest,mysql,dstat,key
- services+=,n-api,n-api-meta,n-cpu,n-cond,n-sch,n-crt
- # placement service mandatory for nova from ocata
- if [[ "stable/newton" != $ZUUL_BRANCH ]]; then
- services+=,placement-api
- fi
- services+=,g-api,g-reg
- services+=,c-sch,c-api,c-vol,c-bak
- services+=,q-svc,q-dhcp,q-meta,q-agt,q-l3
-
- export DEVSTACK_GATE_TEMPEST=1
- export DEVSTACK_GATE_TEMPEST_ALL_PLUGINS=1
- export DEVSTACK_GATE_TEMPEST_REGEX=designate
-
- export DEVSTACK_LOCAL_CONFIG="enable_plugin designate https://opendev.org/openstack/designate"
- export DEVSTACK_LOCAL_CONFIG+=$'\n'"DESIGNATE_SERVICE_PORT_DNS=5322"
- export DEVSTACK_LOCAL_CONFIG+=$'\n'"DESIGNATE_BACKEND_DRIVER={{ backend }}"
-
- export PROJECTS="openstack/designate $PROJECTS"
- export PROJECTS="openstack/python-designateclient $PROJECTS"
- export PROJECTS="openstack/designate-dashboard $PROJECTS"
- export PROJECTS="openstack/designate-tempest-plugin $PROJECTS"
-
- export BRANCH_OVERRIDE=default
- if [ "$BRANCH_OVERRIDE" != "default" ] ; then
- export OVERRIDE_ZUUL_BRANCH=$BRANCH_OVERRIDE
- fi
-
- if [ "{{ identity_v3_only }}" == "1" ] ; then
- export DEVSTACK_LOCAL_CONFIG+=$'\n'"ENABLE_IDENTITY_V2=False"
- fi
-
- if [ "{{ manager_model }}" == "1" ] ; then
- export DEVSTACK_LOCAL_CONFIG+=$'\n'"disable_service designate-worker designate-producer"
- export DEVSTACK_LOCAL_CONFIG+=$'\n'"enable_service designate-pool-manager designate-zone-manager"
- fi
-
- export OVERRIDE_ENABLED_SERVICES=$services
-
- if [ "{{ database }}" == "postgres" ] ; then
- export DEVSTACK_GATE_POSTGRES=1
- fi
-
- export DEVSTACK_GATE_USE_PYTHON3=True
-
- cp devstack-gate/devstack-vm-gate-wrap.sh ./safe-devstack-vm-gate-wrap.sh
- ./safe-devstack-vm-gate-wrap.sh
- executable: /bin/bash
- chdir: '{{ ansible_user_dir }}/workspace'
- environment: '{{ zuul | zuul_legacy_vars }}'
diff --git a/playbooks/legacy/grenade-devstack-designate-pdns4/run.yaml b/playbooks/legacy/grenade-devstack-designate-pdns4/run.yaml
index ae522f7a..7d10569f 100644
--- a/playbooks/legacy/grenade-devstack-designate-pdns4/run.yaml
+++ b/playbooks/legacy/grenade-devstack-designate-pdns4/run.yaml
@@ -31,6 +31,7 @@
[[local|localrc]]
DESIGNATE_SERVICE_PORT_DNS=5322
DESIGNATE_BACKEND_DRIVER=pdns4
+ TEMPEST_PLUGINS=' ../designate-tempest-plugin'
EOF
executable: /bin/bash
@@ -45,7 +46,6 @@
export DEVSTACK_GATE_USE_PYTHON3=True
export DEVSTACK_GATE_TEMPEST=1
- export DEVSTACK_GATE_TEMPEST_ALL_PLUGINS=1
export DEVSTACK_GATE_GRENADE=pullup
export DEVSTACK_GATE_TEMPEST_REGEX="designate_tempest_plugin(?!\.tests.api.v1).*"
export DEVSTACK_GATE_HORIZON=1
diff --git a/releasenotes/notes/akamai-v2-5a3edb35f59a17c2.yaml b/releasenotes/notes/akamai-v2-5a3edb35f59a17c2.yaml
new file mode 100644
index 00000000..56245b0e
--- /dev/null
+++ b/releasenotes/notes/akamai-v2-5a3edb35f59a17c2.yaml
@@ -0,0 +1,8 @@
+---
+features:
+ - |
+ Added new backend driver to support the Akamai FastDNS V2 API
+upgrade:
+ - |
+ The Akamai eDNS API endpoint has been removed, users should migrate to
+ the Akamai v2 driver for the FastDNS V2 API. \ No newline at end of file
diff --git a/releasenotes/notes/drop-py-2-7-737ea2547cb7ea06.yaml b/releasenotes/notes/drop-py-2-7-737ea2547cb7ea06.yaml
index 80922804..6ed0a77a 100644
--- a/releasenotes/notes/drop-py-2-7-737ea2547cb7ea06.yaml
+++ b/releasenotes/notes/drop-py-2-7-737ea2547cb7ea06.yaml
@@ -1,6 +1,7 @@
---
prelude: >
- Drop the Python 2.7 support.
+ Python 2.7 will not be maintained past 2020 and because of that we are
+ dropping Python 2 support.
upgrade:
- |
Python 2.7 support has been dropped. Last release of Designate
diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py
index 67268519..8b1de7cf 100644
--- a/releasenotes/source/conf.py
+++ b/releasenotes/source/conf.py
@@ -45,7 +45,6 @@ extensions = [
repository_name = 'openstack/designate'
bug_project = 'designate'
bug_tag = ''
-html_last_updated_fmt = '%Y-%m-%d %H:%M'
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
diff --git a/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po b/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po
index 336a55b4..dcd94650 100644
--- a/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po
+++ b/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po
@@ -5,7 +5,7 @@ msgid ""
msgstr ""
"Project-Id-Version: designate\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-12-25 00:23+0000\n"
+"POT-Creation-Date: 2020-03-08 23:30+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
@@ -43,15 +43,12 @@ msgstr "6.0.0"
msgid "7.0.0"
msgstr "7.0.0"
-msgid "7.0.0-17"
-msgstr "7.0.0-17"
+msgid "7.0.0-19"
+msgstr "7.0.0-19"
msgid "8.0.0"
msgstr "8.0.0"
-msgid "8.0.0-12"
-msgstr "8.0.0-12"
-
msgid "9.0.0"
msgstr "9.0.0"
@@ -300,9 +297,6 @@ msgstr ""
"alternative to specifying several topics for notification consumers in "
"configuration of service that emits notifications."
-msgid "Drop the Python 2.7 support."
-msgstr "Drop the Python 2.7 support."
-
msgid ""
"Fixed `bug 1768824`_ which could cause the service_statuses table to be "
"flooded with duplicate service entries."
diff --git a/requirements.txt b/requirements.txt
index 9445351f..ccdae9f7 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -49,3 +49,4 @@ debtcollector>=1.2.0 # Apache-2.0
os-win>=3.0.0 # Apache-2.0
monasca-statsd>=1.1.0 # Apache-2.0
futurist>=1.2.0 # Apache-2.0
+edgegrid-python>=1.1.1 # Apache-2.0
diff --git a/setup.cfg b/setup.cfg
index 3495e4ab..94519a3e 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -75,6 +75,7 @@ designate.backend =
pdns4 = designate.backend.impl_pdns4:PDNS4Backend
dynect = designate.backend.impl_dynect:DynECTBackend
akamai = designate.backend.impl_akamai:AkamaiBackend
+ akamai_v2 = designate.backend.impl_akamai_v2:AkamaiBackend
nsd4 = designate.backend.impl_nsd4:NSD4Backend
infoblox = designate.backend.impl_infoblox:InfobloxBackend
fake = designate.backend.impl_fake:FakeBackend