deb-designate/designate/backend/impl_mysqlbind9.py

347 lines
12 KiB
Python

# 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.
import os
from designate.openstack.common import cfg
from designate.openstack.common import log as logging
from designate import utils
from designate.backend import base
from sqlalchemy.ext.sqlsoup import SqlSoup
from sqlalchemy.engine.url import _parse_rfc1738_args
from designate.sqlalchemy.session import get_engine
from designate.sqlalchemy.session import SQLOPTS
LOG = logging.getLogger(__name__)
cfg.CONF.register_group(cfg.OptGroup(
name='backend:mysqlbind9', title="Configuration for BIND9+MySQL Backend"
))
cfg.CONF.register_opts([
cfg.StrOpt('rndc-host', default='127.0.0.1', help='RNDC Host'),
cfg.IntOpt('rndc-port', default=953, help='RNDC Port'),
cfg.StrOpt('rndc-config-file',
default=None, help='RNDC Config File'),
cfg.StrOpt('rndc-key-file', default=None, help='RNDC Key File'),
cfg.StrOpt('dns-server-type', default='master',
help='slave or master DNS server?'),
cfg.BoolOpt('write-database', default=True,
help='Write to the DNS mysqlbind database?'),
cfg.StrOpt('database-dns-table',
default='dns_domains',
help='DNS schema'),
], group='backend:mysqlbind9')
cfg.CONF.register_opts(SQLOPTS, group='backend:mysqlbind9')
class MySQLBind9Backend(base.Backend):
__plugin_name__ = 'mysqlbind9'
def get_url_data(self):
url = _parse_rfc1738_args(cfg.CONF[self.name].database_connection)
return url.translate_connect_args()
def get_dns_table(self, table=None):
"""
Get a Table object from SQLSoup
:param table: Overridable table name
"""
table = table or cfg.CONF[self.name].database_dns_table
return getattr(self._db, table)
def start(self):
super(MySQLBind9Backend, self).start()
if cfg.CONF[self.name].write_database:
self._engine = get_engine(self.name)
self._db = SqlSoup(self._engine)
self._sync_domains()
def _add_soa_record(self, domain, servers):
"""
add the single SOA record for this domain. Must create the
data from attributes of the domain
"""
table = self.get_dns_table()
data_rec = "%s. %s. %d %d %d %d %d" % (
servers[0]['name'],
domain['email'].replace("@", "."),
domain['serial'],
domain['refresh'],
domain['retry'],
domain['expire'],
domain['minimum'])
# use the domain id for records that don't have a match
# in designate's records table
table.insert(
tenant_id=domain['tenant_id'],
domain_id=domain['id'],
designate_rec_id=domain['id'],
name=domain['name'],
ttl=domain['ttl'],
type='SOA',
data=data_rec)
self._db.commit()
def _add_ns_records(self, domain, servers):
"""
add the NS records, one for each server, for this domain
"""
table = self.get_dns_table()
# use the domain id for records that don't have a match
# in designate's records table
for server in servers:
table.insert(
tenant_id=domain['tenant_id'],
domain_id=domain['id'],
designate_rec_id=domain['id'],
name=domain['name'],
ttl=domain['ttl'],
type='NS',
data=server['name'])
self._db.commit()
def _insert_db_record(self, tenant_id, domain_id, record):
"""
generic db insertion method for a domain record
"""
table = self.get_dns_table()
table.insert(
tenant_id=tenant_id,
domain_id=domain_id,
designate_rec_id=record['id'],
name=record['name'],
ttl=record['ttl'],
type=record['type'],
data=record['data'])
self._db.commit()
def _update_ns_records(self, domain, servers):
"""
delete and re-add all NS records : easier to just delete all
NS records and then replace - in the case of adding new NS
servers
"""
table = self.get_dns_table()
all_ns_rec = table.filter_by(tenant_id=domain['tenant_id'],
domain_id=domain['id'],
type=u'NS')
# delete all NS records
all_ns_rec.delete()
# add all NS records (might have new servers)
self._db.commit()
self._add_ns_records(domain, servers)
def _update_db_record(self, tenant_id, record):
"""
generic domain db record update method
"""
table = self.get_dns_table()
q = table.filter_by(
tenant_id=tenant_id,
domain_id=record['domain_id'],
designate_rec_id=record['id'])
q.update({'ttl': record['ttl'],
'type': record['type'],
'data': record['data']})
self._db.commit()
def _update_soa_record(self, domain, servers):
"""
update the one single SOA record for the domain
"""
LOG.debug("_update_soa_record()")
table = self.get_dns_table()
# there will only ever be -one- of these
existing_record = table.filter_by(tenant_id=domain['tenant_id'],
domain_id=domain['id'],
type=u'SOA')
data_rec = "%s. %s. %d %d %d %d %d" % (
servers[0]['name'],
domain['email'].replace("@", "."),
domain['serial'],
domain['refresh'],
domain['retry'],
domain['expire'],
domain['minimum'])
existing_record.update(
{'ttl': domain['ttl'],
'type': u'SOA',
'data': data_rec})
self._db.commit()
# def _update_domain_ttl(self, domain):
# LOG.debug("_update_soa_record()")
# table = self.get_dns_table()
#
# # there will only ever be -one- of these
# domain_records = table.filter_by(domain_id=domain['id'])
#
# domain_records.update({'ttl': domain['ttl']})
#
# self._db.commit()
def _delete_db_record(self, tenant_id, record):
"""
delete a specific record for a given domain
"""
table = self.get_dns_table()
LOG.debug("_delete_db_record")
q = table.filter_by(
tenant_id=tenant_id,
domain_id=record['domain_id'],
designate_rec_id=record['id'])
q.delete()
self._db.commit()
def _delete_db_domain_records(self, tenant_id, domain_id):
"""
delete all records for a given domain
"""
LOG.debug('_delete_db_domain_records()')
table = self.get_dns_table()
# delete all records for the domain id
q = table.filter_by(tenant_id=tenant_id,
domain_id=domain_id)
q.delete()
self._db.commit()
def create_domain(self, context, domain):
LOG.debug('create_domain()')
if cfg.CONF[self.name].write_database:
servers = self.central_service.get_servers(self.admin_context)
self._add_soa_record(domain, servers)
self._add_ns_records(domain, servers)
self._sync_domains()
def update_domain(self, context, domain):
LOG.debug('update_domain()')
if cfg.CONF[self.name].write_database:
servers = self.central_service.get_servers(self.admin_context)
self._update_soa_record(domain, servers)
self._update_ns_records(domain, servers)
def delete_domain(self, context, domain):
LOG.debug('delete_domain()')
if cfg.CONF[self.name].write_database:
self._delete_db_domain_records(domain['tenant_id'],
domain['id'])
self._sync_domains()
def create_record(self, context, domain, record):
LOG.debug('create_record()')
if cfg.CONF[self.name].write_database:
self._insert_db_record(domain['tenant_id'],
domain['id'],
record)
def update_record(self, context, domain, record):
LOG.debug('update_record()')
if cfg.CONF[self.name].write_database:
self._update_db_record(domain['tenant_id'],
record)
def delete_record(self, context, domain, record):
LOG.debug('Delete Record')
if cfg.CONF[self.name].write_database:
self._delete_db_record(domain['tenant_id'],
record)
def _sync_domains(self):
"""
Update the zone file and reconfig rndc to update bind.
Unike regular bind, this only needs to be done upon adding
or deleting domains as mysqlbind takes care of updating
bind upon regular record changes
"""
LOG.debug('Synchronising domains')
domains = self.central_service.get_domains(self.admin_context)
output_folder = os.path.join(os.path.abspath(cfg.CONF.state_path),
'bind9')
# Create the output folder tree if necessary
if not os.path.exists(output_folder):
os.makedirs(output_folder)
output_path = os.path.join(output_folder, 'zones.config')
abs_state_path = os.path.abspath(cfg.CONF.state_path)
LOG.debug("Getting ready to write zones.config at %s" % output_path)
# NOTE(CapTofu): Might have to adapt this later on?
url = self.get_url_data()
utils.render_template_to_file('mysql-bind9-config.jinja2',
output_path,
domains=domains,
state_path=abs_state_path,
dns_server_type=cfg.CONF[self.name].
dns_server_type,
dns_db_schema=url['database'],
dns_db_table=cfg.CONF[self.name].
database_dns_table,
dns_db_host=url['host'],
dns_db_user=url['username'],
dns_db_password=url['password'])
# only do this if domain create, domain delete
rndc_call = [
'rndc',
'-s', cfg.CONF[self.name].rndc_host,
'-p', str(cfg.CONF[self.name].rndc_port),
]
if cfg.CONF[self.name].rndc_config_file:
rndc_call.extend(['-c', self.config.rndc_config_file])
if cfg.CONF[self.name].rndc_key_file:
rndc_call.extend(['-k', self.config.rndc_key_file])
rndc_call.extend(['reconfig'])
utils.execute(*rndc_call)