From ff26ed98e7dd51cc3a5b88ac63714a9a5104b6a9 Mon Sep 17 00:00:00 2001 From: lvdongbing Date: Wed, 22 Jun 2016 21:11:09 -0400 Subject: [PATCH] Replace float with decimal Floating point calculations are inaccurate, often lose precision even when storing into database. So we replace float with decimal. Change-Id: I3782264015e0b0e260f0c046dc7c3ea94eab0b52 --- bilean/common/utils.py | 38 +++++++++++ .../migrate_repo/versions/001_bilean_init.py | 24 +++---- bilean/db/sqlalchemy/models.py | 25 ++++---- bilean/engine/actions/base.py | 13 ++-- bilean/engine/consumption.py | 24 +++---- bilean/engine/flows/flow.py | 8 +-- bilean/engine/user.py | 64 +++++++++---------- bilean/plugins/base.py | 60 +++++++---------- bilean/scheduler/cron_scheduler.py | 4 +- 9 files changed, 139 insertions(+), 121 deletions(-) diff --git a/bilean/common/utils.py b/bilean/common/utils.py index e7f8929..17bc9cb 100644 --- a/bilean/common/utils.py +++ b/bilean/common/utils.py @@ -15,7 +15,9 @@ Utilities module. ''' import datetime +import decimal import random +import six import string from cryptography.fernet import Fernet @@ -27,6 +29,7 @@ from oslo_config import cfg from oslo_log import log as logging from oslo_utils import encodeutils from oslo_utils import strutils +from oslo_utils import timeutils from bilean.common import exception from bilean.common.i18n import _ @@ -156,3 +159,38 @@ def format_time(value): value = value.replace(microsecond=0) value = value.isoformat() return value + + +def format_time_to_seconds(t): + """Format datetime to seconds from 1970-01-01 00:00:00 UTC.""" + epoch = datetime.datetime.utcfromtimestamp(0) + if isinstance(t, datetime.datetime): + return (t - epoch).total_seconds() + if isinstance(t, six.string_types): + dt = timeutils.parse_strtime(t) + return (dt - epoch).total_seconds() + return t + + +def make_decimal(value): + """Format float to decimal.""" + if isinstance(value, decimal.Decimal): + return value + if isinstance(value, float): + return decimal.Decimal.from_float(value) + return decimal.Decimal(str(value)) + + +def format_decimal(value, num=8): + """Format decimal and keep num decimals.""" + if not isinstance(value, decimal.Decimal): + value = make_decimal(value) + dec = "0.%s" % ('0' * num) + return value.quantize(decimal.Decimal(dec)) + + +def dec2str(value): + """Decimal to str and keep 2 decimals.""" + if not isinstance(value, decimal.Decimal): + value = make_decimal(value) + return str(value.quantize(decimal.Decimal('0.00'))) diff --git a/bilean/db/sqlalchemy/migrate_repo/versions/001_bilean_init.py b/bilean/db/sqlalchemy/migrate_repo/versions/001_bilean_init.py index 9074bc2..7cfb62e 100644 --- a/bilean/db/sqlalchemy/migrate_repo/versions/001_bilean_init.py +++ b/bilean/db/sqlalchemy/migrate_repo/versions/001_bilean_init.py @@ -29,10 +29,10 @@ def upgrade(migrate_engine): sqlalchemy.String(36), sqlalchemy.ForeignKey('policy.id'), nullable=True), - sqlalchemy.Column('balance', sqlalchemy.Float), - sqlalchemy.Column('rate', sqlalchemy.Float), + sqlalchemy.Column('balance', sqlalchemy.Numeric(20, 8)), + sqlalchemy.Column('rate', sqlalchemy.Numeric(20, 8)), sqlalchemy.Column('credit', sqlalchemy.Integer), - sqlalchemy.Column('last_bill', sqlalchemy.DateTime), + sqlalchemy.Column('last_bill', sqlalchemy.Numeric(24, 8)), sqlalchemy.Column('status', sqlalchemy.String(255)), sqlalchemy.Column('status_reason', sqlalchemy.Text), sqlalchemy.Column('created_at', sqlalchemy.DateTime), @@ -83,9 +83,9 @@ def upgrade(migrate_engine): sqlalchemy.Column('rule_id', sqlalchemy.String(36), nullable=False), sqlalchemy.Column('resource_type', sqlalchemy.String(36), nullable=False), - sqlalchemy.Column('last_bill', sqlalchemy.DateTime), + sqlalchemy.Column('last_bill', sqlalchemy.Numeric(24, 8)), sqlalchemy.Column('properties', types.Dict), - sqlalchemy.Column('rate', sqlalchemy.Float, nullable=False), + sqlalchemy.Column('rate', sqlalchemy.Numeric(20, 8), nullable=False), sqlalchemy.Column('created_at', sqlalchemy.DateTime), sqlalchemy.Column('updated_at', sqlalchemy.DateTime), sqlalchemy.Column('deleted_at', sqlalchemy.DateTime), @@ -118,10 +118,10 @@ def upgrade(migrate_engine): sqlalchemy.Column('user_id', sqlalchemy.String(36)), sqlalchemy.Column('resource_id', sqlalchemy.String(36)), sqlalchemy.Column('resource_type', sqlalchemy.String(255)), - sqlalchemy.Column('start_time', sqlalchemy.DateTime), - sqlalchemy.Column('end_time', sqlalchemy.DateTime), - sqlalchemy.Column('rate', sqlalchemy.Float), - sqlalchemy.Column('cost', sqlalchemy.Float), + sqlalchemy.Column('start_time', sqlalchemy.Numeric(24, 8)), + sqlalchemy.Column('end_time', sqlalchemy.Numeric(24, 8)), + sqlalchemy.Column('rate', sqlalchemy.Numeric(20, 8)), + sqlalchemy.Column('cost', sqlalchemy.Numeric(20, 8)), sqlalchemy.Column('meta_data', types.Dict), mysql_engine='InnoDB', mysql_charset='utf8' @@ -134,7 +134,7 @@ def upgrade(migrate_engine): sqlalchemy.Column('user_id', sqlalchemy.String(36)), sqlalchemy.Column('type', sqlalchemy.String(255)), sqlalchemy.Column('timestamp', sqlalchemy.DateTime), - sqlalchemy.Column('value', sqlalchemy.Float), + sqlalchemy.Column('value', sqlalchemy.Numeric(20, 8)), sqlalchemy.Column('meta_data', types.Dict), mysql_engine='InnoDB', mysql_charset='utf8' @@ -150,8 +150,8 @@ def upgrade(migrate_engine): sqlalchemy.Column('action', sqlalchemy.String(255)), sqlalchemy.Column('cause', sqlalchemy.String(255)), sqlalchemy.Column('owner', sqlalchemy.String(36)), - sqlalchemy.Column('start_time', sqlalchemy.Float(precision='24,8')), - sqlalchemy.Column('end_time', sqlalchemy.Float(precision='24,8')), + sqlalchemy.Column('start_time', sqlalchemy.Numeric(24, 8)), + sqlalchemy.Column('end_time', sqlalchemy.Numeric(24, 8)), sqlalchemy.Column('timeout', sqlalchemy.Integer), sqlalchemy.Column('inputs', types.Dict), sqlalchemy.Column('outputs', types.Dict), diff --git a/bilean/db/sqlalchemy/models.py b/bilean/db/sqlalchemy/models.py index 3fe41ce..4ee0d7f 100644 --- a/bilean/db/sqlalchemy/models.py +++ b/bilean/db/sqlalchemy/models.py @@ -102,11 +102,10 @@ class User(BASE, BileanBase, SoftDelete, StateAware, models.TimestampMixin): sqlalchemy.String(36), sqlalchemy.ForeignKey('policy.id'), nullable=True) - balance = sqlalchemy.Column(sqlalchemy.Float, default=0.0) - rate = sqlalchemy.Column(sqlalchemy.Float, default=0.0) + balance = sqlalchemy.Column(sqlalchemy.Numeric(20, 8), default=0.0) + rate = sqlalchemy.Column(sqlalchemy.Numeric(20, 8), default=0.0) credit = sqlalchemy.Column(sqlalchemy.Integer, default=0) - last_bill = sqlalchemy.Column( - sqlalchemy.DateTime, default=timeutils.utcnow()) + last_bill = sqlalchemy.Column(sqlalchemy.Numeric(24, 8)) class Policy(BASE, BileanBase, SoftDelete, models.TimestampMixin): @@ -146,8 +145,8 @@ class Resource(BASE, BileanBase, SoftDelete, models.TimestampMixin): rule_id = sqlalchemy.Column(sqlalchemy.String(36), nullable=True) user = relationship(User, backref=backref('resources')) resource_type = sqlalchemy.Column(sqlalchemy.String(36), nullable=False) - rate = sqlalchemy.Column(sqlalchemy.Float, nullable=False) - last_bill = sqlalchemy.Column(sqlalchemy.DateTime) + rate = sqlalchemy.Column(sqlalchemy.Numeric(20, 8), nullable=False) + last_bill = sqlalchemy.Column(sqlalchemy.Numeric(24, 8)) properties = sqlalchemy.Column(types.Dict) @@ -163,8 +162,8 @@ class Action(BASE, BileanBase, StateAware, models.TimestampMixin): action = sqlalchemy.Column(sqlalchemy.String(255)) cause = sqlalchemy.Column(sqlalchemy.String(255)) owner = sqlalchemy.Column(sqlalchemy.String(36)) - start_time = sqlalchemy.Column(sqlalchemy.Float(precision='24,8')) - end_time = sqlalchemy.Column(sqlalchemy.Float(precision='24,8')) + start_time = sqlalchemy.Column(sqlalchemy.Numeric(24, 8)) + end_time = sqlalchemy.Column(sqlalchemy.Numeric(24, 8)) timeout = sqlalchemy.Column(sqlalchemy.Integer) inputs = sqlalchemy.Column(types.Dict) outputs = sqlalchemy.Column(types.Dict) @@ -212,10 +211,10 @@ class Consumption(BASE, BileanBase): user_id = sqlalchemy.Column(sqlalchemy.String(36)) resource_id = sqlalchemy.Column(sqlalchemy.String(36)) resource_type = sqlalchemy.Column(sqlalchemy.String(255)) - start_time = sqlalchemy.Column(sqlalchemy.DateTime) - end_time = sqlalchemy.Column(sqlalchemy.DateTime) - rate = sqlalchemy.Column(sqlalchemy.Float) - cost = sqlalchemy.Column(sqlalchemy.Float) + start_time = sqlalchemy.Column(sqlalchemy.Numeric(24, 8)) + end_time = sqlalchemy.Column(sqlalchemy.Numeric(24, 8)) + rate = sqlalchemy.Column(sqlalchemy.Numeric(20, 8)) + cost = sqlalchemy.Column(sqlalchemy.Numeric(20, 8)) meta_data = sqlalchemy.Column(types.Dict) @@ -229,7 +228,7 @@ class Recharge(BASE, BileanBase): user_id = sqlalchemy.Column(sqlalchemy.String(36)) type = sqlalchemy.Column(sqlalchemy.String(255)) timestamp = sqlalchemy.Column(sqlalchemy.DateTime) - value = sqlalchemy.Column(sqlalchemy.Float) + value = sqlalchemy.Column(sqlalchemy.Numeric(20, 8)) meta_data = sqlalchemy.Column(types.Dict) diff --git a/bilean/engine/actions/base.py b/bilean/engine/actions/base.py index 11de5cb..a72382e 100644 --- a/bilean/engine/actions/base.py +++ b/bilean/engine/actions/base.py @@ -21,6 +21,7 @@ from bilean.common import context as req_context from bilean.common import exception from bilean.common.i18n import _ from bilean.common.i18n import _LE +from bilean.common import utils from bilean.db import api as db_api from bilean.engine import event as EVENT @@ -104,8 +105,8 @@ class Action(object): # working on the action. It also serves as a lock. self.owner = kwargs.get('owner', None) - self.start_time = kwargs.get('start_time', None) - self.end_time = kwargs.get('end_time', None) + self.start_time = utils.make_decimal(kwargs.get('start_time', 0)) + self.end_time = utils.make_decimal(kwargs.get('end_time', 0)) # Timeout is a placeholder in case some actions may linger too long self.timeout = kwargs.get('timeout', cfg.CONF.default_action_timeout) @@ -141,8 +142,8 @@ class Action(object): 'action': self.action, 'cause': self.cause, 'owner': self.owner, - 'start_time': self.start_time, - 'end_time': self.end_time, + 'start_time': utils.format_decimal(self.start_time), + 'end_time': utils.format_decimal(self.end_time), 'timeout': self.timeout, 'status': self.status, 'status_reason': self.status_reason, @@ -364,8 +365,8 @@ class Action(object): 'target': self.target, 'cause': self.cause, 'owner': self.owner, - 'start_time': self.start_time, - 'end_time': self.end_time, + 'start_time': utils.dec2str(self.start_time), + 'end_time': utils.dec2str(self.end_time), 'timeout': self.timeout, 'status': self.status, 'status_reason': self.status_reason, diff --git a/bilean/engine/consumption.py b/bilean/engine/consumption.py index 185cf5a..8aa79c7 100644 --- a/bilean/engine/consumption.py +++ b/bilean/engine/consumption.py @@ -30,10 +30,10 @@ class Consumption(object): self.resource_id = kwargs.get('resource_id') self.resource_type = kwargs.get('resource_type') - self.start_time = kwargs.get('start_time') - self.end_time = kwargs.get('end_time') - self.rate = kwargs.get('rate') - self.cost = kwargs.get('cost') + self.start_time = utils.make_decimal(kwargs.get('start_time', 0)) + self.end_time = utils.make_decimal(kwargs.get('end_time', 0)) + self.rate = utils.make_decimal(kwargs.get('rate', 0)) + self.cost = utils.make_decimal(kwargs.get('cost', 0)) self.metadata = kwargs.get('metadata') @classmethod @@ -87,10 +87,10 @@ class Consumption(object): 'user_id': self.user_id, 'resource_id': self.resource_id, 'resource_type': self.resource_type, - 'start_time': self.start_time, - 'end_time': self.end_time, - 'rate': self.rate, - 'cost': self.cost, + 'start_time': utils.format_decimal(self.start_time), + 'end_time': utils.format_decimal(self.end_time), + 'rate': utils.format_decimal(self.rate), + 'cost': utils.format_decimal(self.cost), 'meta_data': self.metadata, } @@ -109,10 +109,10 @@ class Consumption(object): 'user_id': self.user_id, 'resource_id': self.resource_id, 'resource_type': self.resource_type, - 'start_time': utils.format_time(self.start_time), - 'end_time': utils.format_time(self.end_time), - 'rate': self.rate, - 'cost': self.cost, + 'start_time': utils.dec2str(self.start_time), + 'end_time': utils.dec2str(self.end_time), + 'rate': utils.dec2str(self.rate), + 'cost': utils.dec2str(self.cost), 'metadata': self.metadata, } return consumption diff --git a/bilean/engine/flows/flow.py b/bilean/engine/flows/flow.py index e0b5d6b..b0e4c96 100644 --- a/bilean/engine/flows/flow.py +++ b/bilean/engine/flows/flow.py @@ -22,6 +22,7 @@ from taskflow.types import failure as ft from bilean.common import exception from bilean.common.i18n import _LE +from bilean.common import utils from bilean.engine import policy as policy_mod from bilean.engine import user as user_mod from bilean.plugins import base as plugin_base @@ -77,7 +78,7 @@ class CreateResourceTask(task.Task): # Update resource with rule_id and rate resource.rule_id = rule.id - resource.rate = rule.get_price(resource) + resource.rate = utils.make_decimal(rule.get_price(resource)) resource.store(context) def revert(self, context, resource, result, **kwargs): @@ -96,7 +97,7 @@ class UpdateResourceTask(task.Task): old_rate = resource.rate resource.properties = values.get('properties') rule = plugin_base.Rule.load(context, rule_id=resource.rule_id) - resource.rate = rule.get_price(resource) + resource.rate = utils.make_decimal(rule.get_price(resource)) resource.delta_rate = resource.rate - old_rate resource.store(context) @@ -177,8 +178,7 @@ class UpdateUserRateTask(task.Task): def execute(self, context, user_obj, user_bak, resource, *args, **kwargs): user_obj.update_rate(context, resource.delta_rate, - timestamp=resource.last_bill, - delayed_cost=resource.delayed_cost) + timestamp=resource.last_bill) def revert(self, context, user_obj, user_bak, resource, result, *args, **kwargs): diff --git a/bilean/engine/user.py b/bilean/engine/user.py index a232120..2384f38 100644 --- a/bilean/engine/user.py +++ b/bilean/engine/user.py @@ -12,6 +12,7 @@ # under the License. import six +import time from bilean.common import exception from bilean.common.i18n import _ @@ -24,8 +25,8 @@ from bilean.plugins import base as plugin_base from oslo_config import cfg from oslo_log import log as logging -from oslo_utils import timeutils +wallclock = time.time LOG = logging.getLogger(__name__) @@ -38,16 +39,14 @@ class User(object): 'INIT', 'FREE', 'ACTIVE', 'WARNING', 'FREEZE', ) - ALLOW_DELAY_TIME = 10 - def __init__(self, user_id, **kwargs): self.id = user_id self.name = kwargs.get('name') self.policy_id = kwargs.get('policy_id') - self.balance = kwargs.get('balance', 0) - self.rate = kwargs.get('rate', 0.0) + self.balance = utils.make_decimal(kwargs.get('balance', 0)) + self.rate = utils.make_decimal(kwargs.get('rate', 0)) self.credit = kwargs.get('credit', 0) - self.last_bill = kwargs.get('last_bill') + self.last_bill = utils.make_decimal(kwargs.get('last_bill', 0)) self.status = kwargs.get('status', self.INIT) self.status_reason = kwargs.get('status_reason', 'Init user') @@ -65,10 +64,10 @@ class User(object): values = { 'name': self.name, 'policy_id': self.policy_id, - 'balance': self.balance, - 'rate': self.rate, + 'balance': utils.format_decimal(self.balance), + 'rate': utils.format_decimal(self.rate), 'credit': self.credit, - 'last_bill': self.last_bill, + 'last_bill': utils.format_decimal(self.last_bill), 'status': self.status, 'status_reason': self.status_reason, 'created_at': self.created_at, @@ -156,7 +155,7 @@ class User(object): if not realtime: return u if u.rate > 0 and u.status != u.FREEZE: - seconds = (timeutils.utcnow() - u.last_bill).total_seconds() + seconds = utils.make_decimal(wallclock()) - u.last_bill u.balance -= u.rate * seconds return u @@ -194,10 +193,10 @@ class User(object): 'id': self.id, 'name': self.name, 'policy_id': self.policy_id, - 'balance': self.balance, - 'rate': self.rate, + 'balance': utils.dec2str(self.balance), + 'rate': utils.dec2str(self.rate), 'credit': self.credit, - 'last_bill': utils.format_time(self.last_bill), + 'last_bill': utils.dec2str(self.last_bill), 'status': self.status, 'status_reason': self.status_reason, 'created_at': utils.format_time(self.created_at), @@ -213,7 +212,7 @@ class User(object): self.status_reason = reason self.store(context) - def update_rate(self, context, delta_rate, timestamp=None, delayed_cost=0): + def update_rate(self, context, delta_rate, timestamp=None): """Update user's rate and update user status. :param context: The request context. @@ -223,18 +222,15 @@ class User(object): adjust balance by delayed_cost. """ - if delta_rate == 0 and delayed_cost == 0: - return - # Settle account before update rate - self._settle_account(context, timestamp=timestamp, - delayed_cost=delayed_cost) + self._settle_account(context, delta_rate=delta_rate, + timestamp=timestamp) old_rate = self.rate new_rate = old_rate + delta_rate if old_rate == 0 and new_rate > 0: # Set last_bill when status change to 'ACTIVE' from 'FREE' - self.last_bill = timeutils.utcnow() + self.last_bill = timestamp or wallclock() reason = _("Status change to 'ACTIVE' cause resource creation.") self.status = self.ACTIVE self.status_reason = reason @@ -262,7 +258,7 @@ class User(object): param timestamp: Record when recharge action occurs. param metadata: Some other keyword. """ - self.balance += value + self.balance += utils.make_decimal(value) if self.status == self.INIT and self.balance > 0: self.status = self.FREE self.status_reason = "Recharged" @@ -293,31 +289,31 @@ class User(object): 'bilean.scheduler.cron_scheduler', group='scheduler') prior_notify_time = cfg.CONF.scheduler.prior_notify_time * 3600 - rest_usage = prior_notify_time * self.rate - if self.balance > rest_usage: - return False - return True + rest_usage = utils.make_decimal(prior_notify_time) * self.rate + return self.balance < rest_usage def do_delete(self, context): db_api.user_delete(context, self.id) return True - def _settle_account(self, context, timestamp=None, delayed_cost=0): - if self.rate == 0 and delayed_cost == 0: + def _settle_account(self, context, delta_rate=0, timestamp=None): + if self.rate == 0: LOG.info(_LI("Ignore settlement action because user is in '%s' " "status."), self.status) return - # Calculate user's cost before last_bill and now - cost = 0 - if self.rate > 0 and self.last_bill: - timestamp = timestamp or timeutils.utcnow() - total_seconds = (timestamp - self.last_bill).total_seconds() - cost = self.rate * total_seconds + # Calculate user's cost between last_bill and now + now = utils.make_decimal(wallclock()) + delayed_cost = 0 + if delta_rate != 0: + delayed_seconds = now - timestamp + delayed_cost = delayed_seconds * utils.make_decimal(delta_rate) + usage_seconds = now - self.last_bill + cost = self.rate * usage_seconds total_cost = cost + delayed_cost self.balance -= total_cost - self.last_bill = timestamp + self.last_bill = now def settle_account(self, context, task=None): '''Settle account for user.''' diff --git a/bilean/plugins/base.py b/bilean/plugins/base.py index 53874e1..b45eaaa 100644 --- a/bilean/plugins/base.py +++ b/bilean/plugins/base.py @@ -10,6 +10,8 @@ # License for the specific language governing permissions and limitations # under the License. +import time + from oslo_config import cfg from oslo_log import log as logging from oslo_utils import timeutils @@ -22,6 +24,7 @@ from bilean.db import api as db_api from bilean.engine import consumption as consumption_mod from bilean.engine import environment +wallclock = time.time LOG = logging.getLogger(__name__) @@ -233,8 +236,6 @@ class Resource(object): something else. """ - ALLOW_DELAY_TIME = 10 - def __new__(cls, id, user_id, res_type, properties, **kwargs): """Create a new resource of the appropriate class. @@ -258,8 +259,8 @@ class Resource(object): self.properties = properties self.rule_id = kwargs.get('rule_id') - self.rate = kwargs.get('rate', 0) - self.last_bill = kwargs.get('last_bill') + self.rate = utils.make_decimal(kwargs.get('rate', 0)) + self.last_bill = utils.make_decimal(kwargs.get('last_bill', 0)) self.created_at = kwargs.get('created_at') self.updated_at = kwargs.get('updated_at') @@ -267,7 +268,6 @@ class Resource(object): # Properties pass to user to help settle account, not store to db self.delta_rate = 0 - self.delayed_cost = 0 self.consumption = None def store(self, context): @@ -278,8 +278,8 @@ class Resource(object): 'resource_type': self.resource_type, 'properties': self.properties, 'rule_id': self.rule_id, - 'rate': self.rate, - 'last_bill': self.last_bill, + 'rate': utils.format_decimal(self.rate), + 'last_bill': utils.format_decimal(self.last_bill), 'created_at': self.created_at, 'updated_at': self.updated_at, 'deleted_at': self.deleted_at, @@ -304,19 +304,13 @@ class Resource(object): self.created_at = resource.created_at return - now = timeutils.utcnow() - self.last_bill = now + self.last_bill = utils.make_decimal(wallclock()) create_time = self.properties.get('created_at') if create_time is not None: - created_at = timeutils.parse_strtime(create_time) - delayed_seconds = (now - created_at).total_seconds() - # Engine handle resource creation is delayed because of something, - # we suppose less than ALLOW_DELAY_TIME is acceptable. - if delayed_seconds > self.ALLOW_DELAY_TIME: - self.delayed_cost = self.delta_rate * delayed_seconds - self.last_bill = created_at + sec = utils.format_time_to_seconds(create_time) + self.last_bill = utils.make_decimal(sec) - values.update(last_bill=self.last_bill) + values.update(last_bill=utils.format_decimal(self.last_bill)) resource = db_api.resource_create(context, values) self.created_at = resource.created_at @@ -326,19 +320,14 @@ class Resource(object): return update_time = self.properties.get('updated_at') - now = timeutils.utcnow() - updated_at = now + updated_at = utils.make_decimal(wallclock()) if update_time is not None: - updated_at = timeutils.parse_strtime(update_time) - delayed_seconds = (now - updated_at).total_seconds() - # Engine handle resource update is delayed because of something, - # we suppose less than ALLOW_DELAY_TIME is acceptable. - if delayed_seconds > self.ALLOW_DELAY_TIME: - self.delayed_cost = self.delta_rate * delayed_seconds + sec = utils.format_time_to_seconds(update_time) + updated_at = utils.make_decimal(sec) # Generate consumption between lass bill and update time old_rate = self.rate - self.delta_rate - cost = (updated_at - self.last_bill).total_seconds() * old_rate + cost = (updated_at - self.last_bill) * old_rate params = {'resource_id': self.id, 'resource_type': self.resource_type, 'start_time': self.last_bill, @@ -349,7 +338,7 @@ class Resource(object): self.consumption = consumption_mod.Consumption(self.user_id, **params) self.last_bill = updated_at - values.update(last_bill=updated_at) + values.update(last_bill=utils.format_decimal(updated_at)) db_api.resource_update(context, self.id, values) def _delete(self, context, soft_delete=True): @@ -359,18 +348,13 @@ class Resource(object): return delete_time = self.properties.get('deleted_at') - now = timeutils.utcnow() - deleted_at = now + deleted_at = utils.make_decimal(wallclock()) if delete_time is not None: - deleted_at = timeutils.parse_strtime(delete_time) - delayed_seconds = (now - deleted_at).total_seconds() - # Engine handle resource deletion is delayed because of something, - # we suppose less than ALLOW_DELAY_TIME is acceptable. - if delayed_seconds > self.ALLOW_DELAY_TIME: - self.delayed_cost = self.delta_rate * delayed_seconds + sec = utils.format_time_to_seconds(delete_time) + deleted_at = utils.make_decimal(sec) # Generate consumption between lass bill and delete time - cost = (deleted_at - self.last_bill).total_seconds() * self.rate + cost = (deleted_at - self.last_bill) * self.rate params = {'resource_id': self.id, 'resource_type': self.resource_type, 'start_time': self.last_bill, @@ -447,8 +431,8 @@ class Resource(object): 'resource_type': self.resource_type, 'properties': self.properties, 'rule_id': self.rule_id, - 'rate': self.rate, - 'last_bill': utils.format_time(self.last_bill), + 'rate': utils.dec2str(self.rate), + 'last_bill': utils.dec2str(self.last_bill), 'created_at': utils.format_time(self.created_at), 'updated_at': utils.format_time(self.updated_at), 'deleted_at': utils.format_time(self.deleted_at), diff --git a/bilean/scheduler/cron_scheduler.py b/bilean/scheduler/cron_scheduler.py index cd12cb3..10041b1 100644 --- a/bilean/scheduler/cron_scheduler.py +++ b/bilean/scheduler/cron_scheduler.py @@ -179,7 +179,7 @@ class CronScheduler(object): def _add_notify_job(self, user): if user.rate == 0: return False - total_seconds = user.balance / user.rate + total_seconds = float(user.balance / user.rate) prior_notify_time = cfg.CONF.scheduler.prior_notify_time * 3600 notify_seconds = total_seconds - prior_notify_time notify_seconds = notify_seconds if notify_seconds > 0 else 0 @@ -198,7 +198,7 @@ class CronScheduler(object): def _add_freeze_job(self, user): if user.rate == 0: return False - total_seconds = user.balance / user.rate + total_seconds = float(user.balance / user.rate) run_date = timeutils.utcnow() + timedelta(seconds=total_seconds) job_params = {'run_date': run_date} job_id = self._generate_job_id(user.id, self.FREEZE)