Add a new API for abandoning a zone from storage
Add a new API for abandoning of zones from storage. The new API would be v2/zones/<zone_id>/tasks/abandon Only POST would be allowed on this API. This just removes it from storage and does not take any action on the backends. Recordsets would not have this feature. By default this is restricted by policy to admins. Deleting a zone returns 202. Abandoning returns 204. Zone now displays action too. Change-Id: I213b567d1b609670b7c659f6e9780bdac240c89b Closes-Bug: 1413024
This commit is contained in:
parent
04c3bc7be1
commit
c3afc3f4bb
@ -248,14 +248,8 @@ class ZonesController(rest.RestController):
|
||||
response = pecan.response
|
||||
context = request.environ['context']
|
||||
|
||||
# TODO(kiall): Validate we have a sane UUID for zone_id
|
||||
|
||||
zone = self.central_api.delete_domain(context, zone_id)
|
||||
|
||||
if zone['status'] == 'DELETING':
|
||||
response.status_int = 202
|
||||
else:
|
||||
response.status_int = 204
|
||||
self.central_api.delete_domain(context, zone_id)
|
||||
response.status_int = 202
|
||||
|
||||
# NOTE: This is a hack and a half.. But Pecan needs it.
|
||||
return ''
|
||||
|
@ -20,6 +20,7 @@ from designate.api.v2.controllers.zones.tasks.transfer_requests \
|
||||
import TransferRequestsController as TRC
|
||||
from designate.api.v2.controllers.zones.tasks.transfer_accepts \
|
||||
import TransferAcceptsController as TRA
|
||||
from designate.api.v2.controllers.zones.tasks import abandon
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
@ -28,3 +29,4 @@ class TasksController(rest.RestController):
|
||||
|
||||
transfer_accepts = TRA()
|
||||
transfer_requests = TRC()
|
||||
abandon = abandon.AbandonController()
|
||||
|
40
designate/api/v2/controllers/zones/tasks/abandon.py
Normal file
40
designate/api/v2/controllers/zones/tasks/abandon.py
Normal file
@ -0,0 +1,40 @@
|
||||
# Copyright (c) 2015 Rackspace Hosting
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 pecan
|
||||
|
||||
from designate import utils
|
||||
from designate.api.v2.controllers import rest
|
||||
|
||||
|
||||
class AbandonController(rest.RestController):
|
||||
|
||||
@pecan.expose(template='json:', content_type='application/json')
|
||||
@utils.validate_uuid('zone_id')
|
||||
def post_all(self, zone_id):
|
||||
"""Abandon a zone"""
|
||||
request = pecan.request
|
||||
response = pecan.response
|
||||
context = request.environ['context']
|
||||
context.abandon = 'True'
|
||||
|
||||
# abandon the zone
|
||||
zone = self.central_api.delete_domain(context, zone_id)
|
||||
if zone.deleted_at:
|
||||
response.status_int = 204
|
||||
else:
|
||||
response.status_int = 500
|
||||
|
||||
# NOTE: This is a hack and a half.. But Pecan needs it.
|
||||
return ''
|
@ -39,6 +39,7 @@ class ZonesView(base_view.BaseView):
|
||||
"ttl": zone['ttl'],
|
||||
"serial": zone['serial'],
|
||||
"status": zone['status'],
|
||||
"action": zone['action'],
|
||||
"version": zone['version'],
|
||||
"created_at": zone['created_at'],
|
||||
"updated_at": zone['updated_at'],
|
||||
|
@ -30,6 +30,7 @@ from oslo_concurrency import lockutils
|
||||
|
||||
from designate.i18n import _LI
|
||||
from designate.i18n import _LC
|
||||
from designate.i18n import _LW
|
||||
from designate import context as dcontext
|
||||
from designate import exceptions
|
||||
from designate import network_api
|
||||
@ -914,7 +915,10 @@ class Service(service.RPCService):
|
||||
'tenant_id': domain.tenant_id
|
||||
}
|
||||
|
||||
policy.check('delete_domain', context, target)
|
||||
if hasattr(context, 'abandon') and context.abandon:
|
||||
policy.check('abandon_domain', context, target)
|
||||
else:
|
||||
policy.check('delete_domain', context, target)
|
||||
|
||||
# Prevent deletion of a zone which has child zones
|
||||
criterion = {'parent_domain_id': domain_id}
|
||||
@ -923,9 +927,12 @@ class Service(service.RPCService):
|
||||
raise exceptions.DomainHasSubdomain('Please delete any subdomains '
|
||||
'before deleting this domain')
|
||||
|
||||
domain = self._delete_domain_in_storage(context, domain)
|
||||
|
||||
self.pool_manager_api.delete_domain(context, domain)
|
||||
if hasattr(context, 'abandon') and context.abandon:
|
||||
LOG.info(_LW("Abandoning zone '%(zone)s'") % {'zone': domain.name})
|
||||
domain = self.storage.delete_domain(context, domain.id)
|
||||
else:
|
||||
domain = self._delete_domain_in_storage(context, domain)
|
||||
self.pool_manager_api.delete_domain(context, domain)
|
||||
|
||||
return domain
|
||||
|
||||
@ -1997,6 +2004,8 @@ class Service(service.RPCService):
|
||||
# used to indicate the domain has been deleted and not the deleted
|
||||
# column. The deleted column is needed for unique constraints.
|
||||
if deleted:
|
||||
# TODO(vinod): Pass a domain to delete_domain rather than id so
|
||||
# that the action, status and serial are updated correctly.
|
||||
self.storage.delete_domain(context, domain.id)
|
||||
|
||||
def _update_record_status(self, context, domain_id, status, serial):
|
||||
|
@ -29,12 +29,13 @@ LOG = logging.getLogger(__name__)
|
||||
class DesignateContext(context.RequestContext):
|
||||
|
||||
_all_tenants = False
|
||||
_abandon = None
|
||||
|
||||
def __init__(self, auth_token=None, user=None, tenant=None, domain=None,
|
||||
user_domain=None, project_domain=None, is_admin=False,
|
||||
read_only=False, show_deleted=False, request_id=None,
|
||||
resource_uuid=None, roles=None, service_catalog=None,
|
||||
all_tenants=False, user_identity=None):
|
||||
all_tenants=False, user_identity=None, abandon=None):
|
||||
# NOTE: user_identity may be passed in, but will be silently dropped as
|
||||
# it is a generated field based on several others.
|
||||
|
||||
@ -56,6 +57,7 @@ class DesignateContext(context.RequestContext):
|
||||
self.service_catalog = service_catalog
|
||||
|
||||
self.all_tenants = all_tenants
|
||||
self.abandon = abandon
|
||||
|
||||
if not hasattr(local.store, 'context'):
|
||||
self.update_store()
|
||||
@ -75,6 +77,7 @@ class DesignateContext(context.RequestContext):
|
||||
'roles': self.roles,
|
||||
'service_catalog': self.service_catalog,
|
||||
'all_tenants': self.all_tenants,
|
||||
'abandon': self.abandon,
|
||||
})
|
||||
|
||||
return copy.deepcopy(d)
|
||||
@ -128,3 +131,13 @@ class DesignateContext(context.RequestContext):
|
||||
if value:
|
||||
policy.check('all_tenants', self)
|
||||
self._all_tenants = value
|
||||
|
||||
@property
|
||||
def abandon(self):
|
||||
return self._abandon
|
||||
|
||||
@abandon.setter
|
||||
def abandon(self, value):
|
||||
if value:
|
||||
policy.check('abandon_domain', self)
|
||||
self._abandon = value
|
||||
|
@ -61,7 +61,13 @@
|
||||
"status": {
|
||||
"type": "string",
|
||||
"description": "Zone Status",
|
||||
"enum": ["ACTIVE", "PENDING", "DELETING", "ERROR"],
|
||||
"enum": ["ACTIVE", "PENDING", "ERROR"],
|
||||
"readOnly": true
|
||||
},
|
||||
"action": {
|
||||
"type": "string",
|
||||
"description": "Zone Action",
|
||||
"enum": ["CREATE", "DELETE", "UPDATE", "NONE"],
|
||||
"readOnly": true
|
||||
},
|
||||
"serial": {
|
||||
|
@ -285,6 +285,11 @@ class SQLAlchemy(object):
|
||||
obj.deleted = obj.id.replace('-', '')
|
||||
obj.deleted_at = timeutils.utcnow()
|
||||
|
||||
# TODO(vinod): Change the action to be null
|
||||
# update the action and status before deleting the object
|
||||
obj.action = 'NONE'
|
||||
obj.status = 'DELETED'
|
||||
|
||||
# NOTE(kiall): It should be impossible for a duplicate exception to
|
||||
# be raised in this call, therefore, it is OK to pass
|
||||
# in "None" as the exc_dup param.
|
||||
|
@ -243,7 +243,7 @@ class ApiV2RecordSetsTest(ApiV2TestCase):
|
||||
self.assertEqual(200, response.status_int)
|
||||
|
||||
# now delete the domain and get the recordsets
|
||||
self.client.delete('/zones/%s' % zone['id'], status=204)
|
||||
self.client.delete('/zones/%s' % zone['id'], status=202)
|
||||
|
||||
# Simulate the domain having been deleted on the backend
|
||||
domain_serial = self.central_service.get_domain(
|
||||
|
@ -334,7 +334,7 @@ class ApiV2ZonesTest(ApiV2TestCase):
|
||||
def test_delete_zone(self):
|
||||
zone = self.create_domain()
|
||||
|
||||
self.client.delete('/zones/%s' % zone['id'], status=204)
|
||||
self.client.delete('/zones/%s' % zone['id'], status=202)
|
||||
|
||||
def test_delete_zone_invalid_id(self):
|
||||
self._assert_invalid_uuid(self.client.delete, '/zones/%s')
|
||||
@ -354,6 +354,18 @@ class ApiV2ZonesTest(ApiV2TestCase):
|
||||
self._assert_exception('domain_not_found', 404, self.client.delete,
|
||||
url)
|
||||
|
||||
def test_abandon_zone(self):
|
||||
zone = self.create_domain()
|
||||
url = '/zones/%s/tasks/abandon' % zone.id
|
||||
|
||||
# Ensure that we get permission denied
|
||||
self._assert_exception('forbidden', 403, self.client.post_json, url)
|
||||
|
||||
# Ensure that abandon zone succeeds with the right policy
|
||||
self.policy({'abandon_domain': '@'})
|
||||
response = self.client.post_json(url)
|
||||
self.assertEqual(204, response.status_int)
|
||||
|
||||
# Zone import/export
|
||||
def test_missing_origin(self):
|
||||
fixture = self.get_zonefile_fixture(variant='noorigin')
|
||||
|
@ -261,7 +261,9 @@ Delete Zone
|
||||
|
||||
.. http:delete:: zones/(uuid:id)
|
||||
|
||||
Deletes a zone with the specified zone ID.
|
||||
Deletes a zone with the specified zone ID. Deleting a zone is asynchronous.
|
||||
Once pool manager has deleted the zone from all the pool targets, the zone
|
||||
is deleted from storage.
|
||||
|
||||
**Example Request:**
|
||||
|
||||
@ -276,9 +278,9 @@ Delete Zone
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 204 No Content
|
||||
HTTP/1.1 202 Accepted
|
||||
|
||||
:statuscode 204: No content
|
||||
:statuscode 202: Accepted
|
||||
|
||||
Import Zone
|
||||
-----------
|
||||
@ -380,6 +382,33 @@ Export Zone
|
||||
|
||||
Notice how the SOA and NS records are replaced with the Designate server(s).
|
||||
|
||||
Abandon Zone
|
||||
------------
|
||||
|
||||
.. http:post:: /zones/(uuid:id)/tasks/abandon
|
||||
|
||||
When a zone is abandoned it removes the zone from Designate's storage.
|
||||
There is no operation done on the pool targets. This is intended to be used
|
||||
in the cases where Designate's storage is incorrect for whatever reason. By
|
||||
default this is restricted by policy (abandon_domain) to admins.
|
||||
|
||||
**Example Request:**
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /v2/zones/a86dba58-0043-4cc6-a1bb-69d5e86f3ca3/tasks/abandon HTTP/1.1
|
||||
Host: 127.0.0.1:9001
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
|
||||
**Example Response:**
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 204 No content
|
||||
|
||||
:statuscode 204: No content
|
||||
|
||||
Transfer Zone
|
||||
-------------
|
||||
|
||||
|
@ -48,6 +48,7 @@
|
||||
"find_domain": "rule:admin_or_owner",
|
||||
"update_domain": "rule:admin_or_owner",
|
||||
"delete_domain": "rule:admin_or_owner",
|
||||
"abandon_domain": "rule:admin",
|
||||
"count_domains": "rule:admin_or_owner",
|
||||
"touch_domain": "rule:admin_or_owner",
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user