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:
Mark McLoughlin 2012-09-03 20:06:09 +01:00
parent bab694ef1d
commit f2d2e4cb06
5 changed files with 146 additions and 5 deletions

View 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'])

View File

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

View File

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

View File

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

View File

@ -1,4 +1,5 @@
argparse
httplib2
iso8601>=0.1.4
prettytable>=0.6,<0.7
simplejson