1
0
mirror of https://github.com/github/octodns.git synced 2024-05-11 05:55:00 +00:00
Files
github-octodns/octodns/provider/base.py

151 lines
5.1 KiB
Python

#
#
#
from __future__ import absolute_import, division, print_function, \
unicode_literals
from ..source.base import BaseSource
from ..zone import Zone
from logging import getLogger
class UnsafePlan(Exception):
pass
class Plan(object):
log = getLogger('Plan')
MAX_SAFE_UPDATE_PCENT = .3
MAX_SAFE_DELETE_PCENT = .3
MIN_EXISTING_RECORDS = 10
def __init__(self, existing, desired, changes):
self.existing = existing
self.desired = desired
self.changes = changes
change_counts = {
'Create': 0,
'Delete': 0,
'Update': 0
}
for change in changes:
change_counts[change.__class__.__name__] += 1
self.change_counts = change_counts
if self.existing:
self.log.debug('__init__: Creates=%s, Updates=%s, Deletes=%s'
'Existing=%s',
self.change_counts['Create'],
self.change_counts['Update'],
self.change_counts['Delete'],
len(self.existing.records))
else:
self.log.debug('__init__: Creates=%s, Updates=%s, Deletes=%s',
self.change_counts['Create'],
self.change_counts['Update'],
self.change_counts['Delete'])
def raise_if_unsafe(self):
# TODO: what is safe really?
if self.existing and \
len(self.existing.records) >= self.MIN_EXISTING_RECORDS:
existing_record_count = len(self.existing.records)
update_pcent = self.change_counts['Update'] / existing_record_count
delete_pcent = self.change_counts['Delete'] / existing_record_count
if update_pcent > self.MAX_SAFE_UPDATE_PCENT:
raise UnsafePlan('Too many updates, %s is over %s percent'
'(%s/%s)',
update_pcent,
self.MAX_SAFE_UPDATE_PCENT * 100,
self.change_counts['Update'],
existing_record_count)
if delete_pcent > self.MAX_SAFE_DELETE_PCENT:
raise UnsafePlan('Too many deletes, %s is over %s percent'
'(%s/%s)',
delete_pcent,
self.MAX_SAFE_DELETE_PCENT * 100,
self.change_counts['Delete'],
existing_record_count)
def __repr__(self):
return 'Creates={}, Updates={}, Deletes={}, Existing Records={}' \
.format(self.change_counts['Create'], self.change_counts['Update'],
self.change_counts['Delete'],
len(self.existing.records))
class BaseProvider(BaseSource):
def __init__(self, id, apply_disabled=False):
super(BaseProvider, self).__init__(id)
self.log.debug('__init__: id=%s, apply_disabled=%s', id,
apply_disabled)
self.apply_disabled = apply_disabled
def _include_change(self, change):
'''
An opportunity for providers to filter out false positives due to
pecularities in their implementation. E.g. minimum TTLs.
'''
return True
def _extra_changes(self, existing, changes):
'''
An opportunity for providers to add extra changes to the plan that are
necessary to update ancilary record data or configure the zone. E.g.
base NS records.
'''
return []
def plan(self, desired):
self.log.info('plan: desired=%s', desired.name)
existing = Zone(desired.name, desired.sub_zones)
self.populate(existing, target=True)
# compute the changes at the zone/record level
changes = existing.changes(desired, self)
# allow the provider to filter out false positives
before = len(changes)
changes = filter(self._include_change, changes)
after = len(changes)
if before != after:
self.log.info('plan: filtered out %s changes', before - after)
# allow the provider to add extra changes it needs
extra = self._extra_changes(existing, changes)
if extra:
self.log.info('plan: extra changes\n %s', '\n '
.join([str(c) for c in extra]))
changes += extra
if changes:
plan = Plan(existing, desired, changes)
self.log.info('plan: %s', plan)
return plan
self.log.info('plan: No changes')
return None
def apply(self, plan):
'''
Submits actual planned changes to the provider. Returns the number of
changes made
'''
if self.apply_disabled:
self.log.info('apply: disabled')
return 0
self.log.info('apply: making changes')
self._apply(plan)
return len(plan.changes)
def _apply(self, plan):
raise NotImplementedError('Abstract base class, _apply method '
'missing')