diff --git a/designate/backend/impl_akamai_v2.py b/designate/backend/impl_akamai_v2.py new file mode 100644 index 000000000..407f81f6b --- /dev/null +++ b/designate/backend/impl_akamai_v2.py @@ -0,0 +1,199 @@ +# Copyright 2019 Cloudification GmbH +# +# Author: Sergey Kraynev +# +# 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/tests/unit/backend/test_akamai_v2.py b/designate/tests/unit/backend/test_akamai_v2.py new file mode 100644 index 000000000..f3dcfa35f --- /dev/null +++ b/designate/tests/unit/backend/test_akamai_v2.py @@ -0,0 +1,494 @@ +# Copyright 2019 Cloudification GmbH +# +# Author: Sergey Kraynev +# +# 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/devstack/designate_plugins/backend-akamai-v2 b/devstack/designate_plugins/backend-akamai-v2 new file mode 100644 index 000000000..ce6a2198b --- /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 < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null < /dev/null <=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 3495e4ab9..94519a3e2 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