diff --git a/heat/common/timeutils.py b/heat/common/timeutils.py index 9377c84503..9e4d0b8728 100644 --- a/heat/common/timeutils.py +++ b/heat/common/timeutils.py @@ -85,3 +85,13 @@ def round_to_seconds(dt): rounding = 1 return dt + datetime.timedelta(0, rounding, -dt.microsecond) + + +def isotime(at): + """Stringify UTC time in ISO 8601 format. + + :param at: Timestamp in UTC to format. + """ + if at is None: + return None + return at.strftime('%Y-%m-%dT%H:%M:%SZ') diff --git a/heat/engine/api.py b/heat/engine/api.py index 9204a3d31e..c2811c79de 100644 --- a/heat/engine/api.py +++ b/heat/engine/api.py @@ -21,6 +21,7 @@ from heat.common.i18n import _ from heat.common.i18n import _LE from heat.common import param_utils from heat.common import template_format +from heat.common import timeutils as heat_timeutils from heat.engine import constraints as constr from heat.rpc import api as rpc_api @@ -210,13 +211,14 @@ def format_stack(stack, preview=False, resolve_outputs=True): Return a representation of the given stack that matches the API output expectations. """ - updated_time = stack.updated_time and stack.updated_time.isoformat() - created_time = stack.created_time or timeutils.utcnow() - deleted_time = stack.deleted_time and stack.deleted_time.isoformat() + updated_time = heat_timeutils.isotime(stack.updated_time) + created_time = heat_timeutils.isotime(stack.created_time or + timeutils.utcnow()) + deleted_time = heat_timeutils.isotime(stack.deleted_time) info = { rpc_api.STACK_NAME: stack.name, rpc_api.STACK_ID: dict(stack.identifier()), - rpc_api.STACK_CREATION_TIME: created_time.isoformat(), + rpc_api.STACK_CREATION_TIME: created_time, rpc_api.STACK_UPDATED_TIME: updated_time, rpc_api.STACK_DELETION_TIME: deleted_time, rpc_api.STACK_NOTIFICATION_TOPICS: [], # TODO(therve) Not implemented @@ -255,9 +257,9 @@ def format_stack_db_object(stack): Given a stack versioned db object, return a representation of the given stack for a stack listing. """ - updated_time = stack.updated_at and stack.updated_at.isoformat() - created_time = stack.created_at - deleted_time = stack.deleted_at and stack.deleted_at.isoformat() + updated_time = heat_timeutils.isotime(stack.updated_at) + created_time = heat_timeutils.isotime(stack.created_at) + deleted_time = heat_timeutils.isotime(stack.deleted_at) tags = None if stack.tags: @@ -269,7 +271,7 @@ def format_stack_db_object(stack): rpc_api.STACK_ACTION: stack.action, rpc_api.STACK_STATUS: stack.status, rpc_api.STACK_STATUS_DATA: stack.status_reason, - rpc_api.STACK_CREATION_TIME: created_time.isoformat(), + rpc_api.STACK_CREATION_TIME: created_time, rpc_api.STACK_UPDATED_TIME: updated_time, rpc_api.STACK_DELETION_TIME: deleted_time, rpc_api.STACK_OWNER: stack.username, @@ -330,9 +332,9 @@ def format_stack_resource(resource, detail=True, with_props=False, Return a representation of the given resource that matches the API output expectations. """ - created_time = resource.created_time and resource.created_time.isoformat() - last_updated_time = (resource.updated_time and - resource.updated_time.isoformat()) or created_time + created_time = heat_timeutils.isotime(resource.created_time) + last_updated_time = heat_timeutils.isotime( + resource.updated_time or resource.created_time) res = { rpc_api.RES_UPDATED_TIME: last_updated_time, rpc_api.RES_CREATION_TIME: created_time, @@ -386,7 +388,7 @@ def format_event(event, stack_identifier, root_stack_identifier=None): rpc_api.EVENT_ID: dict(event.identifier(stack_identifier)), rpc_api.EVENT_STACK_ID: dict(stack_identifier), rpc_api.EVENT_STACK_NAME: stack_identifier.stack_name, - rpc_api.EVENT_TIMESTAMP: event.created_at.isoformat(), + rpc_api.EVENT_TIMESTAMP: heat_timeutils.isotime(event.created_at), rpc_api.EVENT_RES_NAME: event.resource_name, rpc_api.EVENT_RES_PHYSICAL_ID: event.physical_resource_id, rpc_api.EVENT_RES_ACTION: event.resource_action, @@ -411,7 +413,7 @@ def format_notification_body(stack): else: state = 'Unknown' - updated_at = stack.updated_time and stack.updated_time.isoformat() + updated_at = heat_timeutils.isotime(stack.updated_time) result = { rpc_api.NOTIFY_TENANT_ID: stack.context.tenant_id, rpc_api.NOTIFY_USER_ID: stack.context.username, @@ -423,7 +425,7 @@ def format_notification_body(stack): rpc_api.NOTIFY_STACK_NAME: stack.name, rpc_api.NOTIFY_STATE: state, rpc_api.NOTIFY_STATE_REASON: stack.status_reason, - rpc_api.NOTIFY_CREATE_AT: stack.created_time.isoformat(), + rpc_api.NOTIFY_CREATE_AT: heat_timeutils.isotime(stack.created_time), rpc_api.NOTIFY_DESCRIPTION: stack.t[stack.t.DESCRIPTION], rpc_api.NOTIFY_TAGS: stack.tags, rpc_api.NOTIFY_UPDATE_AT: updated_at @@ -433,14 +435,15 @@ def format_notification_body(stack): def format_watch(watch): - updated_at = watch.updated_at or timeutils.utcnow() + updated_time = heat_timeutils.isotime(watch.updated_at or + timeutils.utcnow()) result = { rpc_api.WATCH_ACTIONS_ENABLED: watch.rule.get( rpc_api.RULE_ACTIONS_ENABLED), rpc_api.WATCH_ALARM_ACTIONS: watch.rule.get( rpc_api.RULE_ALARM_ACTIONS), rpc_api.WATCH_TOPIC: watch.rule.get(rpc_api.RULE_TOPIC), - rpc_api.WATCH_UPDATED_TIME: updated_at.isoformat(), + rpc_api.WATCH_UPDATED_TIME: updated_time, rpc_api.WATCH_DESCRIPTION: watch.rule.get(rpc_api.RULE_DESCRIPTION), rpc_api.WATCH_NAME: watch.name, rpc_api.WATCH_COMPARISON: watch.rule.get(rpc_api.RULE_COMPARISON), @@ -456,8 +459,9 @@ def format_watch(watch): rpc_api.WATCH_STATE_REASON: watch.rule.get(rpc_api.RULE_STATE_REASON), rpc_api.WATCH_STATE_REASON_DATA: watch.rule.get(rpc_api.RULE_STATE_REASON_DATA), - rpc_api.WATCH_STATE_UPDATED_TIME: watch.rule.get( - rpc_api.RULE_STATE_UPDATED_TIME, timeutils.utcnow()).isoformat(), + rpc_api.WATCH_STATE_UPDATED_TIME: heat_timeutils.isotime( + watch.rule.get(rpc_api.RULE_STATE_UPDATED_TIME, + timeutils.utcnow())), rpc_api.WATCH_STATE_VALUE: watch.state, rpc_api.WATCH_STATISTIC: watch.rule.get(rpc_api.RULE_STATISTIC), rpc_api.WATCH_THRESHOLD: watch.rule.get(rpc_api.RULE_THRESHOLD), @@ -484,7 +488,7 @@ def format_watch_data(wd, rule_names): result = { rpc_api.WATCH_DATA_ALARM: rule_names.get(wd.watch_rule_id), rpc_api.WATCH_DATA_METRIC: metric_name, - rpc_api.WATCH_DATA_TIME: wd.created_at.isoformat(), + rpc_api.WATCH_DATA_TIME: heat_timeutils.isotime(wd.created_at), rpc_api.WATCH_DATA_NAMESPACE: namespace, rpc_api.WATCH_DATA: metric_data } @@ -568,7 +572,8 @@ def format_software_config(sc, detail=True): rpc_api.SOFTWARE_CONFIG_ID: sc.id, rpc_api.SOFTWARE_CONFIG_NAME: sc.name, rpc_api.SOFTWARE_CONFIG_GROUP: sc.group, - rpc_api.SOFTWARE_CONFIG_CREATION_TIME: sc.created_at.isoformat() + rpc_api.SOFTWARE_CONFIG_CREATION_TIME: + heat_timeutils.isotime(sc.created_at) } if detail: result[rpc_api.SOFTWARE_CONFIG_CONFIG] = sc.config['config'] @@ -590,11 +595,12 @@ def format_software_deployment(sd): rpc_api.SOFTWARE_DEPLOYMENT_STATUS: sd.status, rpc_api.SOFTWARE_DEPLOYMENT_STATUS_REASON: sd.status_reason, rpc_api.SOFTWARE_DEPLOYMENT_CONFIG_ID: sd.config.id, - rpc_api.SOFTWARE_DEPLOYMENT_CREATION_TIME: sd.created_at.isoformat(), + rpc_api.SOFTWARE_DEPLOYMENT_CREATION_TIME: + heat_timeutils.isotime(sd.created_at), } if sd.updated_at: result[rpc_api.SOFTWARE_DEPLOYMENT_UPDATED_TIME] = ( - sd.updated_at.isoformat()) + heat_timeutils.isotime(sd.updated_at)) return result @@ -607,7 +613,8 @@ def format_snapshot(snapshot): rpc_api.SNAPSHOT_STATUS: snapshot.status, rpc_api.SNAPSHOT_STATUS_REASON: snapshot.status_reason, rpc_api.SNAPSHOT_DATA: snapshot.data, - rpc_api.SNAPSHOT_CREATION_TIME: snapshot.created_at.isoformat(), + rpc_api.SNAPSHOT_CREATION_TIME: + heat_timeutils.isotime(snapshot.created_at), } return result diff --git a/heat/tests/test_engine_api_utils.py b/heat/tests/test_engine_api_utils.py index 3c67b809e5..86e7689710 100644 --- a/heat/tests/test_engine_api_utils.py +++ b/heat/tests/test_engine_api_utils.py @@ -21,6 +21,7 @@ import six from heat.common import exception from heat.common import template_format +from heat.common import timeutils as heat_timeutils from heat.engine import api from heat.engine import event from heat.engine import parameters @@ -94,9 +95,9 @@ class FormatTest(common.HeatTestCase): formatted = api.format_stack_resource(res, False) self.assertEqual(resource_keys, set(formatted.keys())) - self.assertEqual(self.stack.created_time.isoformat(), + self.assertEqual(heat_timeutils.isotime(self.stack.created_time), formatted[rpc_api.RES_CREATION_TIME]) - self.assertEqual(self.stack.updated_time.isoformat(), + self.assertEqual(heat_timeutils.isotime(self.stack.updated_time), formatted[rpc_api.RES_UPDATED_TIME]) self.assertEqual(res.INIT, formatted[rpc_api.RES_ACTION]) @@ -347,7 +348,7 @@ class FormatTest(common.HeatTestCase): 'stacks/test_stack/' + self.stack.id) expected_stack_info = { 'capabilities': [], - 'creation_time': '1970-01-01T00:00:00', + 'creation_time': '1970-01-01T00:00:00Z', 'deletion_time': None, 'description': 'No description', 'disable_rollback': True, @@ -387,7 +388,7 @@ class FormatTest(common.HeatTestCase): self.stack.updated_time = datetime(1970, 1, 1) info = api.format_stack(self.stack) - self.assertEqual('1970-01-01T00:00:00', info['updated_time']) + self.assertEqual('1970-01-01T00:00:00Z', info['updated_time']) @mock.patch.object(api, 'format_stack_outputs') def test_format_stack_adds_outputs(self, mock_fmt_outputs): @@ -1053,7 +1054,8 @@ class FormatSoftwareConfigDeploymentTest(common.HeatTestCase): self.assertEqual([{'name': 'result'}], result['outputs']) self.assertEqual([{'name': 'result'}], result['outputs']) self.assertEqual({}, result['options']) - self.assertEqual(self.now.isoformat(), result['creation_time']) + self.assertEqual(heat_timeutils.isotime(self.now), + result['creation_time']) def test_format_software_config_none(self): self.assertIsNone(api.format_software_config(None)) @@ -1070,8 +1072,10 @@ class FormatSoftwareConfigDeploymentTest(common.HeatTestCase): self.assertEqual(deployment.action, result['action']) self.assertEqual(deployment.status, result['status']) self.assertEqual(deployment.status_reason, result['status_reason']) - self.assertEqual(self.now.isoformat(), result['creation_time']) - self.assertEqual(self.now.isoformat(), result['updated_time']) + self.assertEqual(heat_timeutils.isotime(self.now), + result['creation_time']) + self.assertEqual(heat_timeutils.isotime(self.now), + result['updated_time']) def test_format_software_deployment_none(self): self.assertIsNone(api.format_software_deployment(None)) diff --git a/heat/tests/test_notifications.py b/heat/tests/test_notifications.py index a2f68a46f2..f909774ff0 100644 --- a/heat/tests/test_notifications.py +++ b/heat/tests/test_notifications.py @@ -14,6 +14,7 @@ import mock from oslo_utils import timeutils +from heat.common import timeutils as heat_timeutils from heat.engine import notification from heat.tests import common from heat.tests import utils @@ -54,11 +55,11 @@ class StackTest(common.HeatTestCase): 'stack_identity': 'hay-are-en', 'stack_name': 'fred', 'tenant_id': 'test_tenant_id', - 'create_at': created_time.isoformat(), + 'create_at': heat_timeutils.isotime(created_time), 'state': 'x_f', 'description': 'for test', 'tags': ['tag1', 'tag2'], - 'updated_at': updated_time.isoformat()}) + 'updated_at': heat_timeutils.isotime(updated_time)}) class AutoScaleTest(common.HeatTestCase): @@ -106,10 +107,10 @@ class AutoScaleTest(common.HeatTestCase): 'stack_identity': 'hay-are-en', 'stack_name': 'fred', 'tenant_id': 'test_tenant_id', - 'create_at': stack.created_time.isoformat(), + 'create_at': heat_timeutils.isotime(stack.created_time), 'description': 'for test', 'tags': ['tag1', 'tag2'], - 'updated_at': stack.updated_time.isoformat(), + 'updated_at': heat_timeutils.isotime(stack.updated_time), 'state': 'x_f', 'adjustment_type': 'y', 'groupname': 'c', 'capacity': '5', 'message': 'fred', 'adjustment': 'x'}) @@ -132,10 +133,10 @@ class AutoScaleTest(common.HeatTestCase): 'stack_identity': 'hay-are-en', 'stack_name': 'fred', 'tenant_id': 'test_tenant_id', - 'create_at': stack.created_time.isoformat(), + 'create_at': heat_timeutils.isotime(stack.created_time), 'description': 'for test', 'tags': ['tag1', 'tag2'], - 'updated_at': stack.updated_time.isoformat(), + 'updated_at': heat_timeutils.isotime(stack.updated_time), 'state': 'x_f', 'adjustment_type': 'y', 'groupname': 'c', 'capacity': '5', 'message': 'error', 'adjustment': 'x'})