heat/heat/tests/test_rsrc_defn.py
Zane Bitter b0916ad5bb Cache names of required resources in ResourceDefinition
In order to calculate the dependencies of a ResourceDefinition, we have to
pass it a Stack object. This means we can't infer anything about the
resource's dependencies without instantiating the whole stack, and it means
we can't easily cache the result so we have to walk the entire
properties/metadata every time we need the dependencies.

This change splits a required_resource_names() method (the same as added to
OutputDefinition in the previous patch) out of the dependencies() method.
This new method calculates as much as we can know from the resource
definition alone and caches the result, while the dependencies() method
uses the (cached) result to calculate the graph dependencies for a given
Stack.

This also opens the way in future to changing the Function API to return
dependencies by name instead of as ResourceProxy objects.

Change-Id: Icdd0b2dd41116adae5e57ff3ef0e662ba75e6e2c
2017-07-21 10:44:51 -04:00

298 lines
12 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 six
from heat.common import exception
from heat.common import template_format
from heat.engine.cfn import functions as cfn_funcs
from heat.engine.hot import functions as hot_funcs
from heat.engine import properties
from heat.engine import rsrc_defn
from heat.tests import common
from heat.tests import utils
TEMPLATE_WITH_EX_REF_IMPLICIT_DEPEND = '''
heat_template_version: 2016-10-14
resources:
test1:
type: OS::Heat::TestResource
external_id: foobar
properties:
value: {get_resource: test2}
test2:
type: OS::Heat::TestResource
'''
TEMPLATE_WITH_INVALID_EXPLICIT_DEPEND = '''
heat_template_version: 2016-10-14
resources:
test1:
type: OS::Heat::TestResource
test3:
type: OS::Heat::TestResource
depends_on: test2
'''
class ResourceDefinitionTest(common.HeatTestCase):
def make_me_one_with_everything(self):
return rsrc_defn.ResourceDefinition(
'rsrc', 'SomeType',
properties={'Foo': cfn_funcs.Join(None,
'Fn::Join',
['a', ['b', 'r']]),
'Blarg': 'wibble'},
metadata={'Baz': cfn_funcs.Join(None,
'Fn::Join',
['u', ['q', '', 'x']])},
depends=['other_resource'],
deletion_policy='Retain',
update_policy={'SomePolicy': {}})
def test_properties_default(self):
rd = rsrc_defn.ResourceDefinition('rsrc', 'SomeType')
self.assertEqual({}, rd.properties({}))
def test_properties(self):
rd = self.make_me_one_with_everything()
schema = {
'Foo': properties.Schema(properties.Schema.STRING),
'Blarg': properties.Schema(properties.Schema.STRING, default=''),
'Baz': properties.Schema(properties.Schema.STRING, default='quux'),
}
props = rd.properties(schema)
self.assertEqual('bar', props['Foo'])
self.assertEqual('wibble', props['Blarg'])
self.assertEqual('quux', props['Baz'])
def test_metadata_default(self):
rd = rsrc_defn.ResourceDefinition('rsrc', 'SomeType')
self.assertEqual({}, rd.metadata())
def test_metadata(self):
rd = self.make_me_one_with_everything()
metadata = rd.metadata()
self.assertEqual({'Baz': 'quux'}, metadata)
self.assertIsInstance(metadata['Baz'], six.string_types)
def test_dependencies_default(self):
rd = rsrc_defn.ResourceDefinition('rsrc', 'SomeType')
stack = {'foo': 'FOO', 'bar': 'BAR'}
self.assertEqual(set(), rd.required_resource_names())
self.assertEqual([], list(rd.dependencies(stack)))
def test_dependencies_explicit(self):
rd = rsrc_defn.ResourceDefinition('rsrc', 'SomeType', depends=['foo'])
stack = {'foo': 'FOO', 'bar': 'BAR'}
self.assertEqual({'foo'}, rd.required_resource_names())
self.assertEqual(['FOO'], list(rd.dependencies(stack)))
def test_dependencies_explicit_ext(self):
rd = rsrc_defn.ResourceDefinition('rsrc', 'SomeType', depends=['foo'],
external_id='abc')
stack = {'foo': 'FOO', 'bar': 'BAR'}
self.assertRaises(
exception.InvalidExternalResourceDependency,
rd.dependencies, stack)
def test_dependencies_implicit_ext(self):
t = template_format.parse(TEMPLATE_WITH_EX_REF_IMPLICIT_DEPEND)
stack = utils.parse_stack(t)
rsrc = stack['test1']
self.assertEqual([], list(rsrc.t.dependencies(stack)))
def test_dependencies_explicit_invalid(self):
t = template_format.parse(TEMPLATE_WITH_INVALID_EXPLICIT_DEPEND)
stack = utils.parse_stack(t)
rd = stack.t.resource_definitions(stack)['test3']
self.assertEqual({'test2'}, rd.required_resource_names())
self.assertRaises(exception.InvalidTemplateReference,
lambda: list(rd.dependencies(stack)))
def test_deletion_policy_default(self):
rd = rsrc_defn.ResourceDefinition('rsrc', 'SomeType')
self.assertEqual(rsrc_defn.ResourceDefinition.DELETE,
rd.deletion_policy())
def test_deletion_policy(self):
for policy in rsrc_defn.ResourceDefinition.DELETION_POLICIES:
rd = rsrc_defn.ResourceDefinition('rsrc', 'SomeType',
deletion_policy=policy)
self.assertEqual(policy, rd.deletion_policy())
def test_deletion_policy_invalid(self):
self.assertRaises(AssertionError,
rsrc_defn.ResourceDefinition,
'rsrc', 'SomeType', deletion_policy='foo')
def test_update_policy_default(self):
rd = rsrc_defn.ResourceDefinition('rsrc', 'SomeType')
self.assertEqual({}, rd.update_policy({}))
def test_update_policy(self):
rd = self.make_me_one_with_everything()
policy_schema = {'Foo': properties.Schema(properties.Schema.STRING,
default='bar')}
schema = {
'SomePolicy': properties.Schema(properties.Schema.MAP,
schema=policy_schema),
}
up = rd.update_policy(schema)
self.assertEqual('bar', up['SomePolicy']['Foo'])
def test_freeze(self):
rd = self.make_me_one_with_everything()
frozen = rd.freeze()
self.assertEqual('bar', frozen._properties['Foo'])
self.assertEqual('quux', frozen._metadata['Baz'])
def test_freeze_override(self):
rd = self.make_me_one_with_everything()
frozen = rd.freeze(metadata={'Baz': 'wibble'})
self.assertEqual('bar', frozen._properties['Foo'])
self.assertEqual('wibble', frozen._metadata['Baz'])
def test_render_hot(self):
rd = self.make_me_one_with_everything()
expected_hot = {
'type': 'SomeType',
'properties': {'Foo': {'Fn::Join': ['a', ['b', 'r']]},
'Blarg': 'wibble'},
'metadata': {'Baz': {'Fn::Join': ['u', ['q', '', 'x']]}},
'depends_on': ['other_resource'],
'deletion_policy': 'Retain',
'update_policy': {'SomePolicy': {}},
}
self.assertEqual(expected_hot, rd.render_hot())
def test_render_hot_empty(self):
rd = rsrc_defn.ResourceDefinition('rsrc', 'SomeType')
expected_hot = {
'type': 'SomeType',
}
self.assertEqual(expected_hot, rd.render_hot())
def test_template_equality(self):
class FakeStack(object):
def __init__(self, params):
self.parameters = params
def get_param_defn(value):
stack = FakeStack({'Foo': value})
param_func = hot_funcs.GetParam(stack, 'get_param', 'Foo')
return rsrc_defn.ResourceDefinition('rsrc', 'SomeType',
{'Foo': param_func})
self.assertEqual(get_param_defn('bar'), get_param_defn('baz'))
def test_hash_equal(self):
rd1 = self.make_me_one_with_everything()
rd2 = self.make_me_one_with_everything()
self.assertEqual(rd1, rd2)
self.assertEqual(hash(rd1), hash(rd2))
def test_hash_names(self):
rd1 = rsrc_defn.ResourceDefinition('rsrc1', 'SomeType')
rd2 = rsrc_defn.ResourceDefinition('rsrc2', 'SomeType')
self.assertEqual(rd1, rd2)
self.assertEqual(hash(rd1), hash(rd2))
def test_hash_types(self):
rd1 = rsrc_defn.ResourceDefinition('rsrc', 'SomeType1')
rd2 = rsrc_defn.ResourceDefinition('rsrc', 'SomeType2')
self.assertNotEqual(rd1, rd2)
self.assertNotEqual(hash(rd1), hash(rd2))
class ResourceDefinitionDiffTest(common.HeatTestCase):
def test_properties_diff(self):
before = rsrc_defn.ResourceDefinition('rsrc', 'SomeType',
properties={'Foo': 'blarg'},
update_policy={'baz': 'quux'},
metadata={'baz': 'quux'})
after = rsrc_defn.ResourceDefinition('rsrc', 'SomeType',
properties={'Foo': 'wibble'},
update_policy={'baz': 'quux'},
metadata={'baz': 'quux'})
diff = after - before
self.assertTrue(diff.properties_changed())
self.assertFalse(diff.update_policy_changed())
self.assertFalse(diff.metadata_changed())
self.assertTrue(diff)
def test_update_policy_diff(self):
before = rsrc_defn.ResourceDefinition('rsrc', 'SomeType',
properties={'baz': 'quux'},
update_policy={'Foo': 'blarg'},
metadata={'baz': 'quux'})
after = rsrc_defn.ResourceDefinition('rsrc', 'SomeType',
properties={'baz': 'quux'},
update_policy={'Foo': 'wibble'},
metadata={'baz': 'quux'})
diff = after - before
self.assertFalse(diff.properties_changed())
self.assertTrue(diff.update_policy_changed())
self.assertFalse(diff.metadata_changed())
self.assertTrue(diff)
def test_metadata_diff(self):
before = rsrc_defn.ResourceDefinition('rsrc', 'SomeType',
properties={'baz': 'quux'},
update_policy={'baz': 'quux'},
metadata={'Foo': 'blarg'})
after = rsrc_defn.ResourceDefinition('rsrc', 'SomeType',
properties={'baz': 'quux'},
update_policy={'baz': 'quux'},
metadata={'Foo': 'wibble'})
diff = after - before
self.assertFalse(diff.properties_changed())
self.assertFalse(diff.update_policy_changed())
self.assertTrue(diff.metadata_changed())
self.assertTrue(diff)
def test_no_diff(self):
before = rsrc_defn.ResourceDefinition('rsrc', 'SomeType',
properties={'Foo': 'blarg'},
update_policy={'bar': 'quux'},
metadata={'baz': 'wibble'},
depends=['other_resource'],
deletion_policy='Delete')
after = rsrc_defn.ResourceDefinition('rsrc', 'SomeType',
properties={'Foo': 'blarg'},
update_policy={'bar': 'quux'},
metadata={'baz': 'wibble'},
depends=['other_other_resource'],
deletion_policy='Retain')
diff = after - before
self.assertFalse(diff.properties_changed())
self.assertFalse(diff.update_policy_changed())
self.assertFalse(diff.metadata_changed())
self.assertFalse(diff)