Fix usage-list date range to use UTC time
Fixes bug #1045456 The date range in Nova's os-simple-tenant-usage is expected to be in UTC time since launch/termination dates are stored in the DB in UTC time and we use the client supplied parameters to query DB without conversion. Switch from using datetime.today() to datetime.utcnow() to fix the issue. Add a test for the default date range. Import timeutils from openstack-common so we can control the return value of utcnow() in the tests. Change-Id: Iac77e3a4cc9561714d1492c54cef931f9764531e
This commit is contained in:
parent
bab694ef1d
commit
f2d2e4cb06
126
novaclient/openstack/common/timeutils.py
Normal file
126
novaclient/openstack/common/timeutils.py
Normal file
@ -0,0 +1,126 @@
|
||||
# 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 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 overridden time using a datetime.timedelta."""
|
||||
assert(not utcnow.override_time is None)
|
||||
utcnow.override_time += timedelta
|
||||
|
||||
|
||||
def advance_time_seconds(seconds):
|
||||
"""Advance overridden time by seconds."""
|
||||
advance_time_delta(datetime.timedelta(0, seconds))
|
||||
|
||||
|
||||
def clear_time_override():
|
||||
"""Remove the overridden time."""
|
||||
utcnow.override_time = None
|
||||
|
||||
|
||||
def marshall_now(now=None):
|
||||
"""Make an rpc-safe datetime with microseconds.
|
||||
|
||||
Note: tzinfo is stripped, but not required for relative times."""
|
||||
if not now:
|
||||
now = utcnow()
|
||||
return dict(day=now.day, month=now.month, year=now.year, hour=now.hour,
|
||||
minute=now.minute, second=now.second,
|
||||
microsecond=now.microsecond)
|
||||
|
||||
|
||||
def unmarshall_time(tyme):
|
||||
"""Unmarshall a datetime dict."""
|
||||
return datetime.datetime(day=tyme['day'], month=tyme['month'],
|
||||
year=tyme['year'], hour=tyme['hour'], minute=tyme['minute'],
|
||||
second=tyme['second'], microsecond=tyme['microsecond'])
|
@ -22,6 +22,7 @@ import sys
|
||||
import time
|
||||
|
||||
from novaclient import exceptions
|
||||
from novaclient.openstack.common import timeutils
|
||||
from novaclient import utils
|
||||
from novaclient.v1_1 import servers
|
||||
|
||||
@ -1474,17 +1475,17 @@ def do_usage_list(cs, args):
|
||||
rows = ["Tenant ID", "Instances", "RAM MB-Hours", "CPU Hours",
|
||||
"Disk GB-Hours"]
|
||||
|
||||
now = timeutils.utcnow()
|
||||
|
||||
if args.start:
|
||||
start = datetime.datetime.strptime(args.start, dateformat)
|
||||
else:
|
||||
start = (datetime.datetime.today() -
|
||||
datetime.timedelta(weeks=4))
|
||||
start = now - datetime.timedelta(weeks=4)
|
||||
|
||||
if args.end:
|
||||
end = datetime.datetime.strptime(args.end, dateformat)
|
||||
else:
|
||||
end = (datetime.datetime.today() +
|
||||
datetime.timedelta(days=1))
|
||||
end = now + datetime.timedelta(days=1)
|
||||
|
||||
def simplify_usage(u):
|
||||
simplerows = map(lambda x: x.lower().replace(" ", "_"), rows)
|
||||
|
@ -1,7 +1,7 @@
|
||||
[DEFAULT]
|
||||
|
||||
# The list of modules to copy from openstack-common
|
||||
modules=setup
|
||||
modules=setup,timeutils
|
||||
|
||||
# The base module to hold the copy of openstack.common
|
||||
base=novaclient
|
||||
|
@ -15,6 +15,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import datetime
|
||||
import os
|
||||
import mock
|
||||
import sys
|
||||
@ -23,6 +24,7 @@ import tempfile
|
||||
import novaclient.shell
|
||||
import novaclient.client
|
||||
from novaclient import exceptions
|
||||
from novaclient.openstack.common import timeutils
|
||||
from tests.v1_1 import fakes
|
||||
from tests import utils
|
||||
|
||||
@ -59,6 +61,8 @@ class ShellTest(utils.TestCase):
|
||||
#HACK(bcwaldon): replace this when we start using stubs
|
||||
novaclient.client.get_client_class = self.old_get_client_class
|
||||
|
||||
timeutils.clear_time_override()
|
||||
|
||||
def run_command(self, cmd):
|
||||
self.shell.main(cmd.split())
|
||||
|
||||
@ -353,6 +357,15 @@ class ShellTest(utils.TestCase):
|
||||
'end=2005-02-01T00:00:00&' +
|
||||
'detailed=1')
|
||||
|
||||
def test_usage_list_no_args(self):
|
||||
timeutils.set_time_override(datetime.datetime(2005, 2, 1, 0, 0))
|
||||
self.run_command('usage-list')
|
||||
self.assert_called('GET',
|
||||
'/os-simple-tenant-usage?' +
|
||||
'start=2005-01-04T00:00:00&' +
|
||||
'end=2005-02-02T00:00:00&' +
|
||||
'detailed=1')
|
||||
|
||||
def test_flavor_delete(self):
|
||||
self.run_command("flavor-delete flavordelete")
|
||||
self.assert_called('DELETE', '/flavors/flavordelete')
|
||||
|
@ -1,4 +1,5 @@
|
||||
argparse
|
||||
httplib2
|
||||
iso8601>=0.1.4
|
||||
prettytable>=0.6,<0.7
|
||||
simplejson
|
||||
|
Loading…
Reference in New Issue
Block a user