Merge "Implement deleted zone purging"
This commit is contained in:
commit
f329a813aa
|
@ -51,14 +51,15 @@ class CentralAPI(object):
|
||||||
5.2 - Add Zone Import methods
|
5.2 - Add Zone Import methods
|
||||||
5.3 - Add Zone Export method
|
5.3 - Add Zone Export method
|
||||||
5.4 - Add asynchronous Zone Export methods
|
5.4 - Add asynchronous Zone Export methods
|
||||||
|
5.5 - Add deleted zone purging task
|
||||||
"""
|
"""
|
||||||
RPC_API_VERSION = '5.4'
|
RPC_API_VERSION = '5.5'
|
||||||
|
|
||||||
def __init__(self, topic=None):
|
def __init__(self, topic=None):
|
||||||
topic = topic if topic else cfg.CONF.central_topic
|
topic = topic if topic else cfg.CONF.central_topic
|
||||||
|
|
||||||
target = messaging.Target(topic=topic, version=self.RPC_API_VERSION)
|
target = messaging.Target(topic=topic, version=self.RPC_API_VERSION)
|
||||||
self.client = rpc.get_client(target, version_cap='5.4')
|
self.client = rpc.get_client(target, version_cap='5.5')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_instance(cls):
|
def get_instance(cls):
|
||||||
|
@ -177,6 +178,14 @@ class CentralAPI(object):
|
||||||
LOG.info(_LI("delete_domain: Calling central's delete_domain."))
|
LOG.info(_LI("delete_domain: Calling central's delete_domain."))
|
||||||
return self.client.call(context, 'delete_domain', domain_id=domain_id)
|
return self.client.call(context, 'delete_domain', domain_id=domain_id)
|
||||||
|
|
||||||
|
def purge_domains(self, context, criterion=None, limit=None):
|
||||||
|
LOG.info(_LI(
|
||||||
|
"purge_domains: Calling central's purge_domains."
|
||||||
|
))
|
||||||
|
cctxt = self.client.prepare(version='5.5')
|
||||||
|
return cctxt.call(context, 'purge_domains',
|
||||||
|
criterion=criterion, limit=limit)
|
||||||
|
|
||||||
def count_domains(self, context, criterion=None):
|
def count_domains(self, context, criterion=None):
|
||||||
LOG.info(_LI("count_domains: Calling central's count_domains."))
|
LOG.info(_LI("count_domains: Calling central's count_domains."))
|
||||||
return self.client.call(context, 'count_domains', criterion=criterion)
|
return self.client.call(context, 'count_domains', criterion=criterion)
|
||||||
|
@ -512,7 +521,7 @@ class CentralAPI(object):
|
||||||
request_body=request_body)
|
request_body=request_body)
|
||||||
|
|
||||||
def find_zone_imports(self, context, criterion=None, marker=None,
|
def find_zone_imports(self, context, criterion=None, marker=None,
|
||||||
limit=None, sort_key=None, sort_dir=None):
|
limit=None, sort_key=None, sort_dir=None):
|
||||||
LOG.info(_LI("find_zone_imports: Calling central's "
|
LOG.info(_LI("find_zone_imports: Calling central's "
|
||||||
"find_zone_imports."))
|
"find_zone_imports."))
|
||||||
return self.client.call(context, 'find_zone_imports',
|
return self.client.call(context, 'find_zone_imports',
|
||||||
|
|
|
@ -260,7 +260,7 @@ def notification(notification_type):
|
||||||
|
|
||||||
|
|
||||||
class Service(service.RPCService, service.Service):
|
class Service(service.RPCService, service.Service):
|
||||||
RPC_API_VERSION = '5.4'
|
RPC_API_VERSION = '5.5'
|
||||||
|
|
||||||
target = messaging.Target(version=RPC_API_VERSION)
|
target = messaging.Target(version=RPC_API_VERSION)
|
||||||
|
|
||||||
|
@ -1010,6 +1010,12 @@ class Service(service.RPCService, service.Service):
|
||||||
@notification('dns.domain.delete')
|
@notification('dns.domain.delete')
|
||||||
@synchronized_domain()
|
@synchronized_domain()
|
||||||
def delete_domain(self, context, domain_id):
|
def delete_domain(self, context, domain_id):
|
||||||
|
"""Delete or abandon a domain
|
||||||
|
On abandon, delete the domain from the DB immediately.
|
||||||
|
Otherwise, set action to DELETE and status to PENDING and poke
|
||||||
|
Pool Manager's "delete_domain" to update the resolvers. PM will then
|
||||||
|
poke back to set action to NONE and status to DELETED
|
||||||
|
"""
|
||||||
domain = self.storage.get_domain(context, domain_id)
|
domain = self.storage.get_domain(context, domain_id)
|
||||||
|
|
||||||
target = {
|
target = {
|
||||||
|
@ -1041,6 +1047,9 @@ class Service(service.RPCService, service.Service):
|
||||||
|
|
||||||
@transaction
|
@transaction
|
||||||
def _delete_domain_in_storage(self, context, domain):
|
def _delete_domain_in_storage(self, context, domain):
|
||||||
|
"""Set domain action to DELETE and status to PENDING
|
||||||
|
to have the domain soft-deleted later on
|
||||||
|
"""
|
||||||
|
|
||||||
domain.action = 'DELETE'
|
domain.action = 'DELETE'
|
||||||
domain.status = 'PENDING'
|
domain.status = 'PENDING'
|
||||||
|
@ -1049,6 +1058,19 @@ class Service(service.RPCService, service.Service):
|
||||||
|
|
||||||
return domain
|
return domain
|
||||||
|
|
||||||
|
def purge_domains(self, context, criterion=None, limit=None):
|
||||||
|
"""Purge deleted zones.
|
||||||
|
:returns: number of purged domains
|
||||||
|
"""
|
||||||
|
policy.check('purge_domains', context, criterion)
|
||||||
|
if not criterion:
|
||||||
|
raise exceptions.BadRequest("A criterion is required")
|
||||||
|
|
||||||
|
LOG.debug("Performing purge with limit of %r and criterion of %r"
|
||||||
|
% (limit, criterion))
|
||||||
|
|
||||||
|
return self.storage.purge_domains(context, criterion, limit)
|
||||||
|
|
||||||
def xfr_domain(self, context, domain_id):
|
def xfr_domain(self, context, domain_id):
|
||||||
domain = self.storage.get_domain(context, domain_id)
|
domain = self.storage.get_domain(context, domain_id)
|
||||||
|
|
||||||
|
|
|
@ -225,6 +225,7 @@ class SQLAlchemy(object):
|
||||||
query = self._apply_criterion(table, query, criterion)
|
query = self._apply_criterion(table, query, criterion)
|
||||||
if apply_tenant_criteria:
|
if apply_tenant_criteria:
|
||||||
query = self._apply_tenant_criteria(context, table, query)
|
query = self._apply_tenant_criteria(context, table, query)
|
||||||
|
|
||||||
query = self._apply_deleted_criteria(context, table, query)
|
query = self._apply_deleted_criteria(context, table, query)
|
||||||
|
|
||||||
# Execute the Query
|
# Execute the Query
|
||||||
|
@ -491,11 +492,20 @@ class SQLAlchemy(object):
|
||||||
|
|
||||||
return _set_object_from_model(obj, resultproxy.fetchone())
|
return _set_object_from_model(obj, resultproxy.fetchone())
|
||||||
|
|
||||||
def _delete(self, context, table, obj, exc_notfound):
|
def _delete(self, context, table, obj, exc_notfound, hard_delete=False):
|
||||||
if hasattr(table.c, 'deleted'):
|
"""Perform item deletion or soft-delete.
|
||||||
# Perform a Soft Delete
|
"""
|
||||||
|
|
||||||
|
if hasattr(table.c, 'deleted') and not hard_delete:
|
||||||
|
# Perform item soft-delete.
|
||||||
|
# Set the "status" column to "DELETED" and populate
|
||||||
|
# the "deleted_at" column
|
||||||
|
|
||||||
# TODO(kiall): If the object has any changed fields, they will be
|
# TODO(kiall): If the object has any changed fields, they will be
|
||||||
# persisted here when we don't want that.
|
# persisted here when we don't want that.
|
||||||
|
|
||||||
|
# "deleted" is populated with the object id (rather than being a
|
||||||
|
# boolean) to keep (name, deleted) unique
|
||||||
obj.deleted = obj.id.replace('-', '')
|
obj.deleted = obj.id.replace('-', '')
|
||||||
obj.deleted_at = timeutils.utcnow()
|
obj.deleted_at = timeutils.utcnow()
|
||||||
|
|
||||||
|
|
|
@ -287,6 +287,15 @@ class Storage(DriverPlugin):
|
||||||
:param domain_id: Domain ID to delete.
|
:param domain_id: Domain ID to delete.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def purge_domain(self, context, zone):
|
||||||
|
"""
|
||||||
|
Purge a Domain
|
||||||
|
|
||||||
|
:param context: RPC Context.
|
||||||
|
:param domain: Zone to delete.
|
||||||
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def count_domains(self, context, criterion=None):
|
def count_domains(self, context, criterion=None):
|
||||||
"""
|
"""
|
||||||
|
@ -385,7 +394,7 @@ class Storage(DriverPlugin):
|
||||||
:param domain_id: Domain ID to create the record in.
|
:param domain_id: Domain ID to create the record in.
|
||||||
:param recordset_id: RecordSet ID to create the record in.
|
:param recordset_id: RecordSet ID to create the record in.
|
||||||
:param record: Record object with the values to be created.
|
:param record: Record object with the values to be created.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def get_record(self, context, record_id):
|
def get_record(self, context, record_id):
|
||||||
|
@ -650,7 +659,7 @@ class Storage(DriverPlugin):
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def find_zone_imports(self, context, criterion=None, marker=None,
|
def find_zone_imports(self, context, criterion=None, marker=None,
|
||||||
limit=None, sort_key=None, sort_dir=None):
|
limit=None, sort_key=None, sort_dir=None):
|
||||||
"""
|
"""
|
||||||
Find Zone Imports
|
Find Zone Imports
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ from sqlalchemy.sql.expression import or_
|
||||||
|
|
||||||
from designate import exceptions
|
from designate import exceptions
|
||||||
from designate import objects
|
from designate import objects
|
||||||
|
from designate.i18n import _LI
|
||||||
from designate.sqlalchemy import base as sqlalchemy_base
|
from designate.sqlalchemy import base as sqlalchemy_base
|
||||||
from designate.storage import base as storage_base
|
from designate.storage import base as storage_base
|
||||||
from designate.storage.impl_sqlalchemy import tables
|
from designate.storage.impl_sqlalchemy import tables
|
||||||
|
@ -31,6 +32,8 @@ from designate.storage.impl_sqlalchemy import tables
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
MAXIMUM_SUBDOMAIN_DEPTH = 128
|
||||||
|
|
||||||
cfg.CONF.register_group(cfg.OptGroup(
|
cfg.CONF.register_group(cfg.OptGroup(
|
||||||
name='storage:sqlalchemy', title="Configuration for SQLAlchemy Storage"
|
name='storage:sqlalchemy', title="Configuration for SQLAlchemy Storage"
|
||||||
))
|
))
|
||||||
|
@ -423,11 +426,72 @@ class SQLAlchemyStorage(sqlalchemy_base.SQLAlchemy, storage_base.Storage):
|
||||||
return updated_domain
|
return updated_domain
|
||||||
|
|
||||||
def delete_domain(self, context, domain_id):
|
def delete_domain(self, context, domain_id):
|
||||||
|
"""
|
||||||
|
"""
|
||||||
# Fetch the existing domain, we'll need to return it.
|
# Fetch the existing domain, we'll need to return it.
|
||||||
domain = self._find_domains(context, {'id': domain_id}, one=True)
|
domain = self._find_domains(context, {'id': domain_id}, one=True)
|
||||||
return self._delete(context, tables.domains, domain,
|
return self._delete(context, tables.domains, domain,
|
||||||
exceptions.DomainNotFound)
|
exceptions.DomainNotFound)
|
||||||
|
|
||||||
|
def purge_domain(self, context, zone):
|
||||||
|
"""Effectively remove a zone database record.
|
||||||
|
"""
|
||||||
|
return self._delete(context, tables.domains, zone,
|
||||||
|
exceptions.DomainNotFound, hard_delete=True)
|
||||||
|
|
||||||
|
def _walk_up_domains(self, current, zones_by_id):
|
||||||
|
"""Walk upwards in a zone hierarchy until we find a parent zone
|
||||||
|
that does not belong to "zones_by_id"
|
||||||
|
:returns: parent zone ID or None
|
||||||
|
"""
|
||||||
|
max_steps = MAXIMUM_SUBDOMAIN_DEPTH
|
||||||
|
while current.parent_domain_id in zones_by_id:
|
||||||
|
current = zones_by_id[current.parent_domain_id]
|
||||||
|
max_steps -= 1
|
||||||
|
if max_steps == 0:
|
||||||
|
raise exceptions.IllegalParentDomain("Loop detected in the"
|
||||||
|
" domain hierarchy")
|
||||||
|
|
||||||
|
return current.parent_domain_id
|
||||||
|
|
||||||
|
def purge_domains(self, context, criterion, limit):
|
||||||
|
"""Purge deleted zones.
|
||||||
|
Reparent orphan childrens, if any.
|
||||||
|
Transactions/locks are not needed.
|
||||||
|
:returns: number of purged domains
|
||||||
|
"""
|
||||||
|
if 'deleted' in criterion:
|
||||||
|
context.show_deleted = True
|
||||||
|
|
||||||
|
zones = self.find_domains(
|
||||||
|
context=context,
|
||||||
|
criterion=criterion,
|
||||||
|
limit=limit,
|
||||||
|
)
|
||||||
|
if not zones:
|
||||||
|
LOG.info(_LI("No zones to be purged"))
|
||||||
|
return
|
||||||
|
|
||||||
|
LOG.debug(_LI("Purging %d zones"), len(zones))
|
||||||
|
|
||||||
|
zones_by_id = {z.id: z for z in zones}
|
||||||
|
|
||||||
|
for zone in zones:
|
||||||
|
|
||||||
|
# Reparent child zones, if any.
|
||||||
|
surviving_parent_id = self._walk_up_domains(zone, zones_by_id)
|
||||||
|
query = tables.domains.update().\
|
||||||
|
where(tables.domains.c.parent_domain_id == zone.id).\
|
||||||
|
values(parent_domain_id=surviving_parent_id)
|
||||||
|
|
||||||
|
resultproxy = self.session.execute(query)
|
||||||
|
LOG.debug(_LI("%d child zones updated"), resultproxy.rowcount)
|
||||||
|
|
||||||
|
self.purge_domain(context, zone)
|
||||||
|
|
||||||
|
LOG.info(_LI("Purged %d zones"), len(zones))
|
||||||
|
return len(zones)
|
||||||
|
|
||||||
def count_domains(self, context, criterion=None):
|
def count_domains(self, context, criterion=None):
|
||||||
query = select([func.count(tables.domains.c.id)])
|
query = select([func.count(tables.domains.c.id)])
|
||||||
query = self._apply_criterion(tables.domains, query, criterion)
|
query = self._apply_criterion(tables.domains, query, criterion)
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
import shutil
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
@ -130,3 +131,14 @@ class NetworkAPIFixture(fixtures.Fixture):
|
||||||
self.api = network_api.get_network_api(cfg.CONF.network_api)
|
self.api = network_api.get_network_api(cfg.CONF.network_api)
|
||||||
self.fake = fake_network_api
|
self.fake = fake_network_api
|
||||||
self.addCleanup(self.fake.reset_floatingips)
|
self.addCleanup(self.fake.reset_floatingips)
|
||||||
|
|
||||||
|
|
||||||
|
class ZoneManagerTaskFixture(fixtures.Fixture):
|
||||||
|
def __init__(self, task_cls):
|
||||||
|
super(ZoneManagerTaskFixture, self).__init__()
|
||||||
|
self._task_cls = task_cls
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(ZoneManagerTaskFixture, self).setUp()
|
||||||
|
self.task = self._task_cls()
|
||||||
|
self.task.on_partition_change(range(0, 4095), None, None)
|
||||||
|
|
|
@ -14,8 +14,11 @@
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import datetime
|
||||||
import copy
|
import copy
|
||||||
import random
|
import random
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
import testtools
|
import testtools
|
||||||
|
@ -28,6 +31,7 @@ from oslo_messaging.notify import notifier
|
||||||
from designate import exceptions
|
from designate import exceptions
|
||||||
from designate import objects
|
from designate import objects
|
||||||
from designate.tests.test_central import CentralTestCase
|
from designate.tests.test_central import CentralTestCase
|
||||||
|
from designate.storage.impl_sqlalchemy import tables
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -974,6 +978,284 @@ class CentralServiceTest(CentralTestCase):
|
||||||
with testtools.ExpectedException(exceptions.Forbidden):
|
with testtools.ExpectedException(exceptions.Forbidden):
|
||||||
self.central_service.count_domains(self.get_context())
|
self.central_service.count_domains(self.get_context())
|
||||||
|
|
||||||
|
def _fetch_all_domains(self):
|
||||||
|
"""Fetch all domains including deleted ones
|
||||||
|
"""
|
||||||
|
query = tables.domains.select()
|
||||||
|
return self.central_service.storage.session.execute(query).fetchall()
|
||||||
|
|
||||||
|
def _log_all_domains(self, zones, msg=None):
|
||||||
|
"""Log out a summary of zones
|
||||||
|
"""
|
||||||
|
if msg:
|
||||||
|
LOG.debug("--- %s ---" % msg)
|
||||||
|
cols = ('name', 'status', 'action', 'deleted', 'deleted_at',
|
||||||
|
'parent_domain_id')
|
||||||
|
tpl = "%-35s | %-11s | %-11s | %-32s | %-20s | %s"
|
||||||
|
LOG.debug(tpl % cols)
|
||||||
|
for z in zones:
|
||||||
|
LOG.debug(tpl % tuple(z[k] for k in cols))
|
||||||
|
|
||||||
|
def _assert_count_all_domains(self, n):
|
||||||
|
"""Assert count ALL domains including deleted ones
|
||||||
|
"""
|
||||||
|
zones = self._fetch_all_domains()
|
||||||
|
if len(zones) == n:
|
||||||
|
return
|
||||||
|
|
||||||
|
msg = "failed: %d zones expected, %d found" % (n, len(zones))
|
||||||
|
self._log_all_domains(zones, msg=msg)
|
||||||
|
raise Exception("Unexpected number of zones")
|
||||||
|
|
||||||
|
def _create_deleted_domain(self, name, mock_deletion_time):
|
||||||
|
# Create a domain and set it as deleted
|
||||||
|
domain = self.create_domain(name=name)
|
||||||
|
self._delete_domain(domain, mock_deletion_time)
|
||||||
|
return domain
|
||||||
|
|
||||||
|
def _delete_domain(self, domain, mock_deletion_time):
|
||||||
|
# Set a domain as deleted
|
||||||
|
zid = domain.id.replace('-', '')
|
||||||
|
query = tables.domains.update().\
|
||||||
|
where(tables.domains.c.id == zid).\
|
||||||
|
values(
|
||||||
|
action='NONE',
|
||||||
|
deleted=zid,
|
||||||
|
deleted_at=mock_deletion_time,
|
||||||
|
status='DELETED',
|
||||||
|
)
|
||||||
|
|
||||||
|
pxy = self.central_service.storage.session.execute(query)
|
||||||
|
self.assertEqual(pxy.rowcount, 1)
|
||||||
|
return domain
|
||||||
|
|
||||||
|
@mock.patch.object(notifier.Notifier, "info")
|
||||||
|
def test_purge_domains_nothing_to_purge(self, mock_notifier):
|
||||||
|
# Create a zone
|
||||||
|
self.create_domain()
|
||||||
|
mock_notifier.reset_mock()
|
||||||
|
self._assert_count_all_domains(1)
|
||||||
|
|
||||||
|
now = datetime.datetime(2015, 7, 31, 0, 0)
|
||||||
|
self.central_service.purge_domains(
|
||||||
|
self.admin_context,
|
||||||
|
limit=100,
|
||||||
|
criterion={
|
||||||
|
'status': 'DELETED',
|
||||||
|
'deleted': '!0',
|
||||||
|
'deleted_at': "<=%s" % now
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self._assert_count_all_domains(1)
|
||||||
|
|
||||||
|
@mock.patch.object(notifier.Notifier, "info")
|
||||||
|
def test_purge_domains_one_to_purge(self, mock_notifier):
|
||||||
|
self.create_domain()
|
||||||
|
new = datetime.datetime(2015, 7, 30, 0, 0)
|
||||||
|
now = datetime.datetime(2015, 7, 31, 0, 0)
|
||||||
|
self._create_deleted_domain('example2.org.', new)
|
||||||
|
mock_notifier.reset_mock()
|
||||||
|
self._assert_count_all_domains(2)
|
||||||
|
|
||||||
|
self.central_service.purge_domains(
|
||||||
|
self.admin_context,
|
||||||
|
limit=100,
|
||||||
|
criterion={
|
||||||
|
'deleted': '!0',
|
||||||
|
'deleted_at': "<=%s" % now
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self._assert_count_all_domains(1)
|
||||||
|
|
||||||
|
@mock.patch.object(notifier.Notifier, "info")
|
||||||
|
def test_purge_domains_one_to_purge_out_of_three(self, mock_notifier):
|
||||||
|
self.create_domain()
|
||||||
|
old = datetime.datetime(2015, 7, 20, 0, 0)
|
||||||
|
time_threshold = datetime.datetime(2015, 7, 25, 0, 0)
|
||||||
|
new = datetime.datetime(2015, 7, 30, 0, 0)
|
||||||
|
self._create_deleted_domain('old.org.', old)
|
||||||
|
self._create_deleted_domain('new.org.', new)
|
||||||
|
mock_notifier.reset_mock()
|
||||||
|
self._assert_count_all_domains(3)
|
||||||
|
|
||||||
|
purge_cnt = self.central_service.purge_domains(
|
||||||
|
self.admin_context,
|
||||||
|
limit=100,
|
||||||
|
criterion={
|
||||||
|
'deleted': '!0',
|
||||||
|
'deleted_at': "<=%s" % time_threshold
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self._assert_count_all_domains(2)
|
||||||
|
self.assertEqual(purge_cnt, 1)
|
||||||
|
|
||||||
|
@mock.patch.object(notifier.Notifier, "info")
|
||||||
|
def test_purge_domains_without_time_threshold(self, mock_notifier):
|
||||||
|
self.create_domain()
|
||||||
|
old = datetime.datetime(2015, 7, 20, 0, 0)
|
||||||
|
new = datetime.datetime(2015, 7, 30, 0, 0)
|
||||||
|
self._create_deleted_domain('old.org.', old)
|
||||||
|
self._create_deleted_domain('new.org.', new)
|
||||||
|
mock_notifier.reset_mock()
|
||||||
|
self._assert_count_all_domains(3)
|
||||||
|
|
||||||
|
purge_cnt = self.central_service.purge_domains(
|
||||||
|
self.admin_context,
|
||||||
|
limit=100,
|
||||||
|
criterion={
|
||||||
|
'deleted': '!0',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self._assert_count_all_domains(1)
|
||||||
|
self.assertEqual(purge_cnt, 2)
|
||||||
|
|
||||||
|
@mock.patch.object(notifier.Notifier, "info")
|
||||||
|
def test_purge_domains_without_deleted_criterion(self, mock_notifier):
|
||||||
|
self.create_domain()
|
||||||
|
old = datetime.datetime(2015, 7, 20, 0, 0)
|
||||||
|
time_threshold = datetime.datetime(2015, 7, 25, 0, 0)
|
||||||
|
new = datetime.datetime(2015, 7, 30, 0, 0)
|
||||||
|
self._create_deleted_domain('old.org.', old)
|
||||||
|
self._create_deleted_domain('new.org.', new)
|
||||||
|
mock_notifier.reset_mock()
|
||||||
|
self._assert_count_all_domains(3)
|
||||||
|
|
||||||
|
# Nothing should be purged
|
||||||
|
purge_cnt = self.central_service.purge_domains(
|
||||||
|
self.admin_context,
|
||||||
|
limit=100,
|
||||||
|
criterion={
|
||||||
|
'deleted_at': "<=%s" % time_threshold
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self._assert_count_all_domains(3)
|
||||||
|
self.assertEqual(purge_cnt, None)
|
||||||
|
|
||||||
|
@mock.patch.object(notifier.Notifier, "info")
|
||||||
|
def test_purge_domains_by_name(self, mock_notifier):
|
||||||
|
self.create_domain()
|
||||||
|
|
||||||
|
# The domain is purged (even if it was not deleted)
|
||||||
|
purge_cnt = self.central_service.purge_domains(
|
||||||
|
self.admin_context,
|
||||||
|
limit=100,
|
||||||
|
criterion={
|
||||||
|
'name': 'example.com.'
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self._assert_count_all_domains(0)
|
||||||
|
self.assertEqual(purge_cnt, 1)
|
||||||
|
|
||||||
|
@mock.patch.object(notifier.Notifier, "info")
|
||||||
|
def test_purge_domains_without_any_criterion(self, mock_notifier):
|
||||||
|
with testtools.ExpectedException(exceptions.BadRequest):
|
||||||
|
self.central_service.purge_domains(
|
||||||
|
self.admin_context,
|
||||||
|
limit=100,
|
||||||
|
criterion={},
|
||||||
|
)
|
||||||
|
|
||||||
|
@mock.patch.object(notifier.Notifier, "info")
|
||||||
|
def test_purge_domains_with_sharding(self, mock_notifier):
|
||||||
|
old = datetime.datetime(2015, 7, 20, 0, 0)
|
||||||
|
time_threshold = datetime.datetime(2015, 7, 25, 0, 0)
|
||||||
|
domain = self._create_deleted_domain('old.org.', old)
|
||||||
|
mock_notifier.reset_mock()
|
||||||
|
|
||||||
|
# purge domains in an empty shard
|
||||||
|
self.central_service.purge_domains(
|
||||||
|
self.admin_context,
|
||||||
|
limit=100,
|
||||||
|
criterion={
|
||||||
|
'deleted': '!0',
|
||||||
|
'deleted_at': "<=%s" % time_threshold,
|
||||||
|
'shard': 'BETWEEN 99998, 99999',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
n_zones = self.central_service.count_domains(self.admin_context)
|
||||||
|
self.assertEqual(n_zones, 1)
|
||||||
|
|
||||||
|
# purge domains in a shard that contains the domain created above
|
||||||
|
self.central_service.purge_domains(
|
||||||
|
self.admin_context,
|
||||||
|
limit=100,
|
||||||
|
criterion={
|
||||||
|
'deleted': '!0',
|
||||||
|
'deleted_at': "<=%s" % time_threshold,
|
||||||
|
'shard': 'BETWEEN 0, %d' % domain.shard,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
n_zones = self.central_service.count_domains(self.admin_context)
|
||||||
|
self.assertEqual(n_zones, 0)
|
||||||
|
|
||||||
|
def test_purge_domains_walk_up_domains(self):
|
||||||
|
Zone = namedtuple('Zone', 'id parent_domain_id')
|
||||||
|
zones = [Zone(x + 1, x) for x in range(1234, 1237)]
|
||||||
|
|
||||||
|
zones_by_id = {z.id: z for z in zones}
|
||||||
|
sid = self.central_service.storage._walk_up_domains(
|
||||||
|
zones[0], zones_by_id)
|
||||||
|
self.assertEqual(sid, 1234)
|
||||||
|
|
||||||
|
sid = self.central_service.storage._walk_up_domains(
|
||||||
|
zones[-1], zones_by_id)
|
||||||
|
self.assertEqual(sid, 1234)
|
||||||
|
|
||||||
|
def test_purge_domains_walk_up_domains_loop(self):
|
||||||
|
Zone = namedtuple('Zone', 'id parent_domain_id')
|
||||||
|
zones = [Zone(2, 1), Zone(3, 2), Zone(1, 3)]
|
||||||
|
zones_by_id = {z.id: z for z in zones}
|
||||||
|
with testtools.ExpectedException(exceptions.IllegalParentDomain):
|
||||||
|
self.central_service.storage._walk_up_domains(
|
||||||
|
zones[0], zones_by_id)
|
||||||
|
|
||||||
|
@mock.patch.object(notifier.Notifier, "info")
|
||||||
|
def test_purge_domains_with_orphans(self, mock_notifier):
|
||||||
|
old = datetime.datetime(2015, 7, 20, 0, 0)
|
||||||
|
time_threshold = datetime.datetime(2015, 7, 25, 0, 0)
|
||||||
|
|
||||||
|
# Create a tree of alive and deleted [sub]domains
|
||||||
|
z1 = self.create_domain(name='alive.org.')
|
||||||
|
z2 = self.create_domain(name='deleted.alive.org.')
|
||||||
|
z3 = self.create_domain(name='del2.deleted.alive.org.')
|
||||||
|
z4 = self.create_domain(name='del3.del2.deleted.alive.org.')
|
||||||
|
z5 = self.create_domain(name='alive2.del3.del2.deleted.alive.org.')
|
||||||
|
|
||||||
|
self._delete_domain(z2, old)
|
||||||
|
self._delete_domain(z3, old)
|
||||||
|
self._delete_domain(z4, old)
|
||||||
|
|
||||||
|
self.assertEqual(z2['parent_domain_id'], z1.id)
|
||||||
|
self.assertEqual(z3['parent_domain_id'], z2.id)
|
||||||
|
self.assertEqual(z4['parent_domain_id'], z3.id)
|
||||||
|
self.assertEqual(z5['parent_domain_id'], z4.id)
|
||||||
|
|
||||||
|
self._assert_count_all_domains(5)
|
||||||
|
mock_notifier.reset_mock()
|
||||||
|
|
||||||
|
zones = self._fetch_all_domains()
|
||||||
|
self._log_all_domains(zones)
|
||||||
|
self.central_service.purge_domains(
|
||||||
|
self.admin_context,
|
||||||
|
limit=100,
|
||||||
|
criterion={
|
||||||
|
'deleted': '!0',
|
||||||
|
'deleted_at': "<=%s" % time_threshold
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self._assert_count_all_domains(2)
|
||||||
|
zones = self._fetch_all_domains()
|
||||||
|
self._log_all_domains(zones)
|
||||||
|
for z in zones:
|
||||||
|
if z.name == 'alive.org.':
|
||||||
|
self.assertEqual(z.parent_domain_id, None)
|
||||||
|
elif z.name == 'alive2.del3.del2.deleted.alive.org.':
|
||||||
|
# alive2.del2.deleted.alive.org is to be reparented under
|
||||||
|
# alive.org
|
||||||
|
self.assertEqual(z.parent_domain_id, z1.id)
|
||||||
|
else:
|
||||||
|
raise Exception("Unexpected zone %r" % z)
|
||||||
|
|
||||||
def test_touch_domain(self):
|
def test_touch_domain(self):
|
||||||
# Create a domain
|
# Create a domain
|
||||||
expected_domain = self.create_domain()
|
expected_domain = self.create_domain()
|
||||||
|
@ -1315,7 +1597,7 @@ class CentralServiceTest(CentralTestCase):
|
||||||
raise db_exception.DBDeadlock()
|
raise db_exception.DBDeadlock()
|
||||||
|
|
||||||
with mock.patch.object(self.central_service.storage, 'commit',
|
with mock.patch.object(self.central_service.storage, 'commit',
|
||||||
side_effect=fail_once_then_pass):
|
side_effect=fail_once_then_pass):
|
||||||
# Perform the update
|
# Perform the update
|
||||||
recordset = self.central_service.update_recordset(
|
recordset = self.central_service.update_recordset(
|
||||||
self.admin_context, recordset)
|
self.admin_context, recordset)
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
# Copyright 2015 Hewlett-Packard Development Company, L.P.
|
||||||
|
#
|
||||||
|
# Author: Endre Karlson <endre.karlson@hp.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 datetime
|
||||||
|
|
||||||
|
from oslo_log import log as logging
|
||||||
|
from oslo_utils import timeutils
|
||||||
|
|
||||||
|
from designate.zone_manager import tasks
|
||||||
|
from designate.tests import TestCase
|
||||||
|
from designate.storage.impl_sqlalchemy import tables
|
||||||
|
from designate.tests import fixtures
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class TaskTest(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(TaskTest, self).setUp()
|
||||||
|
|
||||||
|
def _enable_tasks(self, tasks):
|
||||||
|
self.config(
|
||||||
|
enabled_tasks=tasks,
|
||||||
|
group="service:zone_manager")
|
||||||
|
|
||||||
|
|
||||||
|
class DeletedDomainPurgeTest(TaskTest):
|
||||||
|
def setUp(self):
|
||||||
|
super(DeletedDomainPurgeTest, self).setUp()
|
||||||
|
|
||||||
|
self.config(
|
||||||
|
interval=3600,
|
||||||
|
time_threshold=604800,
|
||||||
|
batch_size=100,
|
||||||
|
group="zone_manager_task:domain_purge"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.purge_task_fixture = self.useFixture(
|
||||||
|
fixtures.ZoneManagerTaskFixture(tasks.DeletedDomainPurgeTask)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _create_deleted_zone(self, name, mock_deletion_time):
|
||||||
|
# Create a domain and set it as deleted
|
||||||
|
domain = self.create_domain(name=name)
|
||||||
|
self._delete_domain(domain, mock_deletion_time)
|
||||||
|
return domain
|
||||||
|
|
||||||
|
def _fetch_all_domains(self):
|
||||||
|
"""Fetch all domains including deleted ones
|
||||||
|
"""
|
||||||
|
query = tables.domains.select()
|
||||||
|
return self.central_service.storage.session.execute(query).fetchall()
|
||||||
|
|
||||||
|
def _delete_domain(self, domain, mock_deletion_time):
|
||||||
|
# Set a domain as deleted
|
||||||
|
zid = domain.id.replace('-', '')
|
||||||
|
query = tables.domains.update().\
|
||||||
|
where(tables.domains.c.id == zid).\
|
||||||
|
values(
|
||||||
|
action='NONE',
|
||||||
|
deleted=zid,
|
||||||
|
deleted_at=mock_deletion_time,
|
||||||
|
status='DELETED',
|
||||||
|
)
|
||||||
|
|
||||||
|
pxy = self.central_service.storage.session.execute(query)
|
||||||
|
self.assertEqual(pxy.rowcount, 1)
|
||||||
|
return domain
|
||||||
|
|
||||||
|
def _create_deleted_zones(self):
|
||||||
|
# Create a number of deleted zones in the past days
|
||||||
|
zones = []
|
||||||
|
now = timeutils.utcnow()
|
||||||
|
for age in range(18):
|
||||||
|
age *= (24 * 60 * 60) # seconds
|
||||||
|
delta = datetime.timedelta(seconds=age)
|
||||||
|
deletion_time = now - delta
|
||||||
|
name = "example%d.org." % len(zones)
|
||||||
|
z = self._create_deleted_zone(name, deletion_time)
|
||||||
|
zones.append(z)
|
||||||
|
|
||||||
|
return zones
|
||||||
|
|
||||||
|
def test_purge_zones(self):
|
||||||
|
"""Create 18 zones, run zone_manager, check if 7 zones are remaining
|
||||||
|
"""
|
||||||
|
self.config(quota_domains=1000)
|
||||||
|
self._create_deleted_zones()
|
||||||
|
|
||||||
|
self.purge_task_fixture.task()
|
||||||
|
|
||||||
|
zones = self._fetch_all_domains()
|
||||||
|
LOG.info("Number of zones: %d", len(zones))
|
||||||
|
self.assertEqual(len(zones), 7)
|
|
@ -21,9 +21,9 @@ from mock import patch
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_config import fixture as cfg_fixture
|
from oslo_config import fixture as cfg_fixture
|
||||||
from oslotest import base
|
from oslotest import base
|
||||||
from testtools import ExpectedException as raises # with raises(...): ...
|
|
||||||
import fixtures
|
import fixtures
|
||||||
import mock
|
import mock
|
||||||
|
import testtools
|
||||||
|
|
||||||
from designate import exceptions
|
from designate import exceptions
|
||||||
from designate.central.service import Service
|
from designate.central.service import Service
|
||||||
|
@ -104,13 +104,13 @@ class MockObjectTest(base.BaseTestCase):
|
||||||
o = RoObject(a=1)
|
o = RoObject(a=1)
|
||||||
self.assertEqual(o['a'], 1)
|
self.assertEqual(o['a'], 1)
|
||||||
self.assertEqual(o.a, 1)
|
self.assertEqual(o.a, 1)
|
||||||
with raises(NotImplementedError):
|
with testtools.ExpectedException(NotImplementedError):
|
||||||
o.a = 2
|
o.a = 2
|
||||||
with raises(NotImplementedError):
|
with testtools.ExpectedException(NotImplementedError):
|
||||||
o.new = 1
|
o.new = 1
|
||||||
with raises(NotImplementedError):
|
with testtools.ExpectedException(NotImplementedError):
|
||||||
o['a'] = 2
|
o['a'] = 2
|
||||||
with raises(NotImplementedError):
|
with testtools.ExpectedException(NotImplementedError):
|
||||||
o['new'] = 1
|
o['new'] = 1
|
||||||
|
|
||||||
def test_rw(self):
|
def test_rw(self):
|
||||||
|
@ -123,9 +123,9 @@ class MockObjectTest(base.BaseTestCase):
|
||||||
o['a'] = 3
|
o['a'] = 3
|
||||||
self.assertEqual(o.a, 3)
|
self.assertEqual(o.a, 3)
|
||||||
self.assertEqual(o['a'], 3)
|
self.assertEqual(o['a'], 3)
|
||||||
with raises(NotImplementedError):
|
with testtools.ExpectedException(NotImplementedError):
|
||||||
o.new = 1
|
o.new = 1
|
||||||
with raises(NotImplementedError):
|
with testtools.ExpectedException(NotImplementedError):
|
||||||
o['new'] = 1
|
o['new'] = 1
|
||||||
|
|
||||||
|
|
||||||
|
@ -325,7 +325,7 @@ class CentralServiceTestCase(CentralBasic):
|
||||||
designate.central.service.policy.check = mock.Mock(
|
designate.central.service.policy.check = mock.Mock(
|
||||||
side_effect=exceptions.Forbidden
|
side_effect=exceptions.Forbidden
|
||||||
)
|
)
|
||||||
with raises(exceptions.InvalidTTL):
|
with testtools.ExpectedException(exceptions.InvalidTTL):
|
||||||
self.service._is_valid_ttl(self.context, 3)
|
self.service._is_valid_ttl(self.context, 3)
|
||||||
|
|
||||||
def test__update_soa_secondary(self):
|
def test__update_soa_secondary(self):
|
||||||
|
@ -440,7 +440,7 @@ class CentralServiceTestCase(CentralBasic):
|
||||||
|
|
||||||
# self.assertEqual(parent_domain, '')
|
# self.assertEqual(parent_domain, '')
|
||||||
self.service.check_for_tlds = False
|
self.service.check_for_tlds = False
|
||||||
with raises(exceptions.Forbidden):
|
with testtools.ExpectedException(exceptions.Forbidden):
|
||||||
self.service.create_domain(self.context, MockDomain())
|
self.service.create_domain(self.context, MockDomain())
|
||||||
|
|
||||||
# TODO(Federico) add more create_domain tests
|
# TODO(Federico) add more create_domain tests
|
||||||
|
@ -463,28 +463,28 @@ class CentralDomainTestCase(CentralBasic):
|
||||||
|
|
||||||
def test__is_valid_domain_name_invalid(self):
|
def test__is_valid_domain_name_invalid(self):
|
||||||
self.service._is_blacklisted_domain_name = Mock()
|
self.service._is_blacklisted_domain_name = Mock()
|
||||||
with raises(exceptions.InvalidDomainName):
|
with testtools.ExpectedException(exceptions.InvalidDomainName):
|
||||||
self.service._is_valid_domain_name(self.context, 'example^org.')
|
self.service._is_valid_domain_name(self.context, 'example^org.')
|
||||||
|
|
||||||
def test__is_valid_domain_name_invalid_2(self):
|
def test__is_valid_domain_name_invalid_2(self):
|
||||||
self.service._is_blacklisted_domain_name = Mock()
|
self.service._is_blacklisted_domain_name = Mock()
|
||||||
with raises(exceptions.InvalidDomainName):
|
with testtools.ExpectedException(exceptions.InvalidDomainName):
|
||||||
self.service._is_valid_domain_name(self.context, 'example.tld.')
|
self.service._is_valid_domain_name(self.context, 'example.tld.')
|
||||||
|
|
||||||
def test__is_valid_domain_name_invalid_same_as_tld(self):
|
def test__is_valid_domain_name_invalid_same_as_tld(self):
|
||||||
self.service._is_blacklisted_domain_name = Mock()
|
self.service._is_blacklisted_domain_name = Mock()
|
||||||
with raises(exceptions.InvalidDomainName):
|
with testtools.ExpectedException(exceptions.InvalidDomainName):
|
||||||
self.service._is_valid_domain_name(self.context, 'com.com.')
|
self.service._is_valid_domain_name(self.context, 'com.com.')
|
||||||
|
|
||||||
def test__is_valid_domain_name_invalid_tld(self):
|
def test__is_valid_domain_name_invalid_tld(self):
|
||||||
self.service._is_blacklisted_domain_name = Mock()
|
self.service._is_blacklisted_domain_name = Mock()
|
||||||
with raises(exceptions.InvalidDomainName):
|
with testtools.ExpectedException(exceptions.InvalidDomainName):
|
||||||
self.service._is_valid_domain_name(self.context, 'tld.')
|
self.service._is_valid_domain_name(self.context, 'tld.')
|
||||||
|
|
||||||
def test__is_valid_domain_name_blacklisted(self):
|
def test__is_valid_domain_name_blacklisted(self):
|
||||||
self.service._is_blacklisted_domain_name = Mock(
|
self.service._is_blacklisted_domain_name = Mock(
|
||||||
side_effect=exceptions.InvalidDomainName)
|
side_effect=exceptions.InvalidDomainName)
|
||||||
with raises(exceptions.InvalidDomainName):
|
with testtools.ExpectedException(exceptions.InvalidDomainName):
|
||||||
self.service._is_valid_domain_name(self.context, 'valid.com.')
|
self.service._is_valid_domain_name(self.context, 'valid.com.')
|
||||||
|
|
||||||
def test__is_blacklisted_domain_name(self):
|
def test__is_blacklisted_domain_name(self):
|
||||||
|
@ -510,7 +510,7 @@ class CentralDomainTestCase(CentralBasic):
|
||||||
|
|
||||||
def test__is_valid_recordset_name_no_dot(self):
|
def test__is_valid_recordset_name_no_dot(self):
|
||||||
domain = RoObject(name='example.org.')
|
domain = RoObject(name='example.org.')
|
||||||
with raises(ValueError):
|
with testtools.ExpectedException(ValueError):
|
||||||
self.service._is_valid_recordset_name(self.context, domain,
|
self.service._is_valid_recordset_name(self.context, domain,
|
||||||
'foo.example.org')
|
'foo.example.org')
|
||||||
|
|
||||||
|
@ -519,20 +519,21 @@ class CentralDomainTestCase(CentralBasic):
|
||||||
designate.central.service.cfg.CONF['service:central'].\
|
designate.central.service.cfg.CONF['service:central'].\
|
||||||
max_recordset_name_len = 255
|
max_recordset_name_len = 255
|
||||||
rs_name = 'a' * 255 + '.org.'
|
rs_name = 'a' * 255 + '.org.'
|
||||||
with raises(exceptions.InvalidRecordSetName) as e:
|
with testtools.ExpectedException(exceptions.InvalidRecordSetName) as e:
|
||||||
self.service._is_valid_recordset_name(self.context, domain,
|
self.service._is_valid_recordset_name(self.context, domain,
|
||||||
rs_name)
|
rs_name)
|
||||||
self.assertEqual(e.message, 'Name too long')
|
self.assertEqual(e.message, 'Name too long')
|
||||||
|
|
||||||
def test__is_valid_recordset_name_wrong_domain(self):
|
def test__is_valid_recordset_name_wrong_domain(self):
|
||||||
domain = RoObject(name='example.org.')
|
domain = RoObject(name='example.org.')
|
||||||
with raises(exceptions.InvalidRecordSetLocation):
|
with testtools.ExpectedException(exceptions.InvalidRecordSetLocation):
|
||||||
self.service._is_valid_recordset_name(self.context, domain,
|
self.service._is_valid_recordset_name(self.context, domain,
|
||||||
'foo.example.com.')
|
'foo.example.com.')
|
||||||
|
|
||||||
def test_is_valid_recordset_placement_cname(self):
|
def test_is_valid_recordset_placement_cname(self):
|
||||||
domain = RoObject(name='example.org.')
|
domain = RoObject(name='example.org.')
|
||||||
with raises(exceptions.InvalidRecordSetLocation) as e:
|
with testtools.ExpectedException(exceptions.InvalidRecordSetLocation) \
|
||||||
|
as e:
|
||||||
self.service._is_valid_recordset_placement(
|
self.service._is_valid_recordset_placement(
|
||||||
self.context,
|
self.context,
|
||||||
domain,
|
domain,
|
||||||
|
@ -549,7 +550,8 @@ class CentralDomainTestCase(CentralBasic):
|
||||||
self.service.storage.find_recordsets.return_value = [
|
self.service.storage.find_recordsets.return_value = [
|
||||||
RoObject(id='2')
|
RoObject(id='2')
|
||||||
]
|
]
|
||||||
with raises(exceptions.InvalidRecordSetLocation) as e:
|
with testtools.ExpectedException(exceptions.InvalidRecordSetLocation) \
|
||||||
|
as e:
|
||||||
self.service._is_valid_recordset_placement(
|
self.service._is_valid_recordset_placement(
|
||||||
self.context,
|
self.context,
|
||||||
domain,
|
domain,
|
||||||
|
@ -567,7 +569,8 @@ class CentralDomainTestCase(CentralBasic):
|
||||||
RoObject(),
|
RoObject(),
|
||||||
RoObject()
|
RoObject()
|
||||||
]
|
]
|
||||||
with raises(exceptions.InvalidRecordSetLocation) as e:
|
with testtools.ExpectedException(exceptions.InvalidRecordSetLocation) \
|
||||||
|
as e:
|
||||||
self.service._is_valid_recordset_placement(
|
self.service._is_valid_recordset_placement(
|
||||||
self.context,
|
self.context,
|
||||||
domain,
|
domain,
|
||||||
|
@ -616,7 +619,7 @@ class CentralDomainTestCase(CentralBasic):
|
||||||
self.service.storage.find_domains.return_value = [
|
self.service.storage.find_domains.return_value = [
|
||||||
RoObject(name='foo.example.org.')
|
RoObject(name='foo.example.org.')
|
||||||
]
|
]
|
||||||
with raises(exceptions.InvalidRecordSetLocation):
|
with testtools.ExpectedException(exceptions.InvalidRecordSetLocation):
|
||||||
self.service._is_valid_recordset_placement_subdomain(
|
self.service._is_valid_recordset_placement_subdomain(
|
||||||
self.context,
|
self.context,
|
||||||
domain,
|
domain,
|
||||||
|
@ -702,7 +705,7 @@ class CentralDomainTestCase(CentralBasic):
|
||||||
ns_records=[]
|
ns_records=[]
|
||||||
)
|
)
|
||||||
|
|
||||||
with raises(exceptions.NoServersConfigured):
|
with testtools.ExpectedException(exceptions.NoServersConfigured):
|
||||||
self.service.create_domain(
|
self.service.create_domain(
|
||||||
self.context,
|
self.context,
|
||||||
RoObject(tenant_id='1', name='example.com.', ttl=60,
|
RoObject(tenant_id='1', name='example.com.', ttl=60,
|
||||||
|
@ -793,7 +796,7 @@ class CentralDomainTestCase(CentralBasic):
|
||||||
tenant_id='2',
|
tenant_id='2',
|
||||||
)
|
)
|
||||||
self.service.storage.count_domains.return_value = 2
|
self.service.storage.count_domains.return_value = 2
|
||||||
with raises(exceptions.DomainHasSubdomain):
|
with testtools.ExpectedException(exceptions.DomainHasSubdomain):
|
||||||
self.service.delete_domain(self.context, '1')
|
self.service.delete_domain(self.context, '1')
|
||||||
|
|
||||||
pcheck, ctx, target = \
|
pcheck, ctx, target = \
|
||||||
|
@ -876,7 +879,7 @@ class CentralDomainTestCase(CentralBasic):
|
||||||
tenant_id='2',
|
tenant_id='2',
|
||||||
type='PRIMARY'
|
type='PRIMARY'
|
||||||
)
|
)
|
||||||
with raises(exceptions.BadRequest):
|
with testtools.ExpectedException(exceptions.BadRequest):
|
||||||
self.service.xfr_domain(self.context, '1')
|
self.service.xfr_domain(self.context, '1')
|
||||||
|
|
||||||
def test_count_report(self):
|
def test_count_report(self):
|
||||||
|
@ -923,7 +926,7 @@ class CentralDomainTestCase(CentralBasic):
|
||||||
self.service.count_domains = Mock(return_value=1)
|
self.service.count_domains = Mock(return_value=1)
|
||||||
self.service.count_records = Mock(return_value=2)
|
self.service.count_records = Mock(return_value=2)
|
||||||
self.service.count_tenants = Mock(return_value=3)
|
self.service.count_tenants = Mock(return_value=3)
|
||||||
with raises(exceptions.ReportNotFound):
|
with testtools.ExpectedException(exceptions.ReportNotFound):
|
||||||
self.service.count_report(
|
self.service.count_report(
|
||||||
self.context,
|
self.context,
|
||||||
criterion='bogus'
|
criterion='bogus'
|
||||||
|
@ -951,7 +954,7 @@ class CentralDomainTestCase(CentralBasic):
|
||||||
self.service.storage.get_recordset.return_value = RoObject(
|
self.service.storage.get_recordset.return_value = RoObject(
|
||||||
domain_id='3'
|
domain_id='3'
|
||||||
)
|
)
|
||||||
with raises(exceptions.RecordSetNotFound):
|
with testtools.ExpectedException(exceptions.RecordSetNotFound):
|
||||||
self.service.get_recordset(
|
self.service.get_recordset(
|
||||||
self.context,
|
self.context,
|
||||||
'1',
|
'1',
|
||||||
|
@ -1010,15 +1013,15 @@ class CentralDomainTestCase(CentralBasic):
|
||||||
recordset.obj_get_original_value.return_value = '1'
|
recordset.obj_get_original_value.return_value = '1'
|
||||||
|
|
||||||
recordset.obj_get_changes.return_value = ['tenant_id', 'foo']
|
recordset.obj_get_changes.return_value = ['tenant_id', 'foo']
|
||||||
with raises(exceptions.BadRequest):
|
with testtools.ExpectedException(exceptions.BadRequest):
|
||||||
self.service.update_recordset(self.context, recordset)
|
self.service.update_recordset(self.context, recordset)
|
||||||
|
|
||||||
recordset.obj_get_changes.return_value = ['domain_id', 'foo']
|
recordset.obj_get_changes.return_value = ['domain_id', 'foo']
|
||||||
with raises(exceptions.BadRequest):
|
with testtools.ExpectedException(exceptions.BadRequest):
|
||||||
self.service.update_recordset(self.context, recordset)
|
self.service.update_recordset(self.context, recordset)
|
||||||
|
|
||||||
recordset.obj_get_changes.return_value = ['type', 'foo']
|
recordset.obj_get_changes.return_value = ['type', 'foo']
|
||||||
with raises(exceptions.BadRequest):
|
with testtools.ExpectedException(exceptions.BadRequest):
|
||||||
self.service.update_recordset(self.context, recordset)
|
self.service.update_recordset(self.context, recordset)
|
||||||
|
|
||||||
def test_update_recordset_action_delete(self):
|
def test_update_recordset_action_delete(self):
|
||||||
|
@ -1027,7 +1030,7 @@ class CentralDomainTestCase(CentralBasic):
|
||||||
)
|
)
|
||||||
recordset = Mock()
|
recordset = Mock()
|
||||||
recordset.obj_get_changes.return_value = ['foo']
|
recordset.obj_get_changes.return_value = ['foo']
|
||||||
with raises(exceptions.BadRequest):
|
with testtools.ExpectedException(exceptions.BadRequest):
|
||||||
self.service.update_recordset(self.context, recordset)
|
self.service.update_recordset(self.context, recordset)
|
||||||
|
|
||||||
def test_update_recordset_action_fail_on_managed(self):
|
def test_update_recordset_action_fail_on_managed(self):
|
||||||
|
@ -1042,7 +1045,7 @@ class CentralDomainTestCase(CentralBasic):
|
||||||
recordset.managed = True
|
recordset.managed = True
|
||||||
self.context = Mock()
|
self.context = Mock()
|
||||||
self.context.edit_managed_records = False
|
self.context.edit_managed_records = False
|
||||||
with raises(exceptions.BadRequest):
|
with testtools.ExpectedException(exceptions.BadRequest):
|
||||||
self.service.update_recordset(self.context, recordset)
|
self.service.update_recordset(self.context, recordset)
|
||||||
|
|
||||||
def test_update_recordset(self):
|
def test_update_recordset(self):
|
||||||
|
@ -1169,7 +1172,7 @@ class CentralDomainTestCase(CentralBasic):
|
||||||
)
|
)
|
||||||
self.context = Mock()
|
self.context = Mock()
|
||||||
self.context.edit_managed_records = False
|
self.context.edit_managed_records = False
|
||||||
with raises(exceptions.RecordSetNotFound):
|
with testtools.ExpectedException(exceptions.RecordSetNotFound):
|
||||||
self.service.delete_recordset(self.context, 'd', 'r')
|
self.service.delete_recordset(self.context, 'd', 'r')
|
||||||
|
|
||||||
def test_delete_recordset_action_delete(self):
|
def test_delete_recordset_action_delete(self):
|
||||||
|
@ -1187,7 +1190,7 @@ class CentralDomainTestCase(CentralBasic):
|
||||||
)
|
)
|
||||||
self.context = Mock()
|
self.context = Mock()
|
||||||
self.context.edit_managed_records = False
|
self.context.edit_managed_records = False
|
||||||
with raises(exceptions.BadRequest):
|
with testtools.ExpectedException(exceptions.BadRequest):
|
||||||
self.service.delete_recordset(self.context, 'd', 'r')
|
self.service.delete_recordset(self.context, 'd', 'r')
|
||||||
|
|
||||||
def test_delete_recordset_managed(self):
|
def test_delete_recordset_managed(self):
|
||||||
|
@ -1205,7 +1208,7 @@ class CentralDomainTestCase(CentralBasic):
|
||||||
)
|
)
|
||||||
self.context = Mock()
|
self.context = Mock()
|
||||||
self.context.edit_managed_records = False
|
self.context.edit_managed_records = False
|
||||||
with raises(exceptions.BadRequest):
|
with testtools.ExpectedException(exceptions.BadRequest):
|
||||||
self.service.delete_recordset(self.context, 'd', 'r')
|
self.service.delete_recordset(self.context, 'd', 'r')
|
||||||
|
|
||||||
def test_delete_recordset(self):
|
def test_delete_recordset(self):
|
||||||
|
@ -1291,7 +1294,7 @@ class CentralDomainTestCase(CentralBasic):
|
||||||
tenant_id='2',
|
tenant_id='2',
|
||||||
type='foo',
|
type='foo',
|
||||||
)
|
)
|
||||||
with raises(exceptions.BadRequest):
|
with testtools.ExpectedException(exceptions.BadRequest):
|
||||||
self.service.create_record(
|
self.service.create_record(
|
||||||
self.context,
|
self.context,
|
||||||
1,
|
1,
|
||||||
|
@ -1360,7 +1363,7 @@ class CentralDomainTestCase(CentralBasic):
|
||||||
self.service.storage.get_recordset.return_value = RoObject(
|
self.service.storage.get_recordset.return_value = RoObject(
|
||||||
domain_id=3
|
domain_id=3
|
||||||
)
|
)
|
||||||
with raises(exceptions.RecordNotFound):
|
with testtools.ExpectedException(exceptions.RecordNotFound):
|
||||||
self.service.get_record(self.context, 1, 2, 3)
|
self.service.get_record(self.context, 1, 2, 3)
|
||||||
|
|
||||||
def test_get_record_not_found_2(self):
|
def test_get_record_not_found_2(self):
|
||||||
|
@ -1379,7 +1382,7 @@ class CentralDomainTestCase(CentralBasic):
|
||||||
domain_id=2,
|
domain_id=2,
|
||||||
recordset_id=3
|
recordset_id=3
|
||||||
)
|
)
|
||||||
with raises(exceptions.RecordNotFound):
|
with testtools.ExpectedException(exceptions.RecordNotFound):
|
||||||
self.service.get_record(self.context, 1, 2, 3)
|
self.service.get_record(self.context, 1, 2, 3)
|
||||||
|
|
||||||
def test_get_record(self):
|
def test_get_record(self):
|
||||||
|
@ -1425,15 +1428,15 @@ class CentralDomainTestCase(CentralBasic):
|
||||||
record.obj_get_original_value.return_value = 1
|
record.obj_get_original_value.return_value = 1
|
||||||
|
|
||||||
record.obj_get_changes.return_value = ['tenant_id', 'foo']
|
record.obj_get_changes.return_value = ['tenant_id', 'foo']
|
||||||
with raises(exceptions.BadRequest):
|
with testtools.ExpectedException(exceptions.BadRequest):
|
||||||
self.service.update_record(self.context, record)
|
self.service.update_record(self.context, record)
|
||||||
|
|
||||||
record.obj_get_changes.return_value = ['domain_id', 'foo']
|
record.obj_get_changes.return_value = ['domain_id', 'foo']
|
||||||
with raises(exceptions.BadRequest):
|
with testtools.ExpectedException(exceptions.BadRequest):
|
||||||
self.service.update_record(self.context, record)
|
self.service.update_record(self.context, record)
|
||||||
|
|
||||||
record.obj_get_changes.return_value = ['recordset_id', 'foo']
|
record.obj_get_changes.return_value = ['recordset_id', 'foo']
|
||||||
with raises(exceptions.BadRequest):
|
with testtools.ExpectedException(exceptions.BadRequest):
|
||||||
self.service.update_record(self.context, record)
|
self.service.update_record(self.context, record)
|
||||||
|
|
||||||
def test_update_record_action_delete(self):
|
def test_update_record_action_delete(self):
|
||||||
|
@ -1441,7 +1444,7 @@ class CentralDomainTestCase(CentralBasic):
|
||||||
action='DELETE',
|
action='DELETE',
|
||||||
)
|
)
|
||||||
record = Mock()
|
record = Mock()
|
||||||
with raises(exceptions.BadRequest):
|
with testtools.ExpectedException(exceptions.BadRequest):
|
||||||
self.service.update_record(self.context, record)
|
self.service.update_record(self.context, record)
|
||||||
|
|
||||||
def test_update_record_action_fail_on_managed(self):
|
def test_update_record_action_fail_on_managed(self):
|
||||||
|
@ -1459,7 +1462,7 @@ class CentralDomainTestCase(CentralBasic):
|
||||||
record.obj_get_changes.return_value = ['foo']
|
record.obj_get_changes.return_value = ['foo']
|
||||||
self.context = Mock()
|
self.context = Mock()
|
||||||
self.context.edit_managed_records = False
|
self.context.edit_managed_records = False
|
||||||
with raises(exceptions.BadRequest):
|
with testtools.ExpectedException(exceptions.BadRequest):
|
||||||
self.service.update_record(self.context, record)
|
self.service.update_record(self.context, record)
|
||||||
|
|
||||||
def test_update_record(self):
|
def test_update_record(self):
|
||||||
|
@ -1518,7 +1521,7 @@ class CentralDomainTestCase(CentralBasic):
|
||||||
self.service.storage.get_domain.return_value = RoObject(
|
self.service.storage.get_domain.return_value = RoObject(
|
||||||
action='DELETE',
|
action='DELETE',
|
||||||
)
|
)
|
||||||
with raises(exceptions.BadRequest):
|
with testtools.ExpectedException(exceptions.BadRequest):
|
||||||
self.service.delete_record(self.context, 1, 2, 3)
|
self.service.delete_record(self.context, 1, 2, 3)
|
||||||
|
|
||||||
def test_delete_record_not_found(self):
|
def test_delete_record_not_found(self):
|
||||||
|
@ -1533,7 +1536,7 @@ class CentralDomainTestCase(CentralBasic):
|
||||||
id=888,
|
id=888,
|
||||||
)
|
)
|
||||||
# domain.id != record.domain_id
|
# domain.id != record.domain_id
|
||||||
with raises(exceptions.RecordNotFound):
|
with testtools.ExpectedException(exceptions.RecordNotFound):
|
||||||
self.service.delete_record(self.context, 1, 2, 3)
|
self.service.delete_record(self.context, 1, 2, 3)
|
||||||
|
|
||||||
self.service.storage.get_record.return_value = RoObject(
|
self.service.storage.get_record.return_value = RoObject(
|
||||||
|
@ -1542,7 +1545,7 @@ class CentralDomainTestCase(CentralBasic):
|
||||||
recordset_id=7777,
|
recordset_id=7777,
|
||||||
)
|
)
|
||||||
# recordset.id != record.recordset_id
|
# recordset.id != record.recordset_id
|
||||||
with raises(exceptions.RecordNotFound):
|
with testtools.ExpectedException(exceptions.RecordNotFound):
|
||||||
self.service.delete_record(self.context, 1, 2, 3)
|
self.service.delete_record(self.context, 1, 2, 3)
|
||||||
|
|
||||||
def test_delete_record(self):
|
def test_delete_record(self):
|
||||||
|
@ -1607,7 +1610,7 @@ class CentralDomainTestCase(CentralBasic):
|
||||||
self.context.edit_managed_records = False
|
self.context.edit_managed_records = False
|
||||||
|
|
||||||
with fx_pool_manager:
|
with fx_pool_manager:
|
||||||
with raises(exceptions.BadRequest):
|
with testtools.ExpectedException(exceptions.BadRequest):
|
||||||
self.service.delete_record(self.context, 1, 2, 3)
|
self.service.delete_record(self.context, 1, 2, 3)
|
||||||
|
|
||||||
def test__delete_record_in_storage(self):
|
def test__delete_record_in_storage(self):
|
||||||
|
|
|
@ -19,7 +19,6 @@ import copy
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from testtools import ExpectedException as raises # with raises(...): ...
|
|
||||||
import mock
|
import mock
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
import oslotest.base
|
import oslotest.base
|
||||||
|
@ -188,7 +187,7 @@ class DesignateObjectTest(oslotest.base.BaseTestCase):
|
||||||
self.assertEqual(set(['id', 'nested_list']), obj.obj_what_changed())
|
self.assertEqual(set(['id', 'nested_list']), obj.obj_what_changed())
|
||||||
|
|
||||||
def test_from_list(self):
|
def test_from_list(self):
|
||||||
with raises(NotImplementedError):
|
with testtools.ExpectedException(NotImplementedError):
|
||||||
TestObject.from_list([])
|
TestObject.from_list([])
|
||||||
|
|
||||||
def test_get_schema(self):
|
def test_get_schema(self):
|
||||||
|
@ -212,7 +211,7 @@ class DesignateObjectTest(oslotest.base.BaseTestCase):
|
||||||
schema = obj._obj_validator.schema
|
schema = obj._obj_validator.schema
|
||||||
self.assertEqual(schema, expected)
|
self.assertEqual(schema, expected)
|
||||||
|
|
||||||
with raises(AttributeError): # bug
|
with testtools.ExpectedException(AttributeError): # bug
|
||||||
schema = obj.obj_get_schema()
|
schema = obj.obj_get_schema()
|
||||||
|
|
||||||
@unittest.expectedFailure # bug
|
@unittest.expectedFailure # bug
|
||||||
|
@ -354,7 +353,7 @@ class DesignateObjectTest(oslotest.base.BaseTestCase):
|
||||||
|
|
||||||
def test_update_unexpected_attribute(self):
|
def test_update_unexpected_attribute(self):
|
||||||
obj = TestObject(id='MyID', name='test')
|
obj = TestObject(id='MyID', name='test')
|
||||||
with raises(AttributeError):
|
with testtools.ExpectedException(AttributeError):
|
||||||
obj.update({'id': 'new_id', 'new_key': 3})
|
obj.update({'id': 'new_id', 'new_key': 3})
|
||||||
|
|
||||||
def test_is_valid(self):
|
def test_is_valid(self):
|
||||||
|
@ -609,7 +608,7 @@ class DictObjectMixinTest(oslotest.base.BaseTestCase):
|
||||||
def test_get_missing(self):
|
def test_get_missing(self):
|
||||||
obj = TestObjectDict(name=1)
|
obj = TestObjectDict(name=1)
|
||||||
self.assertFalse(obj.obj_attr_is_set('foo'))
|
self.assertFalse(obj.obj_attr_is_set('foo'))
|
||||||
with raises(AttributeError):
|
with testtools.ExpectedException(AttributeError):
|
||||||
obj.get('foo')
|
obj.get('foo')
|
||||||
|
|
||||||
def test_get_default(self):
|
def test_get_default(self):
|
||||||
|
|
|
@ -17,8 +17,8 @@
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from testtools import ExpectedException as raises # with raises(...): ...
|
|
||||||
import oslotest.base
|
import oslotest.base
|
||||||
|
import testtools
|
||||||
|
|
||||||
from designate import exceptions
|
from designate import exceptions
|
||||||
from designate import objects
|
from designate import objects
|
||||||
|
@ -41,7 +41,7 @@ class DomainTest(oslotest.base.BaseTestCase):
|
||||||
|
|
||||||
def test_masters_none(self):
|
def test_masters_none(self):
|
||||||
domain = objects.Domain()
|
domain = objects.Domain()
|
||||||
with raises(exceptions.RelationNotLoaded):
|
with testtools.ExpectedException(exceptions.RelationNotLoaded):
|
||||||
self.assertEqual(domain.masters, None)
|
self.assertEqual(domain.masters, None)
|
||||||
|
|
||||||
def test_masters(self):
|
def test_masters(self):
|
||||||
|
@ -87,5 +87,5 @@ class DomainTest(oslotest.base.BaseTestCase):
|
||||||
domain = objects.Domain(
|
domain = objects.Domain(
|
||||||
type='SECONDARY',
|
type='SECONDARY',
|
||||||
)
|
)
|
||||||
with raises(exceptions.InvalidObject):
|
with testtools.ExpectedException(exceptions.InvalidObject):
|
||||||
domain.validate()
|
domain.validate()
|
||||||
|
|
|
@ -18,9 +18,9 @@ import itertools
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from testtools import ExpectedException as raises # with raises(...): ...
|
|
||||||
import mock
|
import mock
|
||||||
import oslotest.base
|
import oslotest.base
|
||||||
|
import testtools
|
||||||
|
|
||||||
from designate import exceptions
|
from designate import exceptions
|
||||||
from designate import objects
|
from designate import objects
|
||||||
|
@ -186,9 +186,9 @@ class RecordSetTest(oslotest.base.BaseTestCase):
|
||||||
|
|
||||||
def test_validate_handle_exception(self):
|
def test_validate_handle_exception(self):
|
||||||
rs = create_test_recordset()
|
rs = create_test_recordset()
|
||||||
with mock.patch('designate.objects.DesignateObject.obj_cls_from_name') \
|
fn_name = 'designate.objects.DesignateObject.obj_cls_from_name'
|
||||||
as patched:
|
with mock.patch(fn_name) as patched:
|
||||||
patched.side_effect = KeyError
|
patched.side_effect = KeyError
|
||||||
with raises(exceptions.InvalidObject):
|
with testtools.ExpectedException(exceptions.InvalidObject):
|
||||||
# TODO(Federico): check the attributes of the exception
|
# TODO(Federico): check the attributes of the exception
|
||||||
rs.validate()
|
rs.validate()
|
||||||
|
|
|
@ -29,6 +29,8 @@ LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class PeriodicTask(plugin.ExtensionPlugin):
|
class PeriodicTask(plugin.ExtensionPlugin):
|
||||||
|
"""Abstract Zone Manager periodic task
|
||||||
|
"""
|
||||||
__plugin_ns__ = 'designate.zone_manager_tasks'
|
__plugin_ns__ = 'designate.zone_manager_tasks'
|
||||||
__plugin_type__ = 'zone_manager_task'
|
__plugin_type__ = 'zone_manager_task'
|
||||||
__interval__ = None
|
__interval__ = None
|
||||||
|
@ -40,7 +42,11 @@ class PeriodicTask(plugin.ExtensionPlugin):
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_base_opts(cls):
|
def get_base_opts(cls):
|
||||||
options = [
|
options = [
|
||||||
cfg.IntOpt('interval', default=cls.__interval__),
|
cfg.IntOpt(
|
||||||
|
'interval',
|
||||||
|
default=cls.__interval__,
|
||||||
|
help='Run interval in seconds'
|
||||||
|
),
|
||||||
cfg.IntOpt('per_page', default=100),
|
cfg.IntOpt('per_page', default=100),
|
||||||
]
|
]
|
||||||
return options
|
return options
|
||||||
|
@ -50,12 +56,18 @@ class PeriodicTask(plugin.ExtensionPlugin):
|
||||||
return rpcapi.CentralAPI.get_instance()
|
return rpcapi.CentralAPI.get_instance()
|
||||||
|
|
||||||
def on_partition_change(self, my_partitions, members, event):
|
def on_partition_change(self, my_partitions, members, event):
|
||||||
|
"""Refresh partitions attribute
|
||||||
|
"""
|
||||||
self.my_partitions = my_partitions
|
self.my_partitions = my_partitions
|
||||||
|
|
||||||
def _my_range(self):
|
def _my_range(self):
|
||||||
|
"""Returns first and last partitions
|
||||||
|
"""
|
||||||
return self.my_partitions[0], self.my_partitions[-1]
|
return self.my_partitions[0], self.my_partitions[-1]
|
||||||
|
|
||||||
def _filter_between(self, col):
|
def _filter_between(self, col):
|
||||||
|
"""Generate BETWEEN filter based on _my_range
|
||||||
|
"""
|
||||||
return {col: "BETWEEN %s,%s" % self._my_range()}
|
return {col: "BETWEEN %s,%s" % self._my_range()}
|
||||||
|
|
||||||
def _iter(self, method, *args, **kwargs):
|
def _iter(self, method, *args, **kwargs):
|
||||||
|
@ -116,6 +128,62 @@ class PeriodicExistsTask(PeriodicTask):
|
||||||
for zone in self._iter_zones(ctxt):
|
for zone in self._iter_zones(ctxt):
|
||||||
zone_data = dict(zone)
|
zone_data = dict(zone)
|
||||||
zone_data.update(data)
|
zone_data.update(data)
|
||||||
|
|
||||||
self.notifier.info(ctxt, 'dns.domain.exists', zone_data)
|
self.notifier.info(ctxt, 'dns.domain.exists', zone_data)
|
||||||
|
|
||||||
LOG.info(_LI("Finished emitting events."))
|
LOG.info(_LI("Finished emitting events."))
|
||||||
|
|
||||||
|
|
||||||
|
class DeletedDomainPurgeTask(PeriodicTask):
|
||||||
|
"""Purge deleted domains that are exceeding the grace period time interval.
|
||||||
|
Deleted domains have values in the deleted_at column.
|
||||||
|
Purging means removing them from the database entirely.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__plugin_name__ = 'domain_purge'
|
||||||
|
__interval__ = 3600
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(DeletedDomainPurgeTask, self).__init__()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_cfg_opts(cls):
|
||||||
|
group = cfg.OptGroup(cls.get_canonical_name())
|
||||||
|
options = cls.get_base_opts() + [
|
||||||
|
cfg.IntOpt(
|
||||||
|
'time_threshold',
|
||||||
|
default=604800,
|
||||||
|
help="How old deleted domains should be (deleted_at) to be "
|
||||||
|
"purged, in seconds"
|
||||||
|
),
|
||||||
|
cfg.IntOpt(
|
||||||
|
'batch_size',
|
||||||
|
default=100,
|
||||||
|
help='How many domains to be purged on each run'
|
||||||
|
),
|
||||||
|
]
|
||||||
|
return [(group, options)]
|
||||||
|
|
||||||
|
def __call__(self):
|
||||||
|
"""Call the Central API to perform a purge of deleted zones based on
|
||||||
|
expiration time and sharding range.
|
||||||
|
"""
|
||||||
|
pstart, pend = self._my_range()
|
||||||
|
msg = _LI("Performing deleted domain purging for %(start)s to %(end)s")
|
||||||
|
LOG.info(msg % {"start": pstart, "end": pend})
|
||||||
|
|
||||||
|
delta = datetime.timedelta(seconds=self.options.time_threshold)
|
||||||
|
time_threshold = timeutils.utcnow() - delta
|
||||||
|
LOG.debug("Filtering deleted domains before %s", time_threshold)
|
||||||
|
|
||||||
|
criterion = self._filter_between('shard')
|
||||||
|
criterion['deleted'] = '!0'
|
||||||
|
criterion['deleted_at'] = "<=%s" % time_threshold
|
||||||
|
|
||||||
|
ctxt = context.DesignateContext.get_admin_context()
|
||||||
|
ctxt.all_tenants = True
|
||||||
|
|
||||||
|
self.central_api.purge_domains(
|
||||||
|
ctxt,
|
||||||
|
criterion=criterion,
|
||||||
|
limit=self.options.batch_size,
|
||||||
|
)
|
||||||
|
|
|
@ -219,6 +219,19 @@ debug = False
|
||||||
# Whether to allow synchronous zone exports
|
# Whether to allow synchronous zone exports
|
||||||
#export_synchronous = True
|
#export_synchronous = True
|
||||||
|
|
||||||
|
#------------------------
|
||||||
|
# Deleted domains purging
|
||||||
|
#------------------------
|
||||||
|
[zone_manager_task:domain_purge]
|
||||||
|
# How frequently to purge deleted domains, in seconds
|
||||||
|
#interval = 3600 # 1h
|
||||||
|
|
||||||
|
# How many records to be deleted on each run
|
||||||
|
#batch_size = 100
|
||||||
|
|
||||||
|
# How old deleted records should be (deleted_at) to be purged, in seconds
|
||||||
|
#time_threshold = 604800 # 7 days
|
||||||
|
|
||||||
#-----------------------
|
#-----------------------
|
||||||
# Pool Manager Service
|
# Pool Manager Service
|
||||||
#-----------------------
|
#-----------------------
|
||||||
|
@ -311,6 +324,7 @@ debug = False
|
||||||
#masters = 192.168.27.100:5354
|
#masters = 192.168.27.100:5354
|
||||||
#type = bind9
|
#type = bind9
|
||||||
|
|
||||||
|
|
||||||
##############
|
##############
|
||||||
## Network API
|
## Network API
|
||||||
##############
|
##############
|
||||||
|
|
|
@ -51,6 +51,7 @@
|
||||||
"xfr_domain": "rule:admin_or_owner",
|
"xfr_domain": "rule:admin_or_owner",
|
||||||
"abandon_domain": "rule:admin",
|
"abandon_domain": "rule:admin",
|
||||||
"count_domains": "rule:admin_or_owner",
|
"count_domains": "rule:admin_or_owner",
|
||||||
|
"purge_domains": "rule:admin",
|
||||||
"touch_domain": "rule:admin_or_owner",
|
"touch_domain": "rule:admin_or_owner",
|
||||||
|
|
||||||
"create_recordset": "rule:domain_primary_or_admin",
|
"create_recordset": "rule:domain_primary_or_admin",
|
||||||
|
|
|
@ -112,6 +112,7 @@ designate.manage =
|
||||||
|
|
||||||
designate.zone_manager_tasks =
|
designate.zone_manager_tasks =
|
||||||
periodic_exists = designate.zone_manager.tasks:PeriodicExistsTask
|
periodic_exists = designate.zone_manager.tasks:PeriodicExistsTask
|
||||||
|
domain_purge = designate.zone_manager.tasks:DeletedDomainPurgeTask
|
||||||
|
|
||||||
[build_sphinx]
|
[build_sphinx]
|
||||||
all_files = 1
|
all_files = 1
|
||||||
|
|
Loading…
Reference in New Issue