summaryrefslogtreecommitdiff
path: root/designate/backend/impl_akamai_v2.py
diff options
context:
space:
mode:
Diffstat (limited to 'designate/backend/impl_akamai_v2.py')
-rw-r--r--designate/backend/impl_akamai_v2.py199
1 files changed, 199 insertions, 0 deletions
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'])