Merge "Add a new API for abandoning a zone from storage"
This commit is contained in:
commit
86dddab7e3
@ -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": {
|
||||
|
@ -297,6 +297,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.
|
||||
|
@ -252,7 +252,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
|
||||
-------------
|
||||
|
||||
|
@ -42,6 +42,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