From 8bf7353982ea92ae950fd29dd61cb6a6b8d091b1 Mon Sep 17 00:00:00 2001 From: Zane Bitter Date: Fri, 6 Jul 2012 14:33:19 +0200 Subject: [PATCH] Separate out formatting for Engine API from manager Change-Id: Ib5fc28bc6a3a2f1f3c0d291954e928d4fb036bbb Signed-off-by: Zane Bitter --- heat/engine/api.py | 264 ++++++++++++++++++ heat/engine/manager.py | 170 +++-------- heat/engine/parser.py | 12 +- ...st_manager.py => test_engine_api_utils.py} | 26 +- heat/tests/test_stacks.py | 30 +- 5 files changed, 331 insertions(+), 171 deletions(-) create mode 100644 heat/engine/api.py rename heat/tests/{test_manager.py => test_engine_api_utils.py} (85%) diff --git a/heat/engine/api.py b/heat/engine/api.py new file mode 100644 index 00000000..d11c405c --- /dev/null +++ b/heat/engine/api.py @@ -0,0 +1,264 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# 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 re +import logging +from heat.common import utils as heat_utils +from heat.db import api as db_api +from heat.engine import parser + + +logger = logging.getLogger('heat.engine.manager') + +PARAM_KEYS = ( + PARAM_TIMEOUT, + PARAM_USER_KEY_re, + PARAM_USER_VALUE_fmt, +) = ( + 'TimeoutInMinutes', + re.compile(r'Parameters\.member\.(.*?)\.ParameterKey$'), + 'Parameters.member.%s.ParameterValue', +) + + +def extract_user_params(params): + ''' + Extract a dictionary of user parameters (to e.g. a stack create command) + from the parameter dictionary passed through the API. + + In the API parameters, each user parameter appears as two key-value pairs + with keys of the form: + + Parameters.member.1.ParameterKey + Parameters.member.1.ParameterValue + ''' + def get_param_pairs(): + for k in params: + keymatch = PARAM_USER_KEY_re.match(k) + if keymatch: + key = params[k] + v = PARAM_USER_VALUE_fmt % keymatch.group(1) + try: + value = params[v] + except KeyError: + logger.error('Could not apply parameter %s' % key) + + yield (key, value) + + return dict(get_param_pairs()) + + +def extract_args(params): + ''' + Extract any arguments passed as parameters through the API and return them + as a dictionary. + ''' + kwargs = {} + try: + timeout_mins = int(params.get(PARAM_TIMEOUT, 0)) + except (ValueError, TypeError): + logger.exception('create timeout conversion') + else: + if timeout_mins > 0: + kwargs['timeout_in_minutes'] = timeout_mins + return kwargs + + +def _filter_keys(data, keys): + ''' + Filter the provided data so that only the dictionary keys specified are + present. If keys is None, return all of the data. + ''' + if keys is not None: + data = dict((k, v) for (k, v) in data.iteritems() if k in keys) + + return data + + +STACK_KEYS = ( + STACK_NAME, STACK_ID, + STACK_CREATION_TIME, STACK_UPDATED_TIME, STACK_DELETION_TIME, + STACK_NOTIFICATION_TOPICS, + STACK_DESCRIPTION, STACK_TMPL_DESCRIPTION, + STACK_PARAMETERS, STACK_OUTPUTS, + STACK_STATUS, STACK_STATUS_DATA, + STACK_TIMEOUT, +) = ( + 'StackName', 'StackId', + 'CreationTime', 'LastUpdatedTime', 'DeletionTime', + 'NotificationARNs', + 'Description', 'TemplateDescription', + 'Parameters', 'Outputs', + 'StackStatus', 'StackStatusReason', + PARAM_TIMEOUT, +) + +KEYS_STACK = ( + STACK_NAME, STACK_ID, + STACK_CREATION_TIME, STACK_UPDATED_TIME, + STACK_NOTIFICATION_TOPICS, + STACK_DESCRIPTION, + STACK_PARAMETERS, STACK_DESCRIPTION, STACK_OUTPUTS, + STACK_STATUS, STACK_STATUS_DATA, + STACK_TIMEOUT, +) +KEYS_STACK_SUMMARY = ( + STACK_CREATION_TIME, STACK_DELETION_TIME, + STACK_UPDATED_TIME, + STACK_ID, STACK_NAME, + STACK_TMPL_DESCRIPTION, + STACK_STATUS, STACK_STATUS_DATA, +) + + +STACK_OUTPUT_KEYS = ( + OUTPUT_DESCRIPTION, + OUTPUT_KEY, OUTPUT_VALUE, +) = ( + 'Description', + 'OutputKey', 'OutputValue', +) + + +def format_stack_outputs(stack, outputs): + ''' + Return a representation of the given output template for the given stack + that matches the API output expectations. + ''' + def format_stack_output(k): + return {OUTPUT_DESCRIPTION: outputs[k].get('Description', + 'No description given'), + OUTPUT_KEY: k, + OUTPUT_VALUE: stack.output(k)} + + return [format_stack_output(key) for key in outputs] + + +def format_stack(stack, keys=None): + ''' + Return a representation of the given stack that matches the API output + expectations. + ''' + s = db_api.stack_get(stack.context, stack.id) + info = { + STACK_NAME: stack.name, + STACK_ID: stack.stack_id(), + STACK_CREATION_TIME: heat_utils.strtime(s.created_at), + STACK_UPDATED_TIME: heat_utils.strtime(s.updated_at), + STACK_NOTIFICATION_TOPICS: [], # TODO Not implemented yet + STACK_PARAMETERS: stack.t[parser.PARAMETERS], + STACK_DESCRIPTION: stack.t[parser.DESCRIPTION], + STACK_TMPL_DESCRIPTION: stack.t[parser.DESCRIPTION], + STACK_STATUS: s.status, + STACK_STATUS_DATA: s.status_reason, + } + + # only show the outputs on a completely created stack + if s.status == stack.CREATE_COMPLETE: + info[STACK_OUTPUTS] = format_stack_outputs(stack, stack.outputs) + + return _filter_keys(info, keys) + + +RES_KEYS = ( + RES_DESCRIPTION, RES_UPDATED_TIME, + RES_NAME, RES_PHYSICAL_ID, RES_METADATA, + RES_STATUS, RES_STATUS_DATA, RES_TYPE, + RES_STACK_ID, RES_STACK_NAME, + RES_TIMESTAMP, +) = ( + 'Description', 'LastUpdatedTimestamp', + 'LogicalResourceId', 'PhysicalResourceId', 'Metadata', + 'ResourceStatus', 'ResourceStatusReason', 'ResourceType', + STACK_ID, STACK_NAME, + 'Timestamp', +) + +KEYS_RESOURCE_DETAIL = ( + RES_DESCRIPTION, RES_UPDATED_TIME, + RES_NAME, RES_PHYSICAL_ID, RES_METADATA, + RES_STATUS, RES_STATUS_DATA, RES_TYPE, + RES_STACK_ID, RES_STACK_NAME, +) +KEYS_RESOURCE = ( + RES_DESCRIPTION, + RES_NAME, RES_PHYSICAL_ID, + RES_STATUS, RES_STATUS_DATA, RES_TYPE, + RES_STACK_ID, RES_STACK_NAME, + RES_TIMESTAMP, +) +KEYS_RESOURCE_SUMMARY = ( + RES_UPDATED_TIME, + RES_NAME, RES_PHYSICAL_ID, + RES_STATUS, RES_STATUS_DATA, RES_TYPE, +) + + +def format_stack_resource(resource, keys=None): + ''' + Return a representation of the given resource that matches the API output + expectations. + ''' + rs = db_api.resource_get(resource.context, resource.id) + last_updated_time = rs.updated_at or rs.created_at + attrs = { + RES_DESCRIPTION: resource.parsed_template().get('Description', ''), + RES_UPDATED_TIME: heat_utils.strtime(last_updated_time), + RES_NAME: resource.name, + RES_PHYSICAL_ID: resource.instance_id or '', + RES_METADATA: rs.rsrc_metadata, + RES_STATUS: rs.state, + RES_STATUS_DATA: rs.state_description, + RES_TYPE: resource.t['Type'], + RES_STACK_ID: resource.stack.stack_id(), + RES_STACK_NAME: resource.stack.name, + RES_TIMESTAMP: heat_utils.strtime(last_updated_time), + } + + return _filter_keys(attrs, keys) + + +EVENT_KEYS = ( + EVENT_ID, + EVENT_STACK_ID, EVENT_STACK_NAME, + EVENT_TIMESTAMP, + EVENT_RES_NAME, EVENT_RES_PHYSICAL_ID, + EVENT_RES_STATUS, EVENT_RES_STATUS_DATA, EVENT_RES_TYPE, + EVENT_RES_PROPERTIES, +) = ( + 'EventId', + STACK_ID, STACK_NAME, + RES_TIMESTAMP, + RES_NAME, RES_PHYSICAL_ID, + RES_STATUS, RES_STATUS_DATA, RES_TYPE, + 'ResourceProperties', +) + + +def format_event(event, keys=None): + s = event.stack + attrs = { + EVENT_ID: event.id, + EVENT_STACK_ID: event.stack_id, + EVENT_STACK_NAME: s.name, + EVENT_TIMESTAMP: heat_utils.strtime(event.created_at), + EVENT_RES_NAME: event.logical_resource_id, + EVENT_RES_PHYSICAL_ID: event.physical_resource_id, + EVENT_RES_STATUS: event.name, + EVENT_RES_STATUS_DATA: event.resource_status_reason, + EVENT_RES_TYPE: event.resource_type, + EVENT_RES_PROPERTIES: event.resource_properties, + } + + return _filter_keys(attrs, keys) diff --git a/heat/engine/manager.py b/heat/engine/manager.py index bd9d2506..81b630e7 100644 --- a/heat/engine/manager.py +++ b/heat/engine/manager.py @@ -20,7 +20,6 @@ import logging import webob import json import urlparse -import re import httplib import eventlet @@ -29,6 +28,7 @@ from heat.db import api as db_api from heat.common import config from heat.common import utils as heat_utils from heat.common import context as ctxtlib +from heat.engine import api from heat.engine import parser from heat.engine import resources from heat.engine import watchrule @@ -43,37 +43,6 @@ from novaclient.exceptions import AuthorizationFailure logger = logging.getLogger('heat.engine.manager') greenpool = eventlet.GreenPool() -_param_key = re.compile(r'Parameters\.member\.(.*?)\.ParameterKey$') - - -def _extract_user_params(params): - def get_param_pairs(): - for k in params: - keymatch = _param_key.match(k) - if keymatch: - key = params[k] - v = 'Parameters.member.%s.ParameterValue' % keymatch.group(1) - try: - value = params[v] - except KeyError: - logger.error('Could not apply parameter %s' % key) - - yield (key, value) - - return dict(get_param_pairs()) - - -def _extract_args(params): - kwargs = {} - try: - timeout_mins = int(params.get('TimeoutInMinutes', 0)) - except (ValueError, TypeError): - logger.exception('create timeout conversion') - else: - if timeout_mins > 0: - kwargs['timeout_in_minutes'] = timeout_mins - return kwargs - class EngineManager(manager.Manager): """ @@ -100,21 +69,15 @@ class EngineManager(manager.Manager): auth.authenticate(context) - res = {'stacks': []} stacks = db_api.stack_get_by_user(context) if stacks is None: - return res - for s in stacks: + stacks = [] + + def format_stack_summary(s): stack = parser.Stack.load(context, s.id) - mem = {} - mem['StackId'] = stack.stack_id() - mem['StackName'] = s.name - mem['CreationTime'] = heat_utils.strtime(s.created_at) - mem['TemplateDescription'] = stack.t[parser.DESCRIPTION] - mem['StackStatus'] = s.status - res['stacks'].append(mem) + return api.format_stack(stack, api.KEYS_STACK_SUMMARY) - return res + return {'stacks': [format_stack_summary(s) for s in stacks]} def show_stack(self, context, stack_name, params): """ @@ -125,41 +88,17 @@ class EngineManager(manager.Manager): """ auth.authenticate(context) - res = {'stacks': []} - stacks = [] - if not stack_name: - stacks = [s.name for s in db_api.stack_get_by_user(context)] - logging.debug("No stack name passed, got %s" % stacks) + if stack_name is not None: + s = db_api.stack_get_by_name(context, stack_name) + stacks = [s] if s is not None else [] else: - stacks = [stack_name] - - if not stacks: - logging.debug("No stacks found to process") - return res - - for stack in stacks: - logging.debug("Processing show_stack for %s" % stack) - s = db_api.stack_get_by_name(context, stack) - if s: - stack = parser.Stack.load(context, s.id) - mem = {} - mem['StackId'] = stack.stack_id() - mem['StackName'] = s.name - mem['CreationTime'] = heat_utils.strtime(s.created_at) - mem['LastUpdatedTimestamp'] = heat_utils.strtime(s.updated_at) - mem['NotificationARNs'] = 'TODO' - mem['Parameters'] = stack.t[parser.PARAMETERS] - mem['Description'] = stack.t[parser.DESCRIPTION] - mem['StackStatus'] = s.status - mem['StackStatusReason'] = s.status_reason - - # only show the outputs on a completely created stack - if s.status == stack.CREATE_COMPLETE: - mem['Outputs'] = stack.get_outputs() - - res['stacks'].append(mem) - - return res + stacks = db_api.stack_get_by_user(context) or [] + + def format_stack_detail(s): + stack = parser.Stack.load(context, s.id) + return api.format_stack(stack, api.KEYS_STACK) + + return {'stacks': [format_stack_detail(s) for s in stacks]} def create_stack(self, context, stack_name, template, params): """ @@ -181,7 +120,7 @@ class EngineManager(manager.Manager): tmpl = parser.Template(template) user_params = parser.Parameters(stack_name, tmpl, - _extract_user_params(params)) + api.extract_user_params(params)) stack = parser.Stack(context, stack_name, tmpl, user_params) response = stack.validate() @@ -189,7 +128,7 @@ class EngineManager(manager.Manager): return response stack_id = stack.store() - greenpool.spawn_n(stack.create, **_extract_args(params)) + greenpool.spawn_n(stack.create, **api.extract_args(params)) return {'StackId': stack.stack_id()} @@ -214,7 +153,7 @@ class EngineManager(manager.Manager): try: tmpl = parser.Template(template) user_params = parser.Parameters(stack_name, tmpl, - _extract_user_params(params)) + api.extract_user_params(params)) s = parser.Stack(context, stack_name, tmpl, user_params) except KeyError as ex: res = ('A Fn::FindInMap operation referenced ' @@ -260,20 +199,6 @@ class EngineManager(manager.Manager): greenpool.spawn_n(stack.delete) return None - # Helper for list_events. It's here so we can use it in tests. - def parse_event(self, event): - s = event.stack - return {'EventId': event.id, - 'StackId': event.stack_id, - 'StackName': s.name, - 'Timestamp': heat_utils.strtime(event.created_at), - 'LogicalResourceId': event.logical_resource_id, - 'PhysicalResourceId': event.physical_resource_id, - 'ResourceType': event.resource_type, - 'ResourceStatusReason': event.resource_status_reason, - 'ResourceProperties': event.resource_properties, - 'ResourceStatus': event.name} - def list_events(self, context, stack_name, params): """ The list_events method lists all events associated with a given stack. @@ -293,7 +218,7 @@ class EngineManager(manager.Manager): else: events = db_api.event_get_all_by_user(context) - return {'events': [self.parse_event(e) for e in events]} + return {'events': [api.format_event(e) for e in events]} def event_create(self, context, event): @@ -340,7 +265,8 @@ class EngineManager(manager.Manager): if resource.id is None: raise AttributeError('Resource not created') - return format_stack_resource(stack[resource_name]) + return api.format_stack_resource(stack[resource_name], + api.KEYS_RESOURCE_DETAIL) def describe_stack_resources(self, context, stack_name, physical_resource_id, logical_resource_id): @@ -360,18 +286,15 @@ class EngineManager(manager.Manager): raise AttributeError("The specified stack doesn't exist") stack = parser.Stack.load(context, s.id) - resources = [] - for resource in stack: - if logical_resource_id and resource.name != logical_resource_id: - continue - formatted = format_stack_resource(resource) - # this API call uses Timestamp instead of LastUpdatedTimestamp - formatted['Timestamp'] = formatted['LastUpdatedTimestamp'] - del formatted['LastUpdatedTimestamp'] - del formatted['Metadata'] - resources.append(formatted) - return resources + if logical_resource_id is not None: + name_match = lambda r: r.name == logical_resource_id + else: + name_match = lambda r: True + + return [api.format_stack_resource(resource, api.KEYS_RESOURCE) + for resource in stack if resource.id is not None and + name_match(resource)] def list_stack_resources(self, context, stack_name): auth.authenticate(context) @@ -382,17 +305,8 @@ class EngineManager(manager.Manager): stack = parser.Stack.load(context, s.id) - resources = [] - response_keys = ('ResourceStatus', 'LogicalResourceId', - 'LastUpdatedTimestamp', 'PhysicalResourceId', - 'ResourceType') - for resource in stack: - formatted = format_stack_resource(resource) - for key in formatted.keys(): - if not key in response_keys: - del formatted[key] - resources.append(formatted) - return resources + return [api.format_stack_resource(resource, api.KEYS_RESOURCE_SUMMARY) + for resource in stack if resource.id is not None] def metadata_register_address(self, context, url): config.FLAGS.heat_metadata_server_url = url @@ -510,23 +424,3 @@ class EngineManager(manager.Manager): self.run_rule(None, wr) return [None, wd.data] - - -def format_stack_resource(resource): - """ - Return a representation of the given resource that mathes the API output - expectations. - """ - rs = db_api.resource_get(resource.stack.context, resource.id) - last_updated_time = rs.updated_at or rs.created_at - return { - 'StackId': resource.stack.stack_id(), - 'StackName': resource.stack.name, - 'LogicalResourceId': resource.name, - 'PhysicalResourceId': resource.instance_id or '', - 'ResourceType': resource.t['Type'], - 'LastUpdatedTimestamp': heat_utils.strtime(last_updated_time), - 'Metadata': rs.rsrc_metadata, - 'ResourceStatus': rs.state, - 'ResourceStatusReason': rs.state_description, - } diff --git a/heat/engine/parser.py b/heat/engine/parser.py index e1244a80..555c8144 100644 --- a/heat/engine/parser.py +++ b/heat/engine/parser.py @@ -433,18 +433,12 @@ class Stack(object): db_api.stack_delete(self.context, self.id) def output(self, key): + ''' + Get the value of the specified stack output. + ''' value = self.outputs[key].get('Value', '') return self.resolve_runtime_data(value) - def get_outputs(self): - def output_dict(k): - return {'Description': self.outputs[k].get('Description', - 'No description given'), - 'OutputKey': k, - 'OutputValue': self.output(k)} - - return [output_dict(key) for key in self.outputs] - def restart_resource(self, resource_name): ''' stop resource_name and all that depend on it diff --git a/heat/tests/test_manager.py b/heat/tests/test_engine_api_utils.py similarity index 85% rename from heat/tests/test_manager.py rename to heat/tests/test_engine_api_utils.py index 39382d40..453af34a 100644 --- a/heat/tests/test_manager.py +++ b/heat/tests/test_engine_api_utils.py @@ -17,18 +17,18 @@ import nose import unittest from nose.plugins.attrib import attr -import heat.engine.manager as manager +import heat.engine.api as api -@attr(tag=['unit', 'manager']) +@attr(tag=['unit', 'engine-api']) @attr(speed='fast') -class managerTest(unittest.TestCase): +class EngineApiTest(unittest.TestCase): def test_params_extract(self): p = {'Parameters.member.Foo.ParameterKey': 'foo', 'Parameters.member.Foo.ParameterValue': 'bar', 'Parameters.member.Blarg.ParameterKey': 'blarg', 'Parameters.member.Blarg.ParameterValue': 'wibble'} - params = manager._extract_user_params(p) + params = api.extract_user_params(p) self.assertEqual(len(params), 2) self.assertTrue('foo' in params) self.assertEqual(params['foo'], 'bar') @@ -40,7 +40,7 @@ class managerTest(unittest.TestCase): 'Parameters.member.Foo.Bar.ParameterValue': 'bar', 'Parameters.member.Foo.Baz.ParameterKey': 'blarg', 'Parameters.member.Foo.Baz.ParameterValue': 'wibble'} - params = manager._extract_user_params(p) + params = api.extract_user_params(p) self.assertEqual(len(params), 2) self.assertTrue('foo' in params) self.assertEqual(params['foo'], 'bar') @@ -52,7 +52,7 @@ class managerTest(unittest.TestCase): 'Parameters.member.Foo.Bar.ParameterValue': 'bar', 'Foo.Baz.ParameterKey': 'blarg', 'Foo.Baz.ParameterValue': 'wibble'} - params = manager._extract_user_params(p) + params = api.extract_user_params(p) self.assertEqual(len(params), 1) self.assertTrue('foo' in params) self.assertEqual(params['foo'], 'bar') @@ -60,35 +60,35 @@ class managerTest(unittest.TestCase): def test_params_extract_garbage_prefix(self): p = {'prefixParameters.member.Foo.Bar.ParameterKey': 'foo', 'Parameters.member.Foo.Bar.ParameterValue': 'bar'} - params = manager._extract_user_params(p) + params = api.extract_user_params(p) self.assertFalse(params) def test_params_extract_garbage_suffix(self): p = {'Parameters.member.Foo.Bar.ParameterKeysuffix': 'foo', 'Parameters.member.Foo.Bar.ParameterValue': 'bar'} - params = manager._extract_user_params(p) + params = api.extract_user_params(p) self.assertFalse(params) def test_timeout_extract(self): p = {'TimeoutInMinutes': '5'} - args = manager._extract_args(p) + args = api.extract_args(p) self.assertEqual(args['timeout_in_minutes'], 5) def test_timeout_extract_zero(self): p = {'TimeoutInMinutes': '0'} - args = manager._extract_args(p) + args = api.extract_args(p) self.assertTrue('timeout_in_minutes' not in args) def test_timeout_extract_garbage(self): p = {'TimeoutInMinutes': 'wibble'} - args = manager._extract_args(p) + args = api.extract_args(p) self.assertTrue('timeout_in_minutes' not in args) def test_timeout_extract_none(self): p = {'TimeoutInMinutes': None} - args = manager._extract_args(p) + args = api.extract_args(p) self.assertTrue('timeout_in_minutes' not in args) def test_timeout_extract_not_present(self): - args = manager._extract_args({}) + args = api.extract_args({}) self.assertTrue('timeout_in_minutes' not in args) diff --git a/heat/tests/test_stacks.py b/heat/tests/test_stacks.py index 478eae0b..844bbd8b 100644 --- a/heat/tests/test_stacks.py +++ b/heat/tests/test_stacks.py @@ -33,7 +33,7 @@ from heat.engine import manager from heat.engine import auth -@attr(tag=['unit', 'resource']) +@attr(tag=['unit', 'engine-api', 'resource']) @attr(speed='slow') class stacksTest(unittest.TestCase): def setUp(self): @@ -112,7 +112,10 @@ class stacksTest(unittest.TestCase): self.assertEqual(db_s.status, 'DELETE_COMPLETE') def test_stack_event_list(self): - stack = self.get_wordpress_stack('test_event_list_stack') + ctx = self.create_context('test_event_list_user') + auth.authenticate(ctx).AndReturn(True) + + stack = self.get_wordpress_stack('test_event_list_stack', ctx) self.m.ReplayAll() stack.store() stack.create() @@ -120,9 +123,14 @@ class stacksTest(unittest.TestCase): self.assertNotEqual(stack.resources['WebServer'], None) self.assertTrue(stack.resources['WebServer'].instance_id > 0) - m = manager.EngineManager() - events = db_api.event_get_all_by_stack(None, stack.id) - for ev in [m.parse_event(e) for e in events]: + man = manager.EngineManager() + el = man.list_events(ctx, stack.name, {}) + + self.assertTrue('events' in el) + events = el['events'] + + self.assertEqual(len(events), 2) + for ev in events: self.assertTrue('EventId' in ev) self.assertTrue(ev['EventId'] > 0) @@ -174,12 +182,12 @@ class stacksTest(unittest.TestCase): self.assertTrue(len(sl['stacks']) > 0) for s in sl['stacks']: self.assertTrue('CreationTime' in s) - #self.assertTrue('LastUpdatedTime' in s) + self.assertTrue('LastUpdatedTime' in s) self.assertTrue('StackId' in s) self.assertNotEqual(s['StackId'], None) self.assertTrue('StackName' in s) self.assertTrue('StackStatus' in s) - #self.assertTrue('StackStatusReason' in s) + self.assertTrue('StackStatusReason' in s) self.assertTrue('TemplateDescription' in s) self.assertNotEqual(s['TemplateDescription'].find('WordPress'), -1) @@ -240,7 +248,7 @@ class stacksTest(unittest.TestCase): s = sl['stacks'][0] self.assertTrue('CreationTime' in s) - #self.assertTrue('LastUpdatedTime' in s) + self.assertTrue('LastUpdatedTime' in s) self.assertTrue('StackId' in s) self.assertNotEqual(s['StackId'], None) self.assertTrue('StackName' in s) @@ -265,7 +273,7 @@ class stacksTest(unittest.TestCase): r = man.describe_stack_resource(ctx, 'test_stack_res_desc', 'WebServer') - #self.assertTrue('Description' in r) + self.assertTrue('Description' in r) self.assertTrue('LastUpdatedTimestamp' in r) self.assertTrue('StackId' in r) self.assertNotEqual(r['StackId'], None) @@ -320,7 +328,7 @@ class stacksTest(unittest.TestCase): self.assertEqual(len(resources), 1) r = resources[0] - #self.assertTrue('Description' in r) + self.assertTrue('Description' in r) self.assertTrue('Timestamp' in r) self.assertTrue('StackId' in r) self.assertNotEqual(r['StackId'], None) @@ -400,7 +408,7 @@ class stacksTest(unittest.TestCase): self.assertTrue('LogicalResourceId' in r) self.assertEqual(r['LogicalResourceId'], 'WebServer') self.assertTrue('ResourceStatus' in r) - #self.assertTrue('ResourceStatusReason' in r) + self.assertTrue('ResourceStatusReason' in r) self.assertTrue('ResourceType' in r) def test_stack_resources_list_nonexist_stack(self): -- 2.45.2