Increase cost fields to 28 digits precision
When working with some type of resources, and for some specific billing requirements, we need to set costs that will use up to more than 8 digits on the right side of the comma. By default, the Python object Decimal support 28 digits. Therefore, it makes sense for us to change the MySQL database schema of CloudKitty to use 28 digits as well on the right side. This will avoid confusion for people when using this feature. One can argue that using the `factor` option can also be a solution for that, but as I mentioned, for people used to Python, that can cause confusions because the MySQL DB is using a different precision than the one supported in Python for the data type we use to represent the `cost` field. Change-Id: Ifbf5b2515c7eaf470b48f2695d1e45eeab708a72
This commit is contained in:
parent
1cac7f9610
commit
08497f0d52
@ -0,0 +1,45 @@
|
||||
# Copyright 2018 OpenStack Foundation
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
"""Increase cost fields to 30 digits
|
||||
|
||||
Revision ID: Ifbf5b2515c7
|
||||
Revises: 644faa4491fd
|
||||
Create Date: 2020-09-29 14:22:00.000000
|
||||
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import importlib
|
||||
|
||||
import sqlalchemy as sa
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'Ifbf5b2515c7'
|
||||
down_revision = '644faa4491fd'
|
||||
|
||||
|
||||
def upgrade():
|
||||
down_version_module = importlib.import_module(
|
||||
"cloudkitty.rating.hash.db.sqlalchemy.alembic.versions."
|
||||
"644faa4491fd_update_tenant_id_type_from_uuid_to_text")
|
||||
|
||||
for table_name in ('hashmap_mappings', 'hashmap_thresholds'):
|
||||
with op.batch_alter_table(
|
||||
table_name, reflect_args=down_version_module.get_reflect(
|
||||
table_name)) as batch_op:
|
||||
|
||||
batch_op.alter_column('cost',
|
||||
type_=sa.Numeric(precision=30, scale=28))
|
@ -230,7 +230,7 @@ class HashMapMapping(Base, HashMapBase):
|
||||
sqlalchemy.String(255),
|
||||
nullable=True)
|
||||
cost = sqlalchemy.Column(
|
||||
sqlalchemy.Numeric(20, 8),
|
||||
sqlalchemy.Numeric(30, 28),
|
||||
nullable=False)
|
||||
map_type = sqlalchemy.Column(
|
||||
sqlalchemy.Enum(
|
||||
@ -309,7 +309,7 @@ class HashMapThreshold(Base, HashMapBase):
|
||||
sqlalchemy.Numeric(20, 8),
|
||||
nullable=True)
|
||||
cost = sqlalchemy.Column(
|
||||
sqlalchemy.Numeric(20, 8),
|
||||
sqlalchemy.Numeric(30, 28),
|
||||
nullable=False)
|
||||
map_type = sqlalchemy.Column(
|
||||
sqlalchemy.Enum(
|
||||
|
@ -28,13 +28,13 @@ tests:
|
||||
data:
|
||||
service_id: "6c1b8a30-797f-4b7e-ad66-9879b79059fb"
|
||||
type: "flat"
|
||||
cost: "0.10000000"
|
||||
cost: "0.1000000000000000055511151231"
|
||||
status: 201
|
||||
response_json_paths:
|
||||
$.mapping_id: "6c1b8a30-797f-4b7e-ad66-9879b79059fb"
|
||||
$.service_id: "6c1b8a30-797f-4b7e-ad66-9879b79059fb"
|
||||
$.type: "flat"
|
||||
$.cost: "0.10000000"
|
||||
$.cost: "0.1000000000000000055511151231"
|
||||
response_headers:
|
||||
location: '$SCHEME://$NETLOC/v1/rating/module_config/hashmap/mappings/6c1b8a30-797f-4b7e-ad66-9879b79059fb'
|
||||
|
||||
@ -60,7 +60,7 @@ tests:
|
||||
$.service_id: "6c1b8a30-797f-4b7e-ad66-9879b79059fb"
|
||||
$.level: "2.00000000"
|
||||
$.type: "flat"
|
||||
$.cost: "0.10000000"
|
||||
$.cost: "0.1000000000000000055511151231"
|
||||
response_headers:
|
||||
location: '$SCHEME://$NETLOC/v1/rating/module_config/hashmap/thresholds/6c1b8a30-797f-4b7e-ad66-9879b79059fb'
|
||||
|
||||
@ -103,7 +103,7 @@ tests:
|
||||
$.field_id: "6c1b8a30-797f-4b7e-ad66-9879b79059fb"
|
||||
$.value: "04774238-fcad-11e5-a90e-6391fd56aab2"
|
||||
$.type: "flat"
|
||||
$.cost: "0.10000000"
|
||||
$.cost: "0.1000000000000000055511151231"
|
||||
response_headers:
|
||||
location: '$SCHEME://$NETLOC/v1/rating/module_config/hashmap/mappings/6c1b8a30-797f-4b7e-ad66-9879b79059fb'
|
||||
|
||||
@ -124,7 +124,7 @@ tests:
|
||||
$.field_id: "6c1b8a30-797f-4b7e-ad66-9879b79059fb"
|
||||
$.level: "2.00000000"
|
||||
$.type: "flat"
|
||||
$.cost: "0.10000000"
|
||||
$.cost: "0.1000000000000000055511151231"
|
||||
response_headers:
|
||||
location: '$SCHEME://$NETLOC/v1/rating/module_config/hashmap/thresholds/6c1b8a30-797f-4b7e-ad66-9879b79059fb'
|
||||
|
||||
|
@ -48,7 +48,7 @@ tests:
|
||||
response_json_paths:
|
||||
$.service_id: $RESPONSE['$.service_id']
|
||||
$.type: "flat"
|
||||
$.cost: "0.10000000"
|
||||
$.cost: "0.1000000000000000055511151231"
|
||||
|
||||
- name: delete a flat service mapping
|
||||
url: /v1/rating/module_config/hashmap/mappings/$RESPONSE['$.mapping_id']
|
||||
@ -76,7 +76,7 @@ tests:
|
||||
response_json_paths:
|
||||
$.service_id: $RESPONSE['$.services[0].service_id']
|
||||
$.type: "rate"
|
||||
$.cost: "0.20000000"
|
||||
$.cost: "0.2000000000000000111022302463"
|
||||
|
||||
- name: create a flat service mapping for a tenant
|
||||
url: /v1/rating/module_config/hashmap/mappings
|
||||
@ -93,7 +93,7 @@ tests:
|
||||
response_json_paths:
|
||||
$.service_id: $ENVIRON['hash_service_id']
|
||||
$.type: "flat"
|
||||
$.cost: "0.20000000"
|
||||
$.cost: "0.2000000000000000111022302463"
|
||||
$.tenant_id: "24a7fdae-27ff-11e6-8c4f-6b725a05bf50"
|
||||
|
||||
- name: list service mappings no tenant filtering
|
||||
@ -131,7 +131,7 @@ tests:
|
||||
$.service_id: $ENVIRON['hash_service_id']
|
||||
$.level: "2.00000000"
|
||||
$.type: "flat"
|
||||
$.cost: "0.20000000"
|
||||
$.cost: "0.2000000000000000111022302463"
|
||||
$.tenant_id: "24a7fdae-27ff-11e6-8c4f-6b725a05bf50"
|
||||
|
||||
- name: list service thresholds no tenant filtering
|
||||
@ -191,7 +191,7 @@ tests:
|
||||
response_json_paths:
|
||||
$.field_id: $RESPONSE['$.field_id']
|
||||
$.type: "rate"
|
||||
$.cost: "0.20000000"
|
||||
$.cost: "0.2000000000000000111022302463"
|
||||
|
||||
- name: delete a flat field mapping
|
||||
url: /v1/rating/module_config/hashmap/mappings/$RESPONSE['$.mapping_id']
|
||||
@ -222,7 +222,7 @@ tests:
|
||||
response_json_paths:
|
||||
$.field_id: $RESPONSE['$.fields[0].field_id']
|
||||
$.type: "rate"
|
||||
$.cost: "0.20000000"
|
||||
$.cost: "0.2000000000000000111022302463"
|
||||
response_store_environ:
|
||||
hash_rate_mapping_id: $.mapping_id
|
||||
|
||||
@ -245,7 +245,7 @@ tests:
|
||||
$.mapping_id: $ENVIRON['hash_rate_mapping_id']
|
||||
$.field_id: $ENVIRON['hash_field_id']
|
||||
$.type: "rate"
|
||||
$.cost: "0.30000000"
|
||||
$.cost: "0.2999999999999999888977697537"
|
||||
$.value: "f17a0674-0004-11e6-a16b-cf941f4668c4"
|
||||
|
||||
- name: delete a field
|
||||
|
@ -343,7 +343,8 @@ class HashMapRatingTest(tests.TestCase):
|
||||
mapping = self._db_api.get_mapping(mapping_db.mapping_id)
|
||||
self.assertEqual('flat', mapping.map_type)
|
||||
self.assertEqual('m1.tiny', mapping.value)
|
||||
self.assertEqual(decimal.Decimal('1.337'), mapping.cost)
|
||||
self.assertEqual(
|
||||
decimal.Decimal('1.3369999999999999662492200514'), mapping.cost)
|
||||
self.assertEqual(field_db.id, mapping.field_id)
|
||||
|
||||
def test_list_mappings_from_services(self):
|
||||
@ -524,7 +525,8 @@ class HashMapRatingTest(tests.TestCase):
|
||||
threshold = self._db_api.get_threshold(threshold_db.threshold_id)
|
||||
self.assertEqual('rate', threshold.map_type)
|
||||
self.assertEqual(decimal.Decimal('64'), threshold.level)
|
||||
self.assertEqual(decimal.Decimal('0.1337'), threshold.cost)
|
||||
self.assertEqual(
|
||||
decimal.Decimal('0.1337000000000000132782673745'), threshold.cost)
|
||||
self.assertEqual(field_db.id, threshold.field_id)
|
||||
|
||||
def test_list_thresholds_from_only_group(self):
|
||||
@ -768,11 +770,13 @@ class HashMapRatingTest(tests.TestCase):
|
||||
'mappings': {
|
||||
'_DEFAULT_': {
|
||||
'm1.tiny': {
|
||||
'cost': decimal.Decimal('2'),
|
||||
'cost': decimal.Decimal(
|
||||
'2.0000000000000000000000000000'),
|
||||
'type': 'flat'}},
|
||||
'test_group': {
|
||||
'm1.large': {
|
||||
'cost': decimal.Decimal('13.37'),
|
||||
'cost': decimal.Decimal(
|
||||
'13.3699999999999992184029906639'),
|
||||
'type': 'rate'}}},
|
||||
'thresholds': {}},
|
||||
'memory': {
|
||||
@ -780,14 +784,17 @@ class HashMapRatingTest(tests.TestCase):
|
||||
'thresholds': {
|
||||
'test_group': {
|
||||
64: {
|
||||
'cost': decimal.Decimal('0.03'),
|
||||
'cost': decimal.Decimal(
|
||||
'0.0299999999999999988897769754'),
|
||||
'type': 'flat'},
|
||||
128: {
|
||||
'cost': decimal.Decimal('0.03'),
|
||||
'cost': decimal.Decimal(
|
||||
'0.0299999999999999988897769754'),
|
||||
'type': 'flat'}}}}},
|
||||
'mappings': {
|
||||
'_DEFAULT_': {
|
||||
'cost': decimal.Decimal('1.42'),
|
||||
'cost': decimal.Decimal(
|
||||
'1.4199999999999999289457264240'),
|
||||
'type': 'rate'}},
|
||||
'thresholds': {}}}
|
||||
self.assertEqual(expect,
|
||||
@ -817,11 +824,11 @@ class HashMapRatingTest(tests.TestCase):
|
||||
expected_result = {
|
||||
'_DEFAULT_': {
|
||||
'm1.tiny': {
|
||||
'cost': decimal.Decimal('1.337'),
|
||||
'cost': decimal.Decimal('1.3369999999999999662492200514'),
|
||||
'type': 'flat'}},
|
||||
'test_group': {
|
||||
'm1.large': {
|
||||
'cost': decimal.Decimal('13.37'),
|
||||
'cost': decimal.Decimal('13.3699999999999992184029906639'),
|
||||
'type': 'rate'}}}
|
||||
self.assertEqual(expected_result, result)
|
||||
|
||||
@ -844,7 +851,7 @@ class HashMapRatingTest(tests.TestCase):
|
||||
expected_result = {
|
||||
'test_group': {
|
||||
1000: {
|
||||
'cost': decimal.Decimal('3.1337'),
|
||||
'cost': decimal.Decimal('3.1337000000000001520561454527'),
|
||||
'type': 'flat'}}}
|
||||
self.assertEqual(expected_result, result)
|
||||
|
||||
@ -873,10 +880,14 @@ class HashMapRatingTest(tests.TestCase):
|
||||
actual_data = [actual_data]
|
||||
df_dicts = [d.as_dict(mutable=True) for d in expected_data]
|
||||
compute_list = df_dicts[0]['usage']['compute']
|
||||
compute_list[0]['rating'] = {'price': decimal.Decimal('2.757')}
|
||||
compute_list[1]['rating'] = {'price': decimal.Decimal('5.514')}
|
||||
compute_list[2]['rating'] = {'price': decimal.Decimal('5.514')}
|
||||
compute_list[3]['rating'] = {'price': decimal.Decimal('2.757')}
|
||||
compute_list[0]['rating'] = {'price': decimal.Decimal(
|
||||
'2.756999999999999895194946475')}
|
||||
compute_list[1]['rating'] = {'price': decimal.Decimal(
|
||||
'5.513999999999999790389892950')}
|
||||
compute_list[2]['rating'] = {'price': decimal.Decimal(
|
||||
'5.513999999999999790389892950')}
|
||||
compute_list[3]['rating'] = {'price': decimal.Decimal(
|
||||
'2.756999999999999895194946475')}
|
||||
self.assertEqual(df_dicts, [d.as_dict(mutable=True)
|
||||
for d in actual_data])
|
||||
|
||||
@ -917,10 +928,13 @@ class HashMapRatingTest(tests.TestCase):
|
||||
actual_data = [actual_data]
|
||||
df_dicts = [d.as_dict(mutable=True) for d in expected_data]
|
||||
compute_list = df_dicts[0]['usage']['compute']
|
||||
compute_list[0]['rating'] = {'price': decimal.Decimal('1.337')}
|
||||
compute_list[1]['rating'] = {'price': decimal.Decimal('2.84')}
|
||||
compute_list[0]['rating'] = {'price': decimal.Decimal(
|
||||
'1.336999999999999966249220051')}
|
||||
compute_list[1]['rating'] = {'price': decimal.Decimal(
|
||||
'2.839999999999999857891452848')}
|
||||
compute_list[2]['rating'] = {'price': decimal.Decimal('0')}
|
||||
compute_list[3]['rating'] = {'price': decimal.Decimal('1.47070')}
|
||||
compute_list[3]['rating'] = {'price': decimal.Decimal(
|
||||
'1.470700000000000081623596770')}
|
||||
self.assertEqual(df_dicts, [d.as_dict(mutable=True)
|
||||
for d in actual_data])
|
||||
|
||||
@ -975,10 +989,14 @@ class HashMapRatingTest(tests.TestCase):
|
||||
actual_data = [actual_data]
|
||||
df_dicts = [d.as_dict(mutable=True) for d in expected_data]
|
||||
compute_list = df_dicts[0]['usage']['compute']
|
||||
compute_list[0]['rating'] = {'price': decimal.Decimal('0.1337')}
|
||||
compute_list[1]['rating'] = {'price': decimal.Decimal('0.4')}
|
||||
compute_list[2]['rating'] = {'price': decimal.Decimal('0.4')}
|
||||
compute_list[3]['rating'] = {'price': decimal.Decimal('0.1337')}
|
||||
compute_list[0]['rating'] = {'price': decimal.Decimal(
|
||||
'0.1337000000000000132782673745')}
|
||||
compute_list[1]['rating'] = {'price': decimal.Decimal(
|
||||
'0.4000000000000000222044604926')}
|
||||
compute_list[2]['rating'] = {'price': decimal.Decimal(
|
||||
'0.4000000000000000222044604926')}
|
||||
compute_list[3]['rating'] = {'price': decimal.Decimal(
|
||||
'0.1337000000000000132782673745')}
|
||||
self.assertEqual(df_dicts, [d.as_dict(mutable=True)
|
||||
for d in actual_data])
|
||||
|
||||
@ -1030,10 +1048,14 @@ class HashMapRatingTest(tests.TestCase):
|
||||
actual_data = [actual_data]
|
||||
df_dicts = [d.as_dict(mutable=True) for d in expected_data]
|
||||
compute_list = df_dicts[0]['usage']['compute']
|
||||
compute_list[0]['rating'] = {'price': decimal.Decimal('0.1')}
|
||||
compute_list[1]['rating'] = {'price': decimal.Decimal('0.15')}
|
||||
compute_list[2]['rating'] = {'price': decimal.Decimal('0.15')}
|
||||
compute_list[3]['rating'] = {'price': decimal.Decimal('0.1')}
|
||||
compute_list[0]['rating'] = {'price': decimal.Decimal(
|
||||
'0.1000000000000000055511151231')}
|
||||
compute_list[1]['rating'] = {'price': decimal.Decimal(
|
||||
'0.1499999999999999944488848769')}
|
||||
compute_list[2]['rating'] = {'price': decimal.Decimal(
|
||||
'0.1499999999999999944488848769')}
|
||||
compute_list[3]['rating'] = {'price': decimal.Decimal(
|
||||
'0.1000000000000000055511151231')}
|
||||
self.assertEqual(df_dicts, [d.as_dict(mutable=True)
|
||||
for d in actual_data])
|
||||
|
||||
@ -1175,11 +1197,15 @@ class HashMapRatingTest(tests.TestCase):
|
||||
end=expected_data[0].end)
|
||||
df_dicts = [d.as_dict(mutable=True) for d in expected_data]
|
||||
compute_list = df_dicts[0]['usage']['compute']
|
||||
compute_list[0]['rating'] = {'price': decimal.Decimal('2.487')}
|
||||
compute_list[1]['rating'] = {'price': decimal.Decimal('5.564')}
|
||||
compute_list[0]['rating'] = {'price': decimal.Decimal(
|
||||
'2.486999999999999960698104928')}
|
||||
compute_list[1]['rating'] = {'price': decimal.Decimal(
|
||||
'5.564000000000000155875312656')}
|
||||
# 8vcpu mapping * 2 + service_mapping * 1 + 128m ram threshold * 2
|
||||
compute_list[2]['rating'] = {'price': decimal.Decimal('34.40')}
|
||||
compute_list[3]['rating'] = {'price': decimal.Decimal('2.6357')}
|
||||
compute_list[2]['rating'] = {'price': decimal.Decimal(
|
||||
'34.40000000000000002220446049')}
|
||||
compute_list[3]['rating'] = {'price': decimal.Decimal(
|
||||
'2.635700000000000088840046430')}
|
||||
actual_data = [self._hash.process(d) for d in expected_data]
|
||||
self.assertEqual(df_dicts, [d.as_dict(mutable=True)
|
||||
for d in actual_data])
|
||||
|
@ -157,6 +157,12 @@ Apart from that, it works the same way as a mapping.
|
||||
|
||||
As for mappings, a threshold can be tied to a specific scope/project.
|
||||
|
||||
Cost
|
||||
----
|
||||
The cost option is the actual cost for the rating period. It has a precision of
|
||||
28 decimal digits (on the right side of the number), and 30 digits on the left
|
||||
side of the number.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user