Merge "Improve force delete"
This commit is contained in:
commit
b7c90ff987
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
features:
|
||||||
|
- The behavior of force deletion of clusters (APIv2) has changed.
|
||||||
|
Stack-abandon is no longer used. The response from the force-delete
|
||||||
|
API call now includes the name of the stack which had underlain
|
||||||
|
that deleted cluster.
|
|
@ -84,5 +84,11 @@ def clusters_update(cluster_id, data):
|
||||||
def clusters_delete(cluster_id):
|
def clusters_delete(cluster_id):
|
||||||
data = u.request_data()
|
data = u.request_data()
|
||||||
force = data.get('force', False)
|
force = data.get('force', False)
|
||||||
|
stack_name = api.get_cluster(cluster_id).get(
|
||||||
|
'extra', {}).get(
|
||||||
|
'heat_stack_name', None)
|
||||||
api.terminate_cluster(cluster_id, force=force)
|
api.terminate_cluster(cluster_id, force=force)
|
||||||
return u.render()
|
if force:
|
||||||
|
return u.render({"stack_name": stack_name}, status=200)
|
||||||
|
else:
|
||||||
|
return u.render(res=None, status=204)
|
||||||
|
|
|
@ -19,6 +19,7 @@ from oslo_log import log as logging
|
||||||
|
|
||||||
from sahara import conductor as c
|
from sahara import conductor as c
|
||||||
from sahara import context
|
from sahara import context
|
||||||
|
from sahara import exceptions as ex
|
||||||
from sahara.i18n import _
|
from sahara.i18n import _
|
||||||
from sahara.service import engine as e
|
from sahara.service import engine as e
|
||||||
from sahara.service.heat import commons as heat_common
|
from sahara.service.heat import commons as heat_common
|
||||||
|
@ -194,7 +195,7 @@ class HeatEngine(e.Engine):
|
||||||
def shutdown_cluster(self, cluster, force=False):
|
def shutdown_cluster(self, cluster, force=False):
|
||||||
"""Shutdown specified cluster and all related resources."""
|
"""Shutdown specified cluster and all related resources."""
|
||||||
if force:
|
if force:
|
||||||
heat_shutdown = heat.abandon_stack
|
heat_shutdown = heat.lazy_delete_stack
|
||||||
else:
|
else:
|
||||||
heat_shutdown = heat.delete_stack
|
heat_shutdown = heat.delete_stack
|
||||||
|
|
||||||
|
@ -202,11 +203,11 @@ class HeatEngine(e.Engine):
|
||||||
heat_shutdown(cluster)
|
heat_shutdown(cluster)
|
||||||
except heat_exc.HTTPNotFound:
|
except heat_exc.HTTPNotFound:
|
||||||
LOG.warning('Did not find stack for cluster.')
|
LOG.warning('Did not find stack for cluster.')
|
||||||
except heat_exc.BadRequest:
|
except ex.HeatStackException:
|
||||||
LOG.error("Can't force delete cluster.", exc_info=True)
|
raise
|
||||||
finally:
|
|
||||||
self._clean_job_executions(cluster)
|
self._clean_job_executions(cluster)
|
||||||
self._remove_db_objects(cluster)
|
self._remove_db_objects(cluster)
|
||||||
|
|
||||||
@cpo.event_wrapper(
|
@cpo.event_wrapper(
|
||||||
True, step=_('Create Heat stack'), param=('cluster', 1))
|
True, step=_('Create Heat stack'), param=('cluster', 1))
|
||||||
|
|
|
@ -127,42 +127,27 @@ class TestDeletion(base.SaharaTestCase):
|
||||||
|
|
||||||
@mock.patch('sahara.service.heat.heat_engine.LOG.error')
|
@mock.patch('sahara.service.heat.heat_engine.LOG.error')
|
||||||
@mock.patch('sahara.utils.openstack.heat.delete_stack')
|
@mock.patch('sahara.utils.openstack.heat.delete_stack')
|
||||||
@mock.patch('sahara.utils.openstack.heat.abandon_stack')
|
@mock.patch('sahara.utils.openstack.heat.lazy_delete_stack')
|
||||||
@mock.patch('sahara.service.engine.Engine._remove_db_objects')
|
@mock.patch('sahara.service.engine.Engine._remove_db_objects')
|
||||||
@mock.patch('sahara.service.engine.Engine._clean_job_executions')
|
@mock.patch('sahara.service.engine.Engine._clean_job_executions')
|
||||||
def test_force_delete_calls(self, cj, rdb, abandon, delete, log_err):
|
def test_force_delete_calls(self, cj, rdb, lazy_delete, delete, log_err):
|
||||||
engine = heat_engine.HeatEngine()
|
engine = heat_engine.HeatEngine()
|
||||||
|
|
||||||
# Force delete when Heat service support abandon
|
# Force delete (lazy_delete)
|
||||||
engine.shutdown_cluster(mock.Mock(), force=True)
|
engine.shutdown_cluster(mock.Mock(), force=True)
|
||||||
self.assertEqual(delete.call_count, 0)
|
self.assertEqual(delete.call_count, 0)
|
||||||
self.assertEqual(abandon.call_count, 1)
|
self.assertEqual(lazy_delete.call_count, 1)
|
||||||
self.assertEqual(cj.call_count, 1)
|
self.assertEqual(cj.call_count, 1)
|
||||||
self.assertEqual(rdb.call_count, 1)
|
self.assertEqual(rdb.call_count, 1)
|
||||||
|
|
||||||
delete.reset_mock()
|
delete.reset_mock()
|
||||||
abandon.reset_mock()
|
lazy_delete.reset_mock()
|
||||||
rdb.reset_mock()
|
rdb.reset_mock()
|
||||||
cj.reset_mock()
|
cj.reset_mock()
|
||||||
|
|
||||||
# Regular delete
|
# Regular delete
|
||||||
engine.shutdown_cluster(mock.Mock(), force=False)
|
engine.shutdown_cluster(mock.Mock(), force=False)
|
||||||
self.assertEqual(delete.call_count, 1)
|
self.assertEqual(delete.call_count, 1)
|
||||||
self.assertEqual(abandon.call_count, 0)
|
self.assertEqual(lazy_delete.call_count, 0)
|
||||||
self.assertEqual(cj.call_count, 1)
|
self.assertEqual(cj.call_count, 1)
|
||||||
self.assertEqual(rdb.call_count, 1)
|
self.assertEqual(rdb.call_count, 1)
|
||||||
|
|
||||||
delete.reset_mock()
|
|
||||||
abandon.reset_mock()
|
|
||||||
rdb.reset_mock()
|
|
||||||
cj.reset_mock()
|
|
||||||
|
|
||||||
# Force delete when stack abandon unavailable
|
|
||||||
abandon.side_effect = heat_exc.BadRequest()
|
|
||||||
engine.shutdown_cluster(mock.Mock(), force=True)
|
|
||||||
self.assertEqual(delete.call_count, 0)
|
|
||||||
self.assertEqual(abandon.call_count, 1)
|
|
||||||
self.assertEqual(cj.call_count, 1)
|
|
||||||
self.assertEqual(rdb.call_count, 1)
|
|
||||||
log_err.assert_called_once_with(
|
|
||||||
"Can't force delete cluster.", exc_info=True)
|
|
||||||
|
|
|
@ -20,20 +20,26 @@ from sahara.utils.openstack import heat as heat_u
|
||||||
|
|
||||||
|
|
||||||
class HeatClientTest(testtools.TestCase):
|
class HeatClientTest(testtools.TestCase):
|
||||||
|
@mock.patch('sahara.utils.openstack.heat.get_stack')
|
||||||
@mock.patch('heatclient.client.Client')
|
@mock.patch('heatclient.client.Client')
|
||||||
@mock.patch('sahara.utils.openstack.base.url_for')
|
@mock.patch('sahara.utils.openstack.base.url_for')
|
||||||
@mock.patch('sahara.service.sessions.cache')
|
@mock.patch('sahara.service.sessions.cache')
|
||||||
@mock.patch('sahara.context.ctx')
|
@mock.patch('sahara.context.ctx')
|
||||||
def test_abandon(self, ctx, cache, url_for, heat):
|
def test_deleting(self, ctx, cache, url_for, heat, get_stack):
|
||||||
class _FakeHeatStacks(object):
|
class _FakeHeatStacks(object):
|
||||||
def delete(self, stack):
|
def delete(self, stack):
|
||||||
call_list.append("delete")
|
call_list.append("delete")
|
||||||
|
|
||||||
def abandon(self, stack):
|
call_list = None
|
||||||
call_list.append("abandon")
|
get_stack.return_value = None
|
||||||
|
get_stack.side_effect = lambda *args, **kwargs: call_list.append("get")
|
||||||
|
|
||||||
heat.return_value.stacks = _FakeHeatStacks()
|
heat.return_value.stacks = _FakeHeatStacks()
|
||||||
|
|
||||||
call_list = []
|
call_list = []
|
||||||
heat_u.abandon_stack(mock.Mock())
|
heat_u.lazy_delete_stack(mock.Mock())
|
||||||
self.assertEqual(call_list, ["delete", "abandon"])
|
self.assertEqual(call_list, ["delete"])
|
||||||
|
|
||||||
|
call_list = []
|
||||||
|
heat_u.delete_stack(mock.Mock())
|
||||||
|
self.assertEqual(call_list, ["delete", "get"])
|
||||||
|
|
|
@ -84,13 +84,10 @@ def delete_stack(cluster):
|
||||||
stack = get_stack(stack_name, raise_on_missing=False)
|
stack = get_stack(stack_name, raise_on_missing=False)
|
||||||
|
|
||||||
|
|
||||||
def abandon_stack(cluster):
|
def lazy_delete_stack(cluster):
|
||||||
'''Put stack in deleting state if not already, then abandon it.'''
|
'''Attempt to delete stack once, but do not await successful deletion'''
|
||||||
heat_client = client()
|
|
||||||
stack_name = cluster.stack_name
|
stack_name = cluster.stack_name
|
||||||
base.execute_with_retries(heat_client.stacks.delete, stack_name)
|
base.execute_with_retries(client().stacks.delete, stack_name)
|
||||||
# TODO(jfreud): sleep between calls?
|
|
||||||
base.execute_with_retries(heat_client.stacks.abandon, stack_name)
|
|
||||||
|
|
||||||
|
|
||||||
def get_stack_outputs(cluster):
|
def get_stack_outputs(cluster):
|
||||||
|
|
Loading…
Reference in New Issue