Removing old archive code

Change-Id: I267ecfe3288ae5ad7a7b5a5fdca0ab5f5e56b082
This commit is contained in:
Erik Olof Gunnar Andersson 2023-06-04 21:55:44 -07:00
parent aacea51715
commit bf45ec737b
5 changed files with 0 additions and 693 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -1,2 +0,0 @@
requests
kerberos