3c13cb82a6
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
306 lines
10 KiB
Python
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'})
|