# Copyright 2012 Managed I.T. # # Author: Kiall Mac Innes # Author: Endre Karlson # # 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 abc from oslo_config import cfg from oslo_log import log as logging import re from designate import exceptions from designate.central import rpcapi as central_rpcapi from designate.context import DesignateContext from designate.objects import Record from designate.objects import RecordList from designate.objects import RecordSet from designate.plugin import ExtensionPlugin LOG = logging.getLogger(__name__) class NotificationHandler(ExtensionPlugin): """Base class for notification handlers""" __plugin_ns__ = 'designate.notification.handler' __plugin_type__ = 'handler' def __init__(self, *args, **kw): super(NotificationHandler, self).__init__(*args, **kw) self.central_api = central_rpcapi.CentralAPI() @abc.abstractmethod def get_exchange_topics(self): """ Returns a tuple of (exchange, list(topics)) this handler wishes to receive notifications from. """ @abc.abstractmethod def get_event_types(self): """ Returns a list of event types this handler is capable of processing """ @abc.abstractmethod def process_notification(self, context, event_type, payload): """Processes a given notification""" def get_zone(self, zone_id): """ Return the zone for this context """ context = DesignateContext.get_admin_context(all_tenants=True) return self.central_api.get_zone(context, zone_id) def _create_or_update_recordset(self, context, records, zone_id, name, type, ttl=None): name = name.encode('idna').decode('utf-8') try: # Attempt to create a new recordset. values = { 'name': name, 'type': type, 'ttl': ttl, } recordset = RecordSet(**values) recordset.records = RecordList(objects=records) recordset.validate() recordset = self.central_api.create_recordset( context, zone_id, recordset ) except exceptions.DuplicateRecordSet: # Fetch and update the existing recordset. recordset = self.central_api.find_recordset(context, { 'zone_id': zone_id, 'name': name, 'type': type, }) for record in records: recordset.records.append(record) recordset.validate() recordset = self.central_api.update_recordset( context, recordset ) LOG.debug('Creating record in %s / %s', zone_id, recordset['id']) return recordset def _update_or_delete_recordset(self, context, zone_id, recordset_id, record_to_delete): LOG.debug( 'Deleting record in %s / %s', zone_id, record_to_delete['id'] ) try: recordset = self.central_api.get_recordset( context, zone_id, recordset_id ) # Record not longer in recordset. Lets abort. if record_to_delete not in recordset.records: LOG.debug( 'Record %s not found in recordset %s', record_to_delete['id'], recordset_id ) return # Remove the record from the recordset. recordset.records.remove(record_to_delete) if not recordset.records: # Recordset is now empty. Remove it. self.central_api.delete_recordset( context, zone_id, recordset_id ) return # Recordset still has records, validate it and update it. recordset.validate() self.central_api.update_recordset(context, recordset) except exceptions.RecordSetNotFound: LOG.info( 'Recordset %s for record %s was already removed', recordset_id, record_to_delete['id'] ) class BaseAddressHandler(NotificationHandler): default_formatv4 = ('%(hostname)s.%(domain)s',) default_formatv6 = ('%(hostname)s.%(domain)s',) def _get_ip_data(self, addr_dict): ip = addr_dict['address'] version = addr_dict['version'] data = { 'ip_version': version, } if version == 4: data['ip_address'] = ip.replace('.', '-') ip_data = ip.split(".") for i in [0, 1, 2, 3]: data["octet%s" % i] = ip_data[i] if version == 6: data['ip_address'] = ip.replace(':', '-') ip_data = re.split('::|:', ip) for i in range(len(ip_data)): data["octet%s" % i] = ip_data[i] return data def _get_formatv4(self): return ( cfg.CONF[self.name].get('formatv4') or self.default_formatv4 ) def _get_formatv6(self): return ( cfg.CONF[self.name].get('formatv6') or self.default_formatv6 ) def _create(self, addresses, extra, zone_id, resource_type=None, resource_id=None): """ Create a record from addresses :param addresses: Address objects like {'version': 4, 'ip': '10.0.0.1'} :param extra: Extra data to use when formatting the record :param zone_id: The ID of the designate zone. :param resource_type: The managed resource type :param resource_id: The managed resource ID """ LOG.debug('Using Zone ID: %s', zone_id) zone = self.get_zone(zone_id) LOG.debug('Domain: %r', zone) data = extra.copy() LOG.debug('Event data: %s', data) data['zone'] = zone['name'] context = DesignateContext().elevated() context.all_tenants = True context.edit_managed_records = True for addr in addresses: event_data = data.copy() event_data.update(self._get_ip_data(addr)) if addr['version'] == 4: format = self._get_formatv4() else: format = self._get_formatv6() for fmt in format: recordset_values = { 'zone_id': zone['id'], 'name': fmt % event_data, 'type': 'A' if addr['version'] == 4 else 'AAAA'} record_values = { 'data': addr['address'], 'managed': True, 'managed_plugin_name': self.get_plugin_name(), 'managed_plugin_type': self.get_plugin_type(), 'managed_resource_type': resource_type, 'managed_resource_id': resource_id } self._create_or_update_recordset( context, [Record(**record_values)], **recordset_values ) def _delete(self, zone_id, resource_id=None, resource_type='instance', criterion=None): """ Handle a generic delete of a fixed ip within a zone :param zone_id: The ID of the designate zone. :param resource_id: The managed resource ID :param resource_type: The managed resource type :param criterion: Criterion to search and destroy records """ criterion = criterion or {} context = DesignateContext().elevated() context.all_tenants = True context.edit_managed_records = True criterion.update({ 'zone_id': zone_id, 'managed': True, 'managed_plugin_name': self.get_plugin_name(), 'managed_plugin_type': self.get_plugin_type(), 'managed_resource_id': resource_id, 'managed_resource_type': resource_type }) records = self.central_api.find_records(context, criterion) for record in records: self._update_or_delete_recordset( context, zone_id, record['recordset_id'], record )