Restore timezone information in API response

When oslo_utils isotime was deprecated, we switch to isoformat and lost
the timezone information. This switches back to a function which put the
Z suffix indicating UTC. It's a potential backward incompatible change,
but presumably better than sending incomplete information.

Change-Id: I78ab2dd025f1d00d4afaf72070e481aa8e09c5e8
Closes-Bug: #1607562
This commit is contained in:
Thomas Herve 2016-09-07 16:49:40 +02:00
parent 49226daee0
commit f655726560
4 changed files with 58 additions and 36 deletions

View File

@ -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')

View File

@ -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

View File

@ -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))

View File

@ -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'})