Add support for object storage policies

Introduce a new ObjectStorageMax transformer which
takes a container and bases the service for it on the
policy used for it in swift so that we can charge differently
for each policy.

Change-Id: Id1a0d7783ae72de0a2da36245badbd770508be81
This commit is contained in:
Adrian Turjak 2019-11-06 17:57:12 +13:00
parent 33c5f067ba
commit 226ceacf90
7 changed files with 200 additions and 16 deletions

View File

@ -223,7 +223,7 @@ class BaseCollector(object):
# hashing the name only for swift to get a consistent # hashing the name only for swift to get a consistent
# id for swift billing. Another change will be proposed to # id for swift billing. Another change will be proposed to
# openstack-billing to handle this case as well. # openstack-billing to handle this case as well.
if 'o1.standard' in transformed: if mapping['type'] == "Object Storage Container":
res_id = hashlib.md5(res_id.encode('utf-8')).hexdigest() res_id = hashlib.md5(res_id.encode('utf-8')).hexdigest()
LOG.debug( LOG.debug(

View File

@ -150,3 +150,27 @@ def get_volume_type(volume_type):
return vtype['name'] return vtype['name']
return None return None
@general.disable_ssl_warnings
def get_object_storage_url(project_id):
ks = get_keystone_client()
try:
endpoint = ks.endpoints.list(
service=ks.services.list(type="object-store")[0],
interface="public",
region=CONF.keystone_authtoken.region_name)[0]
return endpoint.url % {'tenant_id': project_id}
except KeyError:
return None
@general.disable_ssl_warnings
def get_container_policy(project_id, container_name):
sess = _get_keystone_session()
url = get_object_storage_url(project_id)
if url:
resp = sess.head("%s/%s" % (url, container_name))
if resp:
return resp.headers.get('X-Storage-Policy')
return None

View File

@ -19,6 +19,8 @@
"""Utility methods for working with WSGI servers.""" """Utility methods for working with WSGI servers."""
import datetime import datetime
import six
from xml.dom import minidom from xml.dom import minidom
from xml.parsers import expat from xml.parsers import expat
@ -61,7 +63,7 @@ class JSONDictSerializer(DictSerializer):
if isinstance(obj, datetime.datetime): if isinstance(obj, datetime.datetime):
_dtime = obj - datetime.timedelta(microseconds=obj.microsecond) _dtime = obj - datetime.timedelta(microseconds=obj.microsecond)
return _dtime.isoformat() return _dtime.isoformat()
return unicode(obj) return six.text_type(obj)
return jsonutils.dumps(data, default=sanitizer) return jsonutils.dumps(data, default=sanitizer)

View File

@ -18,6 +18,7 @@ import mock
from distil.common.constants import date_format from distil.common.constants import date_format
from distil.common import general from distil.common import general
from distil.common import openstack
from distil.tests.unit import base from distil.tests.unit import base
from distil.transformer import arithmetic from distil.transformer import arithmetic
@ -110,7 +111,7 @@ class TestMaxTransformer(base.DistilTestCase):
@mock.patch.object(general, 'get_transformer_config', mock.Mock()) @mock.patch.object(general, 'get_transformer_config', mock.Mock())
class TestStorageMaxTransformer(base.DistilTestCase): class TestBlockStorageMaxTransformer(base.DistilTestCase):
def test_all_different_values(self): def test_all_different_values(self):
""" """
Tests that the transformer correctly grabs the highest value, Tests that the transformer correctly grabs the highest value,
@ -134,7 +135,7 @@ class TestStorageMaxTransformer(base.DistilTestCase):
'metadata': {}}, 'metadata': {}},
] ]
xform = arithmetic.StorageMaxTransformer() xform = arithmetic.BlockStorageMaxTransformer()
usage = xform.transform_usage('some_meter', data, FAKE_DATA.t0, usage = xform.transform_usage('some_meter', data, FAKE_DATA.t0,
FAKE_DATA.t1) FAKE_DATA.t1)
@ -155,7 +156,7 @@ class TestStorageMaxTransformer(base.DistilTestCase):
'metadata': {}}, 'metadata': {}},
] ]
xform = arithmetic.StorageMaxTransformer() xform = arithmetic.BlockStorageMaxTransformer()
usage = xform.transform_usage('some_meter', data, FAKE_DATA.t0, usage = xform.transform_usage('some_meter', data, FAKE_DATA.t0,
FAKE_DATA.t1) FAKE_DATA.t1)
@ -171,7 +172,7 @@ class TestStorageMaxTransformer(base.DistilTestCase):
'metadata': {}}, 'metadata': {}},
] ]
xform = arithmetic.StorageMaxTransformer() xform = arithmetic.BlockStorageMaxTransformer()
usage = xform.transform_usage('some_meter', data, FAKE_DATA.t0, usage = xform.transform_usage('some_meter', data, FAKE_DATA.t0,
FAKE_DATA.t1) FAKE_DATA.t1)
@ -191,13 +192,140 @@ class TestStorageMaxTransformer(base.DistilTestCase):
'metadata': {}}, 'metadata': {}},
] ]
xform = arithmetic.StorageMaxTransformer() xform = arithmetic.BlockStorageMaxTransformer()
usage = xform.transform_usage('some_meter', data, FAKE_DATA.t0, usage = xform.transform_usage('some_meter', data, FAKE_DATA.t0,
FAKE_DATA.t1) FAKE_DATA.t1)
self.assertEqual({'some_meter': 27}, usage) self.assertEqual({'some_meter': 27}, usage)
@mock.patch.object(general, 'get_transformer_config', mock.Mock())
class TestObjectStorageMaxTransformer(base.DistilTestCase):
@mock.patch.object(
openstack, 'get_container_policy', mock.Mock(return_value='test-policy'))
def test_all_different_values(self):
"""
Tests that the transformer correctly grabs the highest value,
when all values are different.
"""
data = [
{'timestamp': FAKE_DATA.t0, 'volume': 12,
'resource_id': '55d37509be3142de963caf82a9c7c447/stuff',
'project_id': '55d37509be3142de963caf82a9c7c447',
'metadata': {}},
{'timestamp': FAKE_DATA.t0_10, 'volume': 3,
'resource_id': '55d37509be3142de963caf82a9c7c447/stuff',
'project_id': '55d37509be3142de963caf82a9c7c447',
'metadata': {}},
{'timestamp': FAKE_DATA.t0_20, 'volume': 7,
'resource_id': '55d37509be3142de963caf82a9c7c447/stuff',
'project_id': '55d37509be3142de963caf82a9c7c447',
'metadata': {}},
{'timestamp': FAKE_DATA.t0_30, 'volume': 3,
'resource_id': '55d37509be3142de963caf82a9c7c447/stuff',
'project_id': '55d37509be3142de963caf82a9c7c447',
'metadata': {}},
{'timestamp': FAKE_DATA.t0_40, 'volume': 25,
'resource_id': '55d37509be3142de963caf82a9c7c447/stuff',
'project_id': '55d37509be3142de963caf82a9c7c447',
'metadata': {}},
{'timestamp': FAKE_DATA.t0_50, 'volume': 2,
'resource_id': '55d37509be3142de963caf82a9c7c447/stuff',
'project_id': '55d37509be3142de963caf82a9c7c447',
'metadata': {}},
{'timestamp': FAKE_DATA.t1, 'volume': 6,
'resource_id': '55d37509be3142de963caf82a9c7c447/stuff',
'project_id': '55d37509be3142de963caf82a9c7c447',
'metadata': {}},
]
xform = arithmetic.ObjectStorageMaxTransformer()
usage = xform.transform_usage('some_meter', data, FAKE_DATA.t0,
FAKE_DATA.t1)
self.assertEqual({'test-policy': 25}, usage)
@mock.patch.object(
openstack, 'get_container_policy', mock.Mock(return_value='test-policy'))
def test_all_same_values(self):
"""
Tests that that transformer correctly grabs any value,
when all values are the same.
"""
data = [
{'timestamp': FAKE_DATA.t0, 'volume': 25,
'resource_id': '55d37509be3142de963caf82a9c7c447/stuff',
'project_id': '55d37509be3142de963caf82a9c7c447',
'metadata': {}},
{'timestamp': FAKE_DATA.t0_30, 'volume': 25,
'resource_id': '55d37509be3142de963caf82a9c7c447/stuff',
'project_id': '55d37509be3142de963caf82a9c7c447',
'metadata': {}},
{'timestamp': FAKE_DATA.t1, 'volume': 25,
'resource_id': '55d37509be3142de963caf82a9c7c447/stuff',
'project_id': '55d37509be3142de963caf82a9c7c447',
'metadata': {}},
]
xform = arithmetic.ObjectStorageMaxTransformer()
usage = xform.transform_usage('some_meter', data, FAKE_DATA.t0,
FAKE_DATA.t1)
self.assertEqual({'test-policy': 25}, usage)
@mock.patch.object(
openstack, 'get_container_policy', mock.Mock(return_value='test-policy'))
def test_none_value(self):
"""
Tests that that transformer correctly handles a None value.
"""
data = [
{'timestamp': FAKE_DATA.t0, 'volume': None,
'resource_id': '55d37509be3142de963caf82a9c7c447/stuff',
'project_id': '55d37509be3142de963caf82a9c7c447',
'metadata': {}},
]
xform = arithmetic.ObjectStorageMaxTransformer()
usage = xform.transform_usage('some_meter', data, FAKE_DATA.t0,
FAKE_DATA.t1)
self.assertEqual({'test-policy': 0}, usage)
@mock.patch.object(
openstack, 'get_container_policy', mock.Mock(return_value=None))
def test_none_and_other_values(self):
"""
Tests that that transformer correctly handles a None value.
"""
data = [
{'timestamp': FAKE_DATA.t0, 'volume': None,
'resource_id': '55d37509be3142de963caf82a9c7c447/stuff',
'project_id': '55d37509be3142de963caf82a9c7c447',
'metadata': {}},
{'timestamp': FAKE_DATA.t0_30, 'volume': 25,
'resource_id': '55d37509be3142de963caf82a9c7c447/stuff',
'project_id': '55d37509be3142de963caf82a9c7c447',
'metadata': {}},
{'timestamp': FAKE_DATA.t1, 'volume': 27,
'resource_id': '55d37509be3142de963caf82a9c7c447/stuff',
'project_id': '55d37509be3142de963caf82a9c7c447',
'metadata': {}},
]
xform = arithmetic.ObjectStorageMaxTransformer()
usage = xform.transform_usage('some_meter', data, FAKE_DATA.t0,
FAKE_DATA.t1)
self.assertEqual({'some_meter': 27}, usage)
@mock.patch.object(general, 'get_transformer_config', mock.Mock())
@mock.patch.object(general, 'get_transformer_config', mock.Mock()) @mock.patch.object(general, 'get_transformer_config', mock.Mock())
class TestSumTransformer(base.DistilTestCase): class TestSumTransformer(base.DistilTestCase):
def test_basic_sum(self): def test_basic_sum(self):

View File

@ -29,17 +29,22 @@ class MaxTransformer(BaseTransformer):
'gigabyte-hours'. 'gigabyte-hours'.
""" """
def _get_max_vol(self, data):
if len(data):
max_vol = max([(v["volume"] if v["volume"] else 0) for v in data])
if max_vol:
return max_vol
return 0
def _transform_usage(self, meter_name, raw_data, start_at, end_at): def _transform_usage(self, meter_name, raw_data, start_at, end_at):
max_vol = max([(v["volume"] if v["volume"] else 0) max_vol = self._get_max_vol(raw_data)
for v in raw_data]) if len(raw_data) else 0
max_vol = max_vol if max_vol else 0
hours = (end_at - start_at).total_seconds() / 3600.0 hours = (end_at - start_at).total_seconds() / 3600.0
return {meter_name: max_vol * hours} return {meter_name: max_vol * hours}
class StorageMaxTransformer(BaseTransformer): class BlockStorageMaxTransformer(MaxTransformer):
""" """
Variantion on the GaugeMax Transformer that checks for Variantion on the GaugeMax Transformer that checks for
volume_type and uses that as the service, or uses the volume_type and uses that as the service, or uses the
@ -50,8 +55,7 @@ class StorageMaxTransformer(BaseTransformer):
if not data: if not data:
return None return None
max_vol = max([(v["volume"] if v["volume"] else 0) max_vol = self._get_max_vol(data)
for v in data]) or 0
if "volume_type" in data[-1]['metadata']: if "volume_type" in data[-1]['metadata']:
vtype = data[-1]['metadata']['volume_type'] vtype = data[-1]['metadata']['volume_type']
@ -65,6 +69,30 @@ class StorageMaxTransformer(BaseTransformer):
return {service: max_vol * hours} return {service: max_vol * hours}
class ObjectStorageMaxTransformer(MaxTransformer):
"""
Variantion on the GaugeMax Transformer that checks for
object storage container policy and uses that as the service,
or uses the default service name.
"""
def _transform_usage(self, name, data, start, end):
if not data:
return None
container_name = data[-1]['resource_id'].split('/')[1]
project_id = data[-1]['project_id']
service = openstack.get_container_policy(project_id, container_name)
if not service:
service = name
max_vol = self._get_max_vol(data)
hours = (end - start).total_seconds() / 3600.0
return {service: max_vol * hours}
class SumTransformer(BaseTransformer): class SumTransformer(BaseTransformer):
"""Transformer for sum-integration of a gauge value for given period. """Transformer for sum-integration of a gauge value for given period.
""" """

View File

@ -35,7 +35,7 @@
meter: volume.size meter: volume.size
service: b1.standard service: b1.standard
type: Volume type: Volume
transformer: max transformer: blockstoragemax
unit: gigabyte unit: gigabyte
metadata: metadata:
name: name:
@ -152,7 +152,7 @@
meter: storage.containers.objects.size meter: storage.containers.objects.size
service: o1.standard service: o1.standard
type: Object Storage Container type: Object Storage Container
transformer: max transformer: objectstoragemax
unit: byte unit: byte
# NOTE(flwang): Nothing in resource_metadata from ceilometer actually. # NOTE(flwang): Nothing in resource_metadata from ceilometer actually.
# But to avoid any unnecessary issue and keeping consistency. Just keep # But to avoid any unnecessary issue and keeping consistency. Just keep

View File

@ -39,7 +39,9 @@ distil.collector =
distil.transformer = distil.transformer =
max = distil.transformer.arithmetic:MaxTransformer max = distil.transformer.arithmetic:MaxTransformer
storagemax = distil.transformer.arithmetic:StorageMaxTransformer storagemax = distil.transformer.arithmetic:BlockStorageMaxTransformer
blockstoragemax = distil.transformer.arithmetic:BlockStorageMaxTransformer
objectstoragemax = distil.transformer.arithmetic:ObjectStorageMaxTransformer
sum = distil.transformer.arithmetic:SumTransformer sum = distil.transformer.arithmetic:SumTransformer
uptime = distil.transformer.conversion:UpTimeTransformer uptime = distil.transformer.conversion:UpTimeTransformer
fromimage = distil.transformer.conversion:FromImageTransformer fromimage = distil.transformer.conversion:FromImageTransformer