a2d04996ae
IOError doesn't seem appropiate for things like invalid URL scheme and template exceeding max size. And requests.RequestException means an ambiguous exception occurred while handling requests, not necessarily an IO error. Change-Id: Ib8108a5b9bf6d6c99545a1c29735521720d3a75a
674 lines
22 KiB
Python
674 lines
22 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 json
|
|
import mock
|
|
from requests import exceptions
|
|
|
|
from oslo.config import cfg
|
|
|
|
cfg.CONF.import_opt('max_resources_per_stack', 'heat.common.config')
|
|
|
|
from heat.common import exception
|
|
from heat.common import template_format
|
|
from heat.common import urlfetch
|
|
from heat.db import api as db_api
|
|
from heat.engine import parser
|
|
from heat.engine import resource
|
|
from heat.engine import scheduler
|
|
from heat.tests.common import HeatTestCase
|
|
from heat.tests import generic_resource as generic_rsrc
|
|
from heat.tests import utils
|
|
|
|
|
|
class NestedStackTest(HeatTestCase):
|
|
test_template = '''
|
|
HeatTemplateFormatVersion: '2012-12-12'
|
|
Resources:
|
|
the_nested:
|
|
Type: AWS::CloudFormation::Stack
|
|
Properties:
|
|
TemplateURL: https://server.test/the.template
|
|
Parameters:
|
|
KeyName: foo
|
|
'''
|
|
|
|
nested_template = '''
|
|
HeatTemplateFormatVersion: '2012-12-12'
|
|
Parameters:
|
|
KeyName:
|
|
Type: String
|
|
Outputs:
|
|
Foo:
|
|
Value: bar
|
|
'''
|
|
|
|
update_template = '''
|
|
HeatTemplateFormatVersion: '2012-12-12'
|
|
Parameters:
|
|
KeyName:
|
|
Type: String
|
|
Outputs:
|
|
Bar:
|
|
Value: foo
|
|
'''
|
|
|
|
def setUp(self):
|
|
super(NestedStackTest, self).setUp()
|
|
self.m.StubOutWithMock(urlfetch, 'get')
|
|
utils.setup_dummy_db()
|
|
|
|
def create_stack(self, template):
|
|
t = template_format.parse(template)
|
|
stack = self.parse_stack(t)
|
|
stack.create()
|
|
self.assertEqual((stack.CREATE, stack.COMPLETE), stack.state)
|
|
return stack
|
|
|
|
def adopt_stack(self, template, adopt_data):
|
|
t = template_format.parse(template)
|
|
stack = self.parse_stack(t, adopt_data)
|
|
stack.adopt()
|
|
self.assertEqual((stack.ADOPT, stack.COMPLETE), stack.state)
|
|
return stack
|
|
|
|
def parse_stack(self, t, data=None):
|
|
ctx = utils.dummy_context('test_username', 'aaaa', 'password')
|
|
stack_name = 'test_stack'
|
|
tmpl = parser.Template(t)
|
|
stack = parser.Stack(ctx, stack_name, tmpl, adopt_stack_data=data)
|
|
stack.store()
|
|
return stack
|
|
|
|
def test_nested_stack_create(self):
|
|
urlfetch.get('https://server.test/the.template').MultipleTimes().\
|
|
AndReturn(self.nested_template)
|
|
self.m.ReplayAll()
|
|
|
|
stack = self.create_stack(self.test_template)
|
|
rsrc = stack['the_nested']
|
|
nested_name = utils.PhysName(stack.name, 'the_nested')
|
|
self.assertEqual(rsrc.physical_resource_name(), nested_name)
|
|
arn_prefix = ('arn:openstack:heat::aaaa:stacks/%s/' %
|
|
rsrc.physical_resource_name())
|
|
self.assertTrue(rsrc.FnGetRefId().startswith(arn_prefix))
|
|
|
|
self.assertEqual('bar', rsrc.FnGetAtt('Outputs.Foo'))
|
|
self.assertRaises(
|
|
exception.InvalidTemplateAttribute, rsrc.FnGetAtt, 'Foo')
|
|
self.assertRaises(
|
|
exception.InvalidTemplateAttribute, rsrc.FnGetAtt, 'Outputs.Bar')
|
|
self.assertRaises(
|
|
exception.InvalidTemplateAttribute, rsrc.FnGetAtt, 'Bar')
|
|
|
|
rsrc.delete()
|
|
self.assertTrue(rsrc.FnGetRefId().startswith(arn_prefix))
|
|
|
|
self.m.VerifyAll()
|
|
|
|
def test_nested_stack_adopt(self):
|
|
resource._register_class('GenericResource',
|
|
generic_rsrc.GenericResource)
|
|
urlfetch.get('https://server.test/the.template').MultipleTimes().\
|
|
AndReturn('''
|
|
HeatTemplateFormatVersion: '2012-12-12'
|
|
Parameters:
|
|
KeyName:
|
|
Type: String
|
|
Resources:
|
|
NestedResource:
|
|
Type: GenericResource
|
|
Outputs:
|
|
Foo:
|
|
Value: bar
|
|
''')
|
|
self.m.ReplayAll()
|
|
|
|
adopt_data = {
|
|
"resources": {
|
|
"the_nested": {
|
|
"resource_id": "test-res-id",
|
|
"resources": {
|
|
"NestedResource": {
|
|
"resource_id": "test-nested-res-id"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
stack = self.adopt_stack(self.test_template, adopt_data)
|
|
self.assertEqual((stack.ADOPT, stack.COMPLETE), stack.state)
|
|
rsrc = stack['the_nested']
|
|
self.assertEqual((rsrc.ADOPT, rsrc.COMPLETE), rsrc.state)
|
|
nested_name = utils.PhysName(stack.name, 'the_nested')
|
|
self.assertEqual(nested_name, rsrc.physical_resource_name())
|
|
self.assertEqual('test-nested-res-id',
|
|
rsrc.nested()['NestedResource'].resource_id)
|
|
rsrc.delete()
|
|
self.m.VerifyAll()
|
|
|
|
def test_nested_stack_adopt_fail(self):
|
|
resource._register_class('GenericResource',
|
|
generic_rsrc.GenericResource)
|
|
urlfetch.get('https://server.test/the.template').MultipleTimes().\
|
|
AndReturn('''
|
|
HeatTemplateFormatVersion: '2012-12-12'
|
|
Parameters:
|
|
KeyName:
|
|
Type: String
|
|
Resources:
|
|
NestedResource:
|
|
Type: GenericResource
|
|
Outputs:
|
|
Foo:
|
|
Value: bar
|
|
''')
|
|
self.m.ReplayAll()
|
|
|
|
adopt_data = {
|
|
"resources": {
|
|
"the_nested": {
|
|
"resource_id": "test-res-id",
|
|
"resources": {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
stack = self.adopt_stack(self.test_template, adopt_data)
|
|
rsrc = stack['the_nested']
|
|
self.assertEqual((rsrc.ADOPT, rsrc.FAILED), rsrc.nested().state)
|
|
nested_name = utils.PhysName(stack.name, 'the_nested')
|
|
self.assertEqual(nested_name, rsrc.physical_resource_name())
|
|
rsrc.delete()
|
|
self.m.VerifyAll()
|
|
|
|
def test_nested_stack_create_with_timeout(self):
|
|
urlfetch.get('https://server.test/the.template').MultipleTimes().\
|
|
AndReturn(self.nested_template)
|
|
self.m.ReplayAll()
|
|
|
|
timeout_template = template_format.parse(
|
|
copy.deepcopy(self.test_template))
|
|
props = timeout_template['Resources']['the_nested']['Properties']
|
|
props['TimeoutInMinutes'] = '50'
|
|
|
|
stack = self.create_stack(json.dumps(timeout_template))
|
|
self.assertEqual((stack.CREATE, stack.COMPLETE), stack.state)
|
|
self.m.VerifyAll()
|
|
|
|
def test_nested_stack_create_exceeds_resource_limit(self):
|
|
cfg.CONF.set_override('max_resources_per_stack', 1)
|
|
resource._register_class('GenericResource',
|
|
generic_rsrc.GenericResource)
|
|
urlfetch.get('https://server.test/the.template').MultipleTimes().\
|
|
AndReturn('''
|
|
HeatTemplateFormatVersion: '2012-12-12'
|
|
Parameters:
|
|
KeyName:
|
|
Type: String
|
|
Resources:
|
|
NestedResource:
|
|
Type: GenericResource
|
|
Outputs:
|
|
Foo:
|
|
Value: bar
|
|
''')
|
|
self.m.ReplayAll()
|
|
|
|
t = template_format.parse(self.test_template)
|
|
stack = self.parse_stack(t)
|
|
stack.create()
|
|
self.assertEqual((stack.CREATE, stack.FAILED), stack.state)
|
|
self.assertIn('Maximum resources per stack exceeded',
|
|
stack.status_reason)
|
|
|
|
self.m.VerifyAll()
|
|
|
|
def test_nested_stack_create_equals_resource_limit(self):
|
|
cfg.CONF.set_override('max_resources_per_stack', 2)
|
|
resource._register_class('GenericResource',
|
|
generic_rsrc.GenericResource)
|
|
urlfetch.get('https://server.test/the.template').MultipleTimes().\
|
|
AndReturn('''
|
|
HeatTemplateFormatVersion: '2012-12-12'
|
|
Parameters:
|
|
KeyName:
|
|
Type: String
|
|
Resources:
|
|
NestedResource:
|
|
Type: GenericResource
|
|
Outputs:
|
|
Foo:
|
|
Value: bar
|
|
''')
|
|
self.m.ReplayAll()
|
|
|
|
t = template_format.parse(self.test_template)
|
|
stack = self.parse_stack(t)
|
|
stack.create()
|
|
self.assertEqual((stack.CREATE, stack.COMPLETE), stack.state)
|
|
self.assertIn('NestedResource',
|
|
stack['the_nested'].nested())
|
|
|
|
self.m.VerifyAll()
|
|
|
|
def test_nested_stack_update(self):
|
|
urlfetch.get('https://server.test/the.template').MultipleTimes().\
|
|
AndReturn(self.nested_template)
|
|
urlfetch.get('https://server.test/new.template').MultipleTimes().\
|
|
AndReturn(self.update_template)
|
|
|
|
self.m.ReplayAll()
|
|
|
|
stack = self.create_stack(self.test_template)
|
|
rsrc = stack['the_nested']
|
|
|
|
original_nested_id = rsrc.resource_id
|
|
t = template_format.parse(self.test_template)
|
|
new_res = copy.deepcopy(t['Resources']['the_nested'])
|
|
new_res['Properties']['TemplateURL'] = (
|
|
'https://server.test/new.template')
|
|
prop_diff = {'TemplateURL': 'https://server.test/new.template'}
|
|
updater = rsrc.handle_update(new_res, {}, prop_diff)
|
|
updater.run_to_completion()
|
|
self.assertIs(True, rsrc.check_update_complete(updater))
|
|
|
|
# Expect the physical resource name staying the same after update,
|
|
# so that the nested was actually updated instead of replaced.
|
|
self.assertEqual(original_nested_id, rsrc.resource_id)
|
|
db_nested = db_api.stack_get(stack.context,
|
|
rsrc.resource_id)
|
|
# Owner_id should be preserved during the update process.
|
|
self.assertEqual(stack.id, db_nested.owner_id)
|
|
|
|
self.assertEqual('foo', rsrc.FnGetAtt('Outputs.Bar'))
|
|
self.assertRaises(
|
|
exception.InvalidTemplateAttribute, rsrc.FnGetAtt, 'Foo')
|
|
self.assertRaises(
|
|
exception.InvalidTemplateAttribute, rsrc.FnGetAtt, 'Outputs.Foo')
|
|
self.assertRaises(
|
|
exception.InvalidTemplateAttribute, rsrc.FnGetAtt, 'Bar')
|
|
|
|
rsrc.delete()
|
|
|
|
self.m.VerifyAll()
|
|
|
|
def test_nested_stack_update_equals_resource_limit(self):
|
|
resource._register_class('GenericResource',
|
|
generic_rsrc.GenericResource)
|
|
urlfetch.get('https://server.test/the.template').MultipleTimes().\
|
|
AndReturn(self.nested_template)
|
|
urlfetch.get('https://server.test/new.template').MultipleTimes().\
|
|
AndReturn('''
|
|
HeatTemplateFormatVersion: '2012-12-12'
|
|
Parameters:
|
|
KeyName:
|
|
Type: String
|
|
Resources:
|
|
NestedResource:
|
|
Type: GenericResource
|
|
Outputs:
|
|
Bar:
|
|
Value: foo
|
|
''')
|
|
self.m.ReplayAll()
|
|
|
|
stack = self.create_stack(self.test_template)
|
|
|
|
cfg.CONF.set_override('max_resources_per_stack', 2)
|
|
|
|
rsrc = stack['the_nested']
|
|
|
|
t = template_format.parse(self.test_template)
|
|
new_res = copy.deepcopy(t['Resources']['the_nested'])
|
|
new_res['Properties']['TemplateURL'] = (
|
|
'https://server.test/new.template')
|
|
prop_diff = {'TemplateURL': 'https://server.test/new.template'}
|
|
updater = rsrc.handle_update(new_res, {}, prop_diff)
|
|
updater.run_to_completion()
|
|
self.assertIs(True, rsrc.check_update_complete(updater))
|
|
|
|
self.assertEqual('foo', rsrc.FnGetAtt('Outputs.Bar'))
|
|
|
|
rsrc.delete()
|
|
|
|
self.m.VerifyAll()
|
|
|
|
def test_nested_stack_update_exceeds_limit(self):
|
|
resource._register_class('GenericResource',
|
|
generic_rsrc.GenericResource)
|
|
urlfetch.get('https://server.test/the.template').MultipleTimes().\
|
|
AndReturn(self.nested_template)
|
|
urlfetch.get('https://server.test/new.template').MultipleTimes().\
|
|
AndReturn('''
|
|
HeatTemplateFormatVersion: '2012-12-12'
|
|
Parameters:
|
|
KeyName:
|
|
Type: String
|
|
Resources:
|
|
NestedResource:
|
|
Type: GenericResource
|
|
Outputs:
|
|
Bar:
|
|
Value: foo
|
|
''')
|
|
self.m.ReplayAll()
|
|
|
|
stack = self.create_stack(self.test_template)
|
|
|
|
cfg.CONF.set_override('max_resources_per_stack', 1)
|
|
|
|
rsrc = stack['the_nested']
|
|
|
|
t = template_format.parse(self.test_template)
|
|
new_res = copy.deepcopy(t['Resources']['the_nested'])
|
|
new_res['Properties']['TemplateURL'] = (
|
|
'https://server.test/new.template')
|
|
prop_diff = {'TemplateURL': 'https://server.test/new.template'}
|
|
ex = self.assertRaises(exception.RequestLimitExceeded,
|
|
rsrc.handle_update, new_res, {}, prop_diff)
|
|
self.assertIn(exception.StackResourceLimitExceeded.msg_fmt,
|
|
str(ex))
|
|
rsrc.delete()
|
|
|
|
self.m.VerifyAll()
|
|
|
|
def test_nested_stack_suspend_resume(self):
|
|
urlfetch.get('https://server.test/the.template').AndReturn(
|
|
self.nested_template)
|
|
self.m.ReplayAll()
|
|
|
|
stack = self.create_stack(self.test_template)
|
|
rsrc = stack['the_nested']
|
|
|
|
scheduler.TaskRunner(rsrc.suspend)()
|
|
self.assertEqual((rsrc.SUSPEND, rsrc.COMPLETE), rsrc.state)
|
|
|
|
scheduler.TaskRunner(rsrc.resume)()
|
|
self.assertEqual((rsrc.RESUME, rsrc.COMPLETE), rsrc.state)
|
|
|
|
rsrc.delete()
|
|
self.m.VerifyAll()
|
|
|
|
def test_nested_stack_three_deep(self):
|
|
root_template = '''
|
|
HeatTemplateFormatVersion: 2012-12-12
|
|
Resources:
|
|
Nested:
|
|
Type: AWS::CloudFormation::Stack
|
|
Properties:
|
|
TemplateURL: 'https://server.test/depth1.template'
|
|
'''
|
|
depth1_template = '''
|
|
HeatTemplateFormatVersion: 2012-12-12
|
|
Resources:
|
|
Nested:
|
|
Type: AWS::CloudFormation::Stack
|
|
Properties:
|
|
TemplateURL: 'https://server.test/depth2.template'
|
|
'''
|
|
depth2_template = '''
|
|
HeatTemplateFormatVersion: 2012-12-12
|
|
Resources:
|
|
Nested:
|
|
Type: AWS::CloudFormation::Stack
|
|
Properties:
|
|
TemplateURL: 'https://server.test/depth3.template'
|
|
Parameters:
|
|
KeyName: foo
|
|
'''
|
|
urlfetch.get(
|
|
'https://server.test/depth1.template').AndReturn(
|
|
depth1_template)
|
|
urlfetch.get(
|
|
'https://server.test/depth2.template').AndReturn(
|
|
depth2_template)
|
|
urlfetch.get(
|
|
'https://server.test/depth3.template').AndReturn(
|
|
self.nested_template)
|
|
self.m.ReplayAll()
|
|
self.create_stack(root_template)
|
|
self.m.VerifyAll()
|
|
|
|
def test_nested_stack_four_deep(self):
|
|
root_template = '''
|
|
HeatTemplateFormatVersion: 2012-12-12
|
|
Resources:
|
|
Nested:
|
|
Type: AWS::CloudFormation::Stack
|
|
Properties:
|
|
TemplateURL: 'https://server.test/depth1.template'
|
|
'''
|
|
depth1_template = '''
|
|
HeatTemplateFormatVersion: 2012-12-12
|
|
Resources:
|
|
Nested:
|
|
Type: AWS::CloudFormation::Stack
|
|
Properties:
|
|
TemplateURL: 'https://server.test/depth2.template'
|
|
'''
|
|
depth2_template = '''
|
|
HeatTemplateFormatVersion: 2012-12-12
|
|
Resources:
|
|
Nested:
|
|
Type: AWS::CloudFormation::Stack
|
|
Properties:
|
|
TemplateURL: 'https://server.test/depth3.template'
|
|
'''
|
|
depth3_template = '''
|
|
HeatTemplateFormatVersion: 2012-12-12
|
|
Resources:
|
|
Nested:
|
|
Type: AWS::CloudFormation::Stack
|
|
Properties:
|
|
TemplateURL: 'https://server.test/depth4.template'
|
|
Parameters:
|
|
KeyName: foo
|
|
'''
|
|
urlfetch.get(
|
|
'https://server.test/depth1.template').AndReturn(
|
|
depth1_template)
|
|
urlfetch.get(
|
|
'https://server.test/depth2.template').AndReturn(
|
|
depth2_template)
|
|
urlfetch.get(
|
|
'https://server.test/depth3.template').AndReturn(
|
|
depth3_template)
|
|
urlfetch.get(
|
|
'https://server.test/depth4.template').AndReturn(
|
|
self.nested_template)
|
|
self.m.ReplayAll()
|
|
t = template_format.parse(root_template)
|
|
stack = self.parse_stack(t)
|
|
stack.create()
|
|
self.assertEqual((stack.CREATE, stack.FAILED), stack.state)
|
|
self.assertIn('Recursion depth exceeds', stack.status_reason)
|
|
self.m.VerifyAll()
|
|
|
|
def test_nested_stack_four_wide(self):
|
|
root_template = '''
|
|
HeatTemplateFormatVersion: 2012-12-12
|
|
Resources:
|
|
Nested:
|
|
Type: AWS::CloudFormation::Stack
|
|
Properties:
|
|
TemplateURL: 'https://server.test/depth1.template'
|
|
Parameters:
|
|
KeyName: foo
|
|
Nested2:
|
|
Type: AWS::CloudFormation::Stack
|
|
Properties:
|
|
TemplateURL: 'https://server.test/depth2.template'
|
|
Parameters:
|
|
KeyName: foo
|
|
Nested3:
|
|
Type: AWS::CloudFormation::Stack
|
|
Properties:
|
|
TemplateURL: 'https://server.test/depth3.template'
|
|
Parameters:
|
|
KeyName: foo
|
|
Nested4:
|
|
Type: AWS::CloudFormation::Stack
|
|
Properties:
|
|
TemplateURL: 'https://server.test/depth4.template'
|
|
Parameters:
|
|
KeyName: foo
|
|
'''
|
|
urlfetch.get(
|
|
'https://server.test/depth1.template').InAnyOrder().AndReturn(
|
|
self.nested_template)
|
|
urlfetch.get(
|
|
'https://server.test/depth2.template').InAnyOrder().AndReturn(
|
|
self.nested_template)
|
|
urlfetch.get(
|
|
'https://server.test/depth3.template').InAnyOrder().AndReturn(
|
|
self.nested_template)
|
|
urlfetch.get(
|
|
'https://server.test/depth4.template').InAnyOrder().AndReturn(
|
|
self.nested_template)
|
|
self.m.ReplayAll()
|
|
self.create_stack(root_template)
|
|
self.m.VerifyAll()
|
|
|
|
def test_nested_stack_infinite_recursion(self):
|
|
template = '''
|
|
HeatTemplateFormatVersion: 2012-12-12
|
|
Resources:
|
|
Nested:
|
|
Type: AWS::CloudFormation::Stack
|
|
Properties:
|
|
TemplateURL: 'https://server.test/the.template'
|
|
'''
|
|
urlfetch.get(
|
|
'https://server.test/the.template').MultipleTimes().AndReturn(
|
|
template)
|
|
self.m.ReplayAll()
|
|
t = template_format.parse(template)
|
|
stack = self.parse_stack(t)
|
|
stack.create()
|
|
self.assertEqual((stack.CREATE, stack.FAILED), stack.state)
|
|
self.assertIn('Recursion depth exceeds', stack.status_reason)
|
|
self.m.VerifyAll()
|
|
|
|
def test_nested_stack_delete(self):
|
|
urlfetch.get('https://server.test/the.template').MultipleTimes().\
|
|
AndReturn(self.nested_template)
|
|
self.m.ReplayAll()
|
|
|
|
stack = self.create_stack(self.test_template)
|
|
rsrc = stack['the_nested']
|
|
scheduler.TaskRunner(rsrc.delete)()
|
|
self.assertEqual((stack.DELETE, stack.COMPLETE), rsrc.state)
|
|
|
|
nested_stack = parser.Stack.load(utils.dummy_context(
|
|
'test_username', 'aaaa', 'password'), rsrc.resource_id)
|
|
self.assertEqual((stack.DELETE, stack.COMPLETE), nested_stack.state)
|
|
|
|
self.m.VerifyAll()
|
|
|
|
def test_nested_stack_delete_then_delete_parent_stack(self):
|
|
urlfetch.get('https://server.test/the.template').MultipleTimes().\
|
|
AndReturn(self.nested_template)
|
|
self.m.ReplayAll()
|
|
|
|
stack = self.create_stack(self.test_template)
|
|
rsrc = stack['the_nested']
|
|
|
|
nested_stack = parser.Stack.load(utils.dummy_context(
|
|
'test_username', 'aaaa', 'password'), rsrc.resource_id)
|
|
nested_stack.delete()
|
|
|
|
stack = parser.Stack.load(utils.dummy_context(
|
|
'test_username', 'aaaa', 'password'), stack.id)
|
|
stack.delete()
|
|
self.assertEqual((stack.DELETE, stack.COMPLETE), stack.state)
|
|
|
|
self.m.VerifyAll()
|
|
|
|
def test_child_params(self):
|
|
t = template_format.parse(self.test_template)
|
|
stack = self.parse_stack(t)
|
|
nested_stack = stack['the_nested']
|
|
nested_stack.properties.data[nested_stack.PARAMETERS] = {'foo': 'bar'}
|
|
|
|
self.assertEqual({'foo': 'bar'}, nested_stack.child_params())
|
|
|
|
@mock.patch.object(urlfetch, 'get')
|
|
def test_child_template_when_file_is_fetched(self, mock_get):
|
|
mock_get.return_value = 'template_file'
|
|
t = template_format.parse(self.test_template)
|
|
stack = self.parse_stack(t)
|
|
nested_stack = stack['the_nested']
|
|
|
|
with mock.patch('heat.common.template_format.parse') as mock_parse:
|
|
mock_parse.return_value = 'child_template'
|
|
self.assertEqual('child_template', nested_stack.child_template())
|
|
mock_parse.assert_called_once_with('template_file')
|
|
|
|
@mock.patch.object(urlfetch, 'get')
|
|
def test_child_template_when_fetching_file_fails(self, mock_get):
|
|
mock_get.side_effect = exceptions.RequestException()
|
|
t = template_format.parse(self.test_template)
|
|
stack = self.parse_stack(t)
|
|
nested_stack = stack['the_nested']
|
|
self.assertRaises(ValueError, nested_stack.child_template)
|
|
|
|
@mock.patch.object(urlfetch, 'get')
|
|
def test_child_template_when_io_error(self, mock_get):
|
|
msg = 'Failed to retrieve template'
|
|
mock_get.side_effect = urlfetch.URLFetchError(msg)
|
|
t = template_format.parse(self.test_template)
|
|
stack = self.parse_stack(t)
|
|
nested_stack = stack['the_nested']
|
|
self.assertRaises(ValueError, nested_stack.child_template)
|
|
|
|
|
|
class ResDataResource(generic_rsrc.GenericResource):
|
|
def handle_create(self):
|
|
db_api.resource_data_set(self, "test", 'A secret value', True)
|
|
|
|
|
|
class ResDataNestedStackTest(NestedStackTest):
|
|
|
|
nested_template = '''
|
|
HeatTemplateFormatVersion: "2012-12-12"
|
|
Parameters:
|
|
KeyName:
|
|
Type: String
|
|
Resources:
|
|
nested_res:
|
|
Type: "res.data.resource"
|
|
Outputs:
|
|
Foo:
|
|
Value: bar
|
|
'''
|
|
|
|
def setUp(self):
|
|
super(ResDataNestedStackTest, self).setUp()
|
|
resource._register_class("res.data.resource", ResDataResource)
|
|
|
|
def test_res_data_delete(self):
|
|
urlfetch.get('https://server.test/the.template').AndReturn(
|
|
self.nested_template)
|
|
self.m.ReplayAll()
|
|
stack = self.create_stack(self.test_template)
|
|
res = stack['the_nested'].nested()['nested_res']
|
|
stack.delete()
|
|
self.assertEqual((stack.DELETE, stack.COMPLETE), stack.state)
|
|
self.assertRaises(exception.NotFound, db_api.resource_data_get, res,
|
|
'test')
|