heat/heat/tests/test_stack_collect_attributes.py
Zane Bitter 3c13cb82a6 Cache dep_attrs for all resources in definitions
When the dep_attrs function was written, it was used only in convergence
after checking a single resource. However, now we also use it to generate
data for the ResourceProxy objects, which is often done for all resources
simultaneously. That means doing O(n^2) dep_attrs() calls, which can really
slow things down when there is a large number of resources with complex
properties (or metadata).

This change adds an all_dep_attrs() call, which runs as fast as dep_attrs()
on everything except get_attr functions and their arguments, but only needs
to be called once instead of once per resource. (The get_attr function can
in future override the default implementation of all_dep_attrs() to be as
efficient as dep_attrs() as well.) The resulting data is cached in the
ResourceDefinition or OutputDefinition so that subsequent calls to their
get_attr() methods with different (or the same) resource names will use the
existing data.

Change-Id: If95f4c04b841519ce3d7492211f2696588c0ed48
Partially-Implements: blueprint stack-definition
Closes-Bug: #1684272
2017-07-21 10:44:51 -04:00

306 lines
10 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 itertools
import six
from heat.common import template_format
from heat.engine import stack
from heat.engine import template
from heat.tests import common
from heat.tests import utils
tmpl1 = """
heat_template_version: 2014-10-16
resources:
AResource:
type: ResourceWithPropsType
properties:
Foo: 'abc'
"""
tmpl2 = """
heat_template_version: 2014-10-16
resources:
AResource:
type: ResourceWithPropsType
properties:
Foo: 'abc'
BResource:
type: ResourceWithPropsType
properties:
Foo: {get_attr: [AResource, attr_A1]}
metadata:
Foo: {get_attr: [AResource, attr_A1]}
outputs:
out1:
value: {get_attr: [AResource, attr_A1]}
"""
tmpl3 = """
heat_template_version: 2014-10-16
resources:
AResource:
type: ResourceWithPropsType
properties:
Foo: 'abc'
BResource:
type: ResourceWithPropsType
properties:
Foo: {get_attr: [AResource, attr_A1]}
Doo: {get_attr: [AResource, attr_A2]}
Bar: {get_attr: [AResource, attr_A3]}
metadata:
first: {get_attr: [AResource, meta_A1]}
second: {get_attr: [AResource, meta_A2]}
third: {get_attr: [AResource, attr_A3]}
outputs:
out1:
value: {get_attr: [AResource, out_A1]}
out2:
value: {get_attr: [AResource, out_A2]}
"""
tmpl4 = """
heat_template_version: 2014-10-16
resources:
AResource:
type: ResourceWithPropsType
properties:
Foo: 'abc'
BResource:
type: ResourceWithPropsType
properties:
Foo: 'abc'
CResource:
type: ResourceWithPropsType
properties:
Foo: 'abc'
DResource:
type: ResourceWithPropsType
properties:
Foo: {get_attr: [AResource, attr_A1]}
Doo: {get_attr: [BResource, attr_B1]}
metadata:
Doo: {get_attr: [CResource, attr_C1]}
outputs:
out1:
value: [{get_attr: [AResource, attr_A1]},
{get_attr: [BResource, attr_B1]},
{get_attr: [CResource, attr_C1]}]
"""
tmpl5 = """
heat_template_version: 2014-10-16
resources:
AResource:
type: ResourceWithPropsType
properties:
Foo: 'abc'
BResource:
type: ResourceWithPropsType
properties:
Foo: {get_attr: [AResource, attr_A1]}
Doo: {get_attr: [AResource, attr_A2]}
metadata:
first: {get_attr: [AResource, meta_A1]}
CResource:
type: ResourceWithPropsType
properties:
Foo: {get_attr: [AResource, attr_A1]}
Doo: {get_attr: [BResource, attr_B2]}
metadata:
Doo: {get_attr: [BResource, attr_B1]}
first: {get_attr: [AResource, meta_A1]}
second: {get_attr: [BResource, meta_B2]}
outputs:
out1:
value: [{get_attr: [AResource, attr_A3]},
{get_attr: [AResource, attr_A4]},
{get_attr: [BResource, attr_B3]}]
"""
tmpl6 = """
heat_template_version: 2015-04-30
resources:
AResource:
type: ResourceWithComplexAttributesType
BResource:
type: ResourceWithPropsType
properties:
Foo: {get_attr: [AResource, list, 1]}
Doo: {get_attr: [AResource, nested_dict, dict, b]}
outputs:
out1:
value: [{get_attr: [AResource, flat_dict, key2]},
{get_attr: [AResource, nested_dict, string]},
{get_attr: [BResource, attr_B3]}]
out2:
value: {get_resource: BResource}
"""
tmpl7 = """
heat_template_version: 2015-10-15
resources:
AResource:
type: ResourceWithPropsType
properties:
Foo: 'abc'
BResource:
type: ResourceWithPropsType
properties:
Foo: {get_attr: [AResource, attr_A1]}
Doo: {get_attr: [AResource, attr_A2]}
metadata:
first: {get_attr: [AResource, meta_A1]}
CResource:
type: ResourceWithPropsType
properties:
Foo: {get_attr: [AResource, attr_A1]}
Doo: {get_attr: [BResource, attr_B2]}
metadata:
Doo: {get_attr: [BResource, attr_B1]}
first: {get_attr: [AResource, meta_A1]}
second: {get_attr: [BResource, meta_B2]}
outputs:
out1:
value: [{get_attr: [AResource, attr_A3]},
{get_attr: [AResource, attr_A4]},
{get_attr: [BResource, attr_B3]},
{get_attr: [CResource]}]
"""
class DepAttrsTest(common.HeatTestCase):
scenarios = [
('no_attr',
dict(tmpl=tmpl1,
expected={'AResource': set()})),
('one_res_one_attr',
dict(tmpl=tmpl2,
expected={'AResource': {'attr_A1'},
'BResource': set()})),
('one_res_several_attrs',
dict(tmpl=tmpl3,
expected={'AResource': {'attr_A1', 'attr_A2', 'attr_A3',
'meta_A1', 'meta_A2'},
'BResource': set()})),
('several_res_one_attr',
dict(tmpl=tmpl4,
expected={'AResource': {'attr_A1'},
'BResource': {'attr_B1'},
'CResource': {'attr_C1'},
'DResource': set()})),
('several_res_several_attrs',
dict(tmpl=tmpl5,
expected={'AResource': {'attr_A1', 'attr_A2', 'meta_A1'},
'BResource': {'attr_B1', 'attr_B2', 'meta_B2'},
'CResource': set()})),
('nested_attr',
dict(tmpl=tmpl6,
expected={'AResource': set([(u'list', 1),
(u'nested_dict', u'dict', u'b')]),
'BResource': set([])})),
('several_res_several_attrs_and_all_attrs',
dict(tmpl=tmpl7,
expected={'AResource': {'attr_A1', 'attr_A2', 'meta_A1'},
'BResource': {'attr_B1', 'attr_B2', 'meta_B2'},
'CResource': set()}))
]
def setUp(self):
super(DepAttrsTest, self).setUp()
self.ctx = utils.dummy_context()
self.parsed_tmpl = template_format.parse(self.tmpl)
self.stack = stack.Stack(self.ctx, 'test_stack',
template.Template(self.parsed_tmpl))
def test_dep_attrs(self):
for res in six.itervalues(self.stack):
definitions = (self.stack.defn.resource_definition(n)
for n in self.parsed_tmpl['resources'])
self.assertEqual(self.expected[res.name],
set(itertools.chain.from_iterable(
d.dep_attrs(res.name) for d in definitions)))
def test_all_dep_attrs(self):
for res in six.itervalues(self.stack):
definitions = (self.stack.defn.resource_definition(n)
for n in self.parsed_tmpl['resources'])
attrs = set(itertools.chain.from_iterable(
d.dep_attrs(res.name, load_all=True) for d in definitions))
self.assertEqual(self.expected[res.name], attrs)
class ReferencedAttrsTest(common.HeatTestCase):
def setUp(self):
super(ReferencedAttrsTest, self).setUp()
parsed_tmpl = template_format.parse(tmpl6)
self.stack = stack.Stack(utils.dummy_context(), 'test_stack',
template.Template(parsed_tmpl))
self.resA = self.stack['AResource']
self.resB = self.stack['BResource']
def test_referenced_attrs_resources(self):
self.assertEqual(self.resA.referenced_attrs(in_resources=True,
in_outputs=False),
{('list', 1), ('nested_dict', 'dict', 'b')})
self.assertEqual(self.resB.referenced_attrs(in_resources=True,
in_outputs=False),
set())
def test_referenced_attrs_outputs(self):
self.assertEqual(self.resA.referenced_attrs(in_resources=False,
in_outputs=True),
{('flat_dict', 'key2'), ('nested_dict', 'string')})
self.assertEqual(self.resB.referenced_attrs(in_resources=False,
in_outputs=True),
{'attr_B3'})
def test_referenced_attrs_single_output(self):
self.assertEqual(self.resA.referenced_attrs(in_resources=False,
in_outputs={'out1'}),
{('flat_dict', 'key2'), ('nested_dict', 'string')})
self.assertEqual(self.resB.referenced_attrs(in_resources=False,
in_outputs={'out1'}),
{'attr_B3'})
self.assertEqual(self.resA.referenced_attrs(in_resources=False,
in_outputs={'out2'}),
set())
self.assertEqual(self.resB.referenced_attrs(in_resources=False,
in_outputs={'out2'}),
set())
def test_referenced_attrs_outputs_list(self):
self.assertEqual(self.resA.referenced_attrs(in_resources=False,
in_outputs={'out1',
'out2'}),
{('flat_dict', 'key2'), ('nested_dict', 'string')})
self.assertEqual(self.resB.referenced_attrs(in_resources=False,
in_outputs={'out1',
'out2'}),
{'attr_B3'})
def test_referenced_attrs_both(self):
self.assertEqual(self.resA.referenced_attrs(in_resources=True,
in_outputs=True),
{('list', 1), ('nested_dict', 'dict', 'b'),
('flat_dict', 'key2'), ('nested_dict', 'string')})
self.assertEqual(self.resB.referenced_attrs(in_resources=True,
in_outputs=True),
{'attr_B3'})