api.RES_ID: dict(resource.identifier()),
api.RES_STACK_ID: dict(resource.stack.identifier()),
api.RES_STACK_NAME: resource.stack.name,
+ api.RES_REQUIRED_BY: resource.required_by(),
}
if detail:
return self
+ def required_by(self, last):
+ '''
+ List the keys that require the specified node.
+ '''
+ if last not in self._graph:
+ raise KeyError
+
+ return self._graph[last].required_by()
+
def __getitem__(self, last):
'''
Return a partial dependency graph consisting of the specified node and
self._add_dependencies(deps, None, self.t)
deps += (self, None)
+ def required_by(self):
+ '''
+ Returns a list of names of resources which directly require this
+ resource as a dependency.
+ '''
+ return list(
+ [r.name for r in self.stack.dependencies.required_by(self)])
+
def keystone(self):
return self.stack.clients.keystone()
RES_DESCRIPTION, RES_UPDATED_TIME,
RES_NAME, RES_PHYSICAL_ID, RES_METADATA, RES_ACTION,
RES_STATUS, RES_STATUS_DATA, RES_TYPE,
- RES_ID, RES_STACK_ID, RES_STACK_NAME,
+ RES_ID, RES_STACK_ID, RES_STACK_NAME, RES_REQUIRED_BY,
) = (
'description', 'updated_time',
'logical_resource_id', 'physical_resource_id', 'metadata',
'resource_action', 'resource_status', 'resource_status_reason',
- 'resource_type', 'resource_identity', STACK_ID, STACK_NAME,
+ 'resource_type', 'resource_identity', STACK_ID, STACK_NAME, 'required_by',
)
EVENT_KEYS = (
for n in ('last', 'mid1', 'mid2', 'mid3'):
self.assertTrue(n in order,
"'%s' not found in dependency order" % n)
+
+ def test_required_by(self):
+ d = Dependencies([('last', 'e1'), ('last', 'mid1'), ('last', 'mid2'),
+ ('mid1', 'e2'), ('mid1', 'mid3'),
+ ('mid2', 'mid3'),
+ ('mid3', 'e3')])
+
+ self.assertEqual(0, len(list(d.required_by('last'))))
+
+ required_by = list(d.required_by('mid3'))
+ self.assertEqual(len(required_by), 2)
+ for n in ('mid1', 'mid2'):
+ self.assertTrue(n in required_by,
+ "'%s' not found in required_by" % n)
+
+ required_by = list(d.required_by('e2'))
+ self.assertEqual(len(required_by), 1)
+ self.assertTrue('mid1' in required_by,
+ "'%s' not found in required_by" % n)
+
+ self.assertRaises(KeyError, d.required_by, 'foo')
# License for the specific language governing permissions and limitations
# under the License.
-
-from heat.tests.common import HeatTestCase
+from heat.common import context
import heat.engine.api as api
+from heat.engine import parser
+from heat.engine import resource
+from heat.openstack.common import uuidutils
+from heat.rpc import api as rpc_api
+from heat.tests.common import HeatTestCase
+from heat.tests import generic_resource as generic_rsrc
+from heat.tests.utils import setup_dummy_db
class EngineApiTest(HeatTestCase):
def test_disable_rollback_extract_bad(self):
self.assertRaises(ValueError, api.extract_args,
{'disable_rollback': 'bad'})
+
+
+class FormatTest(HeatTestCase):
+
+ def setUp(self):
+ super(FormatTest, self).setUp()
+ setup_dummy_db()
+ ctx = context.get_admin_context()
+ self.m.StubOutWithMock(ctx, 'user')
+ ctx.user = 'test_user'
+ ctx.tenant_id = 'test_tenant'
+
+ template = parser.Template({
+ 'Resources': {
+ 'generic1': {'Type': 'GenericResourceType'},
+ 'generic2': {
+ 'Type': 'GenericResourceType',
+ 'DependsOn': 'generic1'}
+ }
+ })
+ resource._register_class('GenericResourceType',
+ generic_rsrc.GenericResource)
+ self.stack = parser.Stack(ctx, 'test_stack', template,
+ stack_id=uuidutils.generate_uuid())
+
+ def test_format_stack_resource(self):
+ res = self.stack['generic1']
+
+ resource_keys = set((
+ rpc_api.RES_UPDATED_TIME,
+ rpc_api.RES_NAME,
+ rpc_api.RES_PHYSICAL_ID,
+ rpc_api.RES_METADATA,
+ rpc_api.RES_ACTION,
+ rpc_api.RES_STATUS,
+ rpc_api.RES_STATUS_DATA,
+ rpc_api.RES_TYPE,
+ rpc_api.RES_ID,
+ rpc_api.RES_STACK_ID,
+ rpc_api.RES_STACK_NAME,
+ rpc_api.RES_REQUIRED_BY))
+
+ resource_details_keys = resource_keys.union(set(
+ (rpc_api.RES_DESCRIPTION, rpc_api.RES_METADATA)))
+
+ formatted = api.format_stack_resource(res, True)
+ self.assertEqual(resource_details_keys, set(formatted.keys()))
+
+ formatted = api.format_stack_resource(res, False)
+ self.assertEqual(resource_keys, set(formatted.keys()))
+
+ def test_format_stack_resource_required_by(self):
+ res1 = api.format_stack_resource(self.stack['generic1'])
+ res2 = api.format_stack_resource(self.stack['generic2'])
+ self.assertEqual(res1['required_by'], ['generic2'])
+ self.assertEqual(res2['required_by'], [])
(rsrc.UPDATE, rsrc.FAILED)):
rsrc.state_set(action, status)
self.assertEqual(None, self.stack.output('TestOutput'))
+
+ @stack_delete_after
+ def test_resource_required_by(self):
+ tmpl = {'Resources': {'AResource': {'Type': 'GenericResourceType'},
+ 'BResource': {'Type': 'GenericResourceType',
+ 'DependsOn': 'AResource'},
+ 'CResource': {'Type': 'GenericResourceType',
+ 'DependsOn': 'BResource'},
+ 'DResource': {'Type': 'GenericResourceType',
+ 'DependsOn': 'BResource'}}}
+
+ self.m.StubOutWithMock(scheduler.TaskRunner, '_sleep')
+ scheduler.TaskRunner._sleep(mox.IsA(int)).MultipleTimes()
+ mox.Replay(scheduler.TaskRunner._sleep)
+
+ self.stack = parser.Stack(self.ctx, 'depends_test_stack',
+ template.Template(tmpl))
+ self.stack.store()
+ self.stack.create()
+ self.assertEqual(self.stack.state,
+ (parser.Stack.CREATE, parser.Stack.COMPLETE))
+
+ self.assertEqual(['BResource'],
+ self.stack['AResource'].required_by())
+ self.assertEqual([],
+ self.stack['CResource'].required_by())
+ required_by = self.stack['BResource'].required_by()
+ self.assertEqual(2, len(required_by))
+ for r in ['CResource', 'DResource']:
+ self.assertIn(r, required_by)