PowerDNS Backend Driver
Change-Id: Ia1fad83a0219778ae4704b32b8d8e3a209d046e9
This commit is contained in:
parent
c52f08ea48
commit
355efb1040
210
moniker/backend/impl_powerdns.py
Normal file
210
moniker/backend/impl_powerdns.py
Normal file
@ -0,0 +1,210 @@
|
||||
# Copyright 2012 Hewlett-Packard Development Company, L.P. All Rights Reserved.
|
||||
# Copyright 2012 Managed I.T.
|
||||
#
|
||||
# Author: Patrick Galbraith <patg@hp.com>
|
||||
# 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 Column, String, Text, Integer
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import exc as sqlalchemy_exceptions
|
||||
from moniker.openstack.common import cfg
|
||||
from moniker.openstack.common import log as logging
|
||||
from moniker import exceptions
|
||||
from moniker.backend import base
|
||||
from moniker.central import api as central_api
|
||||
from moniker.context import MonikerContext
|
||||
from moniker.sqlalchemy.session import get_session, SQLOPTS
|
||||
from moniker.sqlalchemy.models import Base as CommonBase
|
||||
from moniker.sqlalchemy.types import UUID
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
cfg.CONF.register_group(cfg.OptGroup(
|
||||
name='backend:powerdns', title="Configuration for Powerdns Backend"
|
||||
))
|
||||
|
||||
cfg.CONF.register_opts(SQLOPTS, group='backend:powerdns')
|
||||
|
||||
|
||||
class Base(CommonBase):
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
|
||||
|
||||
Base = declarative_base(cls=Base)
|
||||
|
||||
|
||||
class Domain(Base):
|
||||
__tablename__ = 'domains'
|
||||
|
||||
moniker_id = Column(UUID, nullable=False)
|
||||
|
||||
name = Column(String(255), nullable=False, unique=True)
|
||||
master = Column(String(128), nullable=True)
|
||||
last_check = Column(Integer, default=None, nullable=True)
|
||||
type = Column(String(6), nullable=False)
|
||||
notified_serial = Column(Integer, default=None, nullable=True)
|
||||
account = Column(String(40), default=None, nullable=True)
|
||||
|
||||
|
||||
class Record(Base):
|
||||
__tablename__ = 'records'
|
||||
|
||||
moniker_id = Column(UUID, nullable=False)
|
||||
|
||||
domain_id = Column(Integer, default=None, nullable=True)
|
||||
name = Column(String(255), default=None, nullable=True)
|
||||
type = Column(String(10), default=None, nullable=True)
|
||||
content = Column(Text, default=None, nullable=True)
|
||||
ttl = Column(Integer, default=None, nullable=True)
|
||||
prio = Column(Integer, default=None, nullable=True)
|
||||
change_date = Column(Integer, default=None, nullable=True)
|
||||
|
||||
|
||||
class PowerDNSBackend(base.Backend):
|
||||
__plugin_name__ = 'powerdns'
|
||||
|
||||
def start(self):
|
||||
super(PowerDNSBackend, self).start()
|
||||
|
||||
self.session = get_session(self.name)
|
||||
|
||||
def create_domain(self, context, domain):
|
||||
admin_context = MonikerContext.get_admin_context()
|
||||
|
||||
servers = central_api.get_servers(admin_context)
|
||||
|
||||
domain_m = Domain()
|
||||
domain_m.update({
|
||||
'moniker_id': domain['id'],
|
||||
'name': domain['name'].rstrip('.'),
|
||||
'master': servers[0]['name'].rstrip('.'),
|
||||
'type': 'NATIVE',
|
||||
'account': context.tenant_id
|
||||
})
|
||||
domain_m.save(self.session)
|
||||
|
||||
for server in servers:
|
||||
record_m = Record()
|
||||
record_m.update({
|
||||
'moniker_id': server['id'],
|
||||
'domain_id': domain_m.id,
|
||||
'name': domain['name'].rstrip('.'),
|
||||
'type': 'NS',
|
||||
'content': server['name'].rstrip('.')
|
||||
})
|
||||
record_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 = Record()
|
||||
record_m.update({
|
||||
'moniker_id': domain['id'],
|
||||
'domain_id': domain_m.id,
|
||||
'name': domain['name'].rstrip('.'),
|
||||
'type': 'SOA',
|
||||
'content': self._build_soa_content(domain)
|
||||
})
|
||||
record_m.save(self.session)
|
||||
|
||||
def update_domain(self, context, domain):
|
||||
domain_m = self._get_domain(domain['id'])
|
||||
|
||||
soa_record_m = self._get_record(domain=domain_m, type='SOA')
|
||||
|
||||
soa_record_m.update({
|
||||
'content': self._build_soa_content(domain)
|
||||
})
|
||||
|
||||
soa_record_m.save(self.session)
|
||||
|
||||
def delete_domain(self, context, domain):
|
||||
domain_m = self._get_domain(domain['id'])
|
||||
domain_m.delete(self.session)
|
||||
|
||||
self.session.query(Record).filter_by(domain_id=domain_m.id).delete()
|
||||
|
||||
def create_record(self, context, domain, record):
|
||||
domain_m = self._get_domain(domain['id'])
|
||||
record_m = Record()
|
||||
|
||||
record_m.update({
|
||||
'moniker_id': record['id'],
|
||||
'domain_id': domain_m.id,
|
||||
'name': record['name'].rstrip('.'),
|
||||
'type': record['type'],
|
||||
'content': record['data'],
|
||||
'ttl': record['ttl'],
|
||||
'prio': record['priority']
|
||||
})
|
||||
|
||||
record_m.save(self.session)
|
||||
|
||||
def update_record(self, context, domain, record):
|
||||
record_m = self._get_record(record['id'])
|
||||
|
||||
record_m.update({
|
||||
'name': record['name'].rstrip('.'),
|
||||
'type': record['type'],
|
||||
'content': record['data'],
|
||||
'ttl': record['ttl'],
|
||||
'prio': record['priority']
|
||||
})
|
||||
|
||||
record_m.save(self.session)
|
||||
|
||||
def delete_record(self, context, domain, record):
|
||||
record_m = self._get_record(record['id'])
|
||||
record_m.delete(self.session)
|
||||
|
||||
def _build_soa_content(self, domain):
|
||||
return "%s %s. %d %d %d %d %d" % (domain['name'],
|
||||
domain['email'].replace("@", "."),
|
||||
domain['serial'],
|
||||
domain['refresh'],
|
||||
domain['retry'],
|
||||
domain['expire'],
|
||||
domain['minimum'])
|
||||
|
||||
def _get_domain(self, domain_id):
|
||||
query = self.session.query(Domain)
|
||||
|
||||
try:
|
||||
domain = query.filter_by(moniker_id=domain_id).one()
|
||||
except sqlalchemy_exceptions.NoResultFound:
|
||||
raise exceptions.Backend('No domain found')
|
||||
except sqlalchemy_exceptions.MultipleResultsFound:
|
||||
raise exceptions.Backend('Too many domains found')
|
||||
else:
|
||||
return domain
|
||||
|
||||
def _get_record(self, record_id=None, domain=None, type=None):
|
||||
query = self.session.query(Record)
|
||||
|
||||
if record_id:
|
||||
query = query.filter_by(moniker_id=record_id)
|
||||
|
||||
if type:
|
||||
query = query.filter_by(type=type)
|
||||
|
||||
if domain:
|
||||
query = query.filter_by(domain_id=domain.id)
|
||||
|
||||
try:
|
||||
record = query.one()
|
||||
except sqlalchemy_exceptions.NoResultFound:
|
||||
raise exceptions.Backend('No record found')
|
||||
except sqlalchemy_exceptions.MultipleResultsFound:
|
||||
raise exceptions.Backend('Too many records found')
|
||||
else:
|
||||
return record
|
@ -19,6 +19,10 @@ class Base(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Backend(Base):
|
||||
pass
|
||||
|
||||
|
||||
class ConfigurationError(Base):
|
||||
pass
|
||||
|
||||
|
85
moniker/sqlalchemy/models.py
Normal file
85
moniker/sqlalchemy/models.py
Normal file
@ -0,0 +1,85 @@
|
||||
# Copyright 2012 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Author: Patrick Galbraith <patg@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.exc import IntegrityError
|
||||
from sqlalchemy.orm import object_mapper
|
||||
from moniker import exceptions
|
||||
|
||||
|
||||
class Base(object):
|
||||
__abstract__ = True
|
||||
__table_initialized__ = False
|
||||
|
||||
def save(self, session):
|
||||
""" Save this object """
|
||||
session.add(self)
|
||||
|
||||
try:
|
||||
session.flush()
|
||||
except IntegrityError, e:
|
||||
non_unique_strings = (
|
||||
'duplicate entry',
|
||||
'not unique'
|
||||
)
|
||||
|
||||
for non_unique_string in non_unique_strings:
|
||||
if non_unique_string in str(e).lower():
|
||||
raise exceptions.Duplicate(str(e))
|
||||
|
||||
# Not a Duplicate error.. Re-raise.
|
||||
raise
|
||||
|
||||
def delete(self, session):
|
||||
""" Delete this object """
|
||||
session.delete(self)
|
||||
session.flush()
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
setattr(self, key, value)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return getattr(self, key)
|
||||
|
||||
def __iter__(self):
|
||||
columns = dict(object_mapper(self).columns).keys()
|
||||
# NOTE(russellb): Allow models to specify other keys that can be looked
|
||||
# up, beyond the actual db columns. An example would be the 'name'
|
||||
# property for an Instance.
|
||||
if hasattr(self, '_extra_keys'):
|
||||
columns.extend(self._extra_keys())
|
||||
self._i = iter(columns)
|
||||
return self
|
||||
|
||||
def next(self):
|
||||
n = self._i.next()
|
||||
return n, getattr(self, n)
|
||||
|
||||
def update(self, values):
|
||||
""" Make the model object behave like a dict """
|
||||
for k, v in values.iteritems():
|
||||
setattr(self, k, v)
|
||||
|
||||
def iteritems(self):
|
||||
"""
|
||||
Make the model object behave like a dict.
|
||||
|
||||
Includes attributes from joins.
|
||||
"""
|
||||
local = dict(self)
|
||||
joined = dict([(k, v) for k, v in self.__dict__.iteritems()
|
||||
if not k[0] == '_'])
|
||||
local.update(joined)
|
||||
return local.iteritems()
|
@ -17,96 +17,30 @@
|
||||
# under the License.
|
||||
from sqlalchemy import (Column, DateTime, String, Text, Integer, ForeignKey,
|
||||
Enum, Boolean, Unicode)
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.orm import relationship, backref, object_mapper
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import relationship, backref
|
||||
from sqlalchemy.ext.hybrid import hybrid_property
|
||||
from moniker.openstack.common import log as logging
|
||||
from moniker.openstack.common import timeutils
|
||||
from moniker.openstack.common.uuidutils import generate_uuid
|
||||
from moniker import exceptions
|
||||
from moniker.sqlalchemy.types import UUID, Inet
|
||||
from moniker.sqlalchemy.models import Base as CommonBase
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
RECORD_TYPES = ['A', 'AAAA', 'CNAME', 'MX', 'SRV', 'TXT', 'NS', 'PTR']
|
||||
|
||||
|
||||
class Base(object):
|
||||
__abstract__ = True
|
||||
__table_initialized__ = False
|
||||
|
||||
class Base(CommonBase):
|
||||
id = Column(UUID, default=generate_uuid, primary_key=True)
|
||||
|
||||
version = Column(Integer, default=1, nullable=False)
|
||||
created_at = Column(DateTime, default=timeutils.utcnow)
|
||||
updated_at = Column(DateTime, onupdate=timeutils.utcnow)
|
||||
version = Column(Integer, default=1, nullable=False)
|
||||
|
||||
__mapper_args__ = {
|
||||
'version_id_col': version
|
||||
}
|
||||
|
||||
def save(self, session):
|
||||
""" Save this object """
|
||||
session.add(self)
|
||||
|
||||
try:
|
||||
session.flush()
|
||||
except IntegrityError, e:
|
||||
non_unique_strings = (
|
||||
'duplicate entry',
|
||||
'not unique'
|
||||
)
|
||||
|
||||
for non_unique_string in non_unique_strings:
|
||||
if non_unique_string in str(e).lower():
|
||||
raise exceptions.Duplicate(str(e))
|
||||
|
||||
# Not a Duplicate error.. Re-raise.
|
||||
raise
|
||||
|
||||
def delete(self, session):
|
||||
""" Delete this object """
|
||||
session.delete(self)
|
||||
session.flush()
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
setattr(self, key, value)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return getattr(self, key)
|
||||
|
||||
def __iter__(self):
|
||||
columns = dict(object_mapper(self).columns).keys()
|
||||
# NOTE(russellb): Allow models to specify other keys that can be looked
|
||||
# up, beyond the actual db columns. An example would be the 'name'
|
||||
# property for an Instance.
|
||||
if hasattr(self, '_extra_keys'):
|
||||
columns.extend(self._extra_keys())
|
||||
self._i = iter(columns)
|
||||
return self
|
||||
|
||||
def next(self):
|
||||
n = self._i.next()
|
||||
return n, getattr(self, n)
|
||||
|
||||
def update(self, values):
|
||||
""" Make the model object behave like a dict """
|
||||
for k, v in values.iteritems():
|
||||
setattr(self, k, v)
|
||||
|
||||
def iteritems(self):
|
||||
"""
|
||||
Make the model object behave like a dict.
|
||||
|
||||
Includes attributes from joins.
|
||||
"""
|
||||
local = dict(self)
|
||||
joined = dict([(k, v) for k, v in self.__dict__.iteritems()
|
||||
if not k[0] == '_'])
|
||||
local.update(joined)
|
||||
return local.iteritems()
|
||||
|
||||
|
||||
Base = declarative_base(cls=Base)
|
||||
|
||||
|
28
moniker/tests/test_backend/test_powerdns.py
Normal file
28
moniker/tests/test_backend/test_powerdns.py
Normal file
@ -0,0 +1,28 @@
|
||||
# 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 moniker.openstack.common import log as logging
|
||||
from moniker.tests.test_backend import BackendTestCase
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PowerDNSBackendDriverTestCase(BackendTestCase):
|
||||
__test__ = True
|
||||
|
||||
def setUp(self):
|
||||
super(PowerDNSBackendDriverTestCase, self).setUp()
|
||||
|
||||
self.config(backend_driver='powerdns', group='service:agent')
|
1
setup.py
1
setup.py
@ -67,6 +67,7 @@ setup(
|
||||
[moniker.backend]
|
||||
bind9 = moniker.backend.impl_bind9:Bind9Backend
|
||||
mysqlbind9 = moniker.backend.impl_mysqlbind9:MySQLBind9Backend
|
||||
powerdns = moniker.backend.impl_powerdns:PowerDNSBackend
|
||||
rpc = moniker.backend.impl_rpc:RPCBackend
|
||||
fake = moniker.backend.impl_fake:FakeBackend
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user