Add option to force delete zone-files in delete API.
Designate does not delete the zone-files on the back-end when zone is deleted. This results in thousands leftover zone files on backend e.g. bind. Add option in designate zone delete API to force clean/delete zone-files on the back-end. This option is restricted for admin or owner roles. Closes-Bug: 1966517 Change-Id: Ic7b8fee4d4702b0632774d32542b23d7d2a8c253
This commit is contained in:
parent
06b297eaf3
commit
d193b0c70c
@ -394,6 +394,7 @@ Request
|
|||||||
- x-auth-token: x-auth-token
|
- x-auth-token: x-auth-token
|
||||||
- x-auth-all-projects: x-auth-all-projects
|
- x-auth-all-projects: x-auth-all-projects
|
||||||
- x-auth-sudo-project-id: x-auth-sudo-project-id
|
- x-auth-sudo-project-id: x-auth-sudo-project-id
|
||||||
|
- x-designate-hard-delete: x-designate-hard-delete
|
||||||
- zone_id: path_zone_id
|
- zone_id: path_zone_id
|
||||||
|
|
||||||
|
|
||||||
|
@ -30,6 +30,14 @@ x-designate-edit-managed-records:
|
|||||||
required: false
|
required: false
|
||||||
type: bool
|
type: bool
|
||||||
|
|
||||||
|
x-designate-hard-delete:
|
||||||
|
description: |
|
||||||
|
If enabled, this will delete the zone resources (i.e. files) on the
|
||||||
|
back-end.
|
||||||
|
in: header
|
||||||
|
required: false
|
||||||
|
type: bool
|
||||||
|
|
||||||
x-openstack-request-id:
|
x-openstack-request-id:
|
||||||
description: |
|
description: |
|
||||||
ID of the request
|
ID of the request
|
||||||
|
@ -88,6 +88,14 @@ class ContextMiddleware(base.Middleware):
|
|||||||
request.headers.get('X-Designate-Edit-Managed-Records')
|
request.headers.get('X-Designate-Edit-Managed-Records')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _extract_hard_delete(ctxt, request):
|
||||||
|
ctxt.hard_delete = False
|
||||||
|
if request.headers.get('X-Designate-Hard-Delete'):
|
||||||
|
ctxt.hard_delete = strutils.bool_from_string(
|
||||||
|
request.headers.get('X-Designate-Hard-Delete')
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _extract_client_addr(ctxt, request):
|
def _extract_client_addr(ctxt, request):
|
||||||
if hasattr(request, 'client_addr'):
|
if hasattr(request, 'client_addr'):
|
||||||
@ -103,6 +111,7 @@ class ContextMiddleware(base.Middleware):
|
|||||||
self._extract_sudo(ctxt, request)
|
self._extract_sudo(ctxt, request)
|
||||||
self._extract_all_projects(ctxt, request)
|
self._extract_all_projects(ctxt, request)
|
||||||
self._extract_edit_managed_records(ctxt, request)
|
self._extract_edit_managed_records(ctxt, request)
|
||||||
|
self._extract_hard_delete(ctxt, request)
|
||||||
self._extract_dns_hide_counts(ctxt, request)
|
self._extract_dns_hide_counts(ctxt, request)
|
||||||
self._extract_client_addr(ctxt, request)
|
self._extract_client_addr(ctxt, request)
|
||||||
finally:
|
finally:
|
||||||
|
@ -48,5 +48,5 @@ class AgentBackend(DriverPlugin):
|
|||||||
"""Zone is a DNSPython Zone object"""
|
"""Zone is a DNSPython Zone object"""
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def delete_zone(self, zone_name):
|
def delete_zone(self, zone_name, zone_params):
|
||||||
"""Delete a DNS zone"""
|
"""Delete a DNS zone"""
|
||||||
|
@ -71,7 +71,7 @@ class Backend(DriverPlugin):
|
|||||||
LOG.debug('Update Zone')
|
LOG.debug('Update Zone')
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def delete_zone(self, context, zone):
|
def delete_zone(self, context, zone, zone_params):
|
||||||
"""
|
"""
|
||||||
Delete a DNS zone.
|
Delete a DNS zone.
|
||||||
|
|
||||||
|
@ -189,7 +189,7 @@ class AkamaiBackend(base.Backend):
|
|||||||
zone, self.masters, contract_id, gid, project_id, self.target)
|
zone, self.masters, contract_id, gid, project_id, self.target)
|
||||||
self.client.create_zone(payload)
|
self.client.create_zone(payload)
|
||||||
|
|
||||||
def delete_zone(self, context, zone):
|
def delete_zone(self, context, zone, zone_params=None):
|
||||||
"""Delete a DNS zone"""
|
"""Delete a DNS zone"""
|
||||||
LOG.debug('Delete Zone')
|
LOG.debug('Delete Zone')
|
||||||
self.client.delete_zone(zone['name'])
|
self.client.delete_zone(zone['name'])
|
||||||
|
@ -126,7 +126,7 @@ class Bind9Backend(base.Backend):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def delete_zone(self, context, zone):
|
def delete_zone(self, context, zone, zone_params=None):
|
||||||
"""Delete a new Zone by executin rndc
|
"""Delete a new Zone by executin rndc
|
||||||
Do not raise exceptions if the zone does not exist.
|
Do not raise exceptions if the zone does not exist.
|
||||||
"""
|
"""
|
||||||
@ -138,7 +138,8 @@ class Bind9Backend(base.Backend):
|
|||||||
'delzone',
|
'delzone',
|
||||||
'%s %s' % (zone['name'].rstrip('.'), view),
|
'%s %s' % (zone['name'].rstrip('.'), view),
|
||||||
]
|
]
|
||||||
if self._clean_zonefile:
|
if (self._clean_zonefile or (zone_params and
|
||||||
|
zone_params.get('hard_delete'))):
|
||||||
rndc_op.insert(1, '-clean')
|
rndc_op.insert(1, '-clean')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -80,7 +80,7 @@ class DesignateBackend(base.Backend):
|
|||||||
self.client.zones.create(
|
self.client.zones.create(
|
||||||
zone.name, 'SECONDARY', masters=masters)
|
zone.name, 'SECONDARY', masters=masters)
|
||||||
|
|
||||||
def delete_zone(self, context, zone):
|
def delete_zone(self, context, zone, zone_params=None):
|
||||||
LOG.info('Deleting zone %(d_id)s / %(d_name)s',
|
LOG.info('Deleting zone %(d_id)s / %(d_name)s',
|
||||||
{'d_id': zone['id'], 'd_name': zone['name']})
|
{'d_id': zone['id'], 'd_name': zone['name']})
|
||||||
|
|
||||||
|
@ -356,7 +356,7 @@ class DynECTBackend(base.Backend):
|
|||||||
client.put(url, data={'activate': True})
|
client.put(url, data={'activate': True})
|
||||||
client.logout()
|
client.logout()
|
||||||
|
|
||||||
def delete_zone(self, context, zone):
|
def delete_zone(self, context, zone, zone_params=None):
|
||||||
LOG.info('Deleting zone %(d_id)s / %(d_name)s',
|
LOG.info('Deleting zone %(d_id)s / %(d_name)s',
|
||||||
{'d_id': zone['id'], 'd_name': zone['name']})
|
{'d_id': zone['id'], 'd_name': zone['name']})
|
||||||
url = '/Zone/%s' % zone['name'].rstrip('.')
|
url = '/Zone/%s' % zone['name'].rstrip('.')
|
||||||
|
@ -27,5 +27,5 @@ class FakeBackend(base.Backend):
|
|||||||
def create_zone(self, context, zone):
|
def create_zone(self, context, zone):
|
||||||
LOG.info('Create Zone %r', zone)
|
LOG.info('Create Zone %r', zone)
|
||||||
|
|
||||||
def delete_zone(self, context, zone):
|
def delete_zone(self, context, zone, zone_params=None):
|
||||||
LOG.info('Delete Zone %r', zone)
|
LOG.info('Delete Zone %r', zone)
|
||||||
|
@ -114,7 +114,7 @@ class NS1Backend(base.Backend):
|
|||||||
LOG.info("Can't create zone %s because it already exists",
|
LOG.info("Can't create zone %s because it already exists",
|
||||||
zone.name)
|
zone.name)
|
||||||
|
|
||||||
def delete_zone(self, context, zone):
|
def delete_zone(self, context, zone, zone_params=None):
|
||||||
"""Delete a DNS zone"""
|
"""Delete a DNS zone"""
|
||||||
|
|
||||||
# First verify that the zone exists
|
# First verify that the zone exists
|
||||||
|
@ -94,7 +94,7 @@ class NSD4Backend(base.Backend):
|
|||||||
if "already exists" not in str(e):
|
if "already exists" not in str(e):
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def delete_zone(self, context, zone):
|
def delete_zone(self, context, zone, zone_params=None):
|
||||||
LOG.debug('Delete Zone')
|
LOG.debug('Delete Zone')
|
||||||
command = 'delzone %s' % zone['name']
|
command = 'delzone %s' % zone['name']
|
||||||
|
|
||||||
|
@ -125,7 +125,7 @@ class PDNS4Backend(base.Backend):
|
|||||||
LOG.error('Could not delete errored zone %s', zone)
|
LOG.error('Could not delete errored zone %s', zone)
|
||||||
raise exceptions.Backend(e)
|
raise exceptions.Backend(e)
|
||||||
|
|
||||||
def delete_zone(self, context, zone):
|
def delete_zone(self, context, zone, zone_params=None):
|
||||||
"""Delete a DNS zone"""
|
"""Delete a DNS zone"""
|
||||||
|
|
||||||
# First verify that the zone exists -- If it's not present
|
# First verify that the zone exists -- If it's not present
|
||||||
|
@ -1047,7 +1047,11 @@ class Service(service.RPCService):
|
|||||||
zone = self.storage.delete_zone(context, zone.id)
|
zone = self.storage.delete_zone(context, zone.id)
|
||||||
else:
|
else:
|
||||||
zone = self._delete_zone_in_storage(context, zone)
|
zone = self._delete_zone_in_storage(context, zone)
|
||||||
self.worker_api.delete_zone(context, zone)
|
delete_zonefile = False
|
||||||
|
if context.hard_delete:
|
||||||
|
delete_zonefile = True
|
||||||
|
self.worker_api.delete_zone(context, zone,
|
||||||
|
hard_delete=delete_zonefile)
|
||||||
|
|
||||||
return zone
|
return zone
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ def set_defaults():
|
|||||||
'X-Auth-Sudo-Project-ID',
|
'X-Auth-Sudo-Project-ID',
|
||||||
'X-Auth-All-Projects',
|
'X-Auth-All-Projects',
|
||||||
'X-Designate-Edit-Managed-Records',
|
'X-Designate-Edit-Managed-Records',
|
||||||
|
'X-Designate-Hard-Delete',
|
||||||
'OpenStack-DNS-Hide-Counts'],
|
'OpenStack-DNS-Hide-Counts'],
|
||||||
expose_headers=['X-OpenStack-Request-ID',
|
expose_headers=['X-OpenStack-Request-ID',
|
||||||
'Host'],
|
'Host'],
|
||||||
|
@ -43,6 +43,12 @@ deprecated_use_sudo = policy.DeprecatedRule(
|
|||||||
deprecated_reason=base.DEPRECATED_REASON,
|
deprecated_reason=base.DEPRECATED_REASON,
|
||||||
deprecated_since=versionutils.deprecated.WALLABY
|
deprecated_since=versionutils.deprecated.WALLABY
|
||||||
)
|
)
|
||||||
|
deprecated_hard_delete = policy.DeprecatedRule(
|
||||||
|
name="hard_delete",
|
||||||
|
check_str=base.RULE_ADMIN,
|
||||||
|
deprecated_reason=base.DEPRECATED_REASON,
|
||||||
|
deprecated_since=versionutils.deprecated.WALLABY
|
||||||
|
)
|
||||||
|
|
||||||
rules = [
|
rules = [
|
||||||
policy.RuleDefault(
|
policy.RuleDefault(
|
||||||
@ -68,7 +74,13 @@ rules = [
|
|||||||
check_str=base.SYSTEM_ADMIN,
|
check_str=base.SYSTEM_ADMIN,
|
||||||
scope_types=['system'],
|
scope_types=['system'],
|
||||||
description='Accept sudo from user to tenant.',
|
description='Accept sudo from user to tenant.',
|
||||||
deprecated_rule=deprecated_use_sudo)
|
deprecated_rule=deprecated_use_sudo),
|
||||||
|
policy.RuleDefault(
|
||||||
|
name="hard_delete",
|
||||||
|
check_str=base.SYSTEM_ADMIN,
|
||||||
|
scope_types=['system'],
|
||||||
|
description="Clean backend resources associated with zone",
|
||||||
|
deprecated_rule=deprecated_hard_delete),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,16 +32,19 @@ class DesignateContext(context.RequestContext):
|
|||||||
_abandon = None
|
_abandon = None
|
||||||
original_project_id = None
|
original_project_id = None
|
||||||
_edit_managed_records = False
|
_edit_managed_records = False
|
||||||
|
_hard_delete = False
|
||||||
_client_addr = None
|
_client_addr = None
|
||||||
FROM_DICT_EXTRA_KEYS = [
|
FROM_DICT_EXTRA_KEYS = [
|
||||||
'original_project_id', 'service_catalog', 'all_tenants', 'abandon',
|
'original_project_id', 'service_catalog', 'all_tenants', 'abandon',
|
||||||
'edit_managed_records', 'tsigkey_id', 'hide_counts', 'client_addr',
|
'edit_managed_records', 'tsigkey_id', 'hide_counts', 'client_addr',
|
||||||
|
'hard_delete'
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, service_catalog=None, all_tenants=False, abandon=None,
|
def __init__(self, service_catalog=None, all_tenants=False, abandon=None,
|
||||||
tsigkey_id=None, original_project_id=None,
|
tsigkey_id=None, original_project_id=None,
|
||||||
edit_managed_records=False, hide_counts=False,
|
edit_managed_records=False, hide_counts=False,
|
||||||
client_addr=None, user_auth_plugin=None, **kwargs):
|
client_addr=None, user_auth_plugin=None,
|
||||||
|
hard_delete=False, **kwargs):
|
||||||
super(DesignateContext, self).__init__(**kwargs)
|
super(DesignateContext, self).__init__(**kwargs)
|
||||||
|
|
||||||
self.user_auth_plugin = user_auth_plugin
|
self.user_auth_plugin = user_auth_plugin
|
||||||
@ -53,6 +56,7 @@ class DesignateContext(context.RequestContext):
|
|||||||
self.all_tenants = all_tenants
|
self.all_tenants = all_tenants
|
||||||
self.abandon = abandon
|
self.abandon = abandon
|
||||||
self.edit_managed_records = edit_managed_records
|
self.edit_managed_records = edit_managed_records
|
||||||
|
self.hard_delete = hard_delete
|
||||||
self.hide_counts = hide_counts
|
self.hide_counts = hide_counts
|
||||||
self.client_addr = client_addr
|
self.client_addr = client_addr
|
||||||
|
|
||||||
@ -95,6 +99,7 @@ class DesignateContext(context.RequestContext):
|
|||||||
'all_tenants': self.all_tenants,
|
'all_tenants': self.all_tenants,
|
||||||
'abandon': self.abandon,
|
'abandon': self.abandon,
|
||||||
'edit_managed_records': self.edit_managed_records,
|
'edit_managed_records': self.edit_managed_records,
|
||||||
|
'hard_delete': self.hard_delete,
|
||||||
'tsigkey_id': self.tsigkey_id,
|
'tsigkey_id': self.tsigkey_id,
|
||||||
'hide_counts': self.hide_counts,
|
'hide_counts': self.hide_counts,
|
||||||
'client_addr': self.client_addr,
|
'client_addr': self.client_addr,
|
||||||
@ -103,7 +108,7 @@ class DesignateContext(context.RequestContext):
|
|||||||
return copy.deepcopy(d)
|
return copy.deepcopy(d)
|
||||||
|
|
||||||
def elevated(self, show_deleted=None, all_tenants=False,
|
def elevated(self, show_deleted=None, all_tenants=False,
|
||||||
edit_managed_records=False):
|
edit_managed_records=False, hard_delete=False):
|
||||||
"""Return a version of this context with admin flag set.
|
"""Return a version of this context with admin flag set.
|
||||||
Optionally set all_tenants and edit_managed_records
|
Optionally set all_tenants and edit_managed_records
|
||||||
"""
|
"""
|
||||||
@ -124,6 +129,9 @@ class DesignateContext(context.RequestContext):
|
|||||||
if edit_managed_records:
|
if edit_managed_records:
|
||||||
context.edit_managed_records = True
|
context.edit_managed_records = True
|
||||||
|
|
||||||
|
if hard_delete:
|
||||||
|
context.hard_delete = True
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def sudo(self, project_id):
|
def sudo(self, project_id):
|
||||||
@ -182,6 +190,16 @@ class DesignateContext(context.RequestContext):
|
|||||||
policy.check('edit_managed_records', self)
|
policy.check('edit_managed_records', self)
|
||||||
self._edit_managed_records = value
|
self._edit_managed_records = value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hard_delete(self):
|
||||||
|
return self._hard_delete
|
||||||
|
|
||||||
|
@hard_delete.setter
|
||||||
|
def hard_delete(self, value):
|
||||||
|
if value:
|
||||||
|
policy.check('hard_delete', self)
|
||||||
|
self._hard_delete = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def client_addr(self):
|
def client_addr(self):
|
||||||
return self._client_addr
|
return self._client_addr
|
||||||
|
@ -144,3 +144,22 @@ class KeystoneContextMiddlewareTest(oslotest.base.BaseTestCase):
|
|||||||
|
|
||||||
self.app(self.request)
|
self.app(self.request)
|
||||||
self.assertFalse(self.ctxt.edit_managed_records)
|
self.assertFalse(self.ctxt.edit_managed_records)
|
||||||
|
|
||||||
|
def test_hard_delete_in_headers(self):
|
||||||
|
self.request.headers.update({
|
||||||
|
'X-Tenant-ID': 'TenantID',
|
||||||
|
'X-Roles': 'admin',
|
||||||
|
'X-Designate-Hard-Delete': 'True'
|
||||||
|
})
|
||||||
|
|
||||||
|
self.app(self.request)
|
||||||
|
self.assertTrue(self.ctxt.hard_delete)
|
||||||
|
|
||||||
|
def test_hard_delete_not_set(self):
|
||||||
|
self.request.headers.update({
|
||||||
|
'X-Tenant-ID': 'TenantID',
|
||||||
|
'X-Roles': 'admin',
|
||||||
|
})
|
||||||
|
|
||||||
|
self.app(self.request)
|
||||||
|
self.assertFalse(self.ctxt.hard_delete)
|
||||||
|
@ -163,7 +163,7 @@ class Bind9BackendTestCase(oslotest.base.BaseTestCase):
|
|||||||
objects.PoolTarget.from_dict(self.target)
|
objects.PoolTarget.from_dict(self.target)
|
||||||
)
|
)
|
||||||
|
|
||||||
backend.delete_zone(self.admin_context, self.zone)
|
backend.delete_zone(self.admin_context, self.zone, {})
|
||||||
|
|
||||||
mock_execute.assert_called_with(
|
mock_execute.assert_called_with(
|
||||||
['delzone', 'example.com ']
|
['delzone', 'example.com ']
|
||||||
|
@ -264,6 +264,7 @@ class CentralBasic(TestCase):
|
|||||||
'sudo',
|
'sudo',
|
||||||
'abandon',
|
'abandon',
|
||||||
'all_tenants',
|
'all_tenants',
|
||||||
|
'hard_delete'
|
||||||
])
|
])
|
||||||
|
|
||||||
self.service = Service()
|
self.service = Service()
|
||||||
@ -922,6 +923,7 @@ class CentralZoneTestCase(CentralBasic):
|
|||||||
|
|
||||||
def test_delete_zone_has_subzone(self):
|
def test_delete_zone_has_subzone(self):
|
||||||
self.context.abandon = False
|
self.context.abandon = False
|
||||||
|
self.context.hard_delete = False
|
||||||
self.service.storage.get_zone.return_value = RoObject(
|
self.service.storage.get_zone.return_value = RoObject(
|
||||||
name='foo',
|
name='foo',
|
||||||
tenant_id='2',
|
tenant_id='2',
|
||||||
@ -961,6 +963,7 @@ class CentralZoneTestCase(CentralBasic):
|
|||||||
|
|
||||||
def test_delete_zone(self):
|
def test_delete_zone(self):
|
||||||
self.context.abandon = False
|
self.context.abandon = False
|
||||||
|
self.context.hard_delete = False
|
||||||
self.service.storage.get_zone.return_value = RoObject(
|
self.service.storage.get_zone.return_value = RoObject(
|
||||||
name='foo',
|
name='foo',
|
||||||
tenant_id='2',
|
tenant_id='2',
|
||||||
@ -983,6 +986,35 @@ class CentralZoneTestCase(CentralBasic):
|
|||||||
self.assertEqual('foo', out.name)
|
self.assertEqual('foo', out.name)
|
||||||
pcheck, ctx, target = \
|
pcheck, ctx, target = \
|
||||||
designate.central.service.policy.check.call_args[0]
|
designate.central.service.policy.check.call_args[0]
|
||||||
|
|
||||||
|
self.assertEqual('delete_zone', pcheck)
|
||||||
|
|
||||||
|
def test_delete_zone_hard_delete(self):
|
||||||
|
self.context.abandon = False
|
||||||
|
self.context.hard_delete = True
|
||||||
|
self.service.storage.get_zone.return_value = RoObject(
|
||||||
|
name='foo',
|
||||||
|
tenant_id='2',
|
||||||
|
)
|
||||||
|
self.service._delete_zone_in_storage = mock.Mock(
|
||||||
|
return_value=RoObject(
|
||||||
|
name='foo'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.service.storage.count_zones.return_value = 0
|
||||||
|
out = self.service.delete_zone(self.context,
|
||||||
|
CentralZoneTestCase.zone__id)
|
||||||
|
self.assertFalse(self.service.storage.delete_zone.called)
|
||||||
|
self.assertTrue(self.service.worker_api.delete_zone.called)
|
||||||
|
self.assertTrue(designate.central.service.policy.check.called)
|
||||||
|
ctx, deleted_dom = \
|
||||||
|
self.service.worker_api.delete_zone.call_args[0]
|
||||||
|
|
||||||
|
self.assertEqual('foo', deleted_dom.name)
|
||||||
|
self.assertEqual('foo', out.name)
|
||||||
|
pcheck, ctx, target = \
|
||||||
|
designate.central.service.policy.check.call_args[0]
|
||||||
|
|
||||||
self.assertEqual('delete_zone', pcheck)
|
self.assertEqual('delete_zone', pcheck)
|
||||||
|
|
||||||
def test_delete_zone_in_storage(self):
|
def test_delete_zone_in_storage(self):
|
||||||
|
@ -96,6 +96,25 @@ class TestDesignateContext(designate.tests.TestCase):
|
|||||||
with testtools.ExpectedException(exceptions.Forbidden):
|
with testtools.ExpectedException(exceptions.Forbidden):
|
||||||
ctxt.edit_managed_records = True
|
ctxt.edit_managed_records = True
|
||||||
|
|
||||||
|
def test_hard_delete(self):
|
||||||
|
ctxt = context.DesignateContext(
|
||||||
|
user_id='12345', project_id='54321'
|
||||||
|
)
|
||||||
|
admin_ctxt = ctxt.elevated()
|
||||||
|
|
||||||
|
admin_ctxt.hard_delete = True
|
||||||
|
|
||||||
|
self.assertFalse(ctxt.is_admin)
|
||||||
|
self.assertTrue(admin_ctxt.is_admin)
|
||||||
|
self.assertTrue(admin_ctxt.hard_delete)
|
||||||
|
|
||||||
|
def test_hard_delete_failure(self):
|
||||||
|
ctxt = context.DesignateContext(
|
||||||
|
user_id='12345', project_id='54321'
|
||||||
|
)
|
||||||
|
with testtools.ExpectedException(exceptions.Forbidden):
|
||||||
|
ctxt.hard_delete = True
|
||||||
|
|
||||||
@mock.patch.object(policy, 'check')
|
@mock.patch.object(policy, 'check')
|
||||||
def test_sudo(self, mock_policy_check):
|
def test_sudo(self, mock_policy_check):
|
||||||
ctxt = context.DesignateContext(
|
ctxt = context.DesignateContext(
|
||||||
|
@ -179,11 +179,12 @@ class TestService(oslotest.base.BaseTestCase):
|
|||||||
|
|
||||||
def test_delete_zone(self):
|
def test_delete_zone(self):
|
||||||
self.service._do_zone_action = mock.Mock()
|
self.service._do_zone_action = mock.Mock()
|
||||||
|
self.zone_params = {}
|
||||||
|
|
||||||
self.service.delete_zone(self.context, self.zone)
|
self.service.delete_zone(self.context, self.zone)
|
||||||
|
|
||||||
self.service._do_zone_action.assert_called_with(
|
self.service._do_zone_action.assert_called_with(
|
||||||
self.context, self.zone
|
self.context, self.zone, self.zone_params
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_update_zone(self):
|
def test_update_zone(self):
|
||||||
@ -204,15 +205,18 @@ class TestService(oslotest.base.BaseTestCase):
|
|||||||
pool.also_notifies = mock.MagicMock()
|
pool.also_notifies = mock.MagicMock()
|
||||||
pool.also_notifies.__iter__.return_value = []
|
pool.also_notifies.__iter__.return_value = []
|
||||||
self.service.get_pool.return_value = pool
|
self.service.get_pool.return_value = pool
|
||||||
|
self.zone_params = {}
|
||||||
|
|
||||||
self.service._do_zone_action(self.context, self.zone)
|
self.service._do_zone_action(self.context, self.zone,
|
||||||
|
self.zone_params)
|
||||||
|
|
||||||
mock_zone_action.assert_called_with(
|
mock_zone_action.assert_called_with(
|
||||||
self.service.executor,
|
self.service.executor,
|
||||||
self.context,
|
self.context,
|
||||||
pool,
|
pool,
|
||||||
self.zone,
|
self.zone,
|
||||||
self.zone.action
|
self.zone.action,
|
||||||
|
self.zone_params
|
||||||
)
|
)
|
||||||
|
|
||||||
self.service._executor.run.assert_called_with([mock_zone_action()])
|
self.service._executor.run.assert_called_with([mock_zone_action()])
|
||||||
@ -230,15 +234,18 @@ class TestService(oslotest.base.BaseTestCase):
|
|||||||
mock.Mock(host='192.168.1.1', port=53),
|
mock.Mock(host='192.168.1.1', port=53),
|
||||||
]
|
]
|
||||||
self.service.get_pool.return_value = pool
|
self.service.get_pool.return_value = pool
|
||||||
|
self.zone_params = {}
|
||||||
|
|
||||||
self.service._do_zone_action(self.context, self.zone)
|
self.service._do_zone_action(self.context, self.zone,
|
||||||
|
self.zone_params)
|
||||||
|
|
||||||
mock_zone_action.assert_called_with(
|
mock_zone_action.assert_called_with(
|
||||||
self.service.executor,
|
self.service.executor,
|
||||||
self.context,
|
self.context,
|
||||||
pool,
|
pool,
|
||||||
self.zone,
|
self.zone,
|
||||||
self.zone.action
|
self.zone.action,
|
||||||
|
self.zone_params
|
||||||
)
|
)
|
||||||
|
|
||||||
self.service._executor.run.assert_called_with(
|
self.service._executor.run.assert_called_with(
|
||||||
|
@ -166,6 +166,7 @@ class TestZoneActionOnTarget(oslotest.base.BaseTestCase):
|
|||||||
|
|
||||||
self.context = mock.Mock()
|
self.context = mock.Mock()
|
||||||
self.executor = mock.Mock()
|
self.executor = mock.Mock()
|
||||||
|
self.zone_params = mock.Mock()
|
||||||
|
|
||||||
@mock.patch.object(dnsutils, 'notify')
|
@mock.patch.object(dnsutils, 'notify')
|
||||||
def test_call_create(self, mock_notify):
|
def test_call_create(self, mock_notify):
|
||||||
@ -175,6 +176,7 @@ class TestZoneActionOnTarget(oslotest.base.BaseTestCase):
|
|||||||
self.context,
|
self.context,
|
||||||
self.zone,
|
self.zone,
|
||||||
self.target,
|
self.target,
|
||||||
|
self.zone_params
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertTrue(self.actor())
|
self.assertTrue(self.actor())
|
||||||
@ -193,6 +195,7 @@ class TestZoneActionOnTarget(oslotest.base.BaseTestCase):
|
|||||||
self.context,
|
self.context,
|
||||||
self.zone,
|
self.zone,
|
||||||
self.target,
|
self.target,
|
||||||
|
self.zone_params,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertTrue(self.actor())
|
self.assertTrue(self.actor())
|
||||||
@ -211,6 +214,7 @@ class TestZoneActionOnTarget(oslotest.base.BaseTestCase):
|
|||||||
self.context,
|
self.context,
|
||||||
self.zone,
|
self.zone,
|
||||||
self.target,
|
self.target,
|
||||||
|
self.zone_params
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertTrue(self.actor())
|
self.assertTrue(self.actor())
|
||||||
@ -227,6 +231,7 @@ class TestZoneActionOnTarget(oslotest.base.BaseTestCase):
|
|||||||
self.context,
|
self.context,
|
||||||
self.zone,
|
self.zone,
|
||||||
self.target,
|
self.target,
|
||||||
|
self.zone_params
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertFalse(self.actor())
|
self.assertFalse(self.actor())
|
||||||
@ -289,11 +294,13 @@ class TestZoneActor(oslotest.base.BaseTestCase):
|
|||||||
self.context = mock.Mock()
|
self.context = mock.Mock()
|
||||||
self.pool = mock.Mock()
|
self.pool = mock.Mock()
|
||||||
self.executor = mock.Mock()
|
self.executor = mock.Mock()
|
||||||
|
self.zone_params = mock.Mock()
|
||||||
self.actor = zone.ZoneActor(
|
self.actor = zone.ZoneActor(
|
||||||
self.executor,
|
self.executor,
|
||||||
self.context,
|
self.context,
|
||||||
self.pool,
|
self.pool,
|
||||||
mock.Mock(action='CREATE'),
|
mock.Mock(action='CREATE'),
|
||||||
|
self.zone_params
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_threshold_from_config(self):
|
def test_threshold_from_config(self):
|
||||||
|
@ -68,9 +68,9 @@ class WorkerAPI(object):
|
|||||||
return self.client.cast(
|
return self.client.cast(
|
||||||
context, 'update_zone', zone=zone)
|
context, 'update_zone', zone=zone)
|
||||||
|
|
||||||
def delete_zone(self, context, zone):
|
def delete_zone(self, context, zone, hard_delete):
|
||||||
return self.client.cast(
|
return self.client.cast(
|
||||||
context, 'delete_zone', zone=zone)
|
context, 'delete_zone', zone=zone, hard_delete=hard_delete)
|
||||||
|
|
||||||
def recover_shard(self, context, begin, end):
|
def recover_shard(self, context, begin, end):
|
||||||
return self.client.cast(
|
return self.client.cast(
|
||||||
|
@ -141,11 +141,11 @@ class Service(service.RPCService):
|
|||||||
def stop(self, graceful=True):
|
def stop(self, graceful=True):
|
||||||
super(Service, self).stop(graceful)
|
super(Service, self).stop(graceful)
|
||||||
|
|
||||||
def _do_zone_action(self, context, zone):
|
def _do_zone_action(self, context, zone, zone_params=None):
|
||||||
pool = self.get_pool(zone.pool_id)
|
pool = self.get_pool(zone.pool_id)
|
||||||
all_tasks = [
|
all_tasks = [
|
||||||
zonetasks.ZoneAction(self.executor, context, pool, zone,
|
zonetasks.ZoneAction(self.executor, context, pool, zone,
|
||||||
zone.action)
|
zone.action, zone_params)
|
||||||
]
|
]
|
||||||
|
|
||||||
# Send a NOTIFY to each also-notifies
|
# Send a NOTIFY to each also-notifies
|
||||||
@ -177,13 +177,17 @@ class Service(service.RPCService):
|
|||||||
self._do_zone_action(context, zone)
|
self._do_zone_action(context, zone)
|
||||||
|
|
||||||
@rpc.expected_exceptions()
|
@rpc.expected_exceptions()
|
||||||
def delete_zone(self, context, zone):
|
def delete_zone(self, context, zone, hard_delete=False):
|
||||||
"""
|
"""
|
||||||
:param context: Security context information.
|
:param context: Security context information.
|
||||||
:param zone: Zone to be deleted
|
:param zone: Zone to be deleted
|
||||||
|
:param hard_delete: Zone resources (files) to be deleted or not
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
self._do_zone_action(context, zone)
|
zone_params = {}
|
||||||
|
if hard_delete:
|
||||||
|
zone_params.update({'hard_delete': True})
|
||||||
|
self._do_zone_action(context, zone, zone_params)
|
||||||
|
|
||||||
@rpc.expected_exceptions()
|
@rpc.expected_exceptions()
|
||||||
def recover_shard(self, context, begin, end):
|
def recover_shard(self, context, begin, end):
|
||||||
|
@ -44,13 +44,14 @@ class ZoneActionOnTarget(base.Task):
|
|||||||
:return: Success/Failure of the target action (bool)
|
:return: Success/Failure of the target action (bool)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, executor, context, zone, target):
|
def __init__(self, executor, context, zone, target, zone_params):
|
||||||
super(ZoneActionOnTarget, self).__init__(executor)
|
super(ZoneActionOnTarget, self).__init__(executor)
|
||||||
self.zone = zone
|
self.zone = zone
|
||||||
self.action = zone.action
|
self.action = zone.action
|
||||||
self.target = target
|
self.target = target
|
||||||
self.context = context
|
self.context = context
|
||||||
self.task_name = 'ZoneActionOnTarget-%s' % self.action.title()
|
self.task_name = 'ZoneActionOnTarget-%s' % self.action.title()
|
||||||
|
self.zone_params = zone_params
|
||||||
|
|
||||||
def __call__(self):
|
def __call__(self):
|
||||||
LOG.debug(
|
LOG.debug(
|
||||||
@ -70,7 +71,8 @@ class ZoneActionOnTarget(base.Task):
|
|||||||
self.target.backend.create_zone(self.context, self.zone)
|
self.target.backend.create_zone(self.context, self.zone)
|
||||||
SendNotify(self.executor, self.zone, self.target)()
|
SendNotify(self.executor, self.zone, self.target)()
|
||||||
elif self.action == 'DELETE':
|
elif self.action == 'DELETE':
|
||||||
self.target.backend.delete_zone(self.context, self.zone)
|
self.target.backend.delete_zone(self.context, self.zone,
|
||||||
|
self.zone_params)
|
||||||
else:
|
else:
|
||||||
self.target.backend.update_zone(self.context, self.zone)
|
self.target.backend.update_zone(self.context, self.zone)
|
||||||
SendNotify(self.executor, self.zone, self.target)()
|
SendNotify(self.executor, self.zone, self.target)()
|
||||||
@ -195,15 +197,17 @@ class ZoneActor(base.Task):
|
|||||||
of targets (bool)
|
of targets (bool)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, executor, context, pool, zone):
|
def __init__(self, executor, context, pool, zone, zone_params=None):
|
||||||
super(ZoneActor, self).__init__(executor)
|
super(ZoneActor, self).__init__(executor)
|
||||||
self.context = context
|
self.context = context
|
||||||
self.pool = pool
|
self.pool = pool
|
||||||
self.zone = zone
|
self.zone = zone
|
||||||
|
self.zone_params = zone_params
|
||||||
|
|
||||||
def _execute(self):
|
def _execute(self):
|
||||||
results = self.executor.run([
|
results = self.executor.run([
|
||||||
ZoneActionOnTarget(self.executor, self.context, self.zone, target)
|
ZoneActionOnTarget(self.executor, self.context, self.zone, target,
|
||||||
|
self.zone_params)
|
||||||
for target in self.pool.targets
|
for target in self.pool.targets
|
||||||
])
|
])
|
||||||
return results
|
return results
|
||||||
@ -250,13 +254,15 @@ class ZoneAction(base.Task):
|
|||||||
number of nameservers (bool)
|
number of nameservers (bool)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, executor, context, pool, zone, action):
|
def __init__(self, executor, context, pool, zone, action,
|
||||||
|
zone_params=None):
|
||||||
super(ZoneAction, self).__init__(executor)
|
super(ZoneAction, self).__init__(executor)
|
||||||
self.context = context
|
self.context = context
|
||||||
self.pool = pool
|
self.pool = pool
|
||||||
self.zone = zone
|
self.zone = zone
|
||||||
self.action = action
|
self.action = action
|
||||||
self.task_name = 'ZoneAction-%s' % self.action.title()
|
self.task_name = 'ZoneAction-%s' % self.action.title()
|
||||||
|
self.zone_params = zone_params
|
||||||
|
|
||||||
def _wait_for_nameservers(self):
|
def _wait_for_nameservers(self):
|
||||||
"""
|
"""
|
||||||
@ -266,7 +272,7 @@ class ZoneAction(base.Task):
|
|||||||
|
|
||||||
def _zone_action_on_targets(self):
|
def _zone_action_on_targets(self):
|
||||||
actor = ZoneActor(
|
actor = ZoneActor(
|
||||||
self.executor, self.context, self.pool, self.zone
|
self.executor, self.context, self.pool, self.zone, self.zone_params
|
||||||
)
|
)
|
||||||
return actor()
|
return actor()
|
||||||
|
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
fixes:
|
||||||
|
- |
|
||||||
|
Currently designate does not provide zone specific option to delete the
|
||||||
|
zone resources (such as files) on the back-end (e.g. bind9) when the zone
|
||||||
|
is deleted. To fix this add a header ``x-designate-hard-delete`` which will
|
||||||
|
be used in the zone delete API to delete zone files on the back-end. This
|
||||||
|
is in addition to the existing per-pool configration option that will
|
||||||
|
override this new header option. This option is restricted for admin.
|
Loading…
Reference in New Issue
Block a user