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
This commit is contained in:
parent
5025328bc1
commit
ff26ed98e7
@ -15,7 +15,9 @@ Utilities module.
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
import decimal
|
||||||
import random
|
import random
|
||||||
|
import six
|
||||||
import string
|
import string
|
||||||
|
|
||||||
from cryptography.fernet import Fernet
|
from cryptography.fernet import Fernet
|
||||||
@ -27,6 +29,7 @@ from oslo_config import cfg
|
|||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_utils import encodeutils
|
from oslo_utils import encodeutils
|
||||||
from oslo_utils import strutils
|
from oslo_utils import strutils
|
||||||
|
from oslo_utils import timeutils
|
||||||
|
|
||||||
from bilean.common import exception
|
from bilean.common import exception
|
||||||
from bilean.common.i18n import _
|
from bilean.common.i18n import _
|
||||||
@ -156,3 +159,38 @@ def format_time(value):
|
|||||||
value = value.replace(microsecond=0)
|
value = value.replace(microsecond=0)
|
||||||
value = value.isoformat()
|
value = value.isoformat()
|
||||||
return value
|
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')))
|
||||||
|
@ -29,10 +29,10 @@ def upgrade(migrate_engine):
|
|||||||
sqlalchemy.String(36),
|
sqlalchemy.String(36),
|
||||||
sqlalchemy.ForeignKey('policy.id'),
|
sqlalchemy.ForeignKey('policy.id'),
|
||||||
nullable=True),
|
nullable=True),
|
||||||
sqlalchemy.Column('balance', sqlalchemy.Float),
|
sqlalchemy.Column('balance', sqlalchemy.Numeric(20, 8)),
|
||||||
sqlalchemy.Column('rate', sqlalchemy.Float),
|
sqlalchemy.Column('rate', sqlalchemy.Numeric(20, 8)),
|
||||||
sqlalchemy.Column('credit', sqlalchemy.Integer),
|
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', sqlalchemy.String(255)),
|
||||||
sqlalchemy.Column('status_reason', sqlalchemy.Text),
|
sqlalchemy.Column('status_reason', sqlalchemy.Text),
|
||||||
sqlalchemy.Column('created_at', sqlalchemy.DateTime),
|
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('rule_id', sqlalchemy.String(36), nullable=False),
|
||||||
sqlalchemy.Column('resource_type', sqlalchemy.String(36),
|
sqlalchemy.Column('resource_type', sqlalchemy.String(36),
|
||||||
nullable=False),
|
nullable=False),
|
||||||
sqlalchemy.Column('last_bill', sqlalchemy.DateTime),
|
sqlalchemy.Column('last_bill', sqlalchemy.Numeric(24, 8)),
|
||||||
sqlalchemy.Column('properties', types.Dict),
|
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('created_at', sqlalchemy.DateTime),
|
||||||
sqlalchemy.Column('updated_at', sqlalchemy.DateTime),
|
sqlalchemy.Column('updated_at', sqlalchemy.DateTime),
|
||||||
sqlalchemy.Column('deleted_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('user_id', sqlalchemy.String(36)),
|
||||||
sqlalchemy.Column('resource_id', sqlalchemy.String(36)),
|
sqlalchemy.Column('resource_id', sqlalchemy.String(36)),
|
||||||
sqlalchemy.Column('resource_type', sqlalchemy.String(255)),
|
sqlalchemy.Column('resource_type', sqlalchemy.String(255)),
|
||||||
sqlalchemy.Column('start_time', sqlalchemy.DateTime),
|
sqlalchemy.Column('start_time', sqlalchemy.Numeric(24, 8)),
|
||||||
sqlalchemy.Column('end_time', sqlalchemy.DateTime),
|
sqlalchemy.Column('end_time', sqlalchemy.Numeric(24, 8)),
|
||||||
sqlalchemy.Column('rate', sqlalchemy.Float),
|
sqlalchemy.Column('rate', sqlalchemy.Numeric(20, 8)),
|
||||||
sqlalchemy.Column('cost', sqlalchemy.Float),
|
sqlalchemy.Column('cost', sqlalchemy.Numeric(20, 8)),
|
||||||
sqlalchemy.Column('meta_data', types.Dict),
|
sqlalchemy.Column('meta_data', types.Dict),
|
||||||
mysql_engine='InnoDB',
|
mysql_engine='InnoDB',
|
||||||
mysql_charset='utf8'
|
mysql_charset='utf8'
|
||||||
@ -134,7 +134,7 @@ def upgrade(migrate_engine):
|
|||||||
sqlalchemy.Column('user_id', sqlalchemy.String(36)),
|
sqlalchemy.Column('user_id', sqlalchemy.String(36)),
|
||||||
sqlalchemy.Column('type', sqlalchemy.String(255)),
|
sqlalchemy.Column('type', sqlalchemy.String(255)),
|
||||||
sqlalchemy.Column('timestamp', sqlalchemy.DateTime),
|
sqlalchemy.Column('timestamp', sqlalchemy.DateTime),
|
||||||
sqlalchemy.Column('value', sqlalchemy.Float),
|
sqlalchemy.Column('value', sqlalchemy.Numeric(20, 8)),
|
||||||
sqlalchemy.Column('meta_data', types.Dict),
|
sqlalchemy.Column('meta_data', types.Dict),
|
||||||
mysql_engine='InnoDB',
|
mysql_engine='InnoDB',
|
||||||
mysql_charset='utf8'
|
mysql_charset='utf8'
|
||||||
@ -150,8 +150,8 @@ def upgrade(migrate_engine):
|
|||||||
sqlalchemy.Column('action', sqlalchemy.String(255)),
|
sqlalchemy.Column('action', sqlalchemy.String(255)),
|
||||||
sqlalchemy.Column('cause', sqlalchemy.String(255)),
|
sqlalchemy.Column('cause', sqlalchemy.String(255)),
|
||||||
sqlalchemy.Column('owner', sqlalchemy.String(36)),
|
sqlalchemy.Column('owner', sqlalchemy.String(36)),
|
||||||
sqlalchemy.Column('start_time', sqlalchemy.Float(precision='24,8')),
|
sqlalchemy.Column('start_time', sqlalchemy.Numeric(24, 8)),
|
||||||
sqlalchemy.Column('end_time', sqlalchemy.Float(precision='24,8')),
|
sqlalchemy.Column('end_time', sqlalchemy.Numeric(24, 8)),
|
||||||
sqlalchemy.Column('timeout', sqlalchemy.Integer),
|
sqlalchemy.Column('timeout', sqlalchemy.Integer),
|
||||||
sqlalchemy.Column('inputs', types.Dict),
|
sqlalchemy.Column('inputs', types.Dict),
|
||||||
sqlalchemy.Column('outputs', types.Dict),
|
sqlalchemy.Column('outputs', types.Dict),
|
||||||
|
@ -102,11 +102,10 @@ class User(BASE, BileanBase, SoftDelete, StateAware, models.TimestampMixin):
|
|||||||
sqlalchemy.String(36),
|
sqlalchemy.String(36),
|
||||||
sqlalchemy.ForeignKey('policy.id'),
|
sqlalchemy.ForeignKey('policy.id'),
|
||||||
nullable=True)
|
nullable=True)
|
||||||
balance = sqlalchemy.Column(sqlalchemy.Float, default=0.0)
|
balance = sqlalchemy.Column(sqlalchemy.Numeric(20, 8), default=0.0)
|
||||||
rate = sqlalchemy.Column(sqlalchemy.Float, default=0.0)
|
rate = sqlalchemy.Column(sqlalchemy.Numeric(20, 8), default=0.0)
|
||||||
credit = sqlalchemy.Column(sqlalchemy.Integer, default=0)
|
credit = sqlalchemy.Column(sqlalchemy.Integer, default=0)
|
||||||
last_bill = sqlalchemy.Column(
|
last_bill = sqlalchemy.Column(sqlalchemy.Numeric(24, 8))
|
||||||
sqlalchemy.DateTime, default=timeutils.utcnow())
|
|
||||||
|
|
||||||
|
|
||||||
class Policy(BASE, BileanBase, SoftDelete, models.TimestampMixin):
|
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)
|
rule_id = sqlalchemy.Column(sqlalchemy.String(36), nullable=True)
|
||||||
user = relationship(User, backref=backref('resources'))
|
user = relationship(User, backref=backref('resources'))
|
||||||
resource_type = sqlalchemy.Column(sqlalchemy.String(36), nullable=False)
|
resource_type = sqlalchemy.Column(sqlalchemy.String(36), nullable=False)
|
||||||
rate = sqlalchemy.Column(sqlalchemy.Float, nullable=False)
|
rate = sqlalchemy.Column(sqlalchemy.Numeric(20, 8), nullable=False)
|
||||||
last_bill = sqlalchemy.Column(sqlalchemy.DateTime)
|
last_bill = sqlalchemy.Column(sqlalchemy.Numeric(24, 8))
|
||||||
properties = sqlalchemy.Column(types.Dict)
|
properties = sqlalchemy.Column(types.Dict)
|
||||||
|
|
||||||
|
|
||||||
@ -163,8 +162,8 @@ class Action(BASE, BileanBase, StateAware, models.TimestampMixin):
|
|||||||
action = sqlalchemy.Column(sqlalchemy.String(255))
|
action = sqlalchemy.Column(sqlalchemy.String(255))
|
||||||
cause = sqlalchemy.Column(sqlalchemy.String(255))
|
cause = sqlalchemy.Column(sqlalchemy.String(255))
|
||||||
owner = sqlalchemy.Column(sqlalchemy.String(36))
|
owner = sqlalchemy.Column(sqlalchemy.String(36))
|
||||||
start_time = sqlalchemy.Column(sqlalchemy.Float(precision='24,8'))
|
start_time = sqlalchemy.Column(sqlalchemy.Numeric(24, 8))
|
||||||
end_time = sqlalchemy.Column(sqlalchemy.Float(precision='24,8'))
|
end_time = sqlalchemy.Column(sqlalchemy.Numeric(24, 8))
|
||||||
timeout = sqlalchemy.Column(sqlalchemy.Integer)
|
timeout = sqlalchemy.Column(sqlalchemy.Integer)
|
||||||
inputs = sqlalchemy.Column(types.Dict)
|
inputs = sqlalchemy.Column(types.Dict)
|
||||||
outputs = sqlalchemy.Column(types.Dict)
|
outputs = sqlalchemy.Column(types.Dict)
|
||||||
@ -212,10 +211,10 @@ class Consumption(BASE, BileanBase):
|
|||||||
user_id = sqlalchemy.Column(sqlalchemy.String(36))
|
user_id = sqlalchemy.Column(sqlalchemy.String(36))
|
||||||
resource_id = sqlalchemy.Column(sqlalchemy.String(36))
|
resource_id = sqlalchemy.Column(sqlalchemy.String(36))
|
||||||
resource_type = sqlalchemy.Column(sqlalchemy.String(255))
|
resource_type = sqlalchemy.Column(sqlalchemy.String(255))
|
||||||
start_time = sqlalchemy.Column(sqlalchemy.DateTime)
|
start_time = sqlalchemy.Column(sqlalchemy.Numeric(24, 8))
|
||||||
end_time = sqlalchemy.Column(sqlalchemy.DateTime)
|
end_time = sqlalchemy.Column(sqlalchemy.Numeric(24, 8))
|
||||||
rate = sqlalchemy.Column(sqlalchemy.Float)
|
rate = sqlalchemy.Column(sqlalchemy.Numeric(20, 8))
|
||||||
cost = sqlalchemy.Column(sqlalchemy.Float)
|
cost = sqlalchemy.Column(sqlalchemy.Numeric(20, 8))
|
||||||
meta_data = sqlalchemy.Column(types.Dict)
|
meta_data = sqlalchemy.Column(types.Dict)
|
||||||
|
|
||||||
|
|
||||||
@ -229,7 +228,7 @@ class Recharge(BASE, BileanBase):
|
|||||||
user_id = sqlalchemy.Column(sqlalchemy.String(36))
|
user_id = sqlalchemy.Column(sqlalchemy.String(36))
|
||||||
type = sqlalchemy.Column(sqlalchemy.String(255))
|
type = sqlalchemy.Column(sqlalchemy.String(255))
|
||||||
timestamp = sqlalchemy.Column(sqlalchemy.DateTime)
|
timestamp = sqlalchemy.Column(sqlalchemy.DateTime)
|
||||||
value = sqlalchemy.Column(sqlalchemy.Float)
|
value = sqlalchemy.Column(sqlalchemy.Numeric(20, 8))
|
||||||
meta_data = sqlalchemy.Column(types.Dict)
|
meta_data = sqlalchemy.Column(types.Dict)
|
||||||
|
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ from bilean.common import context as req_context
|
|||||||
from bilean.common import exception
|
from bilean.common import exception
|
||||||
from bilean.common.i18n import _
|
from bilean.common.i18n import _
|
||||||
from bilean.common.i18n import _LE
|
from bilean.common.i18n import _LE
|
||||||
|
from bilean.common import utils
|
||||||
from bilean.db import api as db_api
|
from bilean.db import api as db_api
|
||||||
from bilean.engine import event as EVENT
|
from bilean.engine import event as EVENT
|
||||||
|
|
||||||
@ -104,8 +105,8 @@ class Action(object):
|
|||||||
# working on the action. It also serves as a lock.
|
# working on the action. It also serves as a lock.
|
||||||
self.owner = kwargs.get('owner', None)
|
self.owner = kwargs.get('owner', None)
|
||||||
|
|
||||||
self.start_time = kwargs.get('start_time', None)
|
self.start_time = utils.make_decimal(kwargs.get('start_time', 0))
|
||||||
self.end_time = kwargs.get('end_time', None)
|
self.end_time = utils.make_decimal(kwargs.get('end_time', 0))
|
||||||
|
|
||||||
# Timeout is a placeholder in case some actions may linger too long
|
# Timeout is a placeholder in case some actions may linger too long
|
||||||
self.timeout = kwargs.get('timeout', cfg.CONF.default_action_timeout)
|
self.timeout = kwargs.get('timeout', cfg.CONF.default_action_timeout)
|
||||||
@ -141,8 +142,8 @@ class Action(object):
|
|||||||
'action': self.action,
|
'action': self.action,
|
||||||
'cause': self.cause,
|
'cause': self.cause,
|
||||||
'owner': self.owner,
|
'owner': self.owner,
|
||||||
'start_time': self.start_time,
|
'start_time': utils.format_decimal(self.start_time),
|
||||||
'end_time': self.end_time,
|
'end_time': utils.format_decimal(self.end_time),
|
||||||
'timeout': self.timeout,
|
'timeout': self.timeout,
|
||||||
'status': self.status,
|
'status': self.status,
|
||||||
'status_reason': self.status_reason,
|
'status_reason': self.status_reason,
|
||||||
@ -364,8 +365,8 @@ class Action(object):
|
|||||||
'target': self.target,
|
'target': self.target,
|
||||||
'cause': self.cause,
|
'cause': self.cause,
|
||||||
'owner': self.owner,
|
'owner': self.owner,
|
||||||
'start_time': self.start_time,
|
'start_time': utils.dec2str(self.start_time),
|
||||||
'end_time': self.end_time,
|
'end_time': utils.dec2str(self.end_time),
|
||||||
'timeout': self.timeout,
|
'timeout': self.timeout,
|
||||||
'status': self.status,
|
'status': self.status,
|
||||||
'status_reason': self.status_reason,
|
'status_reason': self.status_reason,
|
||||||
|
@ -30,10 +30,10 @@ class Consumption(object):
|
|||||||
self.resource_id = kwargs.get('resource_id')
|
self.resource_id = kwargs.get('resource_id')
|
||||||
self.resource_type = kwargs.get('resource_type')
|
self.resource_type = kwargs.get('resource_type')
|
||||||
|
|
||||||
self.start_time = kwargs.get('start_time')
|
self.start_time = utils.make_decimal(kwargs.get('start_time', 0))
|
||||||
self.end_time = kwargs.get('end_time')
|
self.end_time = utils.make_decimal(kwargs.get('end_time', 0))
|
||||||
self.rate = kwargs.get('rate')
|
self.rate = utils.make_decimal(kwargs.get('rate', 0))
|
||||||
self.cost = kwargs.get('cost')
|
self.cost = utils.make_decimal(kwargs.get('cost', 0))
|
||||||
self.metadata = kwargs.get('metadata')
|
self.metadata = kwargs.get('metadata')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -87,10 +87,10 @@ class Consumption(object):
|
|||||||
'user_id': self.user_id,
|
'user_id': self.user_id,
|
||||||
'resource_id': self.resource_id,
|
'resource_id': self.resource_id,
|
||||||
'resource_type': self.resource_type,
|
'resource_type': self.resource_type,
|
||||||
'start_time': self.start_time,
|
'start_time': utils.format_decimal(self.start_time),
|
||||||
'end_time': self.end_time,
|
'end_time': utils.format_decimal(self.end_time),
|
||||||
'rate': self.rate,
|
'rate': utils.format_decimal(self.rate),
|
||||||
'cost': self.cost,
|
'cost': utils.format_decimal(self.cost),
|
||||||
'meta_data': self.metadata,
|
'meta_data': self.metadata,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,10 +109,10 @@ class Consumption(object):
|
|||||||
'user_id': self.user_id,
|
'user_id': self.user_id,
|
||||||
'resource_id': self.resource_id,
|
'resource_id': self.resource_id,
|
||||||
'resource_type': self.resource_type,
|
'resource_type': self.resource_type,
|
||||||
'start_time': utils.format_time(self.start_time),
|
'start_time': utils.dec2str(self.start_time),
|
||||||
'end_time': utils.format_time(self.end_time),
|
'end_time': utils.dec2str(self.end_time),
|
||||||
'rate': self.rate,
|
'rate': utils.dec2str(self.rate),
|
||||||
'cost': self.cost,
|
'cost': utils.dec2str(self.cost),
|
||||||
'metadata': self.metadata,
|
'metadata': self.metadata,
|
||||||
}
|
}
|
||||||
return consumption
|
return consumption
|
||||||
|
@ -22,6 +22,7 @@ from taskflow.types import failure as ft
|
|||||||
|
|
||||||
from bilean.common import exception
|
from bilean.common import exception
|
||||||
from bilean.common.i18n import _LE
|
from bilean.common.i18n import _LE
|
||||||
|
from bilean.common import utils
|
||||||
from bilean.engine import policy as policy_mod
|
from bilean.engine import policy as policy_mod
|
||||||
from bilean.engine import user as user_mod
|
from bilean.engine import user as user_mod
|
||||||
from bilean.plugins import base as plugin_base
|
from bilean.plugins import base as plugin_base
|
||||||
@ -77,7 +78,7 @@ class CreateResourceTask(task.Task):
|
|||||||
|
|
||||||
# Update resource with rule_id and rate
|
# Update resource with rule_id and rate
|
||||||
resource.rule_id = rule.id
|
resource.rule_id = rule.id
|
||||||
resource.rate = rule.get_price(resource)
|
resource.rate = utils.make_decimal(rule.get_price(resource))
|
||||||
resource.store(context)
|
resource.store(context)
|
||||||
|
|
||||||
def revert(self, context, resource, result, **kwargs):
|
def revert(self, context, resource, result, **kwargs):
|
||||||
@ -96,7 +97,7 @@ class UpdateResourceTask(task.Task):
|
|||||||
old_rate = resource.rate
|
old_rate = resource.rate
|
||||||
resource.properties = values.get('properties')
|
resource.properties = values.get('properties')
|
||||||
rule = plugin_base.Rule.load(context, rule_id=resource.rule_id)
|
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.delta_rate = resource.rate - old_rate
|
||||||
resource.store(context)
|
resource.store(context)
|
||||||
|
|
||||||
@ -177,8 +178,7 @@ class UpdateUserRateTask(task.Task):
|
|||||||
|
|
||||||
def execute(self, context, user_obj, user_bak, resource, *args, **kwargs):
|
def execute(self, context, user_obj, user_bak, resource, *args, **kwargs):
|
||||||
user_obj.update_rate(context, resource.delta_rate,
|
user_obj.update_rate(context, resource.delta_rate,
|
||||||
timestamp=resource.last_bill,
|
timestamp=resource.last_bill)
|
||||||
delayed_cost=resource.delayed_cost)
|
|
||||||
|
|
||||||
def revert(self, context, user_obj, user_bak, resource, result,
|
def revert(self, context, user_obj, user_bak, resource, result,
|
||||||
*args, **kwargs):
|
*args, **kwargs):
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import six
|
import six
|
||||||
|
import time
|
||||||
|
|
||||||
from bilean.common import exception
|
from bilean.common import exception
|
||||||
from bilean.common.i18n import _
|
from bilean.common.i18n import _
|
||||||
@ -24,8 +25,8 @@ from bilean.plugins import base as plugin_base
|
|||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_utils import timeutils
|
|
||||||
|
|
||||||
|
wallclock = time.time
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@ -38,16 +39,14 @@ class User(object):
|
|||||||
'INIT', 'FREE', 'ACTIVE', 'WARNING', 'FREEZE',
|
'INIT', 'FREE', 'ACTIVE', 'WARNING', 'FREEZE',
|
||||||
)
|
)
|
||||||
|
|
||||||
ALLOW_DELAY_TIME = 10
|
|
||||||
|
|
||||||
def __init__(self, user_id, **kwargs):
|
def __init__(self, user_id, **kwargs):
|
||||||
self.id = user_id
|
self.id = user_id
|
||||||
self.name = kwargs.get('name')
|
self.name = kwargs.get('name')
|
||||||
self.policy_id = kwargs.get('policy_id')
|
self.policy_id = kwargs.get('policy_id')
|
||||||
self.balance = kwargs.get('balance', 0)
|
self.balance = utils.make_decimal(kwargs.get('balance', 0))
|
||||||
self.rate = kwargs.get('rate', 0.0)
|
self.rate = utils.make_decimal(kwargs.get('rate', 0))
|
||||||
self.credit = kwargs.get('credit', 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 = kwargs.get('status', self.INIT)
|
||||||
self.status_reason = kwargs.get('status_reason', 'Init user')
|
self.status_reason = kwargs.get('status_reason', 'Init user')
|
||||||
@ -65,10 +64,10 @@ class User(object):
|
|||||||
values = {
|
values = {
|
||||||
'name': self.name,
|
'name': self.name,
|
||||||
'policy_id': self.policy_id,
|
'policy_id': self.policy_id,
|
||||||
'balance': self.balance,
|
'balance': utils.format_decimal(self.balance),
|
||||||
'rate': self.rate,
|
'rate': utils.format_decimal(self.rate),
|
||||||
'credit': self.credit,
|
'credit': self.credit,
|
||||||
'last_bill': self.last_bill,
|
'last_bill': utils.format_decimal(self.last_bill),
|
||||||
'status': self.status,
|
'status': self.status,
|
||||||
'status_reason': self.status_reason,
|
'status_reason': self.status_reason,
|
||||||
'created_at': self.created_at,
|
'created_at': self.created_at,
|
||||||
@ -156,7 +155,7 @@ class User(object):
|
|||||||
if not realtime:
|
if not realtime:
|
||||||
return u
|
return u
|
||||||
if u.rate > 0 and u.status != u.FREEZE:
|
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
|
u.balance -= u.rate * seconds
|
||||||
return u
|
return u
|
||||||
|
|
||||||
@ -194,10 +193,10 @@ class User(object):
|
|||||||
'id': self.id,
|
'id': self.id,
|
||||||
'name': self.name,
|
'name': self.name,
|
||||||
'policy_id': self.policy_id,
|
'policy_id': self.policy_id,
|
||||||
'balance': self.balance,
|
'balance': utils.dec2str(self.balance),
|
||||||
'rate': self.rate,
|
'rate': utils.dec2str(self.rate),
|
||||||
'credit': self.credit,
|
'credit': self.credit,
|
||||||
'last_bill': utils.format_time(self.last_bill),
|
'last_bill': utils.dec2str(self.last_bill),
|
||||||
'status': self.status,
|
'status': self.status,
|
||||||
'status_reason': self.status_reason,
|
'status_reason': self.status_reason,
|
||||||
'created_at': utils.format_time(self.created_at),
|
'created_at': utils.format_time(self.created_at),
|
||||||
@ -213,7 +212,7 @@ class User(object):
|
|||||||
self.status_reason = reason
|
self.status_reason = reason
|
||||||
self.store(context)
|
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.
|
"""Update user's rate and update user status.
|
||||||
|
|
||||||
:param context: The request context.
|
:param context: The request context.
|
||||||
@ -223,18 +222,15 @@ class User(object):
|
|||||||
adjust balance by delayed_cost.
|
adjust balance by delayed_cost.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if delta_rate == 0 and delayed_cost == 0:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Settle account before update rate
|
# Settle account before update rate
|
||||||
self._settle_account(context, timestamp=timestamp,
|
self._settle_account(context, delta_rate=delta_rate,
|
||||||
delayed_cost=delayed_cost)
|
timestamp=timestamp)
|
||||||
|
|
||||||
old_rate = self.rate
|
old_rate = self.rate
|
||||||
new_rate = old_rate + delta_rate
|
new_rate = old_rate + delta_rate
|
||||||
if old_rate == 0 and new_rate > 0:
|
if old_rate == 0 and new_rate > 0:
|
||||||
# Set last_bill when status change to 'ACTIVE' from 'FREE'
|
# 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.")
|
reason = _("Status change to 'ACTIVE' cause resource creation.")
|
||||||
self.status = self.ACTIVE
|
self.status = self.ACTIVE
|
||||||
self.status_reason = reason
|
self.status_reason = reason
|
||||||
@ -262,7 +258,7 @@ class User(object):
|
|||||||
param timestamp: Record when recharge action occurs.
|
param timestamp: Record when recharge action occurs.
|
||||||
param metadata: Some other keyword.
|
param metadata: Some other keyword.
|
||||||
"""
|
"""
|
||||||
self.balance += value
|
self.balance += utils.make_decimal(value)
|
||||||
if self.status == self.INIT and self.balance > 0:
|
if self.status == self.INIT and self.balance > 0:
|
||||||
self.status = self.FREE
|
self.status = self.FREE
|
||||||
self.status_reason = "Recharged"
|
self.status_reason = "Recharged"
|
||||||
@ -293,31 +289,31 @@ class User(object):
|
|||||||
'bilean.scheduler.cron_scheduler',
|
'bilean.scheduler.cron_scheduler',
|
||||||
group='scheduler')
|
group='scheduler')
|
||||||
prior_notify_time = cfg.CONF.scheduler.prior_notify_time * 3600
|
prior_notify_time = cfg.CONF.scheduler.prior_notify_time * 3600
|
||||||
rest_usage = prior_notify_time * self.rate
|
rest_usage = utils.make_decimal(prior_notify_time) * self.rate
|
||||||
if self.balance > rest_usage:
|
return self.balance < rest_usage
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def do_delete(self, context):
|
def do_delete(self, context):
|
||||||
db_api.user_delete(context, self.id)
|
db_api.user_delete(context, self.id)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _settle_account(self, context, timestamp=None, delayed_cost=0):
|
def _settle_account(self, context, delta_rate=0, timestamp=None):
|
||||||
if self.rate == 0 and delayed_cost == 0:
|
if self.rate == 0:
|
||||||
LOG.info(_LI("Ignore settlement action because user is in '%s' "
|
LOG.info(_LI("Ignore settlement action because user is in '%s' "
|
||||||
"status."), self.status)
|
"status."), self.status)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Calculate user's cost before last_bill and now
|
# Calculate user's cost between last_bill and now
|
||||||
cost = 0
|
now = utils.make_decimal(wallclock())
|
||||||
if self.rate > 0 and self.last_bill:
|
delayed_cost = 0
|
||||||
timestamp = timestamp or timeutils.utcnow()
|
if delta_rate != 0:
|
||||||
total_seconds = (timestamp - self.last_bill).total_seconds()
|
delayed_seconds = now - timestamp
|
||||||
cost = self.rate * total_seconds
|
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
|
total_cost = cost + delayed_cost
|
||||||
|
|
||||||
self.balance -= total_cost
|
self.balance -= total_cost
|
||||||
self.last_bill = timestamp
|
self.last_bill = now
|
||||||
|
|
||||||
def settle_account(self, context, task=None):
|
def settle_account(self, context, task=None):
|
||||||
'''Settle account for user.'''
|
'''Settle account for user.'''
|
||||||
|
@ -10,6 +10,8 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_utils import timeutils
|
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 consumption as consumption_mod
|
||||||
from bilean.engine import environment
|
from bilean.engine import environment
|
||||||
|
|
||||||
|
wallclock = time.time
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@ -233,8 +236,6 @@ class Resource(object):
|
|||||||
something else.
|
something else.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
ALLOW_DELAY_TIME = 10
|
|
||||||
|
|
||||||
def __new__(cls, id, user_id, res_type, properties, **kwargs):
|
def __new__(cls, id, user_id, res_type, properties, **kwargs):
|
||||||
"""Create a new resource of the appropriate class.
|
"""Create a new resource of the appropriate class.
|
||||||
|
|
||||||
@ -258,8 +259,8 @@ class Resource(object):
|
|||||||
self.properties = properties
|
self.properties = properties
|
||||||
|
|
||||||
self.rule_id = kwargs.get('rule_id')
|
self.rule_id = kwargs.get('rule_id')
|
||||||
self.rate = kwargs.get('rate', 0)
|
self.rate = utils.make_decimal(kwargs.get('rate', 0))
|
||||||
self.last_bill = kwargs.get('last_bill')
|
self.last_bill = utils.make_decimal(kwargs.get('last_bill', 0))
|
||||||
|
|
||||||
self.created_at = kwargs.get('created_at')
|
self.created_at = kwargs.get('created_at')
|
||||||
self.updated_at = kwargs.get('updated_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
|
# Properties pass to user to help settle account, not store to db
|
||||||
self.delta_rate = 0
|
self.delta_rate = 0
|
||||||
self.delayed_cost = 0
|
|
||||||
self.consumption = None
|
self.consumption = None
|
||||||
|
|
||||||
def store(self, context):
|
def store(self, context):
|
||||||
@ -278,8 +278,8 @@ class Resource(object):
|
|||||||
'resource_type': self.resource_type,
|
'resource_type': self.resource_type,
|
||||||
'properties': self.properties,
|
'properties': self.properties,
|
||||||
'rule_id': self.rule_id,
|
'rule_id': self.rule_id,
|
||||||
'rate': self.rate,
|
'rate': utils.format_decimal(self.rate),
|
||||||
'last_bill': self.last_bill,
|
'last_bill': utils.format_decimal(self.last_bill),
|
||||||
'created_at': self.created_at,
|
'created_at': self.created_at,
|
||||||
'updated_at': self.updated_at,
|
'updated_at': self.updated_at,
|
||||||
'deleted_at': self.deleted_at,
|
'deleted_at': self.deleted_at,
|
||||||
@ -304,19 +304,13 @@ class Resource(object):
|
|||||||
self.created_at = resource.created_at
|
self.created_at = resource.created_at
|
||||||
return
|
return
|
||||||
|
|
||||||
now = timeutils.utcnow()
|
self.last_bill = utils.make_decimal(wallclock())
|
||||||
self.last_bill = now
|
|
||||||
create_time = self.properties.get('created_at')
|
create_time = self.properties.get('created_at')
|
||||||
if create_time is not None:
|
if create_time is not None:
|
||||||
created_at = timeutils.parse_strtime(create_time)
|
sec = utils.format_time_to_seconds(create_time)
|
||||||
delayed_seconds = (now - created_at).total_seconds()
|
self.last_bill = utils.make_decimal(sec)
|
||||||
# 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
|
|
||||||
|
|
||||||
values.update(last_bill=self.last_bill)
|
values.update(last_bill=utils.format_decimal(self.last_bill))
|
||||||
resource = db_api.resource_create(context, values)
|
resource = db_api.resource_create(context, values)
|
||||||
self.created_at = resource.created_at
|
self.created_at = resource.created_at
|
||||||
|
|
||||||
@ -326,19 +320,14 @@ class Resource(object):
|
|||||||
return
|
return
|
||||||
|
|
||||||
update_time = self.properties.get('updated_at')
|
update_time = self.properties.get('updated_at')
|
||||||
now = timeutils.utcnow()
|
updated_at = utils.make_decimal(wallclock())
|
||||||
updated_at = now
|
|
||||||
if update_time is not None:
|
if update_time is not None:
|
||||||
updated_at = timeutils.parse_strtime(update_time)
|
sec = utils.format_time_to_seconds(update_time)
|
||||||
delayed_seconds = (now - updated_at).total_seconds()
|
updated_at = utils.make_decimal(sec)
|
||||||
# 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
|
|
||||||
|
|
||||||
# Generate consumption between lass bill and update time
|
# Generate consumption between lass bill and update time
|
||||||
old_rate = self.rate - self.delta_rate
|
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,
|
params = {'resource_id': self.id,
|
||||||
'resource_type': self.resource_type,
|
'resource_type': self.resource_type,
|
||||||
'start_time': self.last_bill,
|
'start_time': self.last_bill,
|
||||||
@ -349,7 +338,7 @@ class Resource(object):
|
|||||||
self.consumption = consumption_mod.Consumption(self.user_id, **params)
|
self.consumption = consumption_mod.Consumption(self.user_id, **params)
|
||||||
|
|
||||||
self.last_bill = updated_at
|
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)
|
db_api.resource_update(context, self.id, values)
|
||||||
|
|
||||||
def _delete(self, context, soft_delete=True):
|
def _delete(self, context, soft_delete=True):
|
||||||
@ -359,18 +348,13 @@ class Resource(object):
|
|||||||
return
|
return
|
||||||
|
|
||||||
delete_time = self.properties.get('deleted_at')
|
delete_time = self.properties.get('deleted_at')
|
||||||
now = timeutils.utcnow()
|
deleted_at = utils.make_decimal(wallclock())
|
||||||
deleted_at = now
|
|
||||||
if delete_time is not None:
|
if delete_time is not None:
|
||||||
deleted_at = timeutils.parse_strtime(delete_time)
|
sec = utils.format_time_to_seconds(delete_time)
|
||||||
delayed_seconds = (now - deleted_at).total_seconds()
|
deleted_at = utils.make_decimal(sec)
|
||||||
# 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
|
|
||||||
|
|
||||||
# Generate consumption between lass bill and delete time
|
# 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,
|
params = {'resource_id': self.id,
|
||||||
'resource_type': self.resource_type,
|
'resource_type': self.resource_type,
|
||||||
'start_time': self.last_bill,
|
'start_time': self.last_bill,
|
||||||
@ -447,8 +431,8 @@ class Resource(object):
|
|||||||
'resource_type': self.resource_type,
|
'resource_type': self.resource_type,
|
||||||
'properties': self.properties,
|
'properties': self.properties,
|
||||||
'rule_id': self.rule_id,
|
'rule_id': self.rule_id,
|
||||||
'rate': self.rate,
|
'rate': utils.dec2str(self.rate),
|
||||||
'last_bill': utils.format_time(self.last_bill),
|
'last_bill': utils.dec2str(self.last_bill),
|
||||||
'created_at': utils.format_time(self.created_at),
|
'created_at': utils.format_time(self.created_at),
|
||||||
'updated_at': utils.format_time(self.updated_at),
|
'updated_at': utils.format_time(self.updated_at),
|
||||||
'deleted_at': utils.format_time(self.deleted_at),
|
'deleted_at': utils.format_time(self.deleted_at),
|
||||||
|
@ -179,7 +179,7 @@ class CronScheduler(object):
|
|||||||
def _add_notify_job(self, user):
|
def _add_notify_job(self, user):
|
||||||
if user.rate == 0:
|
if user.rate == 0:
|
||||||
return False
|
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
|
prior_notify_time = cfg.CONF.scheduler.prior_notify_time * 3600
|
||||||
notify_seconds = total_seconds - prior_notify_time
|
notify_seconds = total_seconds - prior_notify_time
|
||||||
notify_seconds = notify_seconds if notify_seconds > 0 else 0
|
notify_seconds = notify_seconds if notify_seconds > 0 else 0
|
||||||
@ -198,7 +198,7 @@ class CronScheduler(object):
|
|||||||
def _add_freeze_job(self, user):
|
def _add_freeze_job(self, user):
|
||||||
if user.rate == 0:
|
if user.rate == 0:
|
||||||
return False
|
return False
|
||||||
total_seconds = user.balance / user.rate
|
total_seconds = float(user.balance / user.rate)
|
||||||
run_date = timeutils.utcnow() + timedelta(seconds=total_seconds)
|
run_date = timeutils.utcnow() + timedelta(seconds=total_seconds)
|
||||||
job_params = {'run_date': run_date}
|
job_params = {'run_date': run_date}
|
||||||
job_id = self._generate_job_id(user.id, self.FREEZE)
|
job_id = self._generate_job_id(user.id, self.FREEZE)
|
||||||
|
Loading…
Reference in New Issue
Block a user