designate/designate/backend/impl_akamai.py
Kiall Mac Innes eeeb1dafc3 Only load Suds when using Akamai backend
Rather than always loading suds, and emitting scary warnings when it's
not installed, we should load it only when needed.

Change-Id: Ia3af48c5126e724e08fa5513d463d239027a7511
2016-06-07 16:33:09 +01:00

275 lines
8.9 KiB
Python

# Copyright 2012-2015 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 os
from oslo_log import log as logging
from oslo_config import cfg
from oslo_utils import importutils
from designate import exceptions
from designate import utils
from designate.backend import base
try:
SudsClient = importutils.import_class("suds.client.Client")
HttpAuthenticated = importutils.import_class(
"suds.transport.https.HttpAuthenticated")
except ImportError:
SudsClient = None
class HttpAuthenticated(object):
pass
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
WSDL = os.path.join(os.path.dirname(__file__),
'..',
'resources',
'wsdl',
'EnhancedDNS.xml')
class EnhancedDNSException(exceptions.Backend):
pass
class DelegationExists(exceptions.BadRequest, EnhancedDNSException):
"""
Raised when an attempt to delete a zone which is still delegated to Akamai
is made
"""
error_type = 'delegation_exists'
class DuplicateZone(exceptions.DuplicateZone, EnhancedDNSException):
"""
Raised when an attempt to create a zone which is registered to another
Akamai account is made
"""
pass
class Forbidden(exceptions.Forbidden, EnhancedDNSException):
"""
Raised when an attempt to modify a zone which is registered to another
Akamai account is made.
This appears to be returned when creating a new subzone of zone which
already exists in another Akamai account.
"""
pass
class EnhancedDNSHttpAuthenticated(HttpAuthenticated):
def addenhanceddnsheaders(self, request):
request.headers['Pragma'] = ('akamai-x-get-request-id, '
'akamai-x-cache-on, '
'akamai-x-cache-remote-on, '
'akamai-x-get-cache-key')
def logenhanceddnsheaders(self, response):
request_id = response.headers.get('x-akamai-request-id', '-')
cache = response.headers.get('x-cache', '-')
cache_key = response.headers.get('x-cache-key', '-')
cache_remote = response.headers.get('x-cache-remote', '-')
LOG.debug('Akamai Request-ID: %s, Cache-Key: %s, Cache: %s, '
'Cache-Remote: %s', request_id, cache_key, cache,
cache_remote)
def send(self, request):
self.addenhanceddnsheaders(request)
response = HttpAuthenticated.send(self, request)
self.logenhanceddnsheaders(response)
return response
class EnhancedDNSClient(object):
"""EnhancedDNS SOAP API Client"""
def __init__(self, username, password):
# Ensure Suds (or suds-jerko) have been installed
if SudsClient is None:
raise EnhancedDNSException(
"Dependancy missing, please install suds or suds-jurko")
# Prepare a SUDS transport with the approperiate credentials
transport = EnhancedDNSHttpAuthenticated(
username=username,
password=password,
proxy=utils.get_proxies())
# Prepare a SUDS client
self.client = SudsClient(CONF['backend:akamai'].enhanceddns_wsdl,
transport=transport)
def buildZone(self, zoneName, masters, endCustomerId, tsigKeyName=None,
tsigKey=None, tsigAlgorithm=None):
zone = self.client.factory.create('ns3:Zone')
# Set some defaults
zone.transferMode = "axfr"
zone.type = "edns"
zone.notify = 1
zone.dnssec = False
# Set the remaining options
zone.zoneName = self._sanitizeZoneName(zoneName)
zone.masters = masters
zone.tsigKeyName = tsigKeyName
zone.tsigKey = tsigKey
zone.tsigAlgorithm = tsigAlgorithm
zone.endCustomerId = endCustomerId
return zone
def getZone(self, zoneName):
LOG.debug("Performing getZone with zoneName: %s" % zoneName)
zoneName = self._sanitizeZoneName(zoneName)
try:
return self.client.service.getZone(zoneName=zoneName)
except Exception as e:
raise EnhancedDNSException('Akamai Communication Failure: %s' % e)
def setZones(self, zones):
LOG.debug("Performing setZones")
try:
return self.client.service.setZones(zones=zones)
except Exception as e:
if 'You do not have permission to view this zone' in str(e):
raise DuplicateZone()
elif 'You do not have access to edit this zone' in str(e):
raise Forbidden()
elif 'basic auth failed' in str(e):
raise exceptions.ConfigurationError(
'Invalid Akamai credentials')
else:
raise EnhancedDNSException('Akamai Communication Failure: %s'
% e)
def setZone(self, zone):
LOG.debug("Performing setZone with zoneName: %s" % zone.zoneName)
try:
self.client.service.setZone(zone=zone)
except Exception as e:
if 'You do not have permission to view this zone' in str(e):
raise DuplicateZone()
elif 'You do not have access to edit this zone' in str(e):
raise Forbidden()
elif 'basic auth failed' in str(e):
raise exceptions.ConfigurationError(
'Invalid Akamai credentials')
else:
raise EnhancedDNSException('Akamai Communication Failure: %s'
% e)
def deleteZone(self, zoneName):
LOG.debug("Performing deleteZone with zoneName: %s", zoneName)
zoneName = self._sanitizeZoneName(zoneName)
self.deleteZones(zoneNames=[zoneName])
def deleteZones(self, zoneNames):
LOG.debug("Performing deleteZones with zoneNames: %r", zoneNames)
zoneNames = [self._sanitizeZoneName(zN) for zN in zoneNames]
try:
self.client.service.deleteZones(zoneNames=zoneNames)
except Exception as e:
if 'Could not retrive object ID for zone' in str(e):
# The zone has already been purged, ignore and move on
pass
elif 'The following zones are still delegated to Akamai' in str(e):
raise DelegationExists()
elif 'basic auth failed' in str(e):
raise exceptions.ConfigurationError(
'Invalid Akamai credentials')
else:
raise EnhancedDNSException('Akamai Communication Failure: %s'
% e)
def _sanitizeZoneName(self, zoneName):
return zoneName.rstrip('.').lower()
def build_zone(client, target, zone):
masters = [m.host for m in target.masters]
if target.options.get("tsig_key_name", None):
return client.buildZone(
zone.name,
masters,
zone.id,
target.options["tsig_key_name"],
target.options.get("tsig_key_secret", None),
target.options.get("tsig_key_algorithm", None))
else:
return client.buildZone(
zone.name,
masters,
zone.id)
class AkamaiBackend(base.Backend):
__plugin_name__ = 'akamai'
__backend_status__ = 'release-compatible'
@classmethod
def get_cfg_opts(cls):
group = cfg.OptGroup(
name='backend:akamai', title='Backend options for Akamai'
)
opts = [
cfg.StrOpt('enhanceddns_wsdl',
default='file://%s' % WSDL,
help='Akamai EnhancedDNS WSDL URL'),
]
return [(group, opts)]
def __init__(self, target):
super(AkamaiBackend, self).__init__(target)
self.username = self.options.get('username')
self.password = self.options.get('password')
self.client = EnhancedDNSClient(self.username, self.password)
for m in self.masters:
if m.port != 53:
raise exceptions.ConfigurationError(
"Akamai only supports mDNS instances on port 53")
def create_zone(self, context, zone):
"""Create a DNS zone"""
zone = build_zone(self.client, self.target, zone)
self.client.setZone(zone=zone)
def delete_zone(self, context, zone):
"""Delete a DNS zone"""
self.client.deleteZone(zoneName=zone['name'])