Store template + environment for rollback.

In order to allow the user to roll back to a previous state, we need to
store both the old template and the old environment.

Moved the parameter column from 'stack' table to 'raw_template' table
and renamed it to environment.

Implements: blueprint convergence-parameter-storage
Co-Authored-by: Angus Salkeld <asalkeld@mirantis.com>
Co-Authored-by: Kanagaraj Manickam <kanagaraj.manickam@hp.com>

Change-Id: Ib776f651be0beccdc05b9973f152e2ff901970df
This commit is contained in:
Anant Patil 2015-03-11 10:49:52 +10:00 committed by Angus Salkeld
parent 8687997d65
commit bb5fec7725
11 changed files with 263 additions and 77 deletions

View File

@ -0,0 +1,179 @@
#
# 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 migrate
import sqlalchemy
from heat.db.sqlalchemy import types as heat_db_types
from heat.db.sqlalchemy import utils as migrate_utils
def upgrade(migrate_engine):
if migrate_engine.name == 'sqlite':
upgrade_sqlite(migrate_engine)
return
meta = sqlalchemy.MetaData()
meta.bind = migrate_engine
tmpl_table = sqlalchemy.Table('raw_template', meta, autoload=True)
environment = sqlalchemy.Column('environment', heat_db_types.Json)
environment.create(tmpl_table)
predecessor = sqlalchemy.Column('predecessor', sqlalchemy.Integer)
predecessor.create(tmpl_table)
fkey = migrate.ForeignKeyConstraint(
columns=[tmpl_table.c.predecessor],
refcolumns=[tmpl_table.c.id],
name='predecessor_fkey_ref')
fkey.create()
stack_table = sqlalchemy.Table('stack', meta, autoload=True)
update_query = tmpl_table.update().values(
environment=sqlalchemy.select([stack_table.c.parameters]).
where(tmpl_table.c.id == stack_table.c.raw_template_id).
as_scalar())
migrate_engine.execute(update_query)
stack_table.c.parameters.drop()
def upgrade_sqlite(migrate_engine):
meta = sqlalchemy.MetaData()
meta.bind = migrate_engine
tmpl_table = sqlalchemy.Table('raw_template', meta, autoload=True)
newcols = [
sqlalchemy.Column('environment', heat_db_types.Json),
sqlalchemy.Column('predecessor', sqlalchemy.Integer,
sqlalchemy.ForeignKey('raw_template.id'))]
new_template = migrate_utils.clone_table('new_raw_template',
tmpl_table,
meta, newcols=newcols)
stack_table = sqlalchemy.Table('stack', meta, autoload=True)
ignorecols = [stack_table.c.parameters.name]
new_stack = migrate_utils.clone_table('new_stack', stack_table,
meta, ignorecols=ignorecols)
# migrate parameters to environment
templates = list(tmpl_table.select().order_by(
sqlalchemy.sql.expression.asc(tmpl_table.c.created_at))
.execute())
colnames = [c.name for c in tmpl_table.columns]
for template in templates:
values = dict(zip(colnames,
map(lambda colname: getattr(template, colname),
colnames)))
params = (stack_table.select(stack_table.c.parameters).
where(stack_table.c.raw_template_id == values['id']).
execute().fetchone())
values['environment'] = params
migrate_engine.execute(new_template.insert(values))
# migrate stacks to new table
migrate_utils.migrate_data(migrate_engine,
stack_table,
new_stack,
skip_columns=['parameters'])
# Drop old tables and rename new ones
tmpl_table.drop()
# add the indexes back to new table
_add_indexes(migrate_engine, new_stack)
new_template.rename('raw_template')
def downgrade(migrate_engine):
if migrate_engine.name == 'sqlite':
downgrade_sqlite(migrate_engine)
return
meta = sqlalchemy.MetaData()
meta.bind = migrate_engine
stack_table = sqlalchemy.Table('stack', meta, autoload=True)
parameters = sqlalchemy.Column('parameters', heat_db_types.Json)
parameters.create(stack_table)
tmpl_table = sqlalchemy.Table('raw_template', meta, autoload=True)
update_query = stack_table.update().values(
parameters=sqlalchemy.select([tmpl_table.c.environment]).
where(stack_table.c.raw_template_id == tmpl_table.c.id).
as_scalar())
migrate_engine.execute(update_query)
tmpl_table.c.environment.drop()
fkey = migrate.ForeignKeyConstraint(
columns=[tmpl_table.c.predecessor],
refcolumns=[tmpl_table.c.id],
name='predecessor_fkey_ref')
fkey.drop()
tmpl_table.c.predecessor.drop()
def downgrade_sqlite(migrate_engine):
meta = sqlalchemy.MetaData()
meta.bind = migrate_engine
stack_table = sqlalchemy.Table('stack', meta, autoload=True)
newcols = [sqlalchemy.Column('parameters', heat_db_types.Json)]
new_stack = migrate_utils.clone_table('new_stack', stack_table,
meta, newcols=newcols)
tmpl_table = sqlalchemy.Table('raw_template', meta, autoload=True)
ignorecols = [tmpl_table.c.environment.name, tmpl_table.c.predecessor.name]
new_template = migrate_utils.clone_table('new_raw_template', tmpl_table,
meta, ignorecols=ignorecols)
# migrate stack data to new table
stacks = list(stack_table.select().order_by(
sqlalchemy.sql.expression.asc(stack_table.c.created_at))
.execute())
colnames = [c.name for c in stack_table.columns]
for stack in stacks:
values = dict(zip(colnames,
map(lambda colname: getattr(stack, colname),
colnames)))
migrate_engine.execute(new_stack.insert(values))
update_query = new_stack.update().values(
parameters=sqlalchemy.select([tmpl_table.c.environment]).
where(new_stack.c.raw_template_id == tmpl_table.c.id).
as_scalar())
migrate_engine.execute(update_query)
# migrate template data to new table
migrate_utils.migrate_data(migrate_engine,
tmpl_table,
new_template,
skip_columns=['environment', 'predecessor'])
stack_table.drop()
new_stack.rename('stack')
# add the indexes back to new table
_add_indexes(migrate_engine, new_stack)
def _add_indexes(migrate_engine, stack):
name_index = sqlalchemy.Index('ix_stack_name',
stack.c.name,
mysql_length=255)
tenant_index = sqlalchemy.Index('ix_stack_tenant',
stack.c.tenant,
mysql_length=255)
name_index.create(migrate_engine)
tenant_index.create(migrate_engine)

View File

@ -107,6 +107,9 @@ class RawTemplate(BASE, HeatBase):
id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
template = sqlalchemy.Column(types.Json)
files = sqlalchemy.Column(types.Json)
environment = sqlalchemy.Column('environment', types.Json)
predecessor = sqlalchemy.Column('predecessor', sqlalchemy.Integer,
sqlalchemy.ForeignKey('raw_template.id'))
class StackTag(BASE, HeatBase):
@ -151,7 +154,6 @@ class Stack(BASE, HeatBase, SoftDelete, StateAware):
foreign_keys=[prev_raw_template_id])
username = sqlalchemy.Column(sqlalchemy.String(256))
tenant = sqlalchemy.Column(sqlalchemy.String(256))
parameters = sqlalchemy.Column('parameters', types.Json)
user_creds_id = sqlalchemy.Column(
sqlalchemy.Integer,
sqlalchemy.ForeignKey('user_creds.id'))

View File

@ -147,12 +147,12 @@ class StackResource(resource.Resource):
return self.nested().preview_resources()
def _parse_child_template(self, child_template):
def _parse_child_template(self, child_template, child_env):
parsed_child_template = child_template
if isinstance(parsed_child_template, template.Template):
parsed_child_template = parsed_child_template.t
return template.Template(parsed_child_template,
files=self.stack.t.files)
files=self.stack.t.files, env=child_env)
def _parse_nested_stack(self, stack_name, child_template,
child_params=None, timeout_mins=None,
@ -162,7 +162,14 @@ class StackResource(resource.Resource):
) % cfg.CONF.max_nested_stack_depth
raise exception.RequestLimitExceeded(message=msg)
parsed_template = self._parse_child_template(child_template)
if child_params is None:
child_params = self.child_params()
child_env = environment.get_child_environment(
self.stack.env, child_params,
item_to_remove=self.resource_info)
parsed_template = self._parse_child_template(child_template, child_env)
self._validate_nested_resources(parsed_template)
# Don't overwrite the attributes_schema for subclasses that
@ -177,19 +184,11 @@ class StackResource(resource.Resource):
stack_user_project_id = self.stack.stack_user_project_id
new_nested_depth = self.stack.nested_depth + 1
if child_params is None:
child_params = self.child_params()
child_env = environment.get_child_environment(
self.stack.env, child_params,
item_to_remove=self.resource_info)
# Note we disable rollback for nested stacks, since they
# should be rolled back by the parent stack on failure
nested = parser.Stack(self.context,
stack_name,
parsed_template,
env=child_env,
timeout_mins=timeout_mins,
disable_rollback=True,
parent_resource=self.name,

View File

@ -533,9 +533,6 @@ class EngineService(service.Service):
not cfg.CONF.enable_stack_adopt):
raise exception.NotSupported(feature='Stack Adopt')
tmpl = templatem.Template(template, files=files)
self._validate_new_stack(cnxt, stack_name, tmpl)
if rpc_api.PARAM_ADOPT_STACK_DATA in common_params:
# Override the params with values given with -P option
new_params = common_params[rpc_api.PARAM_ADOPT_STACK_DATA][
@ -544,7 +541,11 @@ class EngineService(service.Service):
params[rpc_api.STACK_PARAMETERS] = new_params
env = environment.Environment(params)
stack = parser.Stack(cnxt, stack_name, tmpl, env,
tmpl = templatem.Template(template, files=files, env=env)
self._validate_new_stack(cnxt, stack_name, tmpl)
stack = parser.Stack(cnxt, stack_name, tmpl,
owner_id=owner_id,
nested_depth=nested_depth,
user_creds_id=user_creds_id,
@ -684,7 +685,12 @@ class EngineService(service.Service):
# Now parse the template and any parameters for the updated
# stack definition.
tmpl = templatem.Template(template, files=files)
env = environment.Environment(params)
if args.get(rpc_api.PARAM_EXISTING, None):
env.patch_previous_parameters(
current_stack.env,
args.get(rpc_api.PARAM_CLEAR_PARAMETERS, []))
tmpl = templatem.Template(template, files=files, env=env)
if len(tmpl[tmpl.RESOURCES]) > cfg.CONF.max_resources_per_stack:
raise exception.RequestLimitExceeded(
message=exception.StackResourceLimitExceeded.msg_fmt)
@ -695,13 +701,8 @@ class EngineService(service.Service):
current_stack.timeout_mins)
common_params.setdefault(rpc_api.PARAM_DISABLE_ROLLBACK,
current_stack.disable_rollback)
env = environment.Environment(params)
if args.get(rpc_api.PARAM_EXISTING, None):
env.patch_previous_parameters(
current_stack.env,
args.get(rpc_api.PARAM_CLEAR_PARAMETERS, []))
updated_stack = parser.Stack(cnxt, stack_name, tmpl,
env, convergence=convergence,
convergence=convergence,
**common_params)
updated_stack.parameters.set_stack_id(current_stack.identifier())

View File

@ -33,7 +33,6 @@ from heat.common import identifier
from heat.common import lifecycle_plugin_utils
from heat.db import api as db_api
from heat.engine import dependencies
from heat.engine import environment
from heat.engine import function
from heat.engine.notification import stack as notification
from heat.engine import parameter_groups as param_groups
@ -136,7 +135,7 @@ class Stack(collections.Mapping):
resources.initialise()
self.env = env or environment.Environment({})
self.env = env or self.t.env
self.parameters = self.t.parameters(
self.identifier(),
user_params=self.env.params,
@ -314,8 +313,7 @@ class Stack(collections.Mapping):
use_stored_context=False):
template = tmpl.Template.load(
context, stack.raw_template_id, stack.raw_template)
env = environment.Environment(stack.parameters)
return cls(context, stack.name, template, env,
return cls(context, stack.name, template, template.env,
stack.id, stack.action, stack.status, stack.status_reason,
stack.timeout, resolve_data, stack.disable_rollback,
parent_resource, owner_id=stack.owner_id,
@ -336,7 +334,6 @@ class Stack(collections.Mapping):
s = {
'name': self._backup_name() if backup else self.name,
'raw_template_id': self.t.store(self.context),
'parameters': self.env.user_env_as_dict(),
'owner_id': self.owner_id,
'username': self.username,
'tenant': self.tenant_id,
@ -806,6 +803,7 @@ class Stack(collections.Mapping):
self.env = newstack.env
self.parameters = newstack.parameters
self.t.files = newstack.t.files
self.t.env = newstack.t.env
self.disable_rollback = newstack.disable_rollback
self.timeout_mins = newstack.timeout_mins
self._set_param_stackid()

View File

@ -23,6 +23,7 @@ from stevedore import extension
from heat.common import exception
from heat.common.i18n import _
from heat.db import api as db_api
from heat.engine import environment
LOG = logging.getLogger(__name__)
@ -106,7 +107,7 @@ class Template(collections.Mapping):
return super(Template, cls).__new__(TemplateClass)
def __init__(self, template, template_id=None, files=None):
def __init__(self, template, template_id=None, files=None, env=None):
'''
Initialise the template with a JSON object and a set of Parameters
'''
@ -114,6 +115,7 @@ class Template(collections.Mapping):
self.t = template
self.files = files or {}
self.maps = self[self.MAPPINGS]
self.env = env or environment.Environment({})
self.version = get_version(self.t, _template_classes.keys())
def __deepcopy__(self, memo):
@ -124,13 +126,15 @@ class Template(collections.Mapping):
'''Retrieve a Template with the given ID from the database.'''
if t is None:
t = db_api.raw_template_get(context, template_id)
return cls(t.template, template_id=template_id, files=t.files)
env = environment.Environment(t.environment)
return cls(t.template, template_id=template_id, files=t.files, env=env)
def store(self, context=None):
'''Store the Template in the database and return its ID.'''
rt = {
'template': self.t,
'files': self.files
'files': self.files,
'environment': self.env.user_env_as_dict()
}
if self.id is None:
new_rt = db_api.raw_template_create(context, rt)

View File

@ -174,17 +174,16 @@ resources:
def get_wordpress_stack(stack_name, ctx):
t = template_format.parse(wp_template)
template = templatem.Template(t)
stack = parser.Stack(ctx, stack_name, template,
environment.Environment({'KeyName': 'test'}))
template = templatem.Template(
t, env=environment.Environment({'KeyName': 'test'}))
stack = parser.Stack(ctx, stack_name, template)
return stack
def get_wordpress_stack_no_params(stack_name, ctx):
t = template_format.parse(wp_template)
template = templatem.Template(t)
stack = parser.Stack(ctx, stack_name, template,
environment.Environment({}))
stack = parser.Stack(ctx, stack_name, template)
return stack
@ -452,10 +451,11 @@ class StackServiceCreateUpdateDeleteTest(common.HeatTestCase):
self.m.StubOutWithMock(environment, 'Environment')
self.m.StubOutWithMock(parser, 'Stack')
templatem.Template(template, files=None).AndReturn(stack.t)
templatem.Template(template, files=None,
env=stack.env).AndReturn(stack.t)
environment.Environment(params).AndReturn(stack.env)
parser.Stack(self.ctx, stack.name,
stack.t, stack.env, owner_id=None,
stack.t, owner_id=None,
nested_depth=0, user_creds_id=None,
stack_user_project_id=None,
convergence=False).AndReturn(stack)
@ -504,11 +504,11 @@ class StackServiceCreateUpdateDeleteTest(common.HeatTestCase):
self.m.StubOutWithMock(environment, 'Environment')
self.m.StubOutWithMock(parser, 'Stack')
templatem.Template(template, files=None).AndReturn(stack.t)
templatem.Template(template, files=None,
env=stack.env).AndReturn(stack.t)
environment.Environment(params).AndReturn(stack.env)
parser.Stack(self.ctx, stack.name,
stack.t,
stack.env,
owner_id=None,
nested_depth=0,
user_creds_id=None,
@ -565,7 +565,7 @@ class StackServiceCreateUpdateDeleteTest(common.HeatTestCase):
stack = db_api.stack_get(self.ctx, result['stack_id'])
self.assertEqual(template, stack.raw_template.template)
self.assertEqual(environment['parameters'],
stack.parameters['parameters'])
stack.raw_template.environment['parameters'])
def test_stack_adopt_saves_input_params(self):
cfg.CONF.set_override('enable_stack_adopt', True)
@ -584,7 +584,7 @@ class StackServiceCreateUpdateDeleteTest(common.HeatTestCase):
stack = db_api.stack_get(self.ctx, result['stack_id'])
self.assertEqual(template, stack.raw_template.template)
self.assertEqual(input_params['parameters'],
stack.parameters['parameters'])
stack.raw_template.environment['parameters'])
def test_stack_adopt_stack_state(self):
cfg.CONF.set_override('enable_stack_adopt', True)
@ -674,18 +674,20 @@ class StackServiceCreateUpdateDeleteTest(common.HeatTestCase):
ctx_no_pwd = utils.dummy_context(password=None)
ctx_no_user = utils.dummy_context(user=None)
templatem.Template(template, files=None).AndReturn(stack.t)
templatem.Template(template, files=None,
env=stack.env).AndReturn(stack.t)
environment.Environment(params).AndReturn(stack.env)
parser.Stack(ctx_no_pwd, stack.name,
stack.t, stack.env, owner_id=None,
stack.t, owner_id=None,
nested_depth=0, user_creds_id=None,
stack_user_project_id=None,
convergence=False).AndReturn(stack)
templatem.Template(template, files=None).AndReturn(stack.t)
templatem.Template(template, files=None,
env=stack.env).AndReturn(stack.t)
environment.Environment(params).AndReturn(stack.env)
parser.Stack(ctx_no_user, stack.name,
stack.t, stack.env, owner_id=None,
stack.t, owner_id=None,
nested_depth=0, user_creds_id=None,
stack_user_project_id=None,
convergence=False).AndReturn(stack)
@ -729,11 +731,11 @@ class StackServiceCreateUpdateDeleteTest(common.HeatTestCase):
self.m.StubOutWithMock(environment, 'Environment')
self.m.StubOutWithMock(parser, 'Stack')
templatem.Template(template, files=None).AndReturn(stack.t)
templatem.Template(template, files=None,
env=stack.env).AndReturn(stack.t)
environment.Environment(params).AndReturn(stack.env)
parser.Stack(self.ctx, stack.name,
stack.t,
stack.env,
owner_id=None,
nested_depth=0,
user_creds_id=None,
@ -1005,10 +1007,11 @@ class StackServiceCreateUpdateDeleteTest(common.HeatTestCase):
self._stub_update_mocks(s, old_stack)
templatem.Template(template, files=None).AndReturn(stack.t)
templatem.Template(template, files=None,
env=stack.env).AndReturn(stack.t)
environment.Environment(params).AndReturn(stack.env)
parser.Stack(self.ctx, stack.name,
stack.t, stack.env,
stack.t,
timeout_mins=60, disable_rollback=True,
convergence=False).AndReturn(stack)
@ -1054,10 +1057,10 @@ class StackServiceCreateUpdateDeleteTest(common.HeatTestCase):
self._stub_update_mocks(s, old_stack)
templatem.Template(wp_template_no_default,
files=None).AndReturn(stack.t)
files=None, env=old_stack.env).AndReturn(stack.t)
environment.Environment(no_params).AndReturn(old_stack.env)
parser.Stack(self.ctx, stack.name,
stack.t, old_stack.env,
stack.t,
timeout_mins=60, disable_rollback=True,
convergence=False).AndReturn(stack)
@ -1098,10 +1101,11 @@ class StackServiceCreateUpdateDeleteTest(common.HeatTestCase):
self._stub_update_mocks(s, old_stack)
templatem.Template(template, files=None).AndReturn(stack.t)
templatem.Template(template, files=None,
env=stack.env).AndReturn(stack.t)
environment.Environment(params).AndReturn(stack.env)
parser.Stack(self.ctx, stack.name,
stack.t, stack.env,
stack.t,
timeout_mins=1, disable_rollback=False,
convergence=False).AndReturn(stack)
@ -1176,10 +1180,11 @@ class StackServiceCreateUpdateDeleteTest(common.HeatTestCase):
self._stub_update_mocks(s, old_stack)
templatem.Template(template, files=None).AndReturn(stack.t)
templatem.Template(template, files=None,
env=stack.env).AndReturn(stack.t)
environment.Environment(params).AndReturn(stack.env)
parser.Stack(self.ctx, stack.name,
stack.t, stack.env,
stack.t,
timeout_mins=60, disable_rollback=True,
convergence=False).AndReturn(stack)
@ -1297,10 +1302,11 @@ class StackServiceCreateUpdateDeleteTest(common.HeatTestCase):
self._stub_update_mocks(s, old_stack)
templatem.Template(template, files=None).AndReturn(stack.t)
templatem.Template(template, files=None,
env=stack.env).AndReturn(stack.t)
environment.Environment(params).AndReturn(stack.env)
parser.Stack(self.ctx, stack.name,
stack.t, stack.env,
stack.t,
timeout_mins=60, disable_rollback=True,
convergence=False).AndReturn(stack)
@ -1354,10 +1360,11 @@ class StackServiceCreateUpdateDeleteTest(common.HeatTestCase):
self._stub_update_mocks(s, old_stack)
templatem.Template(template, files=None).AndReturn(old_stack.t)
templatem.Template(template, files=None,
env=old_stack.env).AndReturn(old_stack.t)
environment.Environment(params).AndReturn(old_stack.env)
parser.Stack(self.ctx, old_stack.name,
old_stack.t, old_stack.env,
old_stack.t,
timeout_mins=60, disable_rollback=True,
convergence=False).AndReturn(old_stack)

View File

@ -273,12 +273,8 @@ class StackTest(common.HeatTestCase):
self.ctx, stk.raw_template_id, stk.raw_template
).AndReturn(t)
env = environment.Environment(stk.parameters)
self.m.StubOutWithMock(environment, 'Environment')
environment.Environment(stk.parameters).AndReturn(env)
self.m.StubOutWithMock(stack.Stack, '__init__')
stack.Stack.__init__(self.ctx, stk.name, t, env, stk.id,
stack.Stack.__init__(self.ctx, stk.name, t, t.env, stk.id,
stk.action, stk.status, stk.status_reason,
stk.timeout, True, stk.disable_rollback,
'parent', owner_id=None,

View File

@ -214,7 +214,7 @@ class StackResourceTest(common.HeatTestCase):
are passed on to the child stack.
"""
self.parent_stack.t.files["foo"] = "bar"
parsed_t = self.parent_resource._parse_child_template(self.templ)
parsed_t = self.parent_resource._parse_child_template(self.templ, None)
self.assertEqual({"foo": "bar"}, parsed_t.files)
@mock.patch('heat.engine.environment.get_child_environment')
@ -245,7 +245,6 @@ class StackResourceTest(common.HeatTestCase):
mock.ANY,
'test_stack-test',
mock.ANY,
env='environment',
timeout_mins=None,
disable_rollback=True,
parent_resource=parent_resource.name,
@ -285,7 +284,6 @@ class StackResourceTest(common.HeatTestCase):
mock.ANY,
'test_stack-test',
mock.ANY,
env='environment',
timeout_mins=None,
disable_rollback=True,
parent_resource=parent_resource.name,
@ -312,9 +310,10 @@ class StackResourceTest(common.HeatTestCase):
'test',
resource_defns[self.ws_resname],
self.parent_stack)
stk_resource.child_params = mock.Mock(return_value={})
stk_resource.child_template = mock.Mock(
return_value=templatem.Template(self.simple_template))
stk_resource.child_params = mock.Mock()
return_value=templatem.Template(self.simple_template,
stk_resource.child_params))
exc = exception.RequestLimitExceeded(message='Validation Failed')
validation_mock = mock.Mock(side_effect=exc)
stk_resource._validate_nested_resources = validation_mock
@ -329,9 +328,9 @@ class StackResourceTest(common.HeatTestCase):
'test',
resource_defns[self.ws_resname],
self.parent_stack)
stk_resource.child_params = mock.Mock(return_value={})
stk_resource.child_template = mock.Mock(
return_value=self.simple_template)
stk_resource.child_params = mock.Mock()
exc = exception.RequestLimitExceeded(message='Validation Failed')
validation_mock = mock.Mock(side_effect=exc)
stk_resource._validate_nested_resources = validation_mock

View File

@ -1332,7 +1332,7 @@ class StackUpdateTest(common.HeatTestCase):
env2 = environment.Environment({'smelly-param': 'smelly'})
self.stack = stack.Stack(self.ctx, 'update_test_stack',
template.Template(tmpl), env1,
template.Template(tmpl, env=env1),
disable_rollback=True)
self.stack.store()
@ -1360,7 +1360,7 @@ class StackUpdateTest(common.HeatTestCase):
self.m.ReplayAll()
updated_stack = stack.Stack(self.ctx, 'updated_stack',
template.Template(tmpl2), env2,
template.Template(tmpl2, env=env2),
disable_rollback=True)
self.stack.update(updated_stack)
self.assertEqual((stack.Stack.UPDATE, stack.Stack.FAILED),
@ -1369,7 +1369,7 @@ class StackUpdateTest(common.HeatTestCase):
self.stack = stack.Stack.load(self.ctx, self.stack.id)
updated_stack2 = stack.Stack(self.ctx, 'updated_stack',
template.Template(tmpl2), env2,
template.Template(tmpl2, env=env2),
disable_rollback=True)
self.stack.update(updated_stack2)

View File

@ -91,11 +91,12 @@ def parse_stack(t, params=None, files=None, stack_name=None,
params = params or {}
files = files or {}
ctx = dummy_context()
templ = template.Template(t, files=files)
templ = template.Template(t, files=files,
env=environment.Environment(params))
templ.store()
if stack_name is None:
stack_name = random_name()
stk = stack.Stack(ctx, stack_name, templ,
environment.Environment(params), stack_id,
stk = stack.Stack(ctx, stack_name, templ, stack_id,
timeout_mins=timeout_mins)
stk.store()
return stk