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
This commit is contained in:
Amrith Kumar 2016-06-07 08:15:24 -04:00 committed by Amrith Kumar
parent fb870b306e
commit 109ff94951
26 changed files with 287 additions and 90 deletions

View File

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

84
trove/common/timeutils.py Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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