Include timezone in timestamp fields

The Neutron 'created_at'/'updated_at' fields on API resources
were inconsistent with other OpenStack projects because we did
not include timezone information. This patch addressed that
problem by adding the zulu time indicator onto the end of the
fields.

Because this could break clients expecting no timezone, this patch
also eliminates the 'timestamp_core' and 'timestamp_ext' extensions
and consolidates them into a new 'timestamp' extension. This makes
the change discoverable via the API.

This is assuming the current API development paradigm where
extensions can come and go depending on the deployment and the client
is expected to handle this by checking the loaded extensions.
Once we decide extensions are permanent, this type of change will
no longer be possible.

Even though this is being proposed late in the cycle, it is better
to get this change in before the release where we expose even more
resources with incorrectly formatted timestamps.

APIImpact
Closes-Bug: #1561200
Change-Id: I2ee2ed4c713d88345adc55b022feb95653eec663
This commit is contained in:
Kevin Benton 2016-09-09 06:20:40 -07:00
parent 465d22180e
commit 424a633fd9
10 changed files with 48 additions and 91 deletions

View File

@ -29,7 +29,7 @@ TIMESTAMP_BODY = {
}
class Timestamp_core(extensions.ExtensionDescriptor):
class Timestamp(extensions.ExtensionDescriptor):
"""Extension class supporting timestamp.
This class is used by neutron's extension framework for adding timestamp
@ -38,21 +38,20 @@ class Timestamp_core(extensions.ExtensionDescriptor):
@classmethod
def get_name(cls):
return "Time Stamp Fields addition for core resources"
return "Resource timestamps"
@classmethod
def get_alias(cls):
return "timestamp_core"
return "standard-attr-timestamp"
@classmethod
def get_description(cls):
return ("This extension can be used for recording "
"create/update timestamps for core resources "
"like port/subnet/network/subnetpools.")
return ("Adds created_at and updated_at fields to all Neutron "
"resources that have Neutron standard attributes.")
@classmethod
def get_updated(cls):
return "2016-03-01T10:00:00-00:00"
return "2016-09-12T10:00:00-00:00"
def get_extended_resources(self, version):
if version != "2.0":

View File

@ -1,47 +0,0 @@
# Copyright 2016 HuaWei Technologies.
#
# 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.
from neutron.api import extensions
class Timestamp_ext(extensions.ExtensionDescriptor):
"""Extension class supporting timestamp.
This class is used by neutron's extension framework for adding timestamp
to neutron extension resources.
"""
@classmethod
def get_name(cls):
return "Standardattr Extension Timestamps"
@classmethod
def get_alias(cls):
return "timestamp_ext"
@classmethod
def get_description(cls):
return ("This extension adds create/update timestamps for all "
"standard neutron resources not included by the "
"'timestamp_core' extension.")
@classmethod
def get_updated(cls):
return "2016-05-05T10:00:00-00:00"
def get_extended_resources(self, version):
# NOTE(kevinbenton): this extension is basically a no-op because
# the timestamp_core extension already defines all of the resources
# now.
return {}

View File

@ -42,7 +42,7 @@ EXT_TO_SERVICE_MAPPING = {
DEFAULT_SERVICE_PLUGINS = {
'auto_allocate': 'auto-allocated-topology',
'tag': 'tag',
'timestamp_core': 'timestamp_core',
'timestamp': 'timestamp',
'network_ip_availability': 'network-ip-availability',
'flavors': 'flavors',
'revisions': 'revisions',

View File

@ -12,9 +12,6 @@
# License for the specific language governing permissions and limitations
# under the License.
import datetime
import time
from neutron_lib import exceptions as n_exc
from oslo_log import log
from oslo_utils import timeutils
@ -46,15 +43,11 @@ class TimeStamp_db_mixin(object):
return query
data = filters[CHANGED_SINCE][0]
try:
# this block checks queried timestamp format.
datetime.datetime.fromtimestamp(time.mktime(
time.strptime(data,
self.ISO8601_TIME_FORMAT)))
changed_since_string = timeutils.parse_isotime(data)
except Exception:
msg = _LW("The input %s must be in the "
"following format: YYYY-MM-DDTHH:MM:SS") % CHANGED_SINCE
"following format: YYYY-MM-DDTHH:MM:SSZ") % CHANGED_SINCE
raise n_exc.InvalidInput(error_message=msg)
changed_since_string = timeutils.parse_isotime(data)
changed_since = (timeutils.
normalize_time(changed_since_string))
target_model_class = list(query._mapper_adapter_map.keys())[0]
@ -94,9 +87,9 @@ class TimeStamp_db_mixin(object):
def _format_timestamp(self, resource_db, result):
result['created_at'] = (resource_db.created_at.
strftime(self.ISO8601_TIME_FORMAT))
strftime(self.ISO8601_TIME_FORMAT)) + 'Z'
result['updated_at'] = (resource_db.updated_at.
strftime(self.ISO8601_TIME_FORMAT))
strftime(self.ISO8601_TIME_FORMAT)) + 'Z'
def extend_resource_dict_timestamp(self, plugin_obj,
resource_res, resource_db):

View File

@ -24,7 +24,7 @@ class TimeStampPlugin(service_base.ServicePluginBase,
ts_db.TimeStamp_db_mixin):
"""Implements Neutron Timestamp Service plugin."""
supported_extension_aliases = ['timestamp_core', 'timestamp_ext']
supported_extension_aliases = ['standard-attr-timestamp']
def __init__(self):
super(TimeStampPlugin, self).__init__()
@ -42,7 +42,7 @@ class TimeStampPlugin(service_base.ServicePluginBase,
@classmethod
def get_plugin_type(cls):
return 'timestamp_core'
return 'timestamp'
def get_plugin_description(self):
return "Neutron core resources timestamp addition support"
return "Adds timestamps to Neutron resources with standard attributes"

View File

@ -34,10 +34,9 @@ NETWORK_API_EXTENSIONS="
sorting, \
standard-attr-description, \
standard-attr-revisions, \
standard-attr-timestamp, \
subnet_allocation, \
tag, \
timestamp_core, \
timestamp_ext, \
trunk, \
trunk-details"
NETWORK_API_EXTENSIONS="$(echo $NETWORK_API_EXTENSIONS | tr -d ' ')"

View File

@ -34,7 +34,7 @@ class TestTimeStamp(base.BaseAdminNetworkTest):
larger_prefix = '10.11.0.0/16'
@classmethod
@test.requires_ext(extension="timestamp_core", service="network")
@test.requires_ext(extension="standard-attr-timestamp", service="network")
def skip_checks(cls):
super(TestTimeStamp, cls).skip_checks()
@ -186,8 +186,9 @@ class TestTimeStampWithL3(base_routers.BaseRouterTest):
def skip_checks(cls):
super(TestTimeStampWithL3, cls).skip_checks()
if not test.is_extension_enabled('timestamp_ext', 'network'):
raise cls.skipException("timestamp_ext extension not enabled")
if not test.is_extension_enabled('standard-attr-timestamp', 'network'):
raise cls.skipException("standard-attr-timestamp extension not "
"enabled")
@classmethod
def resource_setup(cls):
@ -260,8 +261,9 @@ class TestTimeStampWithSecurityGroup(base_security_groups.BaseSecGroupTest):
def skip_checks(cls):
super(TestTimeStampWithSecurityGroup, cls).skip_checks()
if not test.is_extension_enabled('timestamp_ext', 'network'):
raise cls.skipException("timestamp_ext extension not enabled")
if not test.is_extension_enabled('standard-attr-timestamp', 'network'):
raise cls.skipException("standard-attr-timestamp extension not "
"enabled")
@classmethod
def resource_setup(cls):

View File

@ -16,12 +16,13 @@ import datetime
import six
import mock
from oslo_utils import timeutils
from neutron import context
from neutron.db import db_base_plugin_v2
from neutron.db import models_v2
from neutron.db import tag_db as tag_module
from neutron.extensions import timestamp_core as timestamp
from neutron.extensions import timestamp
from neutron import manager
from neutron.tests.unit.db import test_db_base_plugin_v2
@ -38,7 +39,7 @@ class TimeStampExtensionManager(object):
return []
def get_extended_resources(self, version):
return timestamp.Timestamp_core().get_extended_resources(version)
return timestamp.Timestamp().get_extended_resources(version)
class TimeStampTestPlugin(db_base_plugin_v2.NeutronDbPluginV2):
@ -47,7 +48,7 @@ class TimeStampTestPlugin(db_base_plugin_v2.NeutronDbPluginV2):
class TimeStampChangedsinceTestCase(test_db_base_plugin_v2.
NeutronDbPluginV2TestCase):
plugin = ('neutron.tests.unit.extensions.test_timestamp_core.' +
plugin = ('neutron.tests.unit.extensions.test_timestamp.' +
'TimeStampTestPlugin')
def setUp(self):
@ -55,14 +56,14 @@ class TimeStampChangedsinceTestCase(test_db_base_plugin_v2.
super(TimeStampChangedsinceTestCase, self).setUp(plugin=self.plugin,
ext_mgr=ext_mgr)
self.addCleanup(manager.NeutronManager.
get_service_plugins()['timestamp_core'].
get_service_plugins()['timestamp'].
unregister_db_events)
self.addCleanup(manager.NeutronManager.clear_instance)
def setup_coreplugin(self, core_plugin=None):
super(TimeStampChangedsinceTestCase, self).setup_coreplugin(
self.plugin)
self.patched_default_svc_plugins.return_value = ['timestamp_core']
self.patched_default_svc_plugins.return_value = ['timestamp']
def _get_resp_with_changed_since(self, resource_type, changed_since):
query_params = 'changed_since=%s' % changed_since
@ -73,18 +74,12 @@ class TimeStampChangedsinceTestCase(test_db_base_plugin_v2.
def _return_by_timedelay(self, resource, timedelay):
resource_type = six.next(six.iterkeys(resource))
try:
time_create = datetime.datetime.strptime(
resource[resource_type]['updated_at'],
'%Y-%m-%dT%H:%M:%S')
except Exception:
time_create = datetime.datetime.strptime(
resource[resource_type]['updated_at'],
'%Y-%m-%d %H:%M:%S.%f')
time_create = timeutils.parse_isotime(
resource[resource_type]['updated_at'])
time_before = datetime.timedelta(seconds=timedelay)
addedtime_string = (datetime.datetime.
strftime(time_create + time_before,
'%Y-%m-%dT%H:%M:%S'))
'%Y-%m-%dT%H:%M:%S')) + 'Z'
return self._get_resp_with_changed_since(resource_type,
addedtime_string)

View File

@ -0,0 +1,16 @@
---
prelude: >
- The created_at and updated_at fields available on Neutron
resources now include a timezone indicator at the end.
Because this is a change in format, the old 'timestamp_core'
extension has been removed and replaced with a 'timestamp'
extension.
upgrade:
- The 'timestamp_core' extension has been removed and replaced
with the 'standard-attr-timestamp' extension. Objects will still
have timestamps in the 'created_at' and 'updated_at' fields, but
they will have the timestamp appended to the end of them
to be consistent with other OpenStack projects.
fixes:
- Bug 1561200 has been fixed by including the timezone with
Neutron 'created_at' and 'updated_at' fields.

View File

@ -77,7 +77,7 @@ neutron.service_plugins =
segments = neutron.services.segments.plugin:Plugin
network_ip_availability = neutron.services.network_ip_availability.plugin:NetworkIPAvailabilityPlugin
revisions = neutron.services.revisions.revision_plugin:RevisionPlugin
timestamp_core = neutron.services.timestamp.timestamp_plugin:TimeStampPlugin
timestamp = neutron.services.timestamp.timestamp_plugin:TimeStampPlugin
trunk = neutron.services.trunk.plugin:TrunkPlugin
neutron.qos.notification_drivers =
message_queue = neutron.services.qos.notification_drivers.message_queue:RpcQosServiceNotificationDriver