Convert PowerDNS to a Pools Backend
Change-Id: I1bf5f91afa1262d73acb0e494c42bdc8166f8195 Blueprint: transition-powerdns-to-pools
This commit is contained in:
parent
504a2607a8
commit
9b7a253460
@ -5,7 +5,7 @@ set -ex
|
||||
pushd $BASE/new/devstack
|
||||
|
||||
export KEEP_LOCALRC=1
|
||||
export ENABLED_SERVICES=designate,designate-api,designate-central,designate-sink,designate-mdns
|
||||
export ENABLED_SERVICES=designate,designate-api,designate-central,designate-sink,designate-mdns,designate-pool-manager
|
||||
|
||||
echo "DESIGNATE_SERVICE_PORT_DNS=5322" >> $BASE/new/devstack/localrc
|
||||
|
||||
@ -15,7 +15,6 @@ if [ "$DEVSTACK_GATE_DESIGNATE_DRIVER" == "powerdns" ]; then
|
||||
echo "DESIGNATE_BACKEND_DRIVER=powerdns" >> $BASE/new/devstack/localrc
|
||||
|
||||
elif [ "$DEVSTACK_GATE_DESIGNATE_DRIVER" == "bind9" ]; then
|
||||
export ENABLED_SERVICES+=,designate-pool-manager
|
||||
echo "DESIGNATE_BACKEND_DRIVER=bind9_pool" >> $BASE/new/devstack/localrc
|
||||
|
||||
fi
|
||||
|
@ -46,7 +46,15 @@ function install_designate_backend {
|
||||
|
||||
# configure_designate_backend - make configuration changes, including those to other services
|
||||
function configure_designate_backend {
|
||||
iniset $DESIGNATE_CONF service:pool_manager backends powerdns
|
||||
iniset $DESIGNATE_CONF service:mdns slave_nameserver_ips_and_ports "$DESIGNATE_SERVICE_HOST:$DESIGNATE_SERVICE_PORT_DNS"
|
||||
|
||||
iniset $DESIGNATE_CONF backend:powerdns server_ids $DESIGNATE_SERVER_ID
|
||||
iniset $DESIGNATE_CONF backend:powerdns connection `database_connection_url designate_pdns`
|
||||
iniset $DESIGNATE_CONF backend:powerdns masters "$DESIGNATE_SERVICE_HOST:$DESIGNATE_SERVICE_PORT_MDNS"
|
||||
|
||||
iniset $DESIGNATE_CONF backend:powerdns:$DESIGNATE_SERVER_ID host $DESIGNATE_SERVICE_HOST
|
||||
iniset $DESIGNATE_CONF backend:powerdns:$DESIGNATE_SERVER_ID port $DESIGNATE_SERVICE_PORT_DNS
|
||||
|
||||
sudo tee $POWERDNS_CFG_DIR/pdns.conf > /dev/null <<EOF
|
||||
# General Config
|
||||
@ -59,7 +67,8 @@ daemon=yes
|
||||
disable-axfr=no
|
||||
local-address=$DESIGNATE_SERVICE_HOST
|
||||
local-port=$DESIGNATE_SERVICE_PORT_DNS
|
||||
master=yes
|
||||
master=no
|
||||
slave=yes
|
||||
cache-ttl=0
|
||||
query-cache-ttl=0
|
||||
negquery-cache-ttl=0
|
||||
|
@ -1,115 +0,0 @@
|
||||
# lib/designate_plugins/backend-powerdns_mdns
|
||||
# Configure the powerdns_mdns backend
|
||||
|
||||
# Enable with:
|
||||
# DESIGNATE_BACKEND_DRIVER=powerdns_mdns
|
||||
|
||||
# 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_PDNS_MDNS_XTRACE=$(set +o | grep xtrace)
|
||||
set +o xtrace
|
||||
|
||||
# Defaults
|
||||
# --------
|
||||
if is_fedora; then
|
||||
POWERDNS_CFG_DIR=/etc/pdns
|
||||
else
|
||||
POWERDNS_CFG_DIR=/etc/powerdns
|
||||
fi
|
||||
|
||||
# Entry Points
|
||||
# ------------
|
||||
|
||||
# install_designate_backend - install any external requirements
|
||||
function install_designate_backend {
|
||||
if is_ubuntu; then
|
||||
PDNS=pdns-server
|
||||
elif is_fedora || is_suse; then
|
||||
PDNS=pdns
|
||||
else
|
||||
PDNS=pdns-server
|
||||
fi
|
||||
|
||||
install_package $PDNS pdns-backend-mysql
|
||||
sudo rm -rf $POWERDNS_CFG_DIR/pdns.d
|
||||
}
|
||||
|
||||
# configure_designate_backend - make configuration changes, including those to other services
|
||||
function configure_designate_backend {
|
||||
iniset $DESIGNATE_CONF backend:powerdns_mdns connection `database_connection_url designate_pdns_mdns`
|
||||
iniset $DESIGNATE_CONF backend:powerdns_mdns masters "$DESIGNATE_SERVICE_HOST:$DESIGNATE_SERVICE_PORT_MDNS"
|
||||
iniset $DESIGNATE_CONF service:mdns slave_nameserver_ips_and_ports "$DESIGNATE_SERVICE_HOST:$DESIGNATE_SERVICE_PORT_DNS"
|
||||
|
||||
sudo tee $POWERDNS_CFG_DIR/pdns.conf > /dev/null <<EOF
|
||||
# General Config
|
||||
setgid=pdns
|
||||
setuid=pdns
|
||||
config-dir=$POWERDNS_CFG_DIR
|
||||
socket-dir=/var/run
|
||||
guardian=yes
|
||||
daemon=yes
|
||||
disable-axfr=no
|
||||
local-address=$DESIGNATE_SERVICE_HOST
|
||||
local-port=$DESIGNATE_SERVICE_PORT_DNS
|
||||
master=no
|
||||
slave=yes
|
||||
cache-ttl=0
|
||||
query-cache-ttl=0
|
||||
negquery-cache-ttl=0
|
||||
EOF
|
||||
|
||||
if is_service_enabled mysql; then
|
||||
sudo tee -a $POWERDNS_CFG_DIR/pdns.conf > /dev/null <<EOF
|
||||
# Launch gmysql backend
|
||||
launch=gmysql
|
||||
|
||||
# gmysql parameters
|
||||
gmysql-host=$DATABASE_HOST
|
||||
gmysql-user=$DATABASE_USER
|
||||
gmysql-password=$DATABASE_PASSWORD
|
||||
gmysql-dbname=designate_pdns_mdns
|
||||
gmysql-dnssec=yes
|
||||
EOF
|
||||
else
|
||||
die $LINENO "PowerDNS mDNS backend only supports MySQL"
|
||||
fi
|
||||
|
||||
restart_service pdns
|
||||
}
|
||||
|
||||
# init_designate_backend - initialize databases, etc.
|
||||
function init_designate_backend {
|
||||
# (Re)create designate_pdns database
|
||||
recreate_database designate_pdns_mdns utf8
|
||||
|
||||
# Init and migrate designate_pdns database
|
||||
designate-manage powerdns-mdns sync
|
||||
}
|
||||
|
||||
# start_designate_backend - start any external services
|
||||
function start_designate_backend {
|
||||
start_service pdns
|
||||
}
|
||||
|
||||
# stop_designate_backend - stop any external services
|
||||
function stop_designate_backend {
|
||||
stop_service pdns
|
||||
}
|
||||
|
||||
# cleanup_designate_backend - remove transient data and cache
|
||||
function cleanup_designate_backend {
|
||||
:
|
||||
}
|
||||
|
||||
# Restore xtrace
|
||||
$DP_PDNS_MDNS_XTRACE
|
@ -1,8 +1,6 @@
|
||||
# Copyright 2012 Hewlett-Packard Development Company, L.P. All Rights Reserved.
|
||||
# Copyright 2012 Managed I.T.
|
||||
# Copyright 2014 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Author: Patrick Galbraith <patg@hp.com>
|
||||
# Author: Kiall Mac Innes <kiall@managedit.ie>
|
||||
# Author: Kiall Mac Innes <kiall@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
|
||||
@ -15,7 +13,7 @@
|
||||
# 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 base64
|
||||
import copy
|
||||
import threading
|
||||
|
||||
from oslo.config import cfg
|
||||
@ -24,56 +22,40 @@ from oslo.utils import excutils
|
||||
from sqlalchemy.sql import select
|
||||
|
||||
from designate.openstack.common import log as logging
|
||||
from designate.i18n import _LC
|
||||
from designate import exceptions
|
||||
from designate import utils
|
||||
from designate.i18n import _LC
|
||||
from designate.backend import base
|
||||
from designate.backend.impl_powerdns import tables
|
||||
from designate.sqlalchemy import session
|
||||
from designate.sqlalchemy.expressions import InsertFromSelect
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
TSIG_SUPPORTED_ALGORITHMS = ['hmac-md5']
|
||||
|
||||
|
||||
def _map_col(keys, col):
|
||||
return dict([(keys[i], col[i]) for i in range(len(keys))])
|
||||
|
||||
|
||||
class PowerDNSBackend(base.Backend):
|
||||
class PowerDNSBackend(base.PoolBackend):
|
||||
__plugin_name__ = 'powerdns'
|
||||
|
||||
@classmethod
|
||||
def get_cfg_opts(cls):
|
||||
group = cfg.OptGroup(
|
||||
name='backend:powerdns', title="Configuration for PowerDNS Backend"
|
||||
)
|
||||
def _get_common_cfg_opts(cls):
|
||||
opts = copy.deepcopy(options.database_opts)
|
||||
|
||||
opts = [
|
||||
cfg.StrOpt('domain-type', default='NATIVE',
|
||||
help='PowerDNS Domain Type'),
|
||||
cfg.ListOpt('also-notify', default=[],
|
||||
help='List of additional IPs to send NOTIFYs to'),
|
||||
] + options.database_opts
|
||||
|
||||
# TODO(kiall):
|
||||
# Overide the default DB connection registered above, to avoid name
|
||||
# Overide the default DB connection path in order to avoid name
|
||||
# conflicts between the Designate and PowerDNS databases.
|
||||
# CONF.set_default('connection',
|
||||
# 'sqlite:///$state_path/powerdns.sqlite',
|
||||
# group='backend:powerdns')
|
||||
return [(group, opts)]
|
||||
for opt in opts:
|
||||
if opt.name == 'connection':
|
||||
opt.default = 'sqlite:///$state_path/powerdns.sqlite'
|
||||
|
||||
return opts
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(PowerDNSBackend, self).__init__(*args, **kwargs)
|
||||
|
||||
self.local_store = threading.local()
|
||||
|
||||
def start(self):
|
||||
super(PowerDNSBackend, self).start()
|
||||
|
||||
@property
|
||||
def session(self):
|
||||
# NOTE: This uses a thread local store, allowing each greenthread to
|
||||
@ -99,26 +81,6 @@ class PowerDNSBackend(base.Backend):
|
||||
|
||||
return _map_col(query.columns.keys(), resultproxy.fetchone())
|
||||
|
||||
def _update(self, table, values, exc_notfound, id_col=None):
|
||||
if id_col is None:
|
||||
id_col = table.c.id
|
||||
|
||||
query = table.update()\
|
||||
.where(id_col == values[id_col.name])\
|
||||
.values(**values)
|
||||
|
||||
resultproxy = self.session.execute(query)
|
||||
|
||||
if resultproxy.rowcount != 1:
|
||||
raise exc_notfound()
|
||||
|
||||
# Refetch the row, for generated columns etc
|
||||
query = select([table])\
|
||||
.where(id_col == values[id_col.name])
|
||||
resultproxy = self.session.execute(query)
|
||||
|
||||
return _map_col(query.columns.keys(), resultproxy.fetchone())
|
||||
|
||||
def _get(self, table, id_, exc_notfound, id_col=None):
|
||||
if id_col is None:
|
||||
id_col = table.c.id
|
||||
@ -148,143 +110,20 @@ class PowerDNSBackend(base.Backend):
|
||||
if resultproxy.rowcount != 1:
|
||||
raise exc_notfound()
|
||||
|
||||
# TSIG Key Methods
|
||||
def create_tsigkey(self, context, tsigkey):
|
||||
"""Create a TSIG Key"""
|
||||
|
||||
if tsigkey['algorithm'] not in TSIG_SUPPORTED_ALGORITHMS:
|
||||
raise exceptions.NotImplemented('Unsupported algorithm')
|
||||
|
||||
values = {
|
||||
'designate_id': tsigkey['id'],
|
||||
'name': tsigkey['name'],
|
||||
'algorithm': tsigkey['algorithm'],
|
||||
'secret': base64.b64encode(tsigkey['secret'])
|
||||
}
|
||||
|
||||
self._create(tables.tsigkeys, values)
|
||||
|
||||
# NOTE(kiall): Prepare and execute query to install this TSIG Key on
|
||||
# every domain. We use a manual query here since anything
|
||||
# else would be impossibly slow.
|
||||
query_select = select([
|
||||
tables.domains.c.id,
|
||||
"'TSIG-ALLOW-AXFR'",
|
||||
"'%s'" % tsigkey['name']]
|
||||
)
|
||||
|
||||
columns = [
|
||||
tables.domain_metadata.c.domain_id,
|
||||
tables.domain_metadata.c.kind,
|
||||
tables.domain_metadata.c.content,
|
||||
]
|
||||
|
||||
query = InsertFromSelect(tables.domain_metadata, query_select,
|
||||
columns)
|
||||
|
||||
# NOTE(kiall): A TX is required for, at the least, SQLite.
|
||||
self.session.begin()
|
||||
self.session.execute(query)
|
||||
self.session.commit()
|
||||
|
||||
def update_tsigkey(self, context, tsigkey):
|
||||
"""Update a TSIG Key"""
|
||||
values = self._get(
|
||||
tables.tsigkeys,
|
||||
tsigkey['id'],
|
||||
exceptions.TsigKeyNotFound,
|
||||
id_col=tables.tsigkeys.c.designate_id)
|
||||
|
||||
# Store a copy of the original name..
|
||||
original_name = values['name']
|
||||
|
||||
values.update({
|
||||
'name': tsigkey['name'],
|
||||
'algorithm': tsigkey['algorithm'],
|
||||
'secret': base64.b64encode(tsigkey['secret'])
|
||||
})
|
||||
|
||||
self._update(tables.tsigkeys, values,
|
||||
id_col=tables.tsigkeys.c.designate_id,
|
||||
exc_notfound=exceptions.TsigKeyNotFound)
|
||||
|
||||
# If the name changed, Update the necessary DomainMetadata records
|
||||
if original_name != tsigkey['name']:
|
||||
query = tables.domain_metadata.update()\
|
||||
.where(tables.domain_metadata.c.kind == 'TSIG_ALLOW_AXFR')\
|
||||
.where(tables.domain_metadata.c.content == original_name)
|
||||
|
||||
query.values(content=tsigkey['name'])
|
||||
self.session.execute(query)
|
||||
|
||||
def delete_tsigkey(self, context, tsigkey):
|
||||
"""Delete a TSIG Key"""
|
||||
try:
|
||||
# Delete this TSIG Key itself
|
||||
self._delete(
|
||||
tables.tsigkeys, tsigkey['id'],
|
||||
exceptions.TsigKeyNotFound,
|
||||
id_col=tables.tsigkeys.c.designate_id)
|
||||
except exceptions.TsigKeyNotFound:
|
||||
# If the TSIG Key is already gone, that's ok. We're deleting it
|
||||
# anyway, so just log and continue.
|
||||
LOG.critical(_LC('Attempted to delete a TSIG key which is '
|
||||
'not present in the backend. ID: %s') %
|
||||
tsigkey['id'])
|
||||
return
|
||||
|
||||
query = tables.domain_metadata.delete()\
|
||||
.where(tables.domain_metadata.c.kind == 'TSIG-ALLOW-AXFR')\
|
||||
.where(tables.domain_metadata.c.content == tsigkey['name'])
|
||||
self.session.execute(query)
|
||||
|
||||
# Domain Methods
|
||||
def create_domain(self, context, domain):
|
||||
try:
|
||||
self.session.begin()
|
||||
servers = self.central_service.find_servers(self.admin_context)
|
||||
|
||||
domain_values = {
|
||||
'designate_id': domain['id'],
|
||||
'name': domain['name'].rstrip('.'),
|
||||
'master': servers[0]['name'].rstrip('.'),
|
||||
'type': CONF['backend:powerdns'].domain_type,
|
||||
'master': ','.join(CONF['backend:powerdns'].masters),
|
||||
'type': 'SLAVE',
|
||||
'account': context.tenant
|
||||
}
|
||||
|
||||
domain_ref = self._create(tables.domains, domain_values)
|
||||
|
||||
# Install all TSIG Keys on this domain
|
||||
query = select([tables.tsigkeys.c.name])
|
||||
resultproxy = self.session.execute(query)
|
||||
values = [i for i in resultproxy.fetchall()]
|
||||
|
||||
self._update_domainmetadata(domain_ref['id'], 'TSIG-ALLOW-AXFR',
|
||||
values)
|
||||
|
||||
# Install all Also Notify's on this domain
|
||||
self._update_domainmetadata(domain_ref['id'], 'ALSO-NOTIFY',
|
||||
CONF['backend:powerdns'].also_notify)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
self.session.rollback()
|
||||
else:
|
||||
self.session.commit()
|
||||
|
||||
def update_domain(self, context, domain):
|
||||
domain_ref = self._get(tables.domains, domain['id'],
|
||||
exceptions.DomainNotFound,
|
||||
id_col=tables.domains.c.designate_id)
|
||||
|
||||
try:
|
||||
self.session.begin()
|
||||
|
||||
# Update the Records TTLs where necessary
|
||||
query = tables.records.update()\
|
||||
.where(tables.records.c.domain_id == domain_ref['id'])
|
||||
query = query.where(tables.records.c.inherit_ttl == True) # noqa\
|
||||
query = query.values(ttl=domain['ttl'])
|
||||
self.session.execute(query)
|
||||
self._create(tables.domains, domain_values)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
self.session.rollback()
|
||||
@ -293,9 +132,8 @@ class PowerDNSBackend(base.Backend):
|
||||
|
||||
def delete_domain(self, context, domain):
|
||||
try:
|
||||
domain_ref = self._get(tables.domains, domain['id'],
|
||||
exceptions.DomainNotFound,
|
||||
id_col=tables.domains.c.designate_id)
|
||||
self._get(tables.domains, domain['id'], exceptions.DomainNotFound,
|
||||
id_col=tables.domains.c.designate_id)
|
||||
except exceptions.DomainNotFound:
|
||||
# If the Domain is already gone, that's ok. We're deleting it
|
||||
# anyway, so just log and continue.
|
||||
@ -307,189 +145,3 @@ class PowerDNSBackend(base.Backend):
|
||||
self._delete(tables.domains, domain['id'],
|
||||
exceptions.DomainNotFound,
|
||||
id_col=tables.domains.c.designate_id)
|
||||
|
||||
# Ensure the records are deleted
|
||||
query = tables.records.delete()\
|
||||
.where(tables.records.c.domain_id == domain_ref['id'])
|
||||
self.session.execute(query)
|
||||
|
||||
# Ensure domainmetadata is deleted
|
||||
query = tables.domain_metadata.delete()\
|
||||
.where(tables.domain_metadata.c.domain_id == domain_ref['id'])
|
||||
self.session.execute(query)
|
||||
|
||||
# RecordSet Methods
|
||||
def create_recordset(self, context, domain, recordset):
|
||||
try:
|
||||
self.session.begin(subtransactions=True)
|
||||
|
||||
# Create all the records..
|
||||
for record in recordset.records:
|
||||
self.create_record(context, domain, recordset, record)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
self.session.rollback()
|
||||
else:
|
||||
self.session.commit()
|
||||
|
||||
def update_recordset(self, context, domain, recordset):
|
||||
# TODO(kiall): This is a total kludge. Intended as the simplest
|
||||
# possible fix for the issue. This needs to be
|
||||
# re-implemented correctly.
|
||||
try:
|
||||
self.session.begin(subtransactions=True)
|
||||
|
||||
self.delete_recordset(context, domain, recordset)
|
||||
self.create_recordset(context, domain, recordset)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
self.session.rollback()
|
||||
else:
|
||||
self.session.commit()
|
||||
|
||||
def delete_recordset(self, context, domain, recordset):
|
||||
# Ensure records are deleted
|
||||
query = tables.records.delete()\
|
||||
.where(tables.records.c.designate_recordset_id == recordset['id'])
|
||||
self.session.execute(query)
|
||||
|
||||
# Record Methods
|
||||
def create_record(self, context, domain, recordset, record):
|
||||
domain_ref = self._get(tables.domains, domain['id'],
|
||||
exceptions.DomainNotFound,
|
||||
id_col=tables.domains.c.designate_id)
|
||||
|
||||
# Priority is stored in the data field for MX / SRV
|
||||
priority, data = utils.extract_priority_from_data(
|
||||
recordset['type'], record)
|
||||
|
||||
content = self._sanitize_content(recordset['type'], data)
|
||||
ttl = domain['ttl'] if recordset['ttl'] is None else recordset['ttl']
|
||||
|
||||
record_values = {
|
||||
'designate_id': record['id'],
|
||||
'designate_recordset_id': record['recordset_id'],
|
||||
'domain_id': domain_ref['id'],
|
||||
'name': recordset['name'].rstrip('.'),
|
||||
'type': recordset['type'],
|
||||
'content': content,
|
||||
'ttl': ttl,
|
||||
'inherit_ttl': True if recordset['ttl'] is None else False,
|
||||
'prio': priority,
|
||||
'auth': self._is_authoritative(domain, recordset, record)
|
||||
}
|
||||
|
||||
self._create(tables.records, record_values)
|
||||
|
||||
def update_record(self, context, domain, recordset, record):
|
||||
record_ref = self._get_record(record['id'])
|
||||
|
||||
# Priority is stored in the data field for MX / SRV
|
||||
priority, data = utils.extract_priority_from_data(
|
||||
recordset['type'], record)
|
||||
|
||||
content = self._sanitize_content(recordset['type'], data)
|
||||
ttl = domain['ttl'] if recordset['ttl'] is None else recordset['ttl']
|
||||
|
||||
record_ref.update({
|
||||
'content': content,
|
||||
'ttl': ttl,
|
||||
'inherit_ttl': True if recordset['ttl'] is None else False,
|
||||
'prio': priority,
|
||||
'auth': self._is_authoritative(domain, recordset, record)
|
||||
})
|
||||
|
||||
self._update(tables.records, record_ref,
|
||||
exc_notfound=exceptions.RecordNotFound)
|
||||
|
||||
def delete_record(self, context, domain, recordset, record):
|
||||
try:
|
||||
record_ref = self._get(tables.records, record['id'],
|
||||
exceptions.RecordNotFound,
|
||||
id_col=tables.records.c.designate_id)
|
||||
except exceptions.RecordNotFound:
|
||||
# If the Record is already gone, that's ok. We're deleting it
|
||||
# anyway, so just log and continue.
|
||||
LOG.critical(_LC('Attempted to delete a record which is '
|
||||
'not present in the backend. ID: %s') %
|
||||
record['id'])
|
||||
else:
|
||||
self._delete(tables.records, record_ref['id'],
|
||||
exceptions.RecordNotFound)
|
||||
|
||||
# Internal Methods
|
||||
def _update_domainmetadata(self, domain_id, kind, values=None,
|
||||
delete=True):
|
||||
"""Updates a domain's metadata with new values"""
|
||||
# Fetch all current metadata of the specified kind
|
||||
values = values or []
|
||||
|
||||
query = select([tables.domain_metadata.c.content])\
|
||||
.where(tables.domain_metadata.c.domain_id == domain_id)\
|
||||
.where(tables.domain_metadata.c.kind == kind)
|
||||
resultproxy = self.session.execute(query)
|
||||
results = resultproxy.fetchall()
|
||||
|
||||
for metadata_id, content in results:
|
||||
if content not in values:
|
||||
if delete:
|
||||
LOG.debug('Deleting stale domain metadata: %r' %
|
||||
([domain_id, kind, content],))
|
||||
# Delete no longer necessary values
|
||||
# We should never get a notfound here, so UnknownFailure is
|
||||
# a reasonable choice.
|
||||
self._delete(tables.domain_metadata, metadata_id,
|
||||
exceptions.UnknownFailure)
|
||||
else:
|
||||
# Remove pre-existing values from the list of values to insert
|
||||
values.remove(content)
|
||||
|
||||
# Insert new values
|
||||
for value in values:
|
||||
LOG.debug('Inserting new domain metadata: %r' %
|
||||
([domain_id, kind, value],))
|
||||
self._create(
|
||||
tables.domain_metadata,
|
||||
{
|
||||
"domain_id": domain_id,
|
||||
"kind": kind,
|
||||
"content": value
|
||||
})
|
||||
|
||||
def _is_authoritative(self, domain, recordset, record):
|
||||
# NOTE(kiall): See http://doc.powerdns.com/dnssec-modes.html
|
||||
if recordset['type'] == 'NS' and recordset['name'] != domain['name']:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def _sanitize_content(self, type, content):
|
||||
if type in ('CNAME', 'MX', 'SRV', 'NS', 'PTR'):
|
||||
return content.rstrip('.')
|
||||
|
||||
if type in ('TXT', 'SPF'):
|
||||
return '"%s"' % content.replace('"', '\\"')
|
||||
|
||||
return content
|
||||
|
||||
def _get_record(self, record_id=None, domain=None, type_=None):
|
||||
query = select([tables.records])
|
||||
|
||||
if record_id:
|
||||
query = query.where(tables.records.c.designate_id == record_id)
|
||||
|
||||
if type_:
|
||||
query = query.where(tables.records.c.type == type_)
|
||||
|
||||
if domain:
|
||||
query = query.where(tables.records.c.domain_id == domain['id'])
|
||||
|
||||
resultproxy = self.session.execute(query)
|
||||
results = resultproxy.fetchall()
|
||||
|
||||
if len(results) < 1:
|
||||
raise exceptions.RecordNotFound('No record found')
|
||||
elif len(results) > 1:
|
||||
raise exceptions.RecordNotFound('Too many records found')
|
||||
else:
|
||||
return _map_col(query.columns.keys(), results[0])
|
||||
|
@ -13,8 +13,7 @@
|
||||
# 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 sqlalchemy import MetaData, Table, Column, String, Text, Integer, Boolean
|
||||
|
||||
from sqlalchemy import MetaData, Table, Column, String, Integer
|
||||
|
||||
from oslo.config import cfg
|
||||
|
||||
@ -25,27 +24,6 @@ CONF = cfg.CONF
|
||||
|
||||
metadata = MetaData()
|
||||
|
||||
tsigkeys = Table(
|
||||
'tsigkeys', metadata,
|
||||
Column('id', Integer(), primary_key=True, autoincrement=True),
|
||||
|
||||
Column('designate_id', UUID(), nullable=False),
|
||||
Column('name', String(255), default=None, nullable=True),
|
||||
Column('algorithm', String(255), default=None, nullable=True),
|
||||
Column('secret', String(255), default=None, nullable=True),
|
||||
mysql_engine='InnoDB',
|
||||
mysql_charset='utf8')
|
||||
|
||||
domain_metadata = Table(
|
||||
'domainmetadata', metadata,
|
||||
Column('id', Integer(), primary_key=True, autoincrement=True),
|
||||
|
||||
Column('domain_id', Integer(), nullable=False),
|
||||
Column('kind', String(16), default=None, nullable=True),
|
||||
Column('content', Text()),
|
||||
mysql_engine='InnoDB',
|
||||
mysql_charset='utf8')
|
||||
|
||||
domains = Table(
|
||||
'domains', metadata,
|
||||
Column('id', Integer, primary_key=True, autoincrement=True),
|
||||
@ -59,22 +37,3 @@ domains = Table(
|
||||
Column('account', String(40), default=None, nullable=True),
|
||||
mysql_engine='InnoDB',
|
||||
mysql_charset='utf8')
|
||||
|
||||
records = Table(
|
||||
'records', metadata,
|
||||
Column('id', Integer, primary_key=True, autoincrement=True),
|
||||
|
||||
Column('designate_id', UUID(), nullable=False),
|
||||
Column('designate_recordset_id', UUID(), default=None, nullable=True),
|
||||
Column('domain_id', Integer(), default=None, nullable=True),
|
||||
Column('name', String(255), default=None, nullable=True),
|
||||
Column('type', String(10), default=None, nullable=True),
|
||||
Column('content', Text(), default=None, nullable=True),
|
||||
Column('ttl', Integer(), default=None, nullable=True),
|
||||
Column('prio', Integer(), default=None, nullable=True),
|
||||
Column('change_date', Integer(), default=None, nullable=True),
|
||||
Column('ordername', String(255), default=None, nullable=True),
|
||||
Column('auth', Boolean(), default=None, nullable=True),
|
||||
Column('inherit_ttl', Boolean(), default=True),
|
||||
mysql_engine='InnoDB',
|
||||
mysql_charset='utf8')
|
||||
|
@ -1,202 +0,0 @@
|
||||
# Copyright 2014 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Author: Kiall Mac Innes <kiall@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 threading
|
||||
|
||||
from oslo.config import cfg
|
||||
from oslo.db import options
|
||||
from oslo.utils import excutils
|
||||
from sqlalchemy.sql import select
|
||||
|
||||
from designate.openstack.common import log as logging
|
||||
from designate import exceptions
|
||||
from designate.i18n import _LC
|
||||
from designate.backend import base
|
||||
from designate.backend.impl_powerdns_mdns import tables
|
||||
from designate.sqlalchemy import session
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
def _map_col(keys, col):
|
||||
return dict([(keys[i], col[i]) for i in range(len(keys))])
|
||||
|
||||
|
||||
class PowerDNSMDNSBackend(base.Backend):
|
||||
__plugin_name__ = 'powerdns_mdns'
|
||||
|
||||
@classmethod
|
||||
def get_cfg_opts(cls):
|
||||
group = cfg.OptGroup(
|
||||
name='backend:powerdns_mdns',
|
||||
title="Configuration for PowerDNS MDNS Backend"
|
||||
)
|
||||
|
||||
opts = [
|
||||
cfg.ListOpt('masters',
|
||||
help="Master servers from which to transfer from."),
|
||||
] + options.database_opts
|
||||
|
||||
# TODO(kiall):
|
||||
# Overide the default DB connection registered above, to avoid name
|
||||
# conflicts between the Designate and PowerDNS databases.
|
||||
# CONF.set_default('connection',
|
||||
# 'sqlite:///$state_path/powerdns.sqlite',
|
||||
# group='backend:powerdns')
|
||||
return [(group, opts)]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(PowerDNSMDNSBackend, self).__init__(*args, **kwargs)
|
||||
|
||||
self.local_store = threading.local()
|
||||
|
||||
@property
|
||||
def session(self):
|
||||
# NOTE: This uses a thread local store, allowing each greenthread to
|
||||
# have it's own session stored correctly. Without this, each
|
||||
# greenthread may end up using a single global session, which
|
||||
# leads to bad things happening.
|
||||
global LOCAL_STORE
|
||||
|
||||
if not hasattr(self.local_store, 'session'):
|
||||
self.local_store.session = session.get_session(self.name)
|
||||
|
||||
return self.local_store.session
|
||||
|
||||
def _create(self, table, values):
|
||||
query = table.insert()
|
||||
|
||||
resultproxy = self.session.execute(query, values)
|
||||
|
||||
# Refetch the row, for generated columns etc
|
||||
query = select([table])\
|
||||
.where(table.c.id == resultproxy.inserted_primary_key[0])
|
||||
resultproxy = self.session.execute(query)
|
||||
|
||||
return _map_col(query.columns.keys(), resultproxy.fetchone())
|
||||
|
||||
def _get(self, table, id_, exc_notfound, id_col=None):
|
||||
if id_col is None:
|
||||
id_col = table.c.id
|
||||
|
||||
query = select([table])\
|
||||
.where(id_col == id_)
|
||||
|
||||
resultproxy = self.session.execute(query)
|
||||
|
||||
results = resultproxy.fetchall()
|
||||
|
||||
if len(results) != 1:
|
||||
raise exc_notfound()
|
||||
|
||||
# Map col keys to values in result
|
||||
return _map_col(query.columns.keys(), results[0])
|
||||
|
||||
def _delete(self, table, id_, exc_notfound, id_col=None):
|
||||
if id_col is None:
|
||||
id_col = table.c.id
|
||||
|
||||
query = table.delete()\
|
||||
.where(id_col == id_)
|
||||
|
||||
resultproxy = self.session.execute(query)
|
||||
|
||||
if resultproxy.rowcount != 1:
|
||||
raise exc_notfound()
|
||||
|
||||
def create_tsigkey(self, context, tsigkey):
|
||||
pass
|
||||
|
||||
def update_tsigkey(self, context, tsigkey):
|
||||
pass
|
||||
|
||||
def delete_tsigkey(self, context, tsigkey):
|
||||
pass
|
||||
|
||||
def create_server(self, context, server):
|
||||
pass
|
||||
|
||||
def update_server(self, context, server):
|
||||
pass
|
||||
|
||||
def delete_server(self, context, server):
|
||||
pass
|
||||
|
||||
# Domain Methods
|
||||
def create_domain(self, context, domain):
|
||||
try:
|
||||
self.session.begin()
|
||||
|
||||
domain_values = {
|
||||
'designate_id': domain['id'],
|
||||
'name': domain['name'].rstrip('.'),
|
||||
'master': ','.join(CONF['backend:powerdns_mdns'].masters),
|
||||
'type': 'SLAVE',
|
||||
'account': context.tenant
|
||||
}
|
||||
|
||||
self._create(tables.domains, domain_values)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
self.session.rollback()
|
||||
else:
|
||||
self.session.commit()
|
||||
|
||||
def update_domain(self, context, domain):
|
||||
pass
|
||||
|
||||
def delete_domain(self, context, domain):
|
||||
try:
|
||||
self._get(tables.domains, domain['id'], exceptions.DomainNotFound,
|
||||
id_col=tables.domains.c.designate_id)
|
||||
except exceptions.DomainNotFound:
|
||||
# If the Domain is already gone, that's ok. We're deleting it
|
||||
# anyway, so just log and continue.
|
||||
LOG.critical(_LC('Attempted to delete a domain which is '
|
||||
'not present in the backend. ID: %s') %
|
||||
domain['id'])
|
||||
return
|
||||
|
||||
self._delete(tables.domains, domain['id'],
|
||||
exceptions.DomainNotFound,
|
||||
id_col=tables.domains.c.designate_id)
|
||||
|
||||
def create_recordset(self, context, domain, recordset):
|
||||
pass
|
||||
|
||||
def update_recordset(self, context, domain, recordset):
|
||||
pass
|
||||
|
||||
def delete_recordset(self, context, domain, recordset):
|
||||
pass
|
||||
|
||||
def create_record(self, context, domain, recordset, record):
|
||||
pass
|
||||
|
||||
def update_record(self, context, domain, recordset, record):
|
||||
pass
|
||||
|
||||
def delete_record(self, context, domain, recordset, record):
|
||||
pass
|
||||
|
||||
def sync_domain(self, context, domain, records):
|
||||
pass
|
||||
|
||||
def sync_record(self, context, domain, record):
|
||||
pass
|
||||
|
||||
def ping(self, context):
|
||||
pass
|
@ -1,23 +0,0 @@
|
||||
# Copyright 2012-2014 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Author: Kiall Mac Innes <kiall@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 designate.backend.impl_powerdns import tables
|
||||
|
||||
|
||||
# NOTE(kiall): We import the domains table object here in order to ensure
|
||||
# the PowerDNS mDNS based driver uses only this one table. When
|
||||
# the original PowerDNS driver is removed, we'll move the domains
|
||||
# table definition here.
|
||||
domains = tables.domains
|
@ -1,59 +0,0 @@
|
||||
# Copyright 2014 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Author: Kiall Mac Innes <kiall@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 os
|
||||
|
||||
from migrate.versioning import api as versioning_api
|
||||
from oslo.config import cfg
|
||||
from oslo.db.sqlalchemy.migration_cli import manager as migration_manager
|
||||
|
||||
from designate.openstack.common import log as logging
|
||||
from designate.manage import base
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
REPOSITORY = os.path.abspath(os.path.join(os.path.dirname(__file__), '..',
|
||||
'backend', 'impl_powerdns',
|
||||
'migrate_repo'))
|
||||
cfg.CONF.import_opt('connection', 'designate.backend.impl_powerdns_mdns',
|
||||
group='backend:powerdns_mdns')
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
def get_manager():
|
||||
migration_config = {
|
||||
'migration_repo_path': REPOSITORY,
|
||||
'db_url': CONF['backend:powerdns_mdns'].connection}
|
||||
return migration_manager.MigrationManager(migration_config)
|
||||
|
||||
|
||||
class DatabaseCommands(base.Commands):
|
||||
def version(self):
|
||||
current = get_manager().version()
|
||||
latest = versioning_api.version(repository=REPOSITORY).value
|
||||
print("Current: %s Latest: %s" % (current, latest))
|
||||
|
||||
def sync(self):
|
||||
get_manager().upgrade(None)
|
||||
|
||||
@base.args('revision', nargs='?')
|
||||
def upgrade(self, revision):
|
||||
get_manager().upgrade(revision)
|
||||
|
||||
@base.args('revision', nargs='?')
|
||||
def downgrade(self, revision):
|
||||
get_manager().downgrade(revision)
|
@ -26,7 +26,6 @@ class BackendTestCase(tests.TestCase, BackendTestMixin):
|
||||
('fake', dict(backend_driver='fake', group='service:agent')),
|
||||
('nsd4slave', dict(backend_driver='nsd4slave', group='service:agent',
|
||||
server_fixture=NSD4Fixture)),
|
||||
('powerdns', dict(backend_driver='powerdns', group='service:agent')),
|
||||
('ipa', dict(backend_driver='ipa', group='service:agent'))
|
||||
]
|
||||
|
||||
|
@ -1,119 +0,0 @@
|
||||
# Copyright (C) 2013 eNovance SAS <licensing@enovance.com>
|
||||
#
|
||||
# Author: Artom Lifshitz <artom.lifshitz@enovance.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 mock import MagicMock
|
||||
|
||||
from designate import tests
|
||||
from designate.tests import DatabaseFixture
|
||||
from designate.tests.test_backend import BackendTestMixin
|
||||
from designate import utils
|
||||
|
||||
|
||||
# impl_powerdns needs to register its options before being instanciated.
|
||||
# Import it and pretend to use it to avoid flake8 unused import errors.
|
||||
from designate.backend import impl_powerdns
|
||||
impl_powerdns
|
||||
|
||||
REPOSITORY = os.path.abspath(os.path.join(os.path.dirname(__file__),
|
||||
'..', '..',
|
||||
'backend', 'impl_powerdns',
|
||||
'migrate_repo'))
|
||||
|
||||
|
||||
class PowerDNSBackendTestCase(tests.TestCase, BackendTestMixin):
|
||||
|
||||
def get_tsigkey_fixture(self):
|
||||
return super(PowerDNSBackendTestCase, self).get_tsigkey_fixture(
|
||||
values={
|
||||
'id': utils.generate_uuid()
|
||||
}
|
||||
)
|
||||
|
||||
def get_server_fixture(self):
|
||||
return super(PowerDNSBackendTestCase, self).get_server_fixture(
|
||||
values={
|
||||
'id': utils.generate_uuid()
|
||||
}
|
||||
)
|
||||
|
||||
def get_domain_fixture(self):
|
||||
return super(PowerDNSBackendTestCase, self).get_domain_fixture(
|
||||
values={
|
||||
'id': utils.generate_uuid(),
|
||||
'ttl': 42,
|
||||
'serial': 42,
|
||||
'refresh': 42,
|
||||
'retry': 42,
|
||||
'expire': 42,
|
||||
'minimum': 42,
|
||||
}
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
super(PowerDNSBackendTestCase, self).setUp()
|
||||
self.db_fixture = DatabaseFixture.get_fixture(REPOSITORY)
|
||||
self.useFixture(self.db_fixture)
|
||||
self.config(backend_driver='powerdns', group='service:agent')
|
||||
self.config(connection=self.db_fixture.url,
|
||||
group='backend:powerdns')
|
||||
self.backend = self.get_backend_driver()
|
||||
self.backend.start()
|
||||
# Since some CRUD methods in impl_powerdns call central's find_servers
|
||||
# method, mock it up to return our fixture.
|
||||
self.backend.central_service.find_servers = MagicMock(
|
||||
return_value=[self.get_server_fixture()])
|
||||
|
||||
def test_create_tsigkey(self):
|
||||
context = self.get_context()
|
||||
tsigkey = self.get_tsigkey_fixture()
|
||||
self.backend.create_tsigkey(context, tsigkey)
|
||||
|
||||
def test_update_tsigkey(self):
|
||||
context = self.get_context()
|
||||
tsigkey = self.get_tsigkey_fixture()
|
||||
self.backend.create_tsigkey(context, tsigkey)
|
||||
self.backend.update_tsigkey(context, tsigkey)
|
||||
|
||||
def test_delete_tsigkey(self):
|
||||
context = self.get_context()
|
||||
tsigkey = self.get_tsigkey_fixture()
|
||||
self.backend.create_tsigkey(context, tsigkey)
|
||||
self.backend.delete_tsigkey(context, tsigkey)
|
||||
|
||||
def test_create_domain(self):
|
||||
context = self.get_context()
|
||||
server = self.get_server_fixture()
|
||||
domain = self.get_domain_fixture()
|
||||
self.backend.create_server(context, server)
|
||||
self.backend.create_domain(context, domain)
|
||||
|
||||
def test_update_domain(self):
|
||||
context = self.get_context()
|
||||
server = self.get_server_fixture()
|
||||
domain = self.get_domain_fixture()
|
||||
self.backend.create_server(context, server)
|
||||
self.backend.create_domain(context, domain)
|
||||
self.backend.update_domain(context, domain)
|
||||
|
||||
def test_delete_domain(self):
|
||||
context = self.get_context()
|
||||
server = self.get_server_fixture()
|
||||
domain = self.get_domain_fixture()
|
||||
self.backend.create_server(context, server)
|
||||
self.backend.create_domain(context, domain)
|
||||
self.backend.delete_domain(context, domain)
|
@ -71,7 +71,6 @@ designate.backend =
|
||||
bind9 = designate.backend.impl_bind9:Bind9Backend
|
||||
bind9_pool = designate.backend.impl_bind9_pool:Bind9PoolBackend
|
||||
powerdns = designate.backend.impl_powerdns:PowerDNSBackend
|
||||
powerdns_mdns = designate.backend.impl_powerdns_mdns:PowerDNSMDNSBackend
|
||||
rpc = designate.backend.impl_rpc:RPCBackend
|
||||
fake = designate.backend.impl_fake:FakeBackend
|
||||
nsd4slave = designate.backend.impl_nsd4slave:NSD4SlaveBackend
|
||||
@ -92,7 +91,6 @@ designate.manage =
|
||||
database = designate.manage.database:DatabaseCommands
|
||||
pool-manager-cache = designate.manage.pool_manager_cache:DatabaseCommands
|
||||
powerdns = designate.manage.powerdns:DatabaseCommands
|
||||
powerdns-mdns = designate.manage.powerdns_mdns:DatabaseCommands
|
||||
tlds = designate.manage.tlds:TLDCommands
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user