From c79d93bfbc8a79617a6d3ef4e36fb5de55217d02 Mon Sep 17 00:00:00 2001 From: Zhongyue Luo Date: Tue, 5 Jun 2012 09:11:44 +0800 Subject: Keystone should use openstack.common.timeutils Implements blueprint use-common-timeutils 1. Edit openstack-common.conf and import keystone/openstack/common/timeutils.py 2. Replace datetime.utcnow with timeutils.utcnow 3. Replace utils.isotime with timeutils.isotime 4. Remove utils.isotime in common/utils.py and datetime related unittest Change-Id: I4f5a63a368fde8787a0dc0a817c940de685b9ca2 --- keystone/common/utils.py | 11 ---- keystone/openstack/common/timeutils.py | 109 +++++++++++++++++++++++++++++++++ keystone/service.py | 3 +- keystone/token/backends/kvs.py | 7 +-- keystone/token/backends/sql.py | 6 +- keystone/token/core.py | 3 +- openstack-common.conf | 2 +- tests/test_backend.py | 4 +- tests/test_backend_memcache.py | 6 +- tests/test_utils.py | 9 --- 10 files changed, 125 insertions(+), 35 deletions(-) create mode 100644 keystone/openstack/common/timeutils.py diff --git a/keystone/common/utils.py b/keystone/common/utils.py index 7f665a59..19749764 100644 --- a/keystone/common/utils.py +++ b/keystone/common/utils.py @@ -39,7 +39,6 @@ config.register_int('crypt_strength', default=40000) LOG = logging.getLogger(__name__) -ISO_TIME_FORMAT = '%Y-%m-%dT%H:%M:%SZ' MAX_PASSWORD_LENGTH = 4096 @@ -232,16 +231,6 @@ def git(*args): return check_output(['git'] + list(args)) -def isotime(dt_obj): - """Format datetime object as ISO compliant string. - - :param dt_obj: datetime.datetime object - :returns: string representation of datetime object - - """ - return dt_obj.strftime(ISO_TIME_FORMAT) - - def unixtime(dt_obj): """Format datetime object as unix timestamp diff --git a/keystone/openstack/common/timeutils.py b/keystone/openstack/common/timeutils.py new file mode 100644 index 00000000..5eeaf70a --- /dev/null +++ b/keystone/openstack/common/timeutils.py @@ -0,0 +1,109 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# 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. + +""" +Time related utilities and helper functions. +""" + +import calendar +import datetime +import time + +import iso8601 + + +TIME_FORMAT = "%Y-%m-%dT%H:%M:%S" +PERFECT_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%f" + + +def isotime(at=None): + """Stringify time in ISO 8601 format""" + if not at: + at = utcnow() + str = at.strftime(TIME_FORMAT) + tz = at.tzinfo.tzname(None) if at.tzinfo else 'UTC' + str += ('Z' if tz == 'UTC' else tz) + return str + + +def parse_isotime(timestr): + """Parse time from ISO 8601 format""" + try: + return iso8601.parse_date(timestr) + except iso8601.ParseError as e: + raise ValueError(e.message) + except TypeError as e: + raise ValueError(e.message) + + +def strtime(at=None, fmt=PERFECT_TIME_FORMAT): + """Returns formatted utcnow.""" + if not at: + at = utcnow() + return at.strftime(fmt) + + +def parse_strtime(timestr, fmt=PERFECT_TIME_FORMAT): + """Turn a formatted time back into a datetime.""" + return datetime.datetime.strptime(timestr, fmt) + + +def normalize_time(timestamp): + """Normalize time in arbitrary timezone to UTC""" + offset = timestamp.utcoffset() + return timestamp.replace(tzinfo=None) - offset if offset else timestamp + + +def is_older_than(before, seconds): + """Return True if before is older than seconds.""" + return utcnow() - before > datetime.timedelta(seconds=seconds) + + +def utcnow_ts(): + """Timestamp version of our utcnow function.""" + return calendar.timegm(utcnow().timetuple()) + + +def utcnow(): + """Overridable version of utils.utcnow.""" + if utcnow.override_time: + return utcnow.override_time + return datetime.datetime.utcnow() + + +utcnow.override_time = None + + +def set_time_override(override_time=datetime.datetime.utcnow()): + """Override utils.utcnow to return a constant time.""" + utcnow.override_time = override_time + + +def advance_time_delta(timedelta): + """Advance overriden time using a datetime.timedelta.""" + assert(not utcnow.override_time is None) + utcnow.override_time += timedelta + + +def advance_time_seconds(seconds): + """Advance overriden time by seconds.""" + advance_time_delta(datetime.timedelta(0, seconds)) + + +def clear_time_override(): + """Remove the overridden time.""" + utcnow.override_time = None diff --git a/keystone/service.py b/keystone/service.py index d7215198..913b8761 100644 --- a/keystone/service.py +++ b/keystone/service.py @@ -24,6 +24,7 @@ from keystone.common import utils from keystone.common import wsgi from keystone import exception from keystone import identity +from keystone.openstack.common import timeutils from keystone import policy from keystone import token @@ -451,7 +452,7 @@ class TokenController(wsgi.Application): metadata_ref = token_ref['metadata'] expires = token_ref['expires'] if expires is not None: - expires = utils.isotime(expires) + expires = timeutils.isotime(expires) o = {'access': {'token': {'id': token_ref['id'], 'expires': expires, }, diff --git a/keystone/token/backends/kvs.py b/keystone/token/backends/kvs.py index a61be955..442bd4b8 100644 --- a/keystone/token/backends/kvs.py +++ b/keystone/token/backends/kvs.py @@ -15,10 +15,10 @@ # under the License. import copy -import datetime from keystone.common import kvs from keystone import exception +from keystone.openstack.common import timeutils from keystone import token @@ -29,8 +29,7 @@ class Token(kvs.Base, token.Driver): token = self.db.get('token-%s' % token_id) except exception.NotFound: raise exception.TokenNotFound(token_id=token_id) - if (token['expires'] is None - or token['expires'] > datetime.datetime.utcnow()): + if token['expires'] is None or token['expires'] > timeutils.utcnow(): return token else: raise exception.TokenNotFound(token_id=token_id) @@ -50,7 +49,7 @@ class Token(kvs.Base, token.Driver): def list_tokens(self, user_id): tokens = [] - now = datetime.datetime.utcnow() + now = timeutils.utcnow() for token, user_ref in self.db.items(): if not token.startswith('token-'): continue diff --git a/keystone/token/backends/sql.py b/keystone/token/backends/sql.py index 5ba5e3a9..59cc47ca 100644 --- a/keystone/token/backends/sql.py +++ b/keystone/token/backends/sql.py @@ -15,10 +15,10 @@ # under the License. import copy -import datetime from keystone.common import sql from keystone import exception +from keystone.openstack.common import timeutils from keystone import token @@ -50,7 +50,7 @@ class Token(sql.Base, token.Driver): def get_token(self, token_id): session = self.get_session() token_ref = session.query(TokenModel).filter_by(id=token_id).first() - now = datetime.datetime.utcnow() + now = timeutils.utcnow() if token_ref and (not token_ref.expires or now < token_ref.expires): return token_ref.to_dict() else: @@ -80,7 +80,7 @@ class Token(sql.Base, token.Driver): def list_tokens(self, user_id): session = self.get_session() tokens = [] - now = datetime.datetime.utcnow() + now = timeutils.utcnow() for token_ref in session.query(TokenModel)\ .filter(TokenModel.expires > now): token_ref_dict = token_ref.to_dict() diff --git a/keystone/token/core.py b/keystone/token/core.py index 0aa2ce41..aff59fba 100644 --- a/keystone/token/core.py +++ b/keystone/token/core.py @@ -21,6 +21,7 @@ import datetime from keystone.common import manager from keystone import config from keystone import exception +from keystone.openstack.common import timeutils CONF = config.CONF @@ -104,4 +105,4 @@ class Driver(object): """ expire_delta = datetime.timedelta(seconds=CONF.token.expiration) - return datetime.datetime.utcnow() + expire_delta + return timeutils.utcnow() + expire_delta diff --git a/openstack-common.conf b/openstack-common.conf index b96d8123..6042cc6c 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -1,7 +1,7 @@ [DEFAULT] # The list of modules to copy from openstack-common -modules=cfg,importutils,iniparser,jsonutils,setup +modules=cfg,importutils,iniparser,jsonutils,setup,timeutils # The base module to hold the copy of openstack.common base=keystone diff --git a/tests/test_backend.py b/tests/test_backend.py index 85cb4c31..83aabfda 100644 --- a/tests/test_backend.py +++ b/tests/test_backend.py @@ -18,6 +18,7 @@ import datetime import uuid from keystone import exception +from keystone.openstack.common import timeutils class IdentityTests(object): @@ -553,8 +554,7 @@ class TokenTests(object): def test_expired_token(self): token_id = uuid.uuid4().hex - expire_time = datetime.datetime.utcnow() - datetime.timedelta( - minutes=1) + expire_time = timeutils.utcnow() - datetime.timedelta(minutes=1) data = {'id': token_id, 'a': 'b', 'expires': expire_time} data_ref = self.token_api.create_token(token_id, data) self.assertDictEqual(data_ref, data) diff --git a/tests/test_backend_memcache.py b/tests/test_backend_memcache.py index 2c07580b..f18cc9ca 100644 --- a/tests/test_backend_memcache.py +++ b/tests/test_backend_memcache.py @@ -14,13 +14,13 @@ # License for the specific language governing permissions and limitations # under the License. -import datetime -import time import uuid import memcache +from keystone.common import utils from keystone import exception +from keystone.openstack.common import timeutils from keystone import test from keystone.token.backends import memcache as token_memcache @@ -42,7 +42,7 @@ class MemcacheClient(object): """Retrieves the value for a key or None.""" self.check_key(key) obj = self.cache.get(key) - now = time.mktime(datetime.datetime.utcnow().utctimetuple()) + now = utils.unixtime(timeutils.utcnow()) if obj and (obj[1] == 0 or obj[1] > now): return obj[0] else: diff --git a/tests/test_utils.py b/tests/test_utils.py index 52efafcb..0c9c7757 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -29,8 +29,6 @@ # License for the specific language governing permissions and limitations # under the License. -import datetime - from keystone import test from keystone.common import utils @@ -60,13 +58,6 @@ class UtilsTestCase(test.TestCase): self.assertTrue(utils.check_password(password, hashed)) self.assertFalse(utils.check_password(wrong, hashed)) - def test_isotime(self): - dt = datetime.datetime(year=1987, month=10, day=13, - hour=1, minute=2, second=3) - output = utils.isotime(dt) - expected = '1987-10-13T01:02:03Z' - self.assertEqual(output, expected) - def test_auth_str_equal(self): self.assertTrue(utils.auth_str_equal('abc123', 'abc123')) self.assertFalse(utils.auth_str_equal('a', 'aaaaa')) -- cgit