f3044ce2a9
The snapshot table records should be deleted when stack deletion. Change-Id: I4371e12024ae0fa5955ab0da9004e44e66b65486 Closes-Bug: #1437594
447 lines
17 KiB
Python
447 lines
17 KiB
Python
#
|
|
# 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 copy
|
|
import time
|
|
|
|
from keystoneclient import exceptions as kc_exceptions
|
|
import mock
|
|
|
|
from heat.common import exception
|
|
from heat.common import heat_keystoneclient as hkc
|
|
from heat.common import template_format
|
|
from heat.engine.clients.os import keystone
|
|
from heat.engine import resource
|
|
from heat.engine import scheduler
|
|
from heat.engine import stack
|
|
from heat.engine import template
|
|
from heat.objects import snapshot as snapshot_object
|
|
from heat.objects import stack as stack_object
|
|
from heat.objects import user_creds as ucreds_object
|
|
from heat.tests import common
|
|
from heat.tests import fakes
|
|
from heat.tests import generic_resource as generic_rsrc
|
|
from heat.tests import utils
|
|
|
|
empty_template = template_format.parse('''{
|
|
"HeatTemplateFormatVersion" : "2012-12-12",
|
|
}''')
|
|
|
|
|
|
class StackTest(common.HeatTestCase):
|
|
def setUp(self):
|
|
super(StackTest, self).setUp()
|
|
|
|
self.tmpl = template.Template(copy.deepcopy(empty_template))
|
|
self.ctx = utils.dummy_context()
|
|
resource._register_class('GenericResourceType',
|
|
generic_rsrc.GenericResource)
|
|
|
|
def test_delete(self):
|
|
self.stack = stack.Stack(self.ctx, 'delete_test', self.tmpl)
|
|
stack_id = self.stack.store()
|
|
|
|
db_s = stack_object.Stack.get_by_id(self.ctx, stack_id)
|
|
self.assertIsNotNone(db_s)
|
|
|
|
self.stack.delete()
|
|
|
|
db_s = stack_object.Stack.get_by_id(self.ctx, stack_id)
|
|
self.assertIsNone(db_s)
|
|
self.assertEqual((stack.Stack.DELETE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
def test_delete_with_snapshot(self):
|
|
self.stack = stack.Stack(self.ctx, 'delete_test', self.tmpl)
|
|
stack_id = self.stack.store()
|
|
snapshot_fake = {
|
|
'tenant': self.ctx.tenant_id,
|
|
'name': 'Snapshot',
|
|
'stack_id': stack_id,
|
|
'status': 'COMPLETE',
|
|
'data': self.stack.prepare_abandon()
|
|
}
|
|
snapshot_object.Snapshot.create(self.ctx, snapshot_fake)
|
|
|
|
self.assertIsNotNone(snapshot_object.Snapshot.get_all(
|
|
self.ctx, stack_id))
|
|
|
|
self.stack.delete()
|
|
db_s = stack_object.Stack.get_by_id(self.ctx, stack_id)
|
|
self.assertIsNone(db_s)
|
|
self.assertEqual((stack.Stack.DELETE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
self.assertEqual([], snapshot_object.Snapshot.get_all(
|
|
self.ctx, stack_id))
|
|
|
|
def test_delete_user_creds(self):
|
|
self.stack = stack.Stack(self.ctx, 'delete_test', self.tmpl)
|
|
stack_id = self.stack.store()
|
|
|
|
db_s = stack_object.Stack.get_by_id(self.ctx, stack_id)
|
|
self.assertIsNotNone(db_s)
|
|
self.assertIsNotNone(db_s.user_creds_id)
|
|
user_creds_id = db_s.user_creds_id
|
|
db_creds = ucreds_object.UserCreds.get_by_id(db_s.user_creds_id)
|
|
self.assertIsNotNone(db_creds)
|
|
|
|
self.stack.delete()
|
|
|
|
db_s = stack_object.Stack.get_by_id(self.ctx, stack_id)
|
|
self.assertIsNone(db_s)
|
|
db_creds = ucreds_object.UserCreds.get_by_id(user_creds_id)
|
|
self.assertIsNone(db_creds)
|
|
del_db_s = stack_object.Stack.get_by_id(self.ctx,
|
|
stack_id,
|
|
show_deleted=True)
|
|
self.assertIsNone(del_db_s.user_creds_id)
|
|
self.assertEqual((stack.Stack.DELETE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
def test_delete_user_creds_gone_missing(self):
|
|
'''Do not block stack deletion if user_creds is missing.
|
|
|
|
It may happen that user_creds were deleted when a delete operation was
|
|
stopped. We should be resilient to this and still complete the delete
|
|
operation.
|
|
'''
|
|
self.stack = stack.Stack(self.ctx, 'delete_test', self.tmpl)
|
|
stack_id = self.stack.store()
|
|
|
|
db_s = stack_object.Stack.get_by_id(self.ctx, stack_id)
|
|
self.assertIsNotNone(db_s)
|
|
self.assertIsNotNone(db_s.user_creds_id)
|
|
user_creds_id = db_s.user_creds_id
|
|
db_creds = ucreds_object.UserCreds.get_by_id(db_s.user_creds_id)
|
|
self.assertIsNotNone(db_creds)
|
|
|
|
ucreds_object.UserCreds.delete(self.ctx, user_creds_id)
|
|
|
|
self.stack.delete()
|
|
|
|
db_s = stack_object.Stack.get_by_id(self.ctx, stack_id)
|
|
self.assertIsNone(db_s)
|
|
db_creds = ucreds_object.UserCreds.get_by_id(user_creds_id)
|
|
self.assertIsNone(db_creds)
|
|
del_db_s = stack_object.Stack.get_by_id(self.ctx,
|
|
stack_id,
|
|
show_deleted=True)
|
|
self.assertIsNone(del_db_s.user_creds_id)
|
|
self.assertEqual((stack.Stack.DELETE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
def test_delete_user_creds_fail(self):
|
|
'''Do not stop deleting stacks even failed deleting user_creds.
|
|
|
|
It may happen that user_creds were incorrectly saved (truncated) and
|
|
thus cannot be correctly retrieved (and decrypted). In this case,
|
|
stack delete should not be stopped.
|
|
'''
|
|
self.stack = stack.Stack(self.ctx, 'delete_test', self.tmpl)
|
|
stack_id = self.stack.store()
|
|
|
|
db_s = stack_object.Stack.get_by_id(self.ctx, stack_id)
|
|
self.assertIsNotNone(db_s)
|
|
self.assertIsNotNone(db_s.user_creds_id)
|
|
exc = exception.Error('Cannot get user credentials')
|
|
self.patchobject(ucreds_object.UserCreds,
|
|
'get_by_id').side_effect = exc
|
|
|
|
self.stack.delete()
|
|
|
|
db_s = stack_object.Stack.get_by_id(self.ctx, stack_id)
|
|
self.assertIsNone(db_s)
|
|
self.assertEqual((stack.Stack.DELETE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
def test_delete_trust(self):
|
|
self.stub_keystoneclient()
|
|
|
|
self.stack = stack.Stack(self.ctx, 'delete_trust', self.tmpl)
|
|
stack_id = self.stack.store()
|
|
|
|
db_s = stack_object.Stack.get_by_id(self.ctx, stack_id)
|
|
self.assertIsNotNone(db_s)
|
|
|
|
self.stack.delete()
|
|
|
|
db_s = stack_object.Stack.get_by_id(self.ctx, stack_id)
|
|
self.assertIsNone(db_s)
|
|
self.assertEqual((stack.Stack.DELETE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
def test_delete_trust_trustor(self):
|
|
self.stub_keystoneclient(user_id='thetrustor')
|
|
trustor_ctx = utils.dummy_context(user_id='thetrustor')
|
|
|
|
self.stack = stack.Stack(trustor_ctx, 'delete_trust_nt', self.tmpl)
|
|
stack_id = self.stack.store()
|
|
|
|
db_s = stack_object.Stack.get_by_id(self.ctx, stack_id)
|
|
self.assertIsNotNone(db_s)
|
|
|
|
user_creds_id = db_s.user_creds_id
|
|
self.assertIsNotNone(user_creds_id)
|
|
user_creds = ucreds_object.UserCreds.get_by_id(user_creds_id)
|
|
self.assertEqual('thetrustor', user_creds.get('trustor_user_id'))
|
|
|
|
self.stack.delete()
|
|
|
|
db_s = stack_object.Stack.get_by_id(trustor_ctx, stack_id)
|
|
self.assertIsNone(db_s)
|
|
self.assertEqual((stack.Stack.DELETE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
def test_delete_trust_not_trustor(self):
|
|
# Stack gets created with trustor_ctx, deleted with other_ctx
|
|
# then the trust delete should be with stored_ctx
|
|
trustor_ctx = utils.dummy_context(user_id='thetrustor')
|
|
other_ctx = utils.dummy_context(user_id='nottrustor')
|
|
stored_ctx = utils.dummy_context(trust_id='thetrust')
|
|
|
|
mock_kc = self.patchobject(hkc, 'KeystoneClient')
|
|
self.stub_keystoneclient(user_id='thetrustor')
|
|
|
|
mock_sc = self.patchobject(stack.Stack, 'stored_context')
|
|
mock_sc.return_value = stored_ctx
|
|
|
|
self.stack = stack.Stack(trustor_ctx, 'delete_trust_nt', self.tmpl)
|
|
stack_id = self.stack.store()
|
|
|
|
db_s = stack_object.Stack.get_by_id(self.ctx, stack_id)
|
|
self.assertIsNotNone(db_s)
|
|
|
|
user_creds_id = db_s.user_creds_id
|
|
self.assertIsNotNone(user_creds_id)
|
|
user_creds = ucreds_object.UserCreds.get_by_id(user_creds_id)
|
|
self.assertEqual('thetrustor', user_creds.get('trustor_user_id'))
|
|
|
|
mock_kc.return_value = fakes.FakeKeystoneClient(user_id='nottrustor')
|
|
|
|
loaded_stack = stack.Stack.load(other_ctx, self.stack.id)
|
|
loaded_stack.delete()
|
|
mock_sc.assert_called_with()
|
|
|
|
db_s = stack_object.Stack.get_by_id(other_ctx, stack_id)
|
|
self.assertIsNone(db_s)
|
|
self.assertEqual((stack.Stack.DELETE, stack.Stack.COMPLETE),
|
|
loaded_stack.state)
|
|
|
|
def test_delete_trust_backup(self):
|
|
class FakeKeystoneClientFail(fakes.FakeKeystoneClient):
|
|
def delete_trust(self, trust_id):
|
|
raise Exception("Shouldn't delete")
|
|
|
|
mock_kcp = self.patchobject(keystone.KeystoneClientPlugin, '_create',
|
|
return_value=FakeKeystoneClientFail())
|
|
|
|
self.stack = stack.Stack(self.ctx, 'delete_trust', self.tmpl)
|
|
stack_id = self.stack.store()
|
|
|
|
db_s = stack_object.Stack.get_by_id(self.ctx, stack_id)
|
|
self.assertIsNotNone(db_s)
|
|
|
|
self.stack.delete(backup=True)
|
|
|
|
db_s = stack_object.Stack.get_by_id(self.ctx, stack_id)
|
|
self.assertIsNone(db_s)
|
|
self.assertEqual(self.stack.state,
|
|
(stack.Stack.DELETE, stack.Stack.COMPLETE))
|
|
mock_kcp.assert_called_once_with()
|
|
|
|
def test_delete_trust_nested(self):
|
|
class FakeKeystoneClientFail(fakes.FakeKeystoneClient):
|
|
def delete_trust(self, trust_id):
|
|
raise Exception("Shouldn't delete")
|
|
|
|
self.stub_keystoneclient(fake_client=FakeKeystoneClientFail())
|
|
|
|
self.stack = stack.Stack(self.ctx, 'delete_trust_nested', self.tmpl,
|
|
owner_id='owner123')
|
|
stack_id = self.stack.store()
|
|
|
|
db_s = stack_object.Stack.get_by_id(self.ctx, stack_id)
|
|
self.assertIsNotNone(db_s)
|
|
user_creds_id = db_s.user_creds_id
|
|
self.assertIsNotNone(user_creds_id)
|
|
user_creds = ucreds_object.UserCreds.get_by_id(user_creds_id)
|
|
self.assertIsNotNone(user_creds)
|
|
|
|
self.stack.delete()
|
|
|
|
db_s = stack_object.Stack.get_by_id(self.ctx, stack_id)
|
|
self.assertIsNone(db_s)
|
|
user_creds = ucreds_object.UserCreds.get_by_id(user_creds_id)
|
|
self.assertIsNotNone(user_creds)
|
|
self.assertEqual((stack.Stack.DELETE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
def test_delete_trust_fail(self):
|
|
class FakeKeystoneClientFail(fakes.FakeKeystoneClient):
|
|
def delete_trust(self, trust_id):
|
|
raise kc_exceptions.Forbidden("Denied!")
|
|
|
|
mock_kcp = self.patchobject(keystone.KeystoneClientPlugin, '_create',
|
|
return_value=FakeKeystoneClientFail())
|
|
|
|
self.stack = stack.Stack(self.ctx, 'delete_trust', self.tmpl)
|
|
stack_id = self.stack.store()
|
|
|
|
db_s = stack_object.Stack.get_by_id(self.ctx, stack_id)
|
|
self.assertIsNotNone(db_s)
|
|
|
|
self.stack.delete()
|
|
|
|
mock_kcp.assert_called_with()
|
|
self.assertEqual(2, mock_kcp.call_count)
|
|
|
|
db_s = stack_object.Stack.get_by_id(self.ctx, stack_id)
|
|
self.assertIsNotNone(db_s)
|
|
self.assertEqual((stack.Stack.DELETE, stack.Stack.FAILED),
|
|
self.stack.state)
|
|
self.assertIn('Error deleting trust', self.stack.status_reason)
|
|
|
|
def test_delete_deletes_project(self):
|
|
fkc = fakes.FakeKeystoneClient()
|
|
fkc.delete_stack_domain_project = mock.Mock()
|
|
|
|
mock_kcp = self.patchobject(keystone.KeystoneClientPlugin, '_create',
|
|
return_value=fkc)
|
|
|
|
self.stack = stack.Stack(self.ctx, 'delete_trust', self.tmpl)
|
|
stack_id = self.stack.store()
|
|
|
|
self.stack.set_stack_user_project_id(project_id='aproject456')
|
|
|
|
db_s = stack_object.Stack.get_by_id(self.ctx, stack_id)
|
|
self.assertIsNotNone(db_s)
|
|
|
|
self.stack.delete()
|
|
|
|
mock_kcp.assert_called_with()
|
|
|
|
db_s = stack_object.Stack.get_by_id(self.ctx, stack_id)
|
|
self.assertIsNone(db_s)
|
|
self.assertEqual((stack.Stack.DELETE, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
fkc.delete_stack_domain_project.assert_called_once_with(
|
|
project_id='aproject456')
|
|
|
|
def test_delete_rollback(self):
|
|
self.stack = stack.Stack(self.ctx, 'delete_rollback_test',
|
|
self.tmpl, disable_rollback=False)
|
|
stack_id = self.stack.store()
|
|
|
|
db_s = stack_object.Stack.get_by_id(self.ctx, stack_id)
|
|
self.assertIsNotNone(db_s)
|
|
|
|
self.stack.delete(action=self.stack.ROLLBACK)
|
|
|
|
db_s = stack_object.Stack.get_by_id(self.ctx, stack_id)
|
|
self.assertIsNone(db_s)
|
|
self.assertEqual((stack.Stack.ROLLBACK, stack.Stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
def test_delete_badaction(self):
|
|
self.stack = stack.Stack(self.ctx, 'delete_badaction_test', self.tmpl)
|
|
stack_id = self.stack.store()
|
|
|
|
db_s = stack_object.Stack.get_by_id(self.ctx, stack_id)
|
|
self.assertIsNotNone(db_s)
|
|
|
|
self.stack.delete(action="wibble")
|
|
|
|
db_s = stack_object.Stack.get_by_id(self.ctx, stack_id)
|
|
self.assertIsNotNone(db_s)
|
|
self.assertEqual((stack.Stack.DELETE, stack.Stack.FAILED),
|
|
self.stack.state)
|
|
|
|
def test_stack_delete_timeout(self):
|
|
self.stack = stack.Stack(self.ctx, 'delete_test', self.tmpl)
|
|
stack_id = self.stack.store()
|
|
|
|
db_s = stack_object.Stack.get_by_id(self.ctx, stack_id)
|
|
self.assertIsNotNone(db_s)
|
|
|
|
def dummy_task():
|
|
while True:
|
|
yield
|
|
|
|
start_time = time.time()
|
|
mock_tg = self.patchobject(scheduler.DependencyTaskGroup, '__call__',
|
|
return_value=dummy_task())
|
|
mock_wallclock = self.patchobject(scheduler, 'wallclock')
|
|
mock_wallclock.side_effect = [
|
|
start_time,
|
|
start_time + 1,
|
|
start_time + self.stack.timeout_secs() + 1
|
|
]
|
|
|
|
self.stack.delete()
|
|
|
|
self.assertEqual((stack.Stack.DELETE, stack.Stack.FAILED),
|
|
self.stack.state)
|
|
self.assertEqual('Delete timed out', self.stack.status_reason)
|
|
|
|
mock_tg.assert_called_once_with()
|
|
mock_wallclock.assert_called_with()
|
|
self.assertEqual(3, mock_wallclock.call_count)
|
|
|
|
def test_stack_delete_resourcefailure(self):
|
|
tmpl = {'HeatTemplateFormatVersion': '2012-12-12',
|
|
'Resources': {'AResource': {'Type': 'GenericResourceType'}}}
|
|
|
|
mock_rd = self.patchobject(generic_rsrc.GenericResource,
|
|
'handle_delete',
|
|
side_effect=Exception('foo'))
|
|
|
|
self.stack = stack.Stack(self.ctx, 'delete_test_fail',
|
|
template.Template(tmpl))
|
|
|
|
self.stack.store()
|
|
self.stack.create()
|
|
self.assertEqual((self.stack.CREATE, self.stack.COMPLETE),
|
|
self.stack.state)
|
|
|
|
self.stack.delete()
|
|
|
|
mock_rd.assert_called_once()
|
|
self.assertEqual((self.stack.DELETE, self.stack.FAILED),
|
|
self.stack.state)
|
|
self.assertEqual('Resource DELETE failed: Exception: foo',
|
|
self.stack.status_reason)
|
|
|
|
def test_stack_user_project_id_delete_fail(self):
|
|
|
|
class FakeKeystoneClientFail(fakes.FakeKeystoneClient):
|
|
def delete_stack_domain_project(self, project_id):
|
|
raise kc_exceptions.Forbidden("Denied!")
|
|
|
|
mock_kcp = self.patchobject(keystone.KeystoneClientPlugin, '_create',
|
|
return_value=FakeKeystoneClientFail())
|
|
|
|
self.stack = stack.Stack(self.ctx, 'user_project_init',
|
|
self.tmpl,
|
|
stack_user_project_id='aproject1234')
|
|
self.stack.store()
|
|
self.assertEqual('aproject1234', self.stack.stack_user_project_id)
|
|
db_stack = stack_object.Stack.get_by_id(self.ctx, self.stack.id)
|
|
self.assertEqual('aproject1234', db_stack.stack_user_project_id)
|
|
|
|
self.stack.delete()
|
|
|
|
mock_kcp.assert_called_with()
|
|
self.assertEqual((stack.Stack.DELETE, stack.Stack.FAILED),
|
|
self.stack.state)
|
|
self.assertIn('Error deleting project', self.stack.status_reason)
|