Convert PowerDNS to a Pools Backend

Change-Id: I1bf5f91afa1262d73acb0e494c42bdc8166f8195
Blueprint: transition-powerdns-to-pools
This commit is contained in:
Kiall Mac Innes 2014-12-11 21:11:22 +00:00
parent 504a2607a8
commit 9b7a253460
11 changed files with 30 additions and 932 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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])

View File

@ -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')

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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'))
]

View File

@ -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)

View File

@ -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