diff options
author | Amrith Kumar <amrith@tesora.com> | 2016-06-07 08:15:24 -0400 |
---|---|---|
committer | Amrith Kumar <amrith.kumar@gmail.com> | 2017-06-09 16:22:11 +0000 |
commit | 109ff949511c0185ab1dd0beffeb03a8a3332005 (patch) | |
tree | 75adba86b2c07ee9da54c7ea6e8896358f6f2952 | |
parent | fb870b306ec044e8100fe67886d20be84f5807ef (diff) | |
download | trove-109ff949511c0185ab1dd0beffeb03a8a3332005.tar.gz |
Handle isotime deprecation in oslo_utils.timeutils
oslo_utils.timeutils is deprecating isotime(). In reality they are
deprecating some other things as well but Trove doesn't (currently)
use any of those things.
Much has been written on the subject of this deprecation. I think the
proposal to merely replace isotime with datetime.datetime.isoformat()
is a little simplistic. Well intentioned, but nonetheless I believe
that it is simplistic.
The primary issue I could find with oslo_utils.timeutils.isotime() was
the fact that it was naive. I think it could well have been fixed in
oslo_utils but for whatever reason(s) oslo decided not to want to go
that route.
The primary challenge from Trove's perspective is that I want to
respect the existing API contract while at the same time get an
implementation of time handling that is not identical in its flaws
with oslo_utils.timeutils.isotime().
This change set attempts to address that by making
trove.common.timeutils.isotime() that is aware. It also implements a
utcnow_aware() function that is aware.
ISO 8601 allows for four representations of timezone and those are
<time>Z
<time>[+-]hh:mm
<time>[+-]hhmm
<time>[+-]hh
Trove conventionally used the first one, even if the time wasn't
really a UTC time. That's one of the things being fixed here.
In review cp16net asked whether this change removes the 'Z' at the end
of time strings generated by the isotime() function. The answer is
NO. The new isotime() function performs identical to the old and now
deprecated function in oslo_utils.timeutils for UTC (Z) times.
There was a utcnow() function in trove.common.utils which just wrapped
datetime.datetime.utcnow(). That has been moved now to
trove.common.timeutils with the other new time related functions.
There were a couple of places in Trove where code was using
datetime.now() which was not ideal. Those have been corrected now as
well.
Unit tests have been proposed for the new routines.
Closes-Bug: #1532120
Change-Id: Ic5abf6669edd4f1a9fd62e61f437565aa887aebe
26 files changed, 287 insertions, 90 deletions
diff --git a/trove/common/notification.py b/trove/common/notification.py index 73ec7990..49a3aad5 100644 --- a/trove/common/notification.py +++ b/trove/common/notification.py @@ -18,11 +18,11 @@ import copy import traceback from oslo_log import log as logging -from oslo_utils import timeutils from trove.common import cfg from trove.common.exception import TroveError from trove.common.i18n import _ +from trove.common import timeutils from trove.conductor import api as conductor_api from trove import rpc diff --git a/trove/common/timeutils.py b/trove/common/timeutils.py new file mode 100644 index 00000000..44a0b9ff --- /dev/null +++ b/trove/common/timeutils.py @@ -0,0 +1,84 @@ +# Copyright 2016 Tesora Inc. +# 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. + +from datetime import datetime +from datetime import timedelta +from datetime import tzinfo + + +class zulutime(tzinfo): + """A tzinfo class for zulu time""" + + def utcoffset(self, dt): + return timedelta(0) + + def tzname(self, dt): + return "Z" + + def dst(self, dt): + return timedelta(0) + + +def utcnow_aware(): + """An aware utcnow() that uses zulutime for the tzinfo.""" + return datetime.now(zulutime()) + + +def utcnow(): + """A wrapper around datetime.datetime.utcnow(). We're doing this + because it is mock'ed in some places. + """ + return datetime.utcnow() + + +def isotime(tm=None, subsecond=False): + """Stringify a time and return it in an ISO 8601 format. Subsecond + information is only provided if the subsecond parameter is set + to True (default: False). + + If a time (tm) is provided, it will be stringified. If tm is + not provided, the current UTC time is used instead. + + The timezone for UTC time will be provided as 'Z' and not + [+-]00:00. Time zone differential for non UTC times will be + provided as the full six character string format provided by + datetime.datetime.isoformat() namely [+-]NN:NN. + + If an invalid time is provided such that tm.utcoffset() causes + a ValueError, that exception will be propagated. + """ + + _dt = tm if tm else utcnow_aware() + + if not subsecond: + _dt = _dt.replace(microsecond=0) + + # might cause an exception if _dt has a bad utcoffset. + delta = _dt.utcoffset() if _dt.utcoffset() else timedelta(0) + + ts = None + + if delta == timedelta(0): + # either we are provided a naive time (tm) or no tm, or an + # aware UTC time. In any event, we want to use 'Z' for the + # timezone rather than the full 6 character offset. + _dt = _dt.replace(tzinfo=None) + ts = _dt.isoformat() + ts += 'Z' + else: + # an aware non-UTC time was provided + ts = _dt.isoformat() + + return ts diff --git a/trove/common/utils.py b/trove/common/utils.py index 9a81be45..774cfc66 100644 --- a/trove/common/utils.py +++ b/trove/common/utils.py @@ -15,7 +15,6 @@ """I totally stole most of this from melange, thx guys!!!""" import collections -import datetime import inspect import os import shutil @@ -29,7 +28,6 @@ from oslo_log import log as logging from oslo_service import loopingcall from oslo_utils import importutils from oslo_utils import strutils -from oslo_utils import timeutils from passlib import pwd import six import six.moves.urllib.parse as urlparse @@ -46,7 +44,6 @@ import_object = importutils.import_object import_module = importutils.import_module bool_from_string = strutils.bool_from_string execute = processutils.execute -isotime = timeutils.isotime def build_jinja_environment(): @@ -99,10 +96,6 @@ def generate_uuid(): return str(uuid.uuid4()) -def utcnow(): - return datetime.datetime.utcnow() - - def raise_if_process_errored(process, exception): try: err = process.stderr.read() diff --git a/trove/configuration/models.py b/trove/configuration/models.py index 3ca3c2bd..599a1501 100644 --- a/trove/configuration/models.py +++ b/trove/configuration/models.py @@ -13,7 +13,6 @@ # License for the specific language governing permissions and limitations # under the License. -from datetime import datetime import json from oslo_log import log as logging @@ -22,6 +21,7 @@ from trove.common import cfg from trove.common import exception from trove.common.exception import ModelNotFoundError from trove.common.i18n import _ +from trove.common import timeutils from trove.common import utils from trove.datastore import models as dstore_models from trove.db import get_db_api @@ -103,7 +103,7 @@ class Configuration(object): @staticmethod def delete(context, group): - deleted_at = datetime.utcnow() + deleted_at = timeutils.utcnow() Configuration.remove_all_items(context, group.id, deleted_at) group.deleted = True group.deleted_at = deleted_at @@ -313,7 +313,7 @@ class DatastoreConfigurationParameters(object): config_param = DatastoreConfigurationParameters.load_parameter_by_name( version_id, config_param_name) config_param.deleted = True - config_param.deleted_at = datetime.utcnow() + config_param.deleted_at = timeutils.utcnow() config_param.save() @classmethod diff --git a/trove/configuration/service.py b/trove/configuration/service.py index 5cf9ff8b..463cae93 100644 --- a/trove/configuration/service.py +++ b/trove/configuration/service.py @@ -13,8 +13,6 @@ # License for the specific language governing permissions and limitations # under the License. -from datetime import datetime - from oslo_log import log as logging import six @@ -27,6 +25,7 @@ from trove.common import notification from trove.common.notification import StartNotification, EndNotification from trove.common import pagination from trove.common import policy +from trove.common import timeutils from trove.common import wsgi from trove.configuration import models from trove.configuration.models import DBConfigurationParameter @@ -194,7 +193,7 @@ class ConfigurationsController(wsgi.Controller): name=group.name, description=group.description): items = self._configuration_items_list(group, body['configuration']) - deleted_at = datetime.utcnow() + deleted_at = timeutils.utcnow() models.Configuration.remove_all_items(context, group.id, deleted_at) models.Configuration.save(group, items) diff --git a/trove/datastore/models.py b/trove/datastore/models.py index 981489ed..e7a7a200 100644 --- a/trove/datastore/models.py +++ b/trove/datastore/models.py @@ -21,6 +21,7 @@ from trove.common import cfg from trove.common import exception from trove.common.i18n import _ from trove.common.remote import create_nova_client +from trove.common import timeutils from trove.common import utils from trove.db import get_db_api from trove.db import models as dbmodels @@ -631,7 +632,7 @@ class DatastoreVersionMetadata(object): key=key, value=value) if db_record.deleted == 1: db_record.deleted = 0 - db_record.updated_at = utils.utcnow() + db_record.updated_at = timeutils.utcnow() db_record.save() return else: diff --git a/trove/db/models.py b/trove/db/models.py index 0553bcfd..8d8c7ad5 100644 --- a/trove/db/models.py +++ b/trove/db/models.py @@ -19,6 +19,7 @@ from trove.common import exception from trove.common.i18n import _ from trove.common import models from trove.common import pagination +from trove.common import timeutils from trove.common import utils from trove.db import db_query from trove.db import get_db_api @@ -33,7 +34,7 @@ class DatabaseModelBase(models.ModelBase): def create(cls, **values): init_vals = { 'id': utils.generate_uuid(), - 'created': utils.utcnow(), + 'created': timeutils.utcnow(), } if hasattr(cls, 'deleted'): init_vals['deleted'] = False @@ -58,20 +59,20 @@ class DatabaseModelBase(models.ModelBase): def save(self): if not self.is_valid(): raise exception.InvalidModelError(errors=self.errors) - self['updated'] = utils.utcnow() + self['updated'] = timeutils.utcnow() LOG.debug("Saving %(name)s: %(dict)s", {'name': self.__class__.__name__, 'dict': strutils.mask_dict_password(self.__dict__)}) return self.db_api.save(self) def delete(self): - self['updated'] = utils.utcnow() + self['updated'] = timeutils.utcnow() LOG.debug("Deleting %(name)s: %(dict)s", {'name': self.__class__.__name__, 'dict': strutils.mask_dict_password(self.__dict__)}) if self.preserve_on_delete: - self['deleted_at'] = utils.utcnow() + self['deleted_at'] = timeutils.utcnow() self['deleted'] = True return self.db_api.save(self) else: @@ -81,7 +82,7 @@ class DatabaseModelBase(models.ModelBase): for key in values: if hasattr(self, key): setattr(self, key, values[key]) - self['updated'] = utils.utcnow() + self['updated'] = timeutils.utcnow() return self.db_api.save(self) def __init__(self, **kwargs): diff --git a/trove/extensions/common/models.py b/trove/extensions/common/models.py index 4102a425..c67438d0 100644 --- a/trove/extensions/common/models.py +++ b/trove/extensions/common/models.py @@ -18,7 +18,7 @@ from oslo_log import log as logging from trove.common.db import models as guest_models from trove.common import exception from trove.common.remote import create_guest_client -from trove.common import utils +from trove.common import timeutils from trove.db import get_db_api from trove.instance import models as base_models @@ -106,7 +106,7 @@ class RootHistory(object): def __init__(self, instance_id, user): self.id = instance_id self.user = user - self.created = utils.utcnow() + self.created = timeutils.utcnow() def save(self): LOG.debug("Saving %(name)s: %(dict)s", diff --git a/trove/extensions/mgmt/instances/models.py b/trove/extensions/mgmt/instances/models.py index b2b6fb0d..64da1a1e 100644 --- a/trove/extensions/mgmt/instances/models.py +++ b/trove/extensions/mgmt/instances/models.py @@ -19,7 +19,7 @@ from trove.common import cfg from trove.common import exception from trove.common.i18n import _ from trove.common import remote -from trove.common import utils +from trove.common import timeutils from trove.extensions.mysql import models as mysql_models from trove.instance import models as instance_models from trove import rpc @@ -186,12 +186,11 @@ class NotificationTransformer(object): @staticmethod def _get_audit_period(): - now = datetime.datetime.now() - audit_start = utils.isotime( - now - datetime.timedelta( - seconds=CONF.exists_notification_interval), - subsecond=True) - audit_end = utils.isotime(now, subsecond=True) + now = timeutils.utcnow() + start_time = now - datetime.timedelta( + seconds=CONF.exists_notification_interval) + audit_start = timeutils.isotime(start_time) + audit_end = timeutils.isotime(now) return audit_start, audit_end def _get_service_id(self, datastore_manager, id_map): diff --git a/trove/guestagent/backup/backupagent.py b/trove/guestagent/backup/backupagent.py index 71ccaa52..9d13e746 100644 --- a/trove/guestagent/backup/backupagent.py +++ b/trove/guestagent/backup/backupagent.py @@ -15,13 +15,13 @@ # from oslo_log import log as logging +from oslo_utils import timeutils from trove.backup.state import BackupState from trove.common import cfg from trove.common.i18n import _ from trove.common.strategies.storage import get_storage_strategy from trove.conductor import api as conductor_api -from trove.guestagent.common import timeutils from trove.guestagent.dbaas import get_filesystem_volume_stats from trove.guestagent.strategies.backup.base import BackupError from trove.guestagent.strategies.backup.base import UnknownBackupType @@ -74,7 +74,7 @@ class BackupAgent(object): 'state': BackupState.BUILDING, } conductor.update_backup(CONF.guest_id, - sent=timeutils.float_utcnow(), + sent=timeutils.utcnow_ts(microsecond=True), **backup_state) LOG.debug("Updated state for %s to %s.", backup_id, backup_state) @@ -120,7 +120,8 @@ class BackupAgent(object): finally: LOG.info(_("Completed backup %(backup_id)s."), backup_state) conductor.update_backup(CONF.guest_id, - sent=timeutils.float_utcnow(), + sent=timeutils.utcnow_ts( + microsecond=True), **backup_state) LOG.debug("Updated state for %s to %s.", backup_id, backup_state) diff --git a/trove/guestagent/common/timeutils.py b/trove/guestagent/common/timeutils.py deleted file mode 100644 index e5895bd8..00000000 --- a/trove/guestagent/common/timeutils.py +++ /dev/null @@ -1,19 +0,0 @@ -# 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 datetime import datetime - -from oslo_utils import timeutils - - -def float_utcnow(): - return float(datetime.strftime(timeutils.utcnow(), "%s.%f")) diff --git a/trove/guestagent/datastore/service.py b/trove/guestagent/datastore/service.py index d03d87f8..bedd63c3 100644 --- a/trove/guestagent/datastore/service.py +++ b/trove/guestagent/datastore/service.py @@ -18,6 +18,7 @@ import os import time from oslo_log import log as logging +from oslo_utils import timeutils from trove.common import cfg from trove.common import context as trove_context @@ -26,7 +27,7 @@ from trove.common import instance from trove.conductor import api as conductor_api from trove.guestagent.common import guestagent_utils from trove.guestagent.common import operating_system -from trove.guestagent.common import timeutils + LOG = logging.getLogger(__name__) CONF = cfg.CONF @@ -167,7 +168,8 @@ class BaseDbStatus(object): heartbeat = {'service_status': status.description} conductor_api.API(context).heartbeat( - CONF.guest_id, heartbeat, sent=timeutils.float_utcnow()) + CONF.guest_id, heartbeat, + sent=timeutils.utcnow_ts(microsecond=True)) LOG.debug("Successfully cast set_status.") self.status = status else: diff --git a/trove/guestagent/guest_log.py b/trove/guestagent/guest_log.py index 8e93e4a1..bc1cc000 100644 --- a/trove/guestagent/guest_log.py +++ b/trove/guestagent/guest_log.py @@ -12,7 +12,6 @@ # License for the specific language governing permissions and limitations # under the License. -from datetime import datetime import enum import hashlib import os @@ -26,6 +25,7 @@ from trove.common import exception from trove.common.i18n import _ from trove.common.remote import create_swift_client from trove.common import stream_codecs +from trove.common import timeutils from trove.guestagent.common import operating_system from trove.guestagent.common.operating_system import FileMode @@ -404,7 +404,7 @@ class GuestLog(object): 'log': self._name} def _object_name(self): - return 'log-%s' % str(datetime.utcnow()).replace(' ', 'T') + return 'log-%s' % str(timeutils.utcnow()).replace(' ', 'T') def _get_meta_details(self): LOG.debug("Getting meta details for '%s'", self._name) diff --git a/trove/guestagent/models.py b/trove/guestagent/models.py index 4cdc4279..cdf64651 100644 --- a/trove/guestagent/models.py +++ b/trove/guestagent/models.py @@ -20,6 +20,7 @@ from oslo_log import log as logging from trove.common import cfg from trove.common import exception from trove.common.i18n import _ +from trove.common import timeutils from trove.common import utils from trove.db import get_db_api from trove.db import models as dbmodels @@ -56,7 +57,7 @@ class AgentHeartBeat(dbmodels.DatabaseModelBase): def save(self): if not self.is_valid(): raise exception.InvalidModelError(errors=self.errors) - self['updated_at'] = utils.utcnow() + self['updated_at'] = timeutils.utcnow() LOG.debug("Saving %(name)s: %(dict)s", {'name': self.__class__.__name__, 'dict': self.__dict__}) return get_db_api().save(self) diff --git a/trove/instance/models.py b/trove/instance/models.py index d1825ef5..922d3b06 100644 --- a/trove/instance/models.py +++ b/trove/instance/models.py @@ -39,6 +39,7 @@ from trove.common.remote import create_guest_client from trove.common.remote import create_nova_client from trove.common import server_group as srv_grp from trove.common import template +from trove.common import timeutils from trove.common.trove_remote import create_trove_client from trove.common import utils from trove.configuration.models import Configuration @@ -651,7 +652,7 @@ class BaseInstance(SimpleInstance): pass def delete_async(self): - deleted_at = datetime.utcnow() + deleted_at = timeutils.utcnow() self._delete_resources(deleted_at) LOG.debug("Setting instance %s to be deleted.", self.id) self.update_db(deleted=True, deleted_at=deleted_at, @@ -1217,7 +1218,7 @@ class Instance(BuiltInstance): raise exception.BadRequest(_("Instance %s is not a replica" " source.") % self.id) service = InstanceServiceStatus.find_by(instance_id=self.id) - last_heartbeat_delta = datetime.utcnow() - service.updated_at + last_heartbeat_delta = timeutils.utcnow() - service.updated_at agent_expiry_interval = timedelta(seconds=CONF.agent_heartbeat_expiry) if last_heartbeat_delta < agent_expiry_interval: raise exception.BadRequest(_("Replica Source %s cannot be ejected" @@ -1788,7 +1789,7 @@ class InstanceServiceStatus(dbmodels.DatabaseModelBase): self.status_description = value.description def save(self): - self['updated_at'] = utils.utcnow() + self['updated_at'] = timeutils.utcnow() return get_db_api().save(self) status = property(get_status, set_status) diff --git a/trove/limits/views.py b/trove/limits/views.py index 6c483c08..f381d1ca 100644 --- a/trove/limits/views.py +++ b/trove/limits/views.py @@ -14,7 +14,8 @@ # under the License. import datetime -from oslo_utils import timeutils + +from trove.common import timeutils class LimitView(object): @@ -27,7 +28,7 @@ class LimitView(object): next_avail = get_utc(self.rate_limit.get("resetTime", 0)) return {"limit": { - "nextAvailable": timeutils.isotime(at=next_avail), + "nextAvailable": timeutils.isotime(next_avail), "remaining": self.rate_limit.get("remaining", 0), "unit": self.rate_limit.get("unit", ""), "value": self.rate_limit.get("value", ""), diff --git a/trove/module/models.py b/trove/module/models.py index dc9138bb..80df8cb7 100644 --- a/trove/module/models.py +++ b/trove/module/models.py @@ -16,7 +16,6 @@ """Model classes that form the core of Module functionality.""" -from datetime import datetime import hashlib import six from sqlalchemy.sql.expression import or_ @@ -27,6 +26,7 @@ from trove.common import cfg from trove.common import crypto_utils from trove.common import exception from trove.common.i18n import _ +from trove.common import timeutils from trove.common import utils from trove.datastore import models as datastore_models from trove.db import models @@ -266,7 +266,7 @@ class Module(object): module.priority_apply, None) Module.enforce_live_update(module.id, module.live_update, module.md5) module.deleted = True - module.deleted_at = datetime.utcnow() + module.deleted_at = timeutils.utcnow() module.save() @staticmethod @@ -342,7 +342,7 @@ class Module(object): if module.datastore_version_id: module.datastore_version_id = ds_ver_id - module.updated = datetime.utcnow() + module.updated = timeutils.utcnow() DBModule.save(module) @staticmethod @@ -424,7 +424,7 @@ class InstanceModule(object): @staticmethod def delete(context, instance_module): instance_module.deleted = True - instance_module.deleted_at = datetime.utcnow() + instance_module.deleted_at = timeutils.utcnow() instance_module.save() @staticmethod @@ -440,7 +440,7 @@ class InstanceModule(object): @staticmethod def update(context, instance_module): - instance_module.updated = datetime.utcnow() + instance_module.updated = timeutils.utcnow() DBInstanceModule.save(instance_module) diff --git a/trove/quota/models.py b/trove/quota/models.py index 64e225c0..31b7542d 100644 --- a/trove/quota/models.py +++ b/trove/quota/models.py @@ -14,6 +14,7 @@ from trove.common import cfg +from trove.common import timeutils from trove.common import utils from trove.db import models as dbmodels @@ -31,8 +32,8 @@ class Quota(dbmodels.DatabaseModelBase): 'hard_limit', 'id'] def __init__(self, tenant_id, resource, hard_limit, - id=utils.generate_uuid(), created=utils.utcnow(), - update=utils.utcnow()): + id=utils.generate_uuid(), created=timeutils.utcnow(), + update=timeutils.utcnow()): self.tenant_id = tenant_id self.resource = resource self.hard_limit = hard_limit diff --git a/trove/taskmanager/models.py b/trove/taskmanager/models.py index e104df11..6e9272a3 100755 --- a/trove/taskmanager/models.py +++ b/trove/taskmanager/models.py @@ -21,7 +21,6 @@ from eventlet import greenthread from eventlet.timeout import Timeout from novaclient import exceptions as nova_exceptions from oslo_log import log as logging -from oslo_utils import timeutils from swiftclient.client import ClientException from trove.backup import models as bkup_models @@ -61,6 +60,7 @@ from trove.common.remote import create_guest_client from trove.common import server_group as srv_grp from trove.common.strategies.cluster import strategy from trove.common import template +from trove.common import timeutils from trove.common import utils from trove.common.utils import try_recover from trove.extensions.mysql import models as mysql_models @@ -315,7 +315,7 @@ class ClusterTasks(Cluster): LOG.debug("setting cluster %s as deleted.", cluster_id) cluster = DBCluster.find_by(id=cluster_id) cluster.deleted = True - cluster.deleted_at = utils.utcnow() + cluster.deleted_at = timeutils.utcnow() cluster.task_status = tasks.ClusterTasks.NONE cluster.save() LOG.debug("end delete_cluster for id: %s", cluster_id) diff --git a/trove/tests/examples/snippets.py b/trove/tests/examples/snippets.py index 8e539ebf..65d90198 100644 --- a/trove/tests/examples/snippets.py +++ b/trove/tests/examples/snippets.py @@ -71,8 +71,9 @@ def set_fake_stuff(uuid=None, minute=None, unique_id=None): def monkey_patch_uuid_and_date(): import uuid uuid.uuid4 = get_uuid + from trove.common import timeutils from trove.common import utils - utils.utcnow = get_now + timeutils.utcnow = get_now utils.generate_uuid = get_uuid diff --git a/trove/tests/scenario/runners/test_runners.py b/trove/tests/scenario/runners/test_runners.py index a1f2fa1b..d858893d 100644 --- a/trove/tests/scenario/runners/test_runners.py +++ b/trove/tests/scenario/runners/test_runners.py @@ -31,6 +31,7 @@ from troveclient.compat import exceptions from trove.common import cfg from trove.common import exception from trove.common.strategies.strategy import Strategy +from trove.common import timeutils from trove.common import utils from trove.common.utils import poll_until, build_polling_task from trove.tests.config import CONFIG @@ -323,7 +324,7 @@ class TestRunner(object): self.def_timeout = timeout self.instance_info.name = "TEST_" + datetime.datetime.strftime( - datetime.datetime.now(), '%Y_%m_%d__%H_%M_%S') + timeutils.utcnow(), '%Y_%m_%d__%H_%M_%S') self.instance_info.dbaas_datastore = CONFIG.dbaas_datastore self.instance_info.dbaas_datastore_version = ( CONFIG.dbaas_datastore_version) diff --git a/trove/tests/unittests/backup/test_backup_models.py b/trove/tests/unittests/backup/test_backup_models.py index 315a508e..a4964ae6 100644 --- a/trove/tests/unittests/backup/test_backup_models.py +++ b/trove/tests/unittests/backup/test_backup_models.py @@ -23,6 +23,7 @@ from trove.backup import state from trove.common import context from trove.common import exception from trove.common import remote +from trove.common import timeutils from trove.common import utils from trove.db.models import DatabaseModelBase from trove.instance import models as instance_models @@ -54,7 +55,7 @@ class BackupCreateTest(trove_testtools.TestCase): def setUp(self): super(BackupCreateTest, self).setUp() util.init_db() - self.context, self.instance_id = _prep_conf(utils.utcnow()) + self.context, self.instance_id = _prep_conf(timeutils.utcnow()) self.created = False def tearDown(self): @@ -241,7 +242,7 @@ class BackupDeleteTest(trove_testtools.TestCase): def setUp(self): super(BackupDeleteTest, self).setUp() util.init_db() - self.context, self.instance_id = _prep_conf(utils.utcnow()) + self.context, self.instance_id = _prep_conf(timeutils.utcnow()) def tearDown(self): super(BackupDeleteTest, self).tearDown() @@ -272,7 +273,7 @@ class BackupORMTest(trove_testtools.TestCase): def setUp(self): super(BackupORMTest, self).setUp() util.init_db() - self.context, self.instance_id = _prep_conf(utils.utcnow()) + self.context, self.instance_id = _prep_conf(timeutils.utcnow()) self.backup = models.DBBackup.create(tenant_id=self.context.tenant, name=BACKUP_NAME, state=BACKUP_STATE, @@ -449,7 +450,7 @@ class PaginationTests(trove_testtools.TestCase): def setUp(self): super(PaginationTests, self).setUp() util.init_db() - self.context, self.instance_id = _prep_conf(utils.utcnow()) + self.context, self.instance_id = _prep_conf(timeutils.utcnow()) # Create a bunch of backups bkup_info = { 'tenant_id': self.context.tenant, @@ -507,7 +508,7 @@ class OrderingTests(trove_testtools.TestCase): def setUp(self): super(OrderingTests, self).setUp() util.init_db() - now = utils.utcnow() + now = timeutils.utcnow() self.context, self.instance_id = _prep_conf(now) info = { 'tenant_id': self.context.tenant, diff --git a/trove/tests/unittests/common/test_timeutils.py b/trove/tests/unittests/common/test_timeutils.py new file mode 100644 index 00000000..6027b7c1 --- /dev/null +++ b/trove/tests/unittests/common/test_timeutils.py @@ -0,0 +1,129 @@ +# Copyright 2016 Tesora Inc. +# 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. + +from datetime import datetime +from datetime import timedelta +from datetime import tzinfo + +from trove.common import timeutils +from trove.tests.unittests import trove_testtools + + +class bogus_tzinfo(tzinfo): + """A bogus tzinfo class""" + def utcoffset(self, dt): + return timedelta(hours=2) + + def tzname(self, dt): + return "BOGUS" + + def dst(self, dt): + return timedelta(hours=1) + + +class invalid_tzinfo(tzinfo): + """A bogus tzinfo class""" + def utcoffset(self, dt): + return timedelta(hours=25) + + def tzname(self, dt): + return "INVALID" + + def dst(self, dt): + return timedelta(hours=25) + + +class TestTroveTimeutils(trove_testtools.TestCase): + + def setUp(self): + super(TestTroveTimeutils, self).setUp() + + def tearDown(self): + super(TestTroveTimeutils, self).tearDown() + + def test_utcnow_tz(self): + dt = timeutils.utcnow() + + self.assertIsNone(dt.tzinfo) + + def test_utcnow_aware_tz(self): + dt = timeutils.utcnow_aware() + + self.assertEqual(timedelta(0), dt.utcoffset()) + self.assertEqual('Z', dt.tzname()) + + def test_isotime(self): + dt = timeutils.utcnow_aware() + + expected = "%04d-%02d-%02dT%02d:%02d:%02dZ" % ( + dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second) + + self.assertEqual(expected, timeutils.isotime(dt)) + + def test_isotime_subsecond(self): + dt = timeutils.utcnow_aware() + + expected = "%04d-%02d-%02dT%02d:%02d:%02d.%06dZ" % ( + dt.year, dt.month, dt.day, + dt.hour, dt.minute, dt.second, + dt.microsecond) + + self.assertEqual(expected, timeutils.isotime(dt, subsecond=True)) + + def test_isotime_unaware(self): + dt = timeutils.utcnow() + + expected = "%04d-%02d-%02dT%02d:%02d:%02d.%06dZ" % ( + dt.year, dt.month, dt.day, + dt.hour, dt.minute, dt.second, + dt.microsecond) + + self.assertEqual(expected, timeutils.isotime(dt, subsecond=True)) + + def test_isotime_unaware_subsecond(self): + dt = timeutils.utcnow() + + expected = "%04d-%02d-%02dT%02d:%02d:%02d.%06dZ" % ( + dt.year, dt.month, dt.day, + dt.hour, dt.minute, dt.second, + dt.microsecond) + + self.assertEqual(expected, timeutils.isotime(dt, subsecond=True)) + + def test_bogus_unaware(self): + dt = datetime.now(bogus_tzinfo()) + + expected = "%04d-%02d-%02dT%02d:%02d:%02d.%06d+02:00" % ( + dt.year, dt.month, dt.day, + dt.hour, dt.minute, dt.second, + dt.microsecond) + + self.assertEqual(expected, timeutils.isotime(dt, subsecond=True)) + + def test_bogus_unaware_subsecond(self): + dt = datetime.now(bogus_tzinfo()) + + expected = "%04d-%02d-%02dT%02d:%02d:%02d.%06d+02:00" % ( + dt.year, dt.month, dt.day, + dt.hour, dt.minute, dt.second, + dt.microsecond) + + self.assertEqual(expected, timeutils.isotime(dt, subsecond=True)) + + def test_throws_exception(self): + dt = datetime.now() + dt = dt.replace(tzinfo=invalid_tzinfo()) + + self.assertRaises(ValueError, timeutils.isotime, dt) diff --git a/trove/tests/unittests/conductor/test_methods.py b/trove/tests/unittests/conductor/test_methods.py index 86e6a832..56278adc 100644 --- a/trove/tests/unittests/conductor/test_methods.py +++ b/trove/tests/unittests/conductor/test_methods.py @@ -13,6 +13,7 @@ # under the License. from mock import patch +from oslo_utils import timeutils from trove.backup import models as bkup_models from trove.backup import state @@ -20,7 +21,6 @@ from trove.common import exception as t_exception from trove.common.instance import ServiceStatuses from trove.common import utils from trove.conductor import manager as conductor_manager -from trove.guestagent.common import timeutils from trove.instance import models as t_models from trove.tests.unittests import trove_testtools from trove.tests.unittests.util import util @@ -151,7 +151,7 @@ class ConductorMethodTests(trove_testtools.TestCase): build_p = {'service_status': ServiceStatuses.BUILDING.description} iss_id = self._create_iss() iss = self._get_iss(iss_id) - now = timeutils.float_utcnow() + now = timeutils.utcnow_ts(microsecond=True) future = now + 60 self.cond_mgr.heartbeat(None, self.instance_id, new_p, sent=now) self.cond_mgr.heartbeat(None, self.instance_id, build_p, sent=future) @@ -164,7 +164,7 @@ class ConductorMethodTests(trove_testtools.TestCase): build_p = {'service_status': ServiceStatuses.BUILDING.description} iss_id = self._create_iss() iss = self._get_iss(iss_id) - now = timeutils.float_utcnow() + now = timeutils.utcnow_ts(microsecond=True) past = now - 60 self.cond_mgr.heartbeat(None, self.instance_id, new_p, sent=past) self.cond_mgr.heartbeat(None, self.instance_id, build_p, sent=past) @@ -176,7 +176,7 @@ class ConductorMethodTests(trove_testtools.TestCase): new_name = "renamed" bkup_id = self._create_backup(old_name) bkup = self._get_backup(bkup_id) - now = timeutils.float_utcnow() + now = timeutils.utcnow_ts(microsecond=True) future = now + 60 self.cond_mgr.update_backup(None, self.instance_id, bkup_id, sent=now, name=old_name) @@ -190,7 +190,7 @@ class ConductorMethodTests(trove_testtools.TestCase): new_name = "renamed" bkup_id = self._create_backup(old_name) bkup = self._get_backup(bkup_id) - now = timeutils.float_utcnow() + now = timeutils.utcnow_ts(microsecond=True) past = now - 60 self.cond_mgr.update_backup(None, self.instance_id, bkup_id, sent=now, name=old_name) diff --git a/trove/tests/unittests/guestagent/test_models.py b/trove/tests/unittests/guestagent/test_models.py index 783f8e20..6e45834b 100644 --- a/trove/tests/unittests/guestagent/test_models.py +++ b/trove/tests/unittests/guestagent/test_models.py @@ -16,6 +16,7 @@ from datetime import datetime from mock import Mock, MagicMock, patch +from trove.common import timeutils from trove.common import utils from trove.db import models as dbmodels from trove.db.sqlalchemy import api as dbapi @@ -27,7 +28,7 @@ class AgentHeartBeatTest(trove_testtools.TestCase): def setUp(self): super(AgentHeartBeatTest, self).setUp() self.origin_get_db_api = dbmodels.get_db_api - self.origin_utcnow = utils.utcnow + self.origin_utcnow = timeutils.utcnow self.origin_db_api_save = dbapi.save self.origin_is_valid = dbmodels.DatabaseModelBase.is_valid self.origin_generate_uuid = utils.generate_uuid @@ -35,7 +36,7 @@ class AgentHeartBeatTest(trove_testtools.TestCase): def tearDown(self): super(AgentHeartBeatTest, self).tearDown() dbmodels.get_db_api = self.origin_get_db_api - utils.utcnow = self.origin_utcnow + timeutils.utcnow = self.origin_utcnow dbapi.save = self.origin_db_api_save dbmodels.DatabaseModelBase.is_valid = self.origin_is_valid utils.generate_uuid = self.origin_generate_uuid @@ -52,14 +53,14 @@ class AgentHeartBeatTest(trove_testtools.TestCase): @patch('trove.db.models.DatabaseModelBase') def test_save(self, dmb_mock): - utils.utcnow = Mock() + timeutils.utcnow = Mock() dbmodels.get_db_api = MagicMock( return_value=dbmodels.DatabaseModelBase) dbapi.save = Mock() dbmodels.DatabaseModelBase.is_valid = Mock(return_value=True) self.heartBeat = models.AgentHeartBeat() self.heartBeat.save() - self.assertEqual(1, utils.utcnow.call_count) + self.assertEqual(1, timeutils.utcnow.call_count) def test_is_active(self): models.AGENT_HEARTBEAT = 10000000000 diff --git a/trove/tests/unittests/taskmanager/test_models.py b/trove/tests/unittests/taskmanager/test_models.py index 7fbba5dd..53bed50d 100644 --- a/trove/tests/unittests/taskmanager/test_models.py +++ b/trove/tests/unittests/taskmanager/test_models.py @@ -11,7 +11,6 @@ # 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 datetime import os from tempfile import NamedTemporaryFile import uuid @@ -23,7 +22,6 @@ from mock import Mock, MagicMock, patch, PropertyMock, call from novaclient import exceptions as nova_exceptions import novaclient.v2.flavors import novaclient.v2.servers -from oslo_utils import timeutils from swiftclient.client import ClientException from testtools.matchers import Equals, Is @@ -39,6 +37,7 @@ from trove.common.instance import ServiceStatuses from trove.common.notification import TroveInstanceModifyVolume from trove.common import remote import trove.common.template as template +from trove.common import timeutils from trove.common import utils from trove.datastore import models as datastore_models import trove.db.models @@ -651,8 +650,8 @@ class BuiltInstanceTasksTest(trove_testtools.TestCase): datastore_id='id-1', flavor_id='6', manager='mysql', - created=datetime.datetime.utcnow(), - updated=datetime.datetime.utcnow(), + created=timeutils.utcnow(), + updated=timeutils.utcnow(), compute_instance_id='computeinst-id-1', tenant_id='testresize-tenant-id', volume_size='1', |