Persist parent_resource_name and make sure it's available

We are persisting for a number of reasons:
- so we don't have to pass this through ever rpc call
- the API exposes parent_resource (currently always None as
  it is not persisted)

Closes-bug: #1438978
Change-Id: Id2db36c0234a085ec4f0ce2ab114ec483ea29d81
(cherry picked from commit edf86aeac2)
This commit is contained in:
Angus Salkeld 2015-04-07 09:25:50 +10:00 committed by Ethan Lynn
parent 90b5ee3237
commit d07f91615a
15 changed files with 134 additions and 53 deletions

View File

@ -0,0 +1,45 @@
#
# 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 sqlalchemy
from heat.db.sqlalchemy import utils as migrate_utils
def upgrade(migrate_engine):
meta = sqlalchemy.MetaData(bind=migrate_engine)
stack = sqlalchemy.Table('stack', meta, autoload=True)
parent_resource_name = sqlalchemy.Column('parent_resource_name',
sqlalchemy.String(255))
parent_resource_name.create(stack)
def downgrade(migrate_engine):
meta = sqlalchemy.MetaData(bind=migrate_engine)
stack = sqlalchemy.Table('stack', meta, autoload=True)
if migrate_engine.name == 'sqlite':
_downgrade_062_sqlite(migrate_engine, meta, stack)
else:
stack.c.parent_resource_name.drop()
def _downgrade_062_sqlite(migrate_engine, metadata, table):
new_table = migrate_utils.clone_table(
table.name + '__tmp__', table, metadata,
ignorecols=['parent_resource_name'])
migrate_utils.migrate_data(migrate_engine,
table,
new_table,
['parent_resource_name'])

View File

@ -169,6 +169,7 @@ class Stack(BASE, HeatBase, SoftDelete, StateAware):
sqlalchemy.Integer,
sqlalchemy.ForeignKey('user_creds.id'))
owner_id = sqlalchemy.Column(sqlalchemy.String(36))
parent_resource_name = sqlalchemy.Column(sqlalchemy.String(255))
timeout = sqlalchemy.Column(sqlalchemy.Integer)
disable_rollback = sqlalchemy.Column(sqlalchemy.Boolean, nullable=False)
stack_user_project_id = sqlalchemy.Column(sqlalchemy.String(64))

View File

@ -97,7 +97,6 @@ class StackResource(resource.Resource):
if self._nested is None and self.resource_id is not None:
self._nested = parser.Stack.load(self.context,
self.resource_id,
parent_resource=self.name,
show_deleted=show_deleted,
force_reload=force_reload)
@ -259,7 +258,8 @@ class StackResource(resource.Resource):
owner_id=self.stack.id,
user_creds_id=self.stack.user_creds_id,
stack_user_project_id=stack_user_project_id,
nested_depth=new_nested_depth)
nested_depth=new_nested_depth,
parent_resource_name=self.name)
except Exception as ex:
self.raise_local_exception(ex)

View File

@ -266,7 +266,7 @@ class EngineService(service.Service):
by the RPC caller.
"""
RPC_API_VERSION = '1.6'
RPC_API_VERSION = '1.7'
def __init__(self, host, topic, manager=None):
super(EngineService, self).__init__()
@ -544,7 +544,8 @@ class EngineService(service.Service):
params, files, args, owner_id=None,
nested_depth=0, user_creds_id=None,
stack_user_project_id=None,
convergence=False):
convergence=False,
parent_resource_name=None):
# If it is stack-adopt, use parameters from adopt_stack_data
common_params = api.extract_args(args)
if (rpc_api.PARAM_ADOPT_STACK_DATA in common_params and
@ -569,6 +570,7 @@ class EngineService(service.Service):
user_creds_id=user_creds_id,
stack_user_project_id=stack_user_project_id,
convergence=convergence,
parent_resource=parent_resource_name,
**common_params)
self._validate_deferred_auth_context(cnxt, stack)
@ -610,7 +612,7 @@ class EngineService(service.Service):
@context.request_context
def create_stack(self, cnxt, stack_name, template, params, files, args,
owner_id=None, nested_depth=0, user_creds_id=None,
stack_user_project_id=None):
stack_user_project_id=None, parent_resource_name=None):
"""
The create_stack method creates a new stack using the template
provided.
@ -630,6 +632,7 @@ class EngineService(service.Service):
:param user_creds_id: the parent user_creds record for nested stacks
:param stack_user_project_id: the parent stack_user_project_id for
nested stacks
:param parent_resource_name: the parent resource name
"""
LOG.info(_LI('Creating stack %s'), stack_name)
@ -662,7 +665,8 @@ class EngineService(service.Service):
stack = self._parse_template_and_validate_stack(
cnxt, stack_name, template, params, files, args, owner_id,
nested_depth, user_creds_id, stack_user_project_id, convergence)
nested_depth, user_creds_id, stack_user_project_id, convergence,
parent_resource_name)
stack.store()

View File

@ -329,7 +329,7 @@ class Stack(collections.Mapping):
return deps
@classmethod
def load(cls, context, stack_id=None, stack=None, parent_resource=None,
def load(cls, context, stack_id=None, stack=None,
show_deleted=True, use_stored_context=False, force_reload=False):
'''Retrieve a Stack from the database.'''
if stack is None:
@ -345,7 +345,7 @@ class Stack(collections.Mapping):
if force_reload:
stack.refresh()
return cls._from_db(context, stack, parent_resource=parent_resource,
return cls._from_db(context, stack,
use_stored_context=use_stored_context)
@classmethod
@ -367,7 +367,7 @@ class Stack(collections.Mapping):
yield cls._from_db(context, stack, resolve_data=resolve_data)
@classmethod
def _from_db(cls, context, stack, parent_resource=None, resolve_data=True,
def _from_db(cls, context, stack, resolve_data=True,
use_stored_context=False):
template = tmpl.Template.load(
context, stack.raw_template_id, stack.raw_template)
@ -378,7 +378,7 @@ class Stack(collections.Mapping):
timeout_mins=stack.timeout,
resolve_data=resolve_data,
disable_rollback=stack.disable_rollback,
parent_resource=parent_resource,
parent_resource=stack.parent_resource_name,
owner_id=stack.owner_id,
stack_user_project_id=stack.stack_user_project_id,
created_time=stack.created_at,
@ -406,8 +406,6 @@ class Stack(collections.Mapping):
stack = {
'owner_id': self.owner_id,
'username': self.username,
'tenant_id': self.tenant_id,
'timeout_mins': self.timeout_mins,
'disable_rollback': self.disable_rollback,
'stack_user_project_id': self.stack_user_project_id,
'user_creds_id': self.user_creds_id,
@ -420,8 +418,15 @@ class Stack(collections.Mapping):
'action': self.action,
'status': self.status,
'status_reason': self.status_reason})
if not only_db:
if only_db:
stack['parent_resource_name'] = self.parent_resource_name
stack['tenant'] = self.tenant_id
stack['timeout'] = self.timeout_mins
else:
stack['parent_resource'] = self.parent_resource_name
stack['tenant_id'] = self.tenant_id
stack['timeout_mins'] = self.timeout_mins
stack['strict_validate'] = self.strict_validate
return stack
@ -440,11 +445,6 @@ class Stack(collections.Mapping):
s['raw_template_id'] = self.t.store(self.context)
else:
s['raw_template_id'] = self.t.id
# name inconsistencies
s['tenant'] = s['tenant_id']
del s['tenant_id']
s['timeout'] = s['timeout_mins']
del s['timeout_mins']
if self.id:
stack_object.Stack.update_by_id(self.context, self.id, s)

View File

@ -58,6 +58,7 @@ class Stack(
'prev_raw_template_id': fields.IntegerField(),
'prev_raw_template': fields.ObjectField('RawTemplate'),
'tag': fields.ObjectField('StackTag'),
'parent_resource_name': fields.StringField(nullable=True),
}
@staticmethod

View File

@ -181,7 +181,7 @@ class EngineClient(object):
def _create_stack(self, ctxt, stack_name, template, params, files, args,
owner_id=None, nested_depth=0, user_creds_id=None,
stack_user_project_id=None):
stack_user_project_id=None, parent_resource_name=None):
"""
Internal create_stack interface for engine-to-engine communication via
RPC. Allows some additional options which should not be exposed to
@ -190,6 +190,7 @@ class EngineClient(object):
:param nested_depth: nested depth for nested stacks
:param user_creds_id: user_creds record for nested stack
:param stack_user_project_id: stack user project for nested stack
:param parent_resource_name: the parent resource name
"""
return self.call(
ctxt, self.make_msg('create_stack', stack_name=stack_name,
@ -198,8 +199,9 @@ class EngineClient(object):
owner_id=owner_id,
nested_depth=nested_depth,
user_creds_id=user_creds_id,
stack_user_project_id=stack_user_project_id),
version='1.2')
stack_user_project_id=stack_user_project_id,
parent_resource_name=parent_resource_name),
version='1.7')
def update_stack(self, ctxt, stack_identity, template, params,
files, args):

View File

@ -606,6 +606,9 @@ class HeatMigrationsCheckers(test_migrations.WalkVersionsMixin,
self.assertColumnType(engine, tab_name, 'status_reason',
sqlalchemy.Text)
def _check_062(self, engine, data):
self.assertColumnExists(engine, 'stack', 'parent_resource_name')
class TestHeatMigrationsMySQL(HeatMigrationsCheckers,
test_base.MySQLOpportunisticTestCase):

View File

@ -505,8 +505,9 @@ class CfnStackControllerTest(common.HeatTestCase):
'owner_id': None,
'nested_depth': 0,
'user_creds_id': None,
'parent_resource_name': None,
'stack_user_project_id': None}),
version='1.2'
version='1.7'
).AndRaise(failure)
def _stub_rpc_create_stack_call_success(self, stack_name, engine_parms,
@ -532,8 +533,9 @@ class CfnStackControllerTest(common.HeatTestCase):
'owner_id': None,
'nested_depth': 0,
'user_creds_id': None,
'parent_resource_name': None,
'stack_user_project_id': None}),
version='1.2'
version='1.7'
).AndReturn(engine_resp)
self.m.ReplayAll()

View File

@ -738,8 +738,9 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
'owner_id': None,
'nested_depth': 0,
'user_creds_id': None,
'parent_resource_name': None,
'stack_user_project_id': None}),
version='1.2'
version='1.7'
).AndReturn(dict(identity))
self.m.ReplayAll()
@ -800,8 +801,9 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
'owner_id': None,
'nested_depth': 0,
'user_creds_id': None,
'parent_resource_name': None,
'stack_user_project_id': None}),
version='1.2'
version='1.7'
).AndReturn(dict(identity))
self.m.ReplayAll()
@ -886,8 +888,9 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
'owner_id': None,
'nested_depth': 0,
'user_creds_id': None,
'parent_resource_name': None,
'stack_user_project_id': None}),
version='1.2'
version='1.7'
).AndReturn(dict(identity))
self.m.ReplayAll()
@ -929,8 +932,9 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
'owner_id': None,
'nested_depth': 0,
'user_creds_id': None,
'parent_resource_name': None,
'stack_user_project_id': None}),
version='1.2'
version='1.7'
).AndRaise(to_remote_error(AttributeError()))
rpc_client.EngineClient.call(
req.context,
@ -945,8 +949,9 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
'owner_id': None,
'nested_depth': 0,
'user_creds_id': None,
'parent_resource_name': None,
'stack_user_project_id': None}),
version='1.2'
version='1.7'
).AndRaise(to_remote_error(unknown_parameter))
rpc_client.EngineClient.call(
req.context,
@ -961,8 +966,9 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
'owner_id': None,
'nested_depth': 0,
'user_creds_id': None,
'parent_resource_name': None,
'stack_user_project_id': None}),
version='1.2'
version='1.7'
).AndRaise(to_remote_error(missing_parameter))
self.m.ReplayAll()
resp = request_with_middleware(fault.FaultWrapper,
@ -1014,8 +1020,9 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
'owner_id': None,
'nested_depth': 0,
'user_creds_id': None,
'parent_resource_name': None,
'stack_user_project_id': None}),
version='1.2'
version='1.7'
).AndRaise(to_remote_error(error))
self.m.ReplayAll()
@ -1094,8 +1101,9 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
'owner_id': None,
'nested_depth': 0,
'user_creds_id': None,
'parent_resource_name': None,
'stack_user_project_id': None}),
version='1.2'
version='1.7'
).AndRaise(to_remote_error(error))
self.m.ReplayAll()

View File

@ -473,7 +473,8 @@ class StackServiceCreateUpdateDeleteTest(common.HeatTestCase):
stack.t, owner_id=None,
nested_depth=0, user_creds_id=None,
stack_user_project_id=None,
convergence=False).AndReturn(stack)
convergence=False,
parent_resource=None).AndReturn(stack)
self.m.StubOutWithMock(stack, 'validate')
stack.validate().AndReturn(None)
@ -528,7 +529,8 @@ class StackServiceCreateUpdateDeleteTest(common.HeatTestCase):
nested_depth=0,
user_creds_id=None,
stack_user_project_id=None,
convergence=False).AndReturn(stack)
convergence=False,
parent_resource=None).AndReturn(stack)
self.m.StubOutWithMock(stack, 'validate')
stack.validate().AndRaise(exception.StackValidationFailed(
@ -697,7 +699,8 @@ class StackServiceCreateUpdateDeleteTest(common.HeatTestCase):
stack.t, owner_id=None,
nested_depth=0, user_creds_id=None,
stack_user_project_id=None,
convergence=False).AndReturn(stack)
convergence=False,
parent_resource=None).AndReturn(stack)
templatem.Template(template, files=None,
env=stack.env).AndReturn(stack.t)
@ -706,7 +709,8 @@ class StackServiceCreateUpdateDeleteTest(common.HeatTestCase):
stack.t, owner_id=None,
nested_depth=0, user_creds_id=None,
stack_user_project_id=None,
convergence=False).AndReturn(stack)
convergence=False,
parent_resource=None).AndReturn(stack)
self.m.ReplayAll()
@ -755,7 +759,8 @@ class StackServiceCreateUpdateDeleteTest(common.HeatTestCase):
nested_depth=0,
user_creds_id=None,
stack_user_project_id=None,
convergence=False).AndReturn(stack)
convergence=False,
parent_resource=None).AndReturn(stack)
self.m.ReplayAll()
@ -1731,7 +1736,7 @@ class StackServiceTest(common.HeatTestCase):
def test_make_sure_rpc_version(self):
self.assertEqual(
'1.6',
'1.7',
service.EngineService.RPC_API_VERSION,
('RPC version is changed, please update this test to new version '
'and make sure additional test cases are added for RPC APIs '

View File

@ -152,6 +152,7 @@ class EngineRpcAPITestCase(common.HeatTestCase):
call_kwargs['nested_depth'] = 0
call_kwargs['user_creds_id'] = None
call_kwargs['stack_user_project_id'] = None
call_kwargs['parent_resource_name'] = None
expected_message = self.rpcapi.make_msg('create_stack', **call_kwargs)
kwargs['expected_message'] = expected_message
self._test_engine_api('create_stack', 'call', **kwargs)

View File

@ -265,7 +265,8 @@ class StackTest(common.HeatTestCase):
self.assertEqual('test value', stk.root_stack)
def test_load_parent_resource(self):
self.stack = stack.Stack(self.ctx, 'load_parent_resource', self.tmpl)
self.stack = stack.Stack(self.ctx, 'load_parent_resource', self.tmpl,
parent_resource='parent')
self.stack.store()
stk = stack_object.Stack.get_by_id(self.ctx, self.stack.id)
@ -293,8 +294,7 @@ class StackTest(common.HeatTestCase):
current_traversal=None)
self.m.ReplayAll()
stack.Stack.load(self.ctx, stack_id=self.stack.id,
parent_resource='parent')
stack.Stack.load(self.ctx, stack_id=self.stack.id)
self.m.VerifyAll()
@ -1781,13 +1781,11 @@ class StackKwargsForCloningTest(common.HeatTestCase):
not_included=['action', 'status', 'status_reason'])),
('only_db', dict(keep_status=False, only_db=True,
not_included=['action', 'status', 'status_reason',
'parent_resource',
'strict_validate'])),
('keep_status', dict(keep_status=True, only_db=False,
not_included=[])),
('status_db', dict(keep_status=True, only_db=True,
not_included=['parent_resource',
'strict_validate'])),
not_included=['strict_validate'])),
]
def test_kwargs(self):
@ -1801,6 +1799,13 @@ class StackKwargsForCloningTest(common.HeatTestCase):
username='jo', nested_depth=3,
strict_validate=True, convergence=False,
current_traversal=45)
db_map = {'parent_resource': 'parent_resource_name',
'tenant_id': 'tenant', 'timeout_mins': 'timeout'}
test_db_data = {}
for key in test_data:
dbkey = db_map.get(key, key)
test_db_data[dbkey] = test_data[key]
self.stack = stack.Stack(ctx, utils.random_name(), tmpl,
**test_data)
res = self.stack.get_kwargs_for_cloning(keep_status=self.keep_status,
@ -1810,8 +1815,13 @@ class StackKwargsForCloningTest(common.HeatTestCase):
for key in test_data:
if key not in self.not_included:
self.assertEqual(test_data[key], res[key])
dbkey = db_map.get(key, key)
if self.only_db:
self.assertEqual(test_data[key], res[dbkey])
else:
self.assertEqual(test_data[key], res[key])
# just make sure that the kwargs are valid
# (no exception should be raised)
stack.Stack(ctx, utils.random_name(), tmpl, **res)
if not self.only_db:
# just make sure that the kwargs are valid
# (no exception should be raised)
stack.Stack(ctx, utils.random_name(), tmpl, **res)

View File

@ -430,7 +430,6 @@ class StackResourceTest(common.HeatTestCase):
self.m.StubOutWithMock(parser.Stack, 'load')
parser.Stack.load(self.parent_resource.context,
self.parent_resource.resource_id,
parent_resource=self.parent_resource.name,
show_deleted=False,
force_reload=False).AndReturn('s')
self.m.ReplayAll()
@ -443,7 +442,6 @@ class StackResourceTest(common.HeatTestCase):
self.m.StubOutWithMock(parser.Stack, 'load')
parser.Stack.load(self.parent_resource.context,
self.parent_resource.resource_id,
parent_resource=self.parent_resource.name,
show_deleted=False,
force_reload=True).AndReturn('ok')
self.m.ReplayAll()
@ -457,7 +455,6 @@ class StackResourceTest(common.HeatTestCase):
self.m.StubOutWithMock(parser.Stack, 'load')
parser.Stack.load(self.parent_resource.context,
self.parent_resource.resource_id,
parent_resource=self.parent_resource.name,
show_deleted=False,
force_reload=False).AndReturn(None)
self.m.ReplayAll()
@ -475,7 +472,6 @@ class StackResourceTest(common.HeatTestCase):
self.m.StubOutWithMock(parser.Stack, 'load')
parser.Stack.load(self.parent_resource.context,
self.parent_resource.resource_id,
parent_resource=self.parent_resource.name,
show_deleted=False,
force_reload=True).AndReturn('s')
self.m.ReplayAll()
@ -489,7 +485,6 @@ class StackResourceTest(common.HeatTestCase):
self.m.StubOutWithMock(parser.Stack, 'load')
parser.Stack.load(self.parent_resource.context,
self.parent_resource.resource_id,
parent_resource=self.parent_resource.name,
show_deleted=False,
force_reload=True).AndReturn(None)
self.m.ReplayAll()

View File

@ -79,7 +79,11 @@ resource_registry:
template=main_templ,
files={'nested.yaml': nested_templ},
environment=env_templ)
self.assert_resource_is_a_stack(stack_identifier, 'secret1')
nested_ident = self.assert_resource_is_a_stack(stack_identifier,
'secret1')
# prove that resource.parent_resource is populated.
sec2 = self.client.resources.get(nested_ident, 'secret2')
self.assertEqual('secret1', sec2.parent_resource)
def test_no_infinite_recursion(self):
"""Prove that we can override a python resource.