Merge "Removing old archive code"
This commit is contained in:
commit
05e30f87f1
@ -1,437 +0,0 @@
|
||||
# Copyright 2014 Red Hat, Inc.
|
||||
#
|
||||
# Author: Rich Megginson <rmeggins@redhat.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 pprint
|
||||
import time
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_serialization import jsonutils as json
|
||||
from oslo_utils import importutils
|
||||
import requests
|
||||
|
||||
from designate.backend import base
|
||||
from designate import exceptions
|
||||
from designate.i18n import _LE
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
IPA_DEFAULT_PORT = 443
|
||||
|
||||
|
||||
class IPABaseError(exceptions.Backend):
|
||||
error_code = 500
|
||||
error_type = 'unknown_ipa_error'
|
||||
|
||||
|
||||
class IPAAuthError(IPABaseError):
|
||||
error_type = 'authentication_error'
|
||||
|
||||
|
||||
# map of designate domain parameters to the corresponding
|
||||
# ipa parameter
|
||||
# NOTE: ipa manages serial, and does not honor
|
||||
# increment_serial=False - this means the designate serial
|
||||
# and the ipa serial will diverge if updates are made
|
||||
# using increment_serial=False
|
||||
domain2ipa = {'ttl': 'dnsttl', 'email': 'idnssoarname',
|
||||
'serial': 'idnssoaserial', 'expire': 'idnssoaexpire',
|
||||
'minimum': 'idnssoaminimum', 'refresh': 'idnssoarefresh',
|
||||
'retry': 'idnssoaretry'}
|
||||
|
||||
# map of designate record types to ipa
|
||||
rectype2iparectype = {'A': ('arecord', '%(data)s'),
|
||||
'AAAA': ('aaaarecord', '%(data)s'),
|
||||
'MX': ('mxrecord', '%(data)s'),
|
||||
'CNAME': ('cnamerecord', '%(data)s'),
|
||||
'TXT': ('txtrecord', '%(data)s'),
|
||||
'SRV': ('srvrecord', '%(data)s'),
|
||||
'NS': ('nsrecord', '%(data)s'),
|
||||
'PTR': ('ptrrecord', '%(data)s'),
|
||||
'SPF': ('spfrecord', '%(data)s'),
|
||||
'SSHFP': ('sshfprecord', '%(data)s'),
|
||||
'NAPTR': ('naptrrecord', '%(data)s'),
|
||||
'CAA': ('caarecord', '%(data)s'),
|
||||
'CERT': ('certrecord', '%(data)s'),
|
||||
}
|
||||
|
||||
|
||||
IPA_INVALID_DATA = 3009
|
||||
IPA_NOT_FOUND = 4001
|
||||
IPA_DUPLICATE = 4002
|
||||
IPA_NO_CHANGES = 4202
|
||||
|
||||
|
||||
class IPAUnknownError(IPABaseError):
|
||||
pass
|
||||
|
||||
|
||||
class IPACommunicationFailure(IPABaseError):
|
||||
error_type = 'communication_failure'
|
||||
pass
|
||||
|
||||
|
||||
class IPAInvalidData(IPABaseError):
|
||||
error_type = 'invalid_data'
|
||||
pass
|
||||
|
||||
|
||||
class IPADomainNotFound(IPABaseError):
|
||||
error_type = 'domain_not_found'
|
||||
pass
|
||||
|
||||
|
||||
class IPARecordNotFound(IPABaseError):
|
||||
error_type = 'record_not_found'
|
||||
pass
|
||||
|
||||
|
||||
class IPADuplicateDomain(IPABaseError):
|
||||
error_type = 'duplicate_domain'
|
||||
pass
|
||||
|
||||
|
||||
class IPADuplicateRecord(IPABaseError):
|
||||
error_type = 'duplicate_record'
|
||||
pass
|
||||
|
||||
|
||||
ipaerror2exception = {
|
||||
IPA_INVALID_DATA: {
|
||||
'dnszone': IPAInvalidData,
|
||||
'dnsrecord': IPAInvalidData
|
||||
},
|
||||
IPA_NOT_FOUND: {
|
||||
'dnszone': IPADomainNotFound,
|
||||
'dnsrecord': IPARecordNotFound
|
||||
},
|
||||
IPA_DUPLICATE: {
|
||||
'dnszone': IPADuplicateDomain,
|
||||
'dnsrecord': IPADuplicateRecord
|
||||
},
|
||||
# NOTE: Designate will send updates with all fields
|
||||
# even if they have not changed value. If none of
|
||||
# the given values has changed, IPA will return
|
||||
# this error code - this can be ignored
|
||||
IPA_NO_CHANGES: {
|
||||
'dnszone': None,
|
||||
'dnsrecord': None
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def abs2rel_name(domain, rsetname):
|
||||
"""convert rsetname from absolute form foo.bar.tld. to the name
|
||||
relative to the domain. For IPA, if domain is rsetname, then use
|
||||
"@" as the relative name. If rsetname does not end with a subset
|
||||
of the domain, the just return the raw rsetname
|
||||
"""
|
||||
if rsetname.endswith(domain):
|
||||
idx = rsetname.rfind(domain)
|
||||
if idx == 0:
|
||||
rsetname = "@"
|
||||
elif idx > 0:
|
||||
rsetname = rsetname[:idx].rstrip(".")
|
||||
return rsetname
|
||||
|
||||
|
||||
class IPABackend(base.Backend):
|
||||
__plugin_name__ = 'ipa'
|
||||
|
||||
@classmethod
|
||||
def get_cfg_opts(cls):
|
||||
group = cfg.OptGroup(
|
||||
name='backend:ipa', title="Configuration for IPA Backend"
|
||||
)
|
||||
|
||||
opts = [
|
||||
cfg.StrOpt('ipa-host', default='localhost.localdomain',
|
||||
help='IPA RPC listener host - must be FQDN'),
|
||||
cfg.IntOpt('ipa-port', default=IPA_DEFAULT_PORT,
|
||||
help='IPA RPC listener port'),
|
||||
cfg.StrOpt('ipa-client-keytab',
|
||||
help='Kerberos client keytab file'),
|
||||
cfg.StrOpt('ipa-auth-driver-class',
|
||||
default='designate.backend.impl_ipa.auth.IPAAuth',
|
||||
help='Class that implements the authentication '
|
||||
'driver for IPA'),
|
||||
cfg.StrOpt('ipa-ca-cert',
|
||||
help='CA certificate for use with https to IPA'),
|
||||
cfg.StrOpt('ipa-base-url', default='/ipa',
|
||||
help='Base URL for IPA RPC, relative to host[:port]'),
|
||||
cfg.StrOpt('ipa-json-url',
|
||||
default='/json',
|
||||
help='URL for IPA JSON RPC, relative to IPA base URL'),
|
||||
cfg.IntOpt('ipa-connect-retries', default=1,
|
||||
help='How many times Designate will attempt to retry '
|
||||
'the connection to IPA before giving up'),
|
||||
cfg.BoolOpt('ipa-force-ns-use', default=False,
|
||||
help='IPA requires that a specified '
|
||||
'name server or SOA MNAME is resolvable - if this '
|
||||
'option is set, Designate will force IPA to use a '
|
||||
'given name server even if it is not resolvable'),
|
||||
cfg.StrOpt('ipa-version', default='2.65',
|
||||
help='IPA RPC JSON version')
|
||||
]
|
||||
|
||||
return [(group, opts)]
|
||||
|
||||
def start(self):
|
||||
LOG.debug('IPABackend start')
|
||||
self.request = requests.Session()
|
||||
authclassname = cfg.CONF[self.name].ipa_auth_driver_class
|
||||
authclass = importutils.import_class(authclassname)
|
||||
self.request.auth = (
|
||||
authclass(cfg.CONF[self.name].ipa_client_keytab,
|
||||
cfg.CONF[self.name].ipa_host))
|
||||
ipa_base_url = cfg.CONF[self.name].ipa_base_url
|
||||
if ipa_base_url.startswith("http"): # full URL
|
||||
self.baseurl = ipa_base_url
|
||||
else: # assume relative to https://host[:port]
|
||||
self.baseurl = "https://" + cfg.CONF[self.name].ipa_host
|
||||
ipa_port = cfg.CONF[self.name].ipa_port
|
||||
if ipa_port != IPA_DEFAULT_PORT:
|
||||
self.baseurl += ":" + str(ipa_port)
|
||||
self.baseurl += ipa_base_url
|
||||
ipa_json_url = cfg.CONF[self.name].ipa_json_url
|
||||
if ipa_json_url.startswith("http"): # full URL
|
||||
self.jsonurl = ipa_json_url
|
||||
else: # assume relative to https://host[:port]
|
||||
self.jsonurl = self.baseurl + ipa_json_url
|
||||
xtra_hdrs = {'Content-Type': 'application/json',
|
||||
'Referer': self.baseurl}
|
||||
self.request.headers.update(xtra_hdrs)
|
||||
self.request.verify = cfg.CONF[self.name].ipa_ca_cert
|
||||
self.ntries = cfg.CONF[self.name].ipa_connect_retries
|
||||
self.force = cfg.CONF[self.name].ipa_force_ns_use
|
||||
|
||||
def create_zone(self, context, zone):
|
||||
LOG.debug('Create Zone %r' % zone)
|
||||
ipareq = {'method': 'dnszone_add', 'id': 0}
|
||||
params = [zone['name']]
|
||||
servers = self.central_service.get_zone_ns_records(self.admin_context)
|
||||
# just use the first one for zone creation - add the others
|
||||
# later, below - use force because designate assumes the NS
|
||||
# already exists somewhere, is resolvable, and already has
|
||||
# an A/AAAA record
|
||||
args = {'idnssoamname': servers[0]['name']}
|
||||
if self.force:
|
||||
args['force'] = True
|
||||
for dkey, ipakey in list(domain2ipa.items()):
|
||||
if dkey in zone:
|
||||
args[ipakey] = zone[dkey]
|
||||
ipareq['params'] = [params, args]
|
||||
self._call_and_handle_error(ipareq)
|
||||
# add NS records for all of the other servers
|
||||
if len(servers) > 1:
|
||||
ipareq = {'method': 'dnsrecord_add', 'id': 0}
|
||||
params = [zone['name'], "@"]
|
||||
args = {'nsrecord': servers[1:]}
|
||||
if self.force:
|
||||
args['force'] = True
|
||||
ipareq['params'] = [params, args]
|
||||
self._call_and_handle_error(ipareq)
|
||||
|
||||
def update_zone(self, context, zone):
|
||||
LOG.debug('Update Zone %r' % zone)
|
||||
ipareq = {'method': 'dnszone_mod', 'id': 0}
|
||||
params = [zone['name']]
|
||||
args = {}
|
||||
for dkey, ipakey in list(domain2ipa.items()):
|
||||
if dkey in zone:
|
||||
args[ipakey] = zone[dkey]
|
||||
ipareq['params'] = [params, args]
|
||||
self._call_and_handle_error(ipareq)
|
||||
|
||||
def delete_zone(self, context, zone):
|
||||
LOG.debug('Delete Zone %r' % zone)
|
||||
ipareq = {'method': 'dnszone_del', 'id': 0}
|
||||
params = [zone['name']]
|
||||
args = {}
|
||||
ipareq['params'] = [params, args]
|
||||
self._call_and_handle_error(ipareq)
|
||||
|
||||
def create_recordset(self, context, domain, recordset):
|
||||
LOG.debug('Discarding create_recordset call, not-applicable')
|
||||
|
||||
def update_recordset(self, context, domain, recordset):
|
||||
LOG.debug('Update RecordSet %r / %r' % (domain, recordset))
|
||||
# designate allows to update a recordset if there are no
|
||||
# records in it - we should ignore this case
|
||||
if not self._recset_has_records(context, recordset):
|
||||
LOG.debug('No records in %r / %r - skipping' % (domain, recordset))
|
||||
return
|
||||
# The only thing IPA allows is to change the ttl, since that is
|
||||
# stored "per recordset"
|
||||
if 'ttl' not in recordset:
|
||||
return
|
||||
ipareq = {'method': 'dnsrecord_mod', 'id': 0}
|
||||
dname = domain['name']
|
||||
rsetname = abs2rel_name(dname, recordset['name'])
|
||||
params = [domain['name'], rsetname]
|
||||
args = {'dnsttl': recordset['ttl']}
|
||||
ipareq['params'] = [params, args]
|
||||
self._call_and_handle_error(ipareq)
|
||||
|
||||
def delete_recordset(self, context, domain, recordset):
|
||||
LOG.debug('Delete RecordSet %r / %r' % (domain, recordset))
|
||||
# designate allows to delete a recordset if there are no
|
||||
# records in it - we should ignore this case
|
||||
if not self._recset_has_records(context, recordset):
|
||||
LOG.debug('No records in %r / %r - skipping' % (domain, recordset))
|
||||
return
|
||||
ipareq = {'method': 'dnsrecord_mod', 'id': 0}
|
||||
dname = domain['name']
|
||||
rsetname = abs2rel_name(dname, recordset['name'])
|
||||
params = [domain['name'], rsetname]
|
||||
rsettype = rectype2iparectype[recordset['type']][0]
|
||||
args = {rsettype: None}
|
||||
ipareq['params'] = [params, args]
|
||||
self._call_and_handle_error(ipareq)
|
||||
|
||||
def create_record(self, context, domain, recordset, record):
|
||||
LOG.debug('Create Record %r / %r / %r' % (domain, recordset, record))
|
||||
ipareq = {'method': 'dnsrecord_add', 'id': 0}
|
||||
params, args = self._rec_to_ipa_rec(domain, recordset, [record])
|
||||
ipareq['params'] = [params, args]
|
||||
self._call_and_handle_error(ipareq)
|
||||
|
||||
def update_record(self, context, domain, recordset, record):
|
||||
LOG.debug('Update Record %r / %r / %r' % (domain, recordset, record))
|
||||
# for modify operations - IPA does not support a way to change
|
||||
# a particular field in a given record - e.g. for an MX record
|
||||
# with several values, IPA stores them like this:
|
||||
# name: "server1.local."
|
||||
# data: ["10 mx1.server1.local.", "20 mx2.server1.local."]
|
||||
# we could do a search of IPA, compare the values in the
|
||||
# returned array - but that adds an additional round trip
|
||||
# and is error prone
|
||||
# instead, we just get all of the current values and send
|
||||
# them in one big modify
|
||||
criteria = {'recordset_id': record['recordset_id']}
|
||||
reclist = self.central_service.find_records(self.admin_context,
|
||||
criteria)
|
||||
ipareq = {'method': 'dnsrecord_mod', 'id': 0}
|
||||
params, args = self._rec_to_ipa_rec(domain, recordset, reclist)
|
||||
ipareq['params'] = [params, args]
|
||||
self._call_and_handle_error(ipareq)
|
||||
|
||||
def delete_record(self, context, domain, recordset, record):
|
||||
LOG.debug('Delete Record %r / %r / %r' % (domain, recordset, record))
|
||||
ipareq = {'method': 'dnsrecord_del', 'id': 0}
|
||||
params, args = self._rec_to_ipa_rec(domain, recordset, [record])
|
||||
args['del_all'] = 0
|
||||
ipareq['params'] = [params, args]
|
||||
self._call_and_handle_error(ipareq)
|
||||
|
||||
def ping(self, context):
|
||||
LOG.debug('Ping')
|
||||
# NOTE: This call will cause ipa to issue an error, but
|
||||
# 1) it should not throw an exception
|
||||
# 2) the response will indicate ipa is running
|
||||
# 3) the bandwidth usage is minimal
|
||||
ipareq = {'method': 'dnszone_show', 'id': 0}
|
||||
params = ['@']
|
||||
args = {}
|
||||
ipareq['params'] = [params, args]
|
||||
retval = {'result': True}
|
||||
try:
|
||||
self._call_and_handle_error(ipareq)
|
||||
except Exception as e:
|
||||
retval = {'result': False, 'reason': str(e)}
|
||||
return retval
|
||||
|
||||
def _rec_to_ipa_rec(self, domain, recordset, reclist):
|
||||
dname = domain['name']
|
||||
rsetname = abs2rel_name(dname, recordset['name'])
|
||||
params = [dname, rsetname]
|
||||
rectype = recordset['type']
|
||||
vals = []
|
||||
for record in reclist:
|
||||
vals.append(rectype2iparectype[rectype][1] % record)
|
||||
args = {rectype2iparectype[rectype][0]: vals}
|
||||
ttl = recordset.get('ttl') or domain.get('ttl')
|
||||
if ttl is not None:
|
||||
args['dnsttl'] = ttl
|
||||
return params, args
|
||||
|
||||
def _ipa_error_to_exception(self, resp, ipareq):
|
||||
exc = None
|
||||
if resp['error'] is None:
|
||||
return exc
|
||||
errcode = resp['error']['code']
|
||||
method = ipareq['method']
|
||||
methtype = method.split('_')[0]
|
||||
exclass = ipaerror2exception.get(errcode, {}).get(methtype,
|
||||
IPAUnknownError)
|
||||
if exclass:
|
||||
LOG.debug("Error: ipa command [%s] returned error [%s]" %
|
||||
(pprint.pformat(ipareq), pprint.pformat(resp)))
|
||||
elif errcode: # not mapped
|
||||
LOG.debug("Ignoring IPA error code %d: %s" %
|
||||
(errcode, pprint.pformat(resp)))
|
||||
return exclass
|
||||
|
||||
def _call_and_handle_error(self, ipareq):
|
||||
if 'version' not in ipareq['params'][1]:
|
||||
ipareq['params'][1]['version'] = cfg.CONF[self.name].ipa_version
|
||||
need_reauth = False
|
||||
while True:
|
||||
status_code = 200
|
||||
try:
|
||||
if need_reauth:
|
||||
self.request.auth.refresh_auth()
|
||||
rawresp = self.request.post(self.jsonurl,
|
||||
data=json.dumps(ipareq))
|
||||
status_code = rawresp.status_code
|
||||
except IPAAuthError:
|
||||
status_code = 401
|
||||
if status_code == 401:
|
||||
if self.ntries == 0:
|
||||
# persistent inability to auth
|
||||
LOG.error(_LE("Error: could not authenticate to IPA - "
|
||||
"please check for correct keytab file"))
|
||||
# reset for next time
|
||||
self.ntries = cfg.CONF[self.name].ipa_connect_retries
|
||||
raise IPACommunicationFailure()
|
||||
else:
|
||||
LOG.debug("Refresh authentication")
|
||||
need_reauth = True
|
||||
self.ntries -= 1
|
||||
time.sleep(1)
|
||||
else:
|
||||
# successful - reset
|
||||
self.ntries = cfg.CONF[self.name].ipa_connect_retries
|
||||
break
|
||||
try:
|
||||
resp = json.loads(rawresp.text)
|
||||
except ValueError:
|
||||
# response was not json - some sort of error response
|
||||
LOG.debug("Error: unknown error from IPA [%s]" % rawresp.text)
|
||||
raise IPAUnknownError("unable to process response from IPA")
|
||||
# raise the appropriate exception, if error
|
||||
exclass = self._ipa_error_to_exception(resp, ipareq)
|
||||
if exclass:
|
||||
# could add additional info/message to exception here
|
||||
raise exclass()
|
||||
return resp
|
||||
|
||||
def _recset_has_records(self, context, recordset):
|
||||
"""Return True if the recordset has records, False otherwise"""
|
||||
criteria = {'recordset_id': recordset['id']}
|
||||
num = self.central_service.count_records(self.admin_context,
|
||||
criteria)
|
||||
return num > 0
|
@ -1,62 +0,0 @@
|
||||
# Copyright 2014 Red Hat, Inc.
|
||||
#
|
||||
# Author: Rich Megginson <rmeggins@redhat.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 logging
|
||||
import os
|
||||
|
||||
import kerberos
|
||||
from requests import auth
|
||||
|
||||
from designate.backend.impl_ipa import IPAAuthError
|
||||
from designate.i18n import _LE
|
||||
from designate.i18n import _LW
|
||||
from designate.utils import generate_uuid
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IPAAuth(auth.AuthBase):
|
||||
def __init__(self, keytab, hostname):
|
||||
# store the kerberos credentials in memory rather than on disk
|
||||
os.environ['KRB5CCNAME'] = "MEMORY:" + generate_uuid()
|
||||
self.token = None
|
||||
self.keytab = keytab
|
||||
self.hostname = hostname
|
||||
if self.keytab:
|
||||
os.environ['KRB5_CLIENT_KTNAME'] = self.keytab
|
||||
else:
|
||||
LOG.warning(_LW('No IPA client kerberos keytab file given'))
|
||||
|
||||
def __call__(self, request):
|
||||
if not self.token:
|
||||
self.refresh_auth()
|
||||
request.headers['Authorization'] = 'negotiate ' + self.token
|
||||
return request
|
||||
|
||||
def refresh_auth(self):
|
||||
service = "HTTP@" + self.hostname
|
||||
flags = kerberos.GSS_C_MUTUAL_FLAG | kerberos.GSS_C_SEQUENCE_FLAG
|
||||
try:
|
||||
(_, vc) = kerberos.authGSSClientInit(service, flags)
|
||||
except kerberos.GSSError as e:
|
||||
LOG.error(_LE("caught kerberos exception %r") % e)
|
||||
raise IPAAuthError(str(e))
|
||||
try:
|
||||
kerberos.authGSSClientStep(vc, "")
|
||||
except kerberos.GSSError as e:
|
||||
LOG.error(_LE("caught kerberos exception %r") % e)
|
||||
raise IPAAuthError(str(e))
|
||||
self.token = kerberos.authGSSClientResponse(vc)
|
@ -1,153 +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 logging
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_utils import excutils
|
||||
|
||||
from designate import backend
|
||||
from designate.backend import base
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
CFG_GROUP = 'backend:multi'
|
||||
|
||||
|
||||
class MultiBackend(base.Backend):
|
||||
"""
|
||||
Multi-backend backend
|
||||
|
||||
This backend dispatches calls to a master backend and a slave backend.
|
||||
It enforces master/slave ordering semantics as follows:
|
||||
|
||||
Creates for tsigkeys, servers and domains are done on the master first,
|
||||
then on the slave.
|
||||
|
||||
Updates for tsigkeys, servers and domains and all operations on records
|
||||
are done on the master only. It's assumed masters and slaves use an
|
||||
external mechanism to sync existing domains, most likely XFR.
|
||||
|
||||
Deletes are done on the slave first, then on the master.
|
||||
|
||||
If the create on the slave fails, the domain/tsigkey/server is deleted from
|
||||
the master. If delete on the master fails, the domain/tdigkey/server is
|
||||
recreated on the slave.
|
||||
"""
|
||||
__plugin_name__ = 'multi'
|
||||
|
||||
@classmethod
|
||||
def get_cfg_opts(cls):
|
||||
group = cfg.OptGroup(
|
||||
name=CFG_GROUP, title="Configuration for multi-backend Backend"
|
||||
)
|
||||
|
||||
opts = [
|
||||
cfg.StrOpt('master', default='fake', help='Master backend'),
|
||||
cfg.StrOpt('slave', default='fake', help='Slave backend'),
|
||||
]
|
||||
|
||||
return [(group, opts)]
|
||||
|
||||
def __init__(self, central_service):
|
||||
super(MultiBackend, self).__init__(central_service)
|
||||
self.central = central_service
|
||||
self.master = backend.get_backend(cfg.CONF[CFG_GROUP].master,
|
||||
central_service)
|
||||
self.slave = backend.get_backend(cfg.CONF[CFG_GROUP].slave,
|
||||
central_service)
|
||||
|
||||
def start(self):
|
||||
self.master.start()
|
||||
self.slave.start()
|
||||
|
||||
def stop(self):
|
||||
self.slave.stop()
|
||||
self.master.stop()
|
||||
|
||||
def create_tsigkey(self, context, tsigkey):
|
||||
self.master.create_tsigkey(context, tsigkey)
|
||||
try:
|
||||
self.slave.create_tsigkey(context, tsigkey)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
self.master.delete_tsigkey(context, tsigkey)
|
||||
|
||||
def update_tsigkey(self, context, tsigkey):
|
||||
self.master.update_tsigkey(context, tsigkey)
|
||||
|
||||
def delete_tsigkey(self, context, tsigkey):
|
||||
self.slave.delete_tsigkey(context, tsigkey)
|
||||
try:
|
||||
self.master.delete_tsigkey(context, tsigkey)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
self.slave.create_tsigkey(context, tsigkey)
|
||||
|
||||
def create_zone(self, context, zone):
|
||||
self.master.create_zone(context, zone)
|
||||
try:
|
||||
self.slave.create_zone(context, zone)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
self.master.delete_zone(context, zone)
|
||||
|
||||
def update_zone(self, context, zone):
|
||||
self.master.update_zone(context, zone)
|
||||
|
||||
def delete_zone(self, context, zone):
|
||||
# Fetch the full zone from Central first, as we may
|
||||
# have to recreate it on slave if delete on master fails
|
||||
deleted_context = context.deepcopy()
|
||||
deleted_context.show_deleted = True
|
||||
|
||||
full_domain = self.central.find_zone(
|
||||
deleted_context, {'id': zone['id']})
|
||||
|
||||
self.slave.delete_zone(context, zone)
|
||||
try:
|
||||
self.master.delete_zone(context, zone)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
self.slave.create_zone(context, zone)
|
||||
|
||||
[self.slave.create_record(context, zone, record)
|
||||
for record in self.central.find_records(
|
||||
context, {'domain_id': full_domain['id']})]
|
||||
|
||||
def create_recordset(self, context, zone, recordset):
|
||||
self.master.create_recordset(context, zone, recordset)
|
||||
|
||||
def update_recordset(self, context, zone, recordset):
|
||||
self.master.update_recordset(context, zone, recordset)
|
||||
|
||||
def delete_recordset(self, context, zone, recordset):
|
||||
self.master.delete_recordset(context, zone, recordset)
|
||||
|
||||
def create_record(self, context, zone, recordset, record):
|
||||
self.master.create_record(context, zone, recordset, record)
|
||||
|
||||
def update_record(self, context, zone, recordset, record):
|
||||
self.master.update_record(context, zone, recordset, record)
|
||||
|
||||
def delete_record(self, context, zone, recordset, record):
|
||||
self.master.delete_record(context, zone, recordset, record)
|
||||
|
||||
def ping(self, context):
|
||||
return {
|
||||
'master': self.master.ping(context),
|
||||
'slave': self.slave.ping(context)
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
# Copyright 2016 Rackspace, Inc.
|
||||
#
|
||||
# Author: Tim Simmons <tim.simmons@rackspace.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.
|
||||
"""
|
||||
This dumb script allows you to see what's being dumped onto
|
||||
the notifications.info queue
|
||||
|
||||
nabbed from:
|
||||
https://pika.readthedocs.io/en/latest/examples/blocking_consume.html
|
||||
"""
|
||||
import pika
|
||||
|
||||
|
||||
def on_message(channel, method_frame, header_frame, body):
|
||||
print(method_frame.delivery_tag)
|
||||
print(body)
|
||||
channel.basic_ack(delivery_tag=method_frame.delivery_tag)
|
||||
|
||||
|
||||
connection = pika.BlockingConnection()
|
||||
channel = connection.channel()
|
||||
channel.basic_consume(on_message, 'notifications.info')
|
||||
try:
|
||||
channel.start_consuming()
|
||||
except KeyboardInterrupt:
|
||||
channel.stop_consuming()
|
||||
connection.close()
|
@ -1,2 +0,0 @@
|
||||
requests
|
||||
kerberos
|
Loading…
x
Reference in New Issue
Block a user