Merge "Add support for Samples coming from Ceilometer"
This commit is contained in:
@@ -58,17 +58,32 @@ class prop(object):
|
||||
>>> u = User()
|
||||
>>> u.age = 'thirty'
|
||||
TypeError: Invalid type for attr age
|
||||
|
||||
|
||||
By specifying an alias attribute name, that alias will be read when the
|
||||
primary attribute name does not appear within the resource:
|
||||
|
||||
>>> class User(Resource):
|
||||
... name = prop('address', alias='location')
|
||||
...
|
||||
>>> u = User(location='Far Away')
|
||||
>>> print u['address']
|
||||
Far Away
|
||||
"""
|
||||
|
||||
def __init__(self, name, type=None):
|
||||
def __init__(self, name, alias=None, type=None):
|
||||
self.name = name
|
||||
self.type = type
|
||||
self.alias = alias
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
try:
|
||||
return instance._attrs[self.name]
|
||||
except KeyError:
|
||||
raise AttributeError('Unset property: %s' % self.name)
|
||||
try:
|
||||
return instance._attrs[self.alias]
|
||||
except KeyError:
|
||||
raise AttributeError('Unset property: %s' % self.name)
|
||||
|
||||
def __set__(self, instance, value):
|
||||
if self.type and not isinstance(value, self.type):
|
||||
@@ -80,7 +95,10 @@ class prop(object):
|
||||
try:
|
||||
del instance._attrs[self.name]
|
||||
except KeyError:
|
||||
raise AttributeError('Unset property: %s' % self.name)
|
||||
try:
|
||||
del instance._attrs[self.alias]
|
||||
except KeyError:
|
||||
raise AttributeError('Unset property: %s' % self.name)
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
|
59
openstack/telemetry/v2/sample.py
Normal file
59
openstack/telemetry/v2/sample.py
Normal file
@@ -0,0 +1,59 @@
|
||||
# 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.
|
||||
from openstack import resource
|
||||
from openstack.telemetry import telemetry_service
|
||||
|
||||
|
||||
class Sample(resource.Resource):
|
||||
id_attribute = 'sample_id'
|
||||
base_path = '/v2/meters/%(meter)s'
|
||||
service = telemetry_service.TelemetryService()
|
||||
|
||||
# Supported Operations
|
||||
allow_create = True
|
||||
allow_list = True
|
||||
|
||||
# Properties
|
||||
metadata = resource.prop('metadata', alias='resource_metadata')
|
||||
meter = resource.prop('meter', alias='counter_name')
|
||||
project_id = resource.prop('project_id')
|
||||
recorded_at = resource.prop('recorded_at')
|
||||
resource_id = resource.prop('resource_id')
|
||||
sample_id = resource.prop('id', alias='message_id')
|
||||
source = resource.prop('source')
|
||||
generated_at = resource.prop('timestamp')
|
||||
type = resource.prop('type', alias='counter_type')
|
||||
unit = resource.prop('unit', alias='counter_unit')
|
||||
user_id = resource.prop('user_id')
|
||||
volume = resource.prop('volume', alias='counter_volume')
|
||||
|
||||
def __repr__(self):
|
||||
return "sample: %s" % self._attrs
|
||||
|
||||
@classmethod
|
||||
def list(cls, session, path_args=None, **params):
|
||||
url = cls.base_path % path_args
|
||||
resp = session.get(url, service=cls.service, params=params)
|
||||
|
||||
changes = []
|
||||
for item in resp.body:
|
||||
changes.append(cls.existing(**item))
|
||||
return changes
|
||||
|
||||
def create(self, session):
|
||||
url = self.base_path % {'meter': self.meter}
|
||||
# telemetry expects a list of samples
|
||||
resp = session.post(url, service=self.service, json=[self._attrs])
|
||||
|
||||
sample = self.existing(**resp.body.pop())
|
||||
self._attrs['id'] = sample.id
|
||||
self._reset_dirty()
|
138
openstack/tests/telemetry/v2/test_sample.py
Normal file
138
openstack/tests/telemetry/v2/test_sample.py
Normal file
@@ -0,0 +1,138 @@
|
||||
# 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.
|
||||
|
||||
import mock
|
||||
import testtools
|
||||
|
||||
from openstack.telemetry.v2 import sample
|
||||
|
||||
SAMPLE = {
|
||||
'id': None,
|
||||
'metadata': {'1': 'one'},
|
||||
'meter': '2',
|
||||
'project_id': '3',
|
||||
'recorded_at': '4',
|
||||
'resource_id': '5',
|
||||
'source': '6',
|
||||
'timestamp': '7',
|
||||
'type': '8',
|
||||
'unit': '9',
|
||||
'user_id': '10',
|
||||
'volume': '11.1',
|
||||
}
|
||||
|
||||
OLD_SAMPLE = {
|
||||
'counter_name': '1',
|
||||
'counter_type': '2',
|
||||
'counter_unit': '3',
|
||||
'counter_volume': '4',
|
||||
'message_id': None,
|
||||
'project_id': '5',
|
||||
'recorded_at': '6',
|
||||
'resource_id': '7',
|
||||
'resource_metadata': '8',
|
||||
'source': '9',
|
||||
'timestamp': '10',
|
||||
'user_id': '11',
|
||||
}
|
||||
|
||||
|
||||
class TestSample(testtools.TestCase):
|
||||
|
||||
def test_basic(self):
|
||||
sot = sample.Sample(SAMPLE)
|
||||
self.assertIsNone(sot.resource_key)
|
||||
self.assertIsNone(sot.resources_key)
|
||||
self.assertEqual('/v2/meters/%(meter)s', sot.base_path)
|
||||
self.assertEqual('metering', sot.service.service_type)
|
||||
self.assertTrue(sot.allow_create)
|
||||
self.assertFalse(sot.allow_retrieve)
|
||||
self.assertFalse(sot.allow_update)
|
||||
self.assertFalse(sot.allow_delete)
|
||||
self.assertTrue(sot.allow_list)
|
||||
|
||||
def test_make_new(self):
|
||||
sot = sample.Sample(SAMPLE)
|
||||
self.assertIsNone(sot.id)
|
||||
self.assertEqual(SAMPLE['metadata'], sot.metadata)
|
||||
self.assertEqual(SAMPLE['meter'], sot.meter)
|
||||
self.assertEqual(SAMPLE['project_id'], sot.project_id)
|
||||
self.assertEqual(SAMPLE['recorded_at'], sot.recorded_at)
|
||||
self.assertEqual(SAMPLE['resource_id'], sot.resource_id)
|
||||
self.assertIsNone(sot.sample_id)
|
||||
self.assertEqual(SAMPLE['source'], sot.source)
|
||||
self.assertEqual(SAMPLE['timestamp'], sot.generated_at)
|
||||
self.assertEqual(SAMPLE['type'], sot.type)
|
||||
self.assertEqual(SAMPLE['unit'], sot.unit)
|
||||
self.assertEqual(SAMPLE['user_id'], sot.user_id)
|
||||
self.assertEqual(SAMPLE['volume'], sot.volume)
|
||||
|
||||
def test_make_old(self):
|
||||
sot = sample.Sample(OLD_SAMPLE)
|
||||
self.assertIsNone(sot.id)
|
||||
self.assertIsNone(sot.sample_id),
|
||||
self.assertEqual(OLD_SAMPLE['counter_name'], sot.meter)
|
||||
self.assertEqual(OLD_SAMPLE['counter_type'], sot.type)
|
||||
self.assertEqual(OLD_SAMPLE['counter_unit'], sot.unit)
|
||||
self.assertEqual(OLD_SAMPLE['counter_volume'], sot.volume)
|
||||
self.assertEqual(OLD_SAMPLE['project_id'], sot.project_id)
|
||||
self.assertEqual(OLD_SAMPLE['recorded_at'], sot.recorded_at)
|
||||
self.assertEqual(OLD_SAMPLE['resource_id'], sot.resource_id)
|
||||
self.assertEqual(OLD_SAMPLE['resource_metadata'], sot.metadata)
|
||||
self.assertEqual(OLD_SAMPLE['source'], sot.source)
|
||||
self.assertEqual(OLD_SAMPLE['timestamp'], sot.generated_at)
|
||||
self.assertEqual(OLD_SAMPLE['user_id'], sot.user_id)
|
||||
|
||||
def test_list(self):
|
||||
sess = mock.Mock()
|
||||
resp = mock.Mock()
|
||||
resp.body = [SAMPLE, OLD_SAMPLE]
|
||||
sess.get = mock.Mock(return_value=resp)
|
||||
path_args = {'meter': 'name_of_meter'}
|
||||
|
||||
found = sample.Sample.list(sess, path_args=path_args)
|
||||
self.assertEqual(2, len(found))
|
||||
first = found[0]
|
||||
self.assertIsNone(first.id)
|
||||
self.assertIsNone(first.sample_id)
|
||||
self.assertEqual(SAMPLE['metadata'], first.metadata)
|
||||
self.assertEqual(SAMPLE['meter'], first.meter)
|
||||
self.assertEqual(SAMPLE['project_id'], first.project_id)
|
||||
self.assertEqual(SAMPLE['recorded_at'], first.recorded_at)
|
||||
self.assertEqual(SAMPLE['resource_id'], first.resource_id)
|
||||
self.assertEqual(SAMPLE['source'], first.source)
|
||||
self.assertEqual(SAMPLE['timestamp'], first.generated_at)
|
||||
self.assertEqual(SAMPLE['type'], first.type)
|
||||
self.assertEqual(SAMPLE['unit'], first.unit)
|
||||
self.assertEqual(SAMPLE['user_id'], first.user_id)
|
||||
self.assertEqual(SAMPLE['volume'], first.volume)
|
||||
|
||||
def test_create(self):
|
||||
sess = mock.Mock()
|
||||
resp = mock.Mock()
|
||||
resp.body = [SAMPLE]
|
||||
sess.post = mock.Mock(return_value=resp)
|
||||
|
||||
data = {'id': None,
|
||||
'meter': 'temperature',
|
||||
'project_id': 'project',
|
||||
'resource_id': 'resource',
|
||||
'type': 'gauge',
|
||||
'unit': 'instance',
|
||||
'volume': '98.6'}
|
||||
new_sample = sample.Sample.new(**data)
|
||||
|
||||
new_sample.create(sess)
|
||||
url = '/v2/meters/temperature'
|
||||
sess.post.assert_called_with(url, service=new_sample.service,
|
||||
json=[data])
|
||||
self.assertIsNone(new_sample.id)
|
@@ -52,6 +52,7 @@ class FakeResource(resource.Resource):
|
||||
name = resource.prop('name')
|
||||
first = resource.prop('attr1')
|
||||
second = resource.prop('attr2')
|
||||
third = resource.prop('attr3', alias='attr_three')
|
||||
|
||||
|
||||
class ResourceTests(base.TestTransportBase):
|
||||
@@ -229,6 +230,29 @@ class ResourceTests(base.TestTransportBase):
|
||||
else:
|
||||
self.fail("Didn't raise attribute error")
|
||||
|
||||
try:
|
||||
obj.third
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
self.fail("Didn't raise attribute error")
|
||||
|
||||
def test_composite_attr_happy(self):
|
||||
obj = FakeResource.existing(**{'attr3': '3'})
|
||||
|
||||
try:
|
||||
self.assertEqual('3', obj.third)
|
||||
except AttributeError:
|
||||
self.fail("third was not found as expected")
|
||||
|
||||
def test_composite_attr_fallback(self):
|
||||
obj = FakeResource.existing(**{'attr_three': '3'})
|
||||
|
||||
try:
|
||||
self.assertEqual('3', obj.third)
|
||||
except AttributeError:
|
||||
self.fail("third was not found in fallback as expected")
|
||||
|
||||
|
||||
class FakeResponse:
|
||||
def __init__(self, response):
|
||||
|
Reference in New Issue
Block a user