Add support for TSIG to PowerDNS backend
Change-Id: I7c1a8c452706d8d482580520ec8ecef2e0400bc8
This commit is contained in:
parent
49b491f06c
commit
d258d8a844
@ -15,6 +15,9 @@
|
||||
# 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
|
||||
from sqlalchemy.sql import select
|
||||
from sqlalchemy.sql.expression import null
|
||||
from sqlalchemy.orm import exc as sqlalchemy_exceptions
|
||||
from moniker.openstack.common import cfg
|
||||
from moniker.openstack.common import log as logging
|
||||
@ -22,6 +25,7 @@ from moniker import exceptions
|
||||
from moniker.backend import base
|
||||
from moniker.backend.impl_powerdns import models
|
||||
from moniker.sqlalchemy.session import get_session, SQLOPTS
|
||||
from moniker.sqlalchemy.expressions import InsertFromSelect
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
@ -40,8 +44,69 @@ class PowerDNSBackend(base.Backend):
|
||||
|
||||
self.session = get_session(self.name)
|
||||
|
||||
# TSIG Key Methods
|
||||
def create_tsigkey(self, context, tsigkey):
|
||||
""" Create a TSIG Key """
|
||||
tsigkey_m = models.TsigKey()
|
||||
|
||||
tsigkey_m.update({
|
||||
'moniker_id': tsigkey['id'],
|
||||
'name': tsigkey['name'],
|
||||
'algorithm': tsigkey['algorithm'],
|
||||
'secret': base64.b64encode(tsigkey['secret'])
|
||||
})
|
||||
|
||||
tsigkey_m.save(self.session)
|
||||
|
||||
# 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([null(),
|
||||
models.Domain.__table__.c.id,
|
||||
"'TSIG-ALLOW-AXFR'",
|
||||
"'%s'" % tsigkey['name']])
|
||||
query = InsertFromSelect(models.DomainMetadata.__table__, query_select)
|
||||
|
||||
# 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 """
|
||||
tsigkey_m = self._get_tsigkey(tsigkey['id'])
|
||||
|
||||
# Store a copy of the original name..
|
||||
original_name = tsigkey_m.name
|
||||
|
||||
tsigkey_m.update({
|
||||
'name': tsigkey['name'],
|
||||
'algorithm': tsigkey['algorithm'],
|
||||
'secret': base64.b64encode(tsigkey['secret'])
|
||||
})
|
||||
|
||||
tsigkey_m.save(self.session)
|
||||
|
||||
# If the name changed, Update the necessary DomainMetadata records
|
||||
if original_name != tsigkey['name']:
|
||||
self.session.query(models.DomainMetadata)\
|
||||
.filter_by(kind='TSIG-ALLOW-AXFR', content=original_name)\
|
||||
.update(name=tsigkey['name'])
|
||||
|
||||
def delete_tsigkey(self, context, tsigkey):
|
||||
""" Delete a TSIG Key """
|
||||
# Delete this TSIG Key itself
|
||||
tsigkey_m = self._get_tsigkey(tsigkey['id'])
|
||||
tsigkey_m.delete(self.session)
|
||||
|
||||
# Delete this TSIG Key from every domain's metadata
|
||||
self.session.query(models.DomainMetadata)\
|
||||
.filter_by(kind='TSIG-ALLOW-AXFR', content=tsigkey['name'])\
|
||||
.delete()
|
||||
|
||||
# Domain Methods
|
||||
def create_domain(self, context, domain):
|
||||
servers = self.central_service.get_servers()
|
||||
servers = self.central_service.get_servers(context)
|
||||
|
||||
domain_m = models.Domain()
|
||||
domain_m.update({
|
||||
@ -64,6 +129,18 @@ class PowerDNSBackend(base.Backend):
|
||||
})
|
||||
record_m.save(self.session)
|
||||
|
||||
# Install All TSIG Keys on this domain
|
||||
tsigkeys = self.session.query(models.TsigKey).all()
|
||||
|
||||
for tsigkey in tsigkeys:
|
||||
domainmetadata_m = models.DomainMetadata()
|
||||
domainmetadata_m.update({
|
||||
'domain_id': domain_m.id,
|
||||
'kind': "TSIG-ALLOW-AXFR",
|
||||
'content': tsigkey['name']
|
||||
})
|
||||
domainmetadata_m.save(self.session)
|
||||
|
||||
# NOTE(kiall): Do the SOA last, ensuring we don't trigger a NOTIFY
|
||||
# before the NS records are in place.
|
||||
record_m = models.Record()
|
||||
@ -77,7 +154,7 @@ class PowerDNSBackend(base.Backend):
|
||||
record_m.save(self.session)
|
||||
|
||||
def update_domain(self, context, domain):
|
||||
servers = self.central_service.get_servers()
|
||||
servers = self.central_service.get_servers(context)
|
||||
|
||||
domain_m = self._get_domain(domain['id'])
|
||||
|
||||
@ -95,9 +172,15 @@ class PowerDNSBackend(base.Backend):
|
||||
domain_m = self._get_domain(domain['id'])
|
||||
domain_m.delete(self.session)
|
||||
|
||||
# Ensure the records are deleted
|
||||
query = self.session.query(models.Record)
|
||||
query.filter_by(domain_id=domain_m.id).delete()
|
||||
|
||||
# Ensure domainmetadata is deleted
|
||||
query = self.session.query(models.DomainMetadata)
|
||||
query.filter_by(domain_id=domain_m.id).delete()
|
||||
|
||||
# Record Methods
|
||||
def create_record(self, context, domain, record):
|
||||
domain_m = self._get_domain(domain['id'])
|
||||
record_m = models.Record()
|
||||
@ -131,6 +214,7 @@ class PowerDNSBackend(base.Backend):
|
||||
record_m = self._get_record(record['id'])
|
||||
record_m.delete(self.session)
|
||||
|
||||
# Internal Methods
|
||||
def _sanitize_content(self, type, content):
|
||||
if type in ('CNAME', 'MX', 'SRV', 'NS', 'PTR'):
|
||||
return content.rstrip('.')
|
||||
@ -146,6 +230,18 @@ class PowerDNSBackend(base.Backend):
|
||||
domain['expire'],
|
||||
domain['minimum'])
|
||||
|
||||
def _get_tsigkey(self, tsigkey_id):
|
||||
query = self.session.query(models.TsigKey)
|
||||
|
||||
try:
|
||||
tsigkey = query.filter_by(moniker_id=tsigkey_id).one()
|
||||
except sqlalchemy_exceptions.NoResultFound:
|
||||
raise exceptions.TsigKeyNotFound('No tsigkey found')
|
||||
except sqlalchemy_exceptions.MultipleResultsFound:
|
||||
raise exceptions.TsigKeyNotFound('Too many tsigkeys found')
|
||||
else:
|
||||
return tsigkey
|
||||
|
||||
def _get_domain(self, domain_id):
|
||||
query = self.session.query(models.Domain)
|
||||
|
||||
|
@ -42,7 +42,7 @@ records = Table('records', meta,
|
||||
Column('content', String(255), default=None, nullable=True),
|
||||
Column('ttl', Integer(), default=None, nullable=True),
|
||||
Column('prio', Integer(), default=None, nullable=True),
|
||||
Column('change_data', Integer(), default=None,
|
||||
Column('change_date', Integer(), default=None,
|
||||
nullable=True),
|
||||
Column('ordername', String(255), default=None, nullable=True),
|
||||
Column('auth', Boolean(), default=None, nullable=True))
|
||||
|
@ -0,0 +1,53 @@
|
||||
# Copyright 2012 Managed I.T.
|
||||
#
|
||||
# Author: Kiall Mac Innes <kiall@managedit.ie>
|
||||
#
|
||||
# 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 sqlalchemy import MetaData, Table, Column
|
||||
from moniker.sqlalchemy.types import UUID
|
||||
|
||||
meta = MetaData()
|
||||
|
||||
|
||||
def upgrade(migrate_engine):
|
||||
meta.bind = migrate_engine
|
||||
|
||||
tsigkeys_table = Table('tsigkeys', meta, autoload=True)
|
||||
domains_table = Table('domains', meta, autoload=True)
|
||||
records_table = Table('records', meta, autoload=True)
|
||||
|
||||
tsigkeys_moniker_id = Column('moniker_id', UUID())
|
||||
tsigkeys_moniker_id.create(tsigkeys_table)
|
||||
|
||||
domains_moniker_id = Column('moniker_id', UUID())
|
||||
domains_moniker_id.create(domains_table)
|
||||
|
||||
records_moniker_id = Column('moniker_id', UUID())
|
||||
records_moniker_id.create(records_table)
|
||||
|
||||
|
||||
def downgrade(migrate_engine):
|
||||
meta.bind = migrate_engine
|
||||
|
||||
tsigkeys_table = Table('tsigkeys', meta, autoload=True)
|
||||
domains_table = Table('domains', meta, autoload=True)
|
||||
records_table = Table('records', meta, autoload=True)
|
||||
|
||||
tsigkeys_moniker_id = Column('moniker_id', UUID())
|
||||
tsigkeys_moniker_id.drop(tsigkeys_table)
|
||||
|
||||
domains_moniker_id = Column('moniker_id', UUID())
|
||||
domains_moniker_id.drop(domains_table)
|
||||
|
||||
records_moniker_id = Column('moniker_id', UUID())
|
||||
records_moniker_id.drop(records_table)
|
@ -28,6 +28,24 @@ class Base(CommonBase):
|
||||
Base = declarative_base(cls=Base)
|
||||
|
||||
|
||||
class TsigKey(Base):
|
||||
__tablename__ = 'tsigkeys'
|
||||
|
||||
moniker_id = Column(UUID, nullable=False)
|
||||
|
||||
name = Column(String(255), default=None, nullable=True)
|
||||
algorithm = Column(String(255), default=None, nullable=True)
|
||||
secret = Column(String(255), default=None, nullable=True)
|
||||
|
||||
|
||||
class DomainMetadata(Base):
|
||||
__tablename__ = 'domainmetadata'
|
||||
|
||||
domain_id = Column(Integer(), nullable=False)
|
||||
kind = Column(String(16), default=None, nullable=True)
|
||||
content = Column(Text())
|
||||
|
||||
|
||||
class Domain(Base):
|
||||
__tablename__ = 'domains'
|
||||
|
||||
|
40
moniker/sqlalchemy/expressions.py
Normal file
40
moniker/sqlalchemy/expressions.py
Normal file
@ -0,0 +1,40 @@
|
||||
# Copyright 2012 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 sqlalchemy.ext.compiler import compiles
|
||||
from sqlalchemy.sql.expression import Executable, ClauseElement
|
||||
|
||||
|
||||
class InsertFromSelect(Executable, ClauseElement):
|
||||
execution_options = \
|
||||
Executable._execution_options.union({'autocommit': True})
|
||||
|
||||
def __init__(self, table, select):
|
||||
self.table = table
|
||||
self.select = select
|
||||
|
||||
|
||||
@compiles(InsertFromSelect)
|
||||
def visit_insert_from_select(element, compiler, **kw):
|
||||
return "INSERT INTO %s %s" % (
|
||||
compiler.process(element.table, asfrom=True),
|
||||
compiler.process(element.select)
|
||||
)
|
||||
|
||||
|
||||
# # Dialect specific compilation example, should it be needed.
|
||||
# @compiles(InsertFromSelect, 'postgresql')
|
||||
# def visit_insert_from_select(element, compiler, **kw):
|
||||
# ...
|
@ -13,7 +13,6 @@
|
||||
# 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.exc import IntegrityError
|
||||
from sqlalchemy.orm import object_mapper
|
||||
from moniker import exceptions
|
||||
|
Loading…
Reference in New Issue
Block a user