Add backend for Designate using SECONDARY zones
This will allow the use of a Designate installation as a backend using the recently added SECONDARY zones feature. The patch includes bits for devstack and tests. Implement blueprint: d2d-driver Change-Id: I5fcaf36482cf692432f7871ef08b2ae7fefe749a
This commit is contained in:
parent
746fa57eae
commit
e90dbe1ae5
136
contrib/devstack/lib/designate_plugins/backend-designate
Normal file
136
contrib/devstack/lib/designate_plugins/backend-designate
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
# lib/designate_plugins/backend-designate
|
||||||
|
# Configure the designate backend
|
||||||
|
|
||||||
|
# Requirements:
|
||||||
|
# Another Designate service is needed in order to install the SECONDARY zones in it.
|
||||||
|
|
||||||
|
# Enable with:
|
||||||
|
# DESIGNATE_BACKEND_DRIVER=designate
|
||||||
|
|
||||||
|
# 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_D2D_XTRACE=$(set +o | grep xtrace)
|
||||||
|
set +o xtrace
|
||||||
|
|
||||||
|
# Defaults
|
||||||
|
# --------
|
||||||
|
|
||||||
|
# This is the Primary Designate MDNS servers.
|
||||||
|
DESIGNATE_D2D_MASTERS=${DESIGNATE_D2D_MASTERS:-""}
|
||||||
|
|
||||||
|
# DNS server to notify (MiniDNS ip:port)
|
||||||
|
DESIGNATE_D2D_ALSO_NOTIES=${DESIGNATE_D2D_ALSO_NOTIES:-""}
|
||||||
|
|
||||||
|
# DNS server to check SOA etc against
|
||||||
|
DESIGNATE_D2D_NAMESERVERS=${DESIGNATE_D2D_NAMESERVERS:-""}
|
||||||
|
|
||||||
|
# Destination openstack credentials
|
||||||
|
DESIGNATE_D2D_KS_VERSION=${DESIGNATE_D2D_KS_VERSION:-3}
|
||||||
|
|
||||||
|
DESIGNATE_D2D_AUTH_URL=${DESIGNATE_D2D_AUTH_URL:-}
|
||||||
|
DESIGNATE_D2D_USERNAME=${DESIGNATE_D2D_USERNAME:-}
|
||||||
|
DESIGNATE_D2D_PASSWORD=${DESIGNATE_D2D_PASSWORD:-}
|
||||||
|
|
||||||
|
# Keystone V2
|
||||||
|
DESIGNATE_D2D_TENANT_NAME=${DESIGNATE_D2D_TENANT_NAME:-}
|
||||||
|
DESIGNATE_D2D_TENANT_NAME=${DESIGNATE_D2D_TENANT_ID:-}
|
||||||
|
|
||||||
|
# Keystone V3
|
||||||
|
DESIGNATE_D2D_PROJECT_NAME=${DESIGNATE_D2D_PROJECT_NAME:-}
|
||||||
|
DESIGNATE_D2D_PROJECT_DOMAIN_NAME=${DESIGNATE_D2D_PROJECT_DOMAIN_NAME:-}
|
||||||
|
DESIGNATE_D2D_USER_DOMAIN_NAME=${DESIGNATE_D2D_USER_DOMAIN_NAME:-}
|
||||||
|
|
||||||
|
|
||||||
|
# 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 {
|
||||||
|
iniset $DESIGNATE_CONF pool_target:$DESIGNATE_TARGET_ID type designate
|
||||||
|
iniset $DESIGNATE_CONF pool_target:$DESIGNATE_TARGET_ID masters $DESIGNATE_D2D_MASTERS
|
||||||
|
|
||||||
|
options="auth_url: $DESIGNATE_D2D_AUTH_URL, username: $DESIGNATE_D2D_USERNAME, password: $DESIGNATE_D2D_PASSWORD,"
|
||||||
|
if [ "$DESIGNATE_D2D_KS_VERSION" == "2" ]; then
|
||||||
|
if [ ! -z "$DESIGNATE_D2D_TENANT_NAME" ]; then
|
||||||
|
options="$options tenant_name=$DESIGNATE_D2D_TENANT_NAME,"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -z "$DESIGNATE_D2D_TENANT_ID" ]; then
|
||||||
|
options="$options tenant_id=$DESIGNATE_D2D_TENANT_ID,"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -z "$DESIGNATE_D2D_KS_VERSION" == "3" ]; then
|
||||||
|
options="$options project_name: $DESIGNATE_D2D_PROJECT_NAME, project_domain_name=$DESIGNATE_D2D_PROJECT_DOMAIN_NAME, user_domain_name=$DESIGNATE_D2D_USER_DOMAIN_NAME"
|
||||||
|
fi
|
||||||
|
|
||||||
|
iniset $DESIGNATE_CONF pool_target:$DESIGNATE_TARGET_ID options $options
|
||||||
|
|
||||||
|
# Create a Pool Nameserver for each of the Designate nameservers
|
||||||
|
local nameserver_ids=""
|
||||||
|
IFS=',' read -a nameservers <<< "$DESIGNATE_D2D_NAMESERVERS"
|
||||||
|
|
||||||
|
for nameserver in "${nameservers[@]}"; do
|
||||||
|
local nameserver_id=`uuidgen`
|
||||||
|
iniset $DESIGNATE_CONF pool_nameserver:$nameserver_id host $(dig +short A $nameserver | head -n 1)
|
||||||
|
iniset $DESIGNATE_CONF pool_nameserver:$nameserver_id port 53
|
||||||
|
|
||||||
|
# Append the Nameserver ID to the list
|
||||||
|
nameserver_ids+=${nameserver_id},
|
||||||
|
done
|
||||||
|
|
||||||
|
# Configure the Pool for the set of nameserver IDs, minus the trailing comma
|
||||||
|
iniset $DESIGNATE_CONF pool:$DESIGNATE_POOL_ID nameservers "${nameserver_ids:0:-1}"
|
||||||
|
|
||||||
|
# Configure the Pool to Notify the destination Mdns
|
||||||
|
iniset $DESIGNATE_CONF pool:$DESIGNATE_POOL_ID also_notifies "$DESIGNATE_D2D_ALSO_NOTIFIES"
|
||||||
|
}
|
||||||
|
|
||||||
|
# create_designate_ns_records - Create Pool NS Records
|
||||||
|
function create_designate_ns_records_backend {
|
||||||
|
# Build an array of the Designate nameservers.
|
||||||
|
IFS=',' read -a ns_records <<< "$DESIGNATE_D2D_NAMESERVERS"
|
||||||
|
|
||||||
|
# Create a NS Record for each of the Designate nameservers
|
||||||
|
for ns_record in "${ns_records[@]}"; do
|
||||||
|
designate server-create --name "${ns_record%%.}."
|
||||||
|
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_D2D_XTRACE
|
@ -56,6 +56,33 @@ ENABLED_SERVICES+=,designate,designate-central,designate-api,designate-pool-mana
|
|||||||
#DESIGNATE_AKAMAI_NAMESERVERS=a5-64.akam.net,a11-65.akam.net,a13-66.akam.net,a14-64.akam.net,a20-65.akam.net,a22-66.akam.net
|
#DESIGNATE_AKAMAI_NAMESERVERS=a5-64.akam.net,a11-65.akam.net,a13-66.akam.net,a14-64.akam.net,a20-65.akam.net,a22-66.akam.net
|
||||||
#DESIGNATE_AKAMAI_MASTERS=
|
#DESIGNATE_AKAMAI_MASTERS=
|
||||||
|
|
||||||
|
# Designate D2D Backend
|
||||||
|
# NOTEs:
|
||||||
|
# - DESIGNATE_D2D_ALSO_NOTIFIES needs to be set to the source mdns ip:port in
|
||||||
|
# order for designate to receive the proper NOTIFY
|
||||||
|
# - DESIGNATE_D2D_* credentials should be setup either to the source keystone
|
||||||
|
# or the destination
|
||||||
|
#DESIGNATE_D2D_MASTERS=
|
||||||
|
#DESIGNATE_D2D_ALSO_NOTIFIES=
|
||||||
|
#DESIGNATE_D2D_NAMESERVERS=
|
||||||
|
|
||||||
|
# Authentication options
|
||||||
|
#DESIGNATE_D2D_KS_VERSION=3
|
||||||
|
|
||||||
|
#DESIGNATE_D2D_AUTH_URL=
|
||||||
|
#DESIGNATE_D2D_USERNAME=
|
||||||
|
#DESIGNATE_D2D_PASSWORD=
|
||||||
|
|
||||||
|
# Keystone V2
|
||||||
|
#DESIGNATE_D2D_TENANT_NAME=${DESIGNATE_D2D_TENANT_NAME:-}
|
||||||
|
#DESIGNATE_D2D_TENANT_NAME=${DESIGNATE_D2D_TENANT_ID:-}
|
||||||
|
|
||||||
|
# Keystone V3
|
||||||
|
#DESIGNATE_D2D_PROJECT_NAME=
|
||||||
|
#DESIGNATE_D2D_PROJECT_DOMAIN_NAME=
|
||||||
|
#DESIGNATE_D2D_USER_DOMAIN_NAME=
|
||||||
|
|
||||||
|
|
||||||
# Designate Misc Config
|
# Designate Misc Config
|
||||||
# =====================
|
# =====================
|
||||||
|
|
||||||
|
106
designate/backend/impl_designate.py
Normal file
106
designate/backend/impl_designate.py
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||||
|
#
|
||||||
|
# Author: Endre Karlson <endre.karlson@hp.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.
|
||||||
|
from designateclient.v2 import client
|
||||||
|
from designateclient import exceptions
|
||||||
|
from keystoneclient.auth.identity import v2 as v2_auth
|
||||||
|
from keystoneclient.auth.identity import v3 as v3_auth
|
||||||
|
from keystoneclient import session as ks_session
|
||||||
|
from oslo_config import cfg
|
||||||
|
from oslo_log import log as logging
|
||||||
|
|
||||||
|
from designate.backend import base
|
||||||
|
from designate.i18n import _LI
|
||||||
|
from designate.i18n import _LW
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
CONF = cfg.CONF
|
||||||
|
CFG_GROUP = 'backend:designate'
|
||||||
|
|
||||||
|
|
||||||
|
class DesignateBackend(base.Backend):
|
||||||
|
"""
|
||||||
|
Support for Designate to Designate using Secondary zones.
|
||||||
|
"""
|
||||||
|
__plugin_name__ = 'designate'
|
||||||
|
__backend_status__ = 'release-compatible'
|
||||||
|
|
||||||
|
def __init__(self, target):
|
||||||
|
super(DesignateBackend, self).__init__(target)
|
||||||
|
|
||||||
|
self.auth_url = self.options.get('auth_url')
|
||||||
|
self.username = self.options.get('username')
|
||||||
|
self.password = self.options.get('password')
|
||||||
|
|
||||||
|
# ks v2
|
||||||
|
self.tenant_name = self.options.get('tenant_name')
|
||||||
|
self.tenant_id = self.options.get('tenant_id')
|
||||||
|
|
||||||
|
# ks v3
|
||||||
|
self.project_name = self.options.get('project_name')
|
||||||
|
self.project_domain_name = self.options.get(
|
||||||
|
'project_domain_name', 'default')
|
||||||
|
self.user_domain_name = self.options.get('user_domain_name', 'default')
|
||||||
|
self.service_type = self.options.get('service_type', 'dns')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def client(self):
|
||||||
|
return self._get_client()
|
||||||
|
|
||||||
|
def _get_client(self):
|
||||||
|
if self._client is not None:
|
||||||
|
return self._client
|
||||||
|
|
||||||
|
if (self.tenant_id is not None or self.tenant_name is not None):
|
||||||
|
auth = v2_auth.Password(
|
||||||
|
auth_url=self.auth_url,
|
||||||
|
username=self.username,
|
||||||
|
password=self.password,
|
||||||
|
tenant_id=self.tenant_id,
|
||||||
|
tenant_name=self.tenant_name)
|
||||||
|
elif self.project_name is not None:
|
||||||
|
auth = v3_auth.Password(
|
||||||
|
auth_url=self.auth_url,
|
||||||
|
username=self.username,
|
||||||
|
password=self.password,
|
||||||
|
project_name=self.project_name,
|
||||||
|
project_domain_name=self.project_domain_name,
|
||||||
|
user_domain_name=self.user_domain_name)
|
||||||
|
else:
|
||||||
|
auth = None
|
||||||
|
|
||||||
|
session = ks_session.Session(auth=auth)
|
||||||
|
self._client = client.Client(
|
||||||
|
session=session, service_type=self.service_type)
|
||||||
|
return self._client
|
||||||
|
|
||||||
|
def create_domain(self, context, domain):
|
||||||
|
msg = _LI('Creating domain %(d_id)s / %(d_name)s')
|
||||||
|
LOG.info(msg, {'d_id': domain['id'], 'd_name': domain['name']})
|
||||||
|
|
||||||
|
masters = ["%s:%s" % (i.host, i.port) for i in self.masters]
|
||||||
|
self.client.zones.create(
|
||||||
|
domain.name, 'SECONDARY', masters=masters)
|
||||||
|
|
||||||
|
def delete_domain(self, context, domain):
|
||||||
|
msg = _LI('Deleting domain %(d_id)s / %(d_name)s')
|
||||||
|
LOG.info(msg, {'d_id': domain['id'], 'd_name': domain['name']})
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.client.zones.delete(domain.name)
|
||||||
|
except exceptions.NotFound:
|
||||||
|
msg = _LW("Zone %s not found on remote Designate, Ignoring")
|
||||||
|
LOG.warn(msg, domain.id)
|
0
designate/tests/unit/test_backend/__init__.py
Normal file
0
designate/tests/unit/test_backend/__init__.py
Normal file
120
designate/tests/unit/test_backend/test_designate.py
Normal file
120
designate/tests/unit/test_backend/test_designate.py
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||||
|
#
|
||||||
|
# Author: Endre Karlson <endre.karlson@hp.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 uuid
|
||||||
|
|
||||||
|
|
||||||
|
from designateclient import exceptions
|
||||||
|
from mock import patch
|
||||||
|
from mock import NonCallableMagicMock
|
||||||
|
from mock import Mock
|
||||||
|
from oslo_log import log as logging
|
||||||
|
import oslotest.base
|
||||||
|
import testtools
|
||||||
|
|
||||||
|
from designate import objects
|
||||||
|
from designate.backend import impl_designate
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def create_zone():
|
||||||
|
id_ = str(uuid.uuid4())
|
||||||
|
return objects.Domain(
|
||||||
|
id=id_,
|
||||||
|
name='%s-example.com.' % id_,
|
||||||
|
email='root@example.com',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class RoObject(dict):
|
||||||
|
def __setitem__(self, *a):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def __setattr__(self, *a):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def __getattr__(self, k):
|
||||||
|
return self[k]
|
||||||
|
|
||||||
|
|
||||||
|
class DesignateBackendTest(oslotest.base.BaseTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(DesignateBackendTest, self).setUp()
|
||||||
|
opts = RoObject(
|
||||||
|
username='user',
|
||||||
|
password='secret',
|
||||||
|
project_name='project',
|
||||||
|
project_domain_name='project_domain',
|
||||||
|
user_domain_name='user_domain'
|
||||||
|
)
|
||||||
|
self.target = RoObject({
|
||||||
|
'id': '4588652b-50e7-46b9-b688-a9bad40a873e',
|
||||||
|
'type': 'dyndns',
|
||||||
|
'masters': [RoObject({'host': '192.0.2.1', 'port': 53})],
|
||||||
|
'options': opts
|
||||||
|
})
|
||||||
|
|
||||||
|
# Backends blow up when trying to self.admin_context = ... due to
|
||||||
|
# policy not being initialized
|
||||||
|
self.admin_context = Mock()
|
||||||
|
get_context_patcher = patch(
|
||||||
|
'designate.context.DesignateContext.get_admin_context')
|
||||||
|
get_context = get_context_patcher.start()
|
||||||
|
get_context.return_value = self.admin_context
|
||||||
|
|
||||||
|
self.backend = impl_designate.DesignateBackend(self.target)
|
||||||
|
|
||||||
|
# Mock client
|
||||||
|
self.client = NonCallableMagicMock()
|
||||||
|
zones = NonCallableMagicMock(spec_set=[
|
||||||
|
'create', 'delete'])
|
||||||
|
self.client.configure_mock(zones=zones)
|
||||||
|
|
||||||
|
def test_create_domain(self):
|
||||||
|
zone = create_zone()
|
||||||
|
masters = ["%(host)s:%(port)s" % self.target.masters[0]]
|
||||||
|
with patch.object(
|
||||||
|
self.backend, '_get_client', return_value=self.client):
|
||||||
|
self.backend.create_domain(self.admin_context, zone)
|
||||||
|
self.client.zones.create.assert_called_once_with(
|
||||||
|
zone.name, 'SECONDARY', masters=masters)
|
||||||
|
|
||||||
|
def test_delete_domain(self):
|
||||||
|
zone = create_zone()
|
||||||
|
with patch.object(
|
||||||
|
self.backend, '_get_client', return_value=self.client):
|
||||||
|
self.backend.delete_domain(self.admin_context, zone)
|
||||||
|
self.client.zones.delete.assert_called_once_with(zone.name)
|
||||||
|
|
||||||
|
def test_delete_domain_notfound(self):
|
||||||
|
zone = create_zone()
|
||||||
|
self.client.delete.side_effect = exceptions.NotFound
|
||||||
|
with patch.object(
|
||||||
|
self.backend, '_get_client', return_value=self.client):
|
||||||
|
self.backend.delete_domain(self.admin_context, zone)
|
||||||
|
self.client.zones.delete.assert_called_once_with(zone.name)
|
||||||
|
|
||||||
|
def test_delete_domain_exc(self):
|
||||||
|
class Exc(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
zone = create_zone()
|
||||||
|
self.client.zones.delete.side_effect = Exc()
|
||||||
|
with testtools.ExpectedException(Exc):
|
||||||
|
with patch.object(
|
||||||
|
self.backend, '_get_client', return_value=self.client):
|
||||||
|
self.backend.delete_domain(self.admin_context, zone)
|
||||||
|
self.client.zones.delete.assert_called_once_with(zone.name)
|
@ -24,6 +24,7 @@ Paste
|
|||||||
PasteDeploy>=1.5.0
|
PasteDeploy>=1.5.0
|
||||||
pbr<2.0,>=1.6
|
pbr<2.0,>=1.6
|
||||||
pecan>=1.0.0
|
pecan>=1.0.0
|
||||||
|
python-designateclient>=1.0.0
|
||||||
python-neutronclient>=2.6.0
|
python-neutronclient>=2.6.0
|
||||||
Routes!=2.0,!=2.1,>=1.12.3;python_version=='2.7'
|
Routes!=2.0,!=2.1,>=1.12.3;python_version=='2.7'
|
||||||
Routes!=2.0,>=1.12.3;python_version!='2.7'
|
Routes!=2.0,>=1.12.3;python_version!='2.7'
|
||||||
|
@ -81,6 +81,7 @@ designate.notification.handler =
|
|||||||
|
|
||||||
designate.backend =
|
designate.backend =
|
||||||
bind9 = designate.backend.impl_bind9:Bind9Backend
|
bind9 = designate.backend.impl_bind9:Bind9Backend
|
||||||
|
designate = designate.backend.impl_designate:DesignateBackend
|
||||||
powerdns = designate.backend.impl_powerdns:PowerDNSBackend
|
powerdns = designate.backend.impl_powerdns:PowerDNSBackend
|
||||||
dynect = designate.backend.impl_dynect:DynECTBackend
|
dynect = designate.backend.impl_dynect:DynECTBackend
|
||||||
akamai = designate.backend.impl_akamai:AkamaiBackend
|
akamai = designate.backend.impl_akamai:AkamaiBackend
|
||||||
|
Loading…
Reference in New Issue
Block a user