diff --git a/ceilometer/image/__init__.py b/ceilometer/image/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/ceilometer/image/glance.py b/ceilometer/image/glance.py new file mode 100644 index 00000000..1c744bd9 --- /dev/null +++ b/ceilometer/image/glance.py @@ -0,0 +1,137 @@ +# -*- encoding: utf-8 -*- +# +# Copyright © 2012 New Dream Network, LLC (DreamHost) +# +# Author: Julien Danjou +# +# 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. +"""Common code for working with images +""" + +from __future__ import absolute_import + +import itertools + +from keystoneclient.v2_0 import client as ksclient +from glance.registry import client + +from ceilometer import plugin +from ceilometer.counter import Counter +from ceilometer.openstack.common import cfg +from ceilometer.openstack.common import timeutils + +cfg.CONF.register_opts( + [ + cfg.StrOpt('glance_registry_host', + default='localhost', + help="URL of Glance API server"), + cfg.IntOpt('glance_registry_port', + default=9191, + help="URL of Glance API server"), + cfg.StrOpt('glance_username', + default="glance", + help="Username to use for Glance access"), + cfg.StrOpt('glance_password', + default="admin", + help="Password to use for Glance access"), + cfg.StrOpt('glance_tenant_id', + default="", + help="Tenant ID to use for Glance access"), + cfg.StrOpt('glance_tenant_name', + default="admin", + help="Tenant name to use for Glance access"), + cfg.StrOpt('glance_auth_url', + default="http://localhost:5000/v2.0", + help="Auth URL to use for Glance access"), + ]) + + +class _Base(plugin.PollsterBase): + + @staticmethod + def get_registry_client(): + k = ksclient.Client(username=cfg.CONF.glance_username, + password=cfg.CONF.glance_password, + tenant_id=cfg.CONF.glance_tenant_id, + tenant_name=cfg.CONF.glance_tenant_name, + auth_url=cfg.CONF.glance_auth_url) + return client.RegistryClient(cfg.CONF.glance_registry_host, + cfg.CONF.glance_registry_port, + auth_tok=k.auth_token) + + def iter_images(self): + """Iterate over all images.""" + # We need to ask for both public and non public to get all images. + client = self.get_registry_client() + return itertools.chain( + client.get_images_detailed(filters={"is_public": True}), + client.get_images_detailed(filters={"is_public": False})) + + @staticmethod + def extract_image_metadata(image): + return dict([(k, image[k]) + for k in [ + "status", + "is_public", + "name", + "deleted", + "container_format", + "created_at", + "disk_format", + "updated_at", + "properties", + "min_disk", + "protected", + "location", + "checksum", + "deleted_at", + "min_ram", + "size", + ] + ]) + + +class ImagePollster(_Base): + + def get_counters(self, manager, context): + for image in self.iter_images(): + yield Counter( + source='?', + name='image', + type='absolute', + volume=1, + user_id=None, + project_id=image['owner'], + resource_id=image['id'], + timestamp=timeutils.isotime(), + duration=None, + resource_metadata=self.extract_image_metadata(image), + ) + + +class ImageSizePollster(_Base): + + def get_counters(self, manager, context): + for image in self.iter_images(): + yield Counter( + source='?', + name='image_size', + type='absolute', + volume=image['size'], + user_id=None, + project_id=image['owner'], + resource_id=image['id'], + timestamp=timeutils.isotime(), + duration=None, + resource_metadata=self.extract_image_metadata(image), + ) diff --git a/setup.py b/setup.py index b4ff50e6..e1ae09fa 100755 --- a/setup.py +++ b/setup.py @@ -53,6 +53,8 @@ setuptools.setup( [ceilometer.poll.central] network_floatingip = ceilometer.network.floatingip:FloatingIPPollster + image = ceilometer.image.glance:ImagePollster + image_size = ceilometer.image.glance:ImageSizePollster [ceilometer.storage] log = ceilometer.storage.impl_log:LogStorage diff --git a/tests/image/test_glance.py b/tests/image/test_glance.py new file mode 100644 index 00000000..a977dc6c --- /dev/null +++ b/tests/image/test_glance.py @@ -0,0 +1,108 @@ +# -*- encoding: utf-8 -*- +# +# Copyright © 2012 New Dream Network, LLC (DreamHost) +# +# Author: Julien Danjou +# +# 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 ceilometer.tests import base +from ceilometer.image import glance +from ceilometer.central import manager +from ceilometer.openstack.common import context + + +IMAGE_LIST = [ + {u'status': u'queued', + u'name': "some name", + u'deleted': False, + u'container_format': None, + u'created_at': u'2012-09-18T16:29:46', + u'disk_format': None, + u'updated_at': u'2012-09-18T16:29:46', + u'properties': {}, + u'min_disk': 0, + u'protected': False, + u'id': u'1d21a8d0-25f4-4e0a-b4ec-85f40237676b', + u'location': None, + u'checksum': None, + u'owner': u'4c8364fc20184ed7971b76602aa96184', + u'is_public': True, + u'deleted_at': None, + u'min_ram': 0, + u'size': 2048}, + {u'status': u'active', + u'name': "hello world", + u'deleted': False, + u'container_format': None, + u'created_at': u'2012-09-18T16:27:41', + u'disk_format': None, + u'updated_at': u'2012-09-18T16:27:41', + u'properties': {}, + u'min_disk': 0, + u'protected': False, + u'id': u'22be9f90-864d-494c-aa74-8035fd535989', + u'location': None, + u'checksum': None, + u'owner': u'9e4f98287a0246daa42eaf4025db99d4', + u'is_public': True, + u'deleted_at': None, + u'min_ram': 0, + u'size': 0}, + {u'status': u'queued', + u'name': None, + u'deleted': False, + u'container_format': None, + u'created_at': u'2012-09-18T16:23:27', + u'disk_format': "raw", + u'updated_at': u'2012-09-18T16:23:27', + u'properties': {}, + u'min_disk': 0, + u'protected': False, + u'id': u'8d133f6c-38a8-403c-b02c-7071b69b432d', + u'location': None, + u'checksum': None, + u'owner': u'5f8806a76aa34ee8b8fc8397bd154319', + u'is_public': True, + u'deleted_at': None, + u'min_ram': 0, + u'size': 1024}, +] + + +class TestImagePollster(base.TestCase): + + @staticmethod + def fake_glance_iter_images(foobar): + return iter(IMAGE_LIST) + + def setUp(self): + super(TestImagePollster, self).setUp() + self.context = context.RequestContext('admin', 'admin', is_admin=True) + self.manager = manager.AgentManager() + self.stubs.Set(glance._Base, 'iter_images', self.fake_glance_iter_images) + + def test_glance_image_counter(self): + counters = list(glance.ImagePollster().get_counters(self.manager, + self.context)) + self.assertEqual(len(counters), 3) + for counter in counters: + self.assertEqual(counter.volume, 1) + + def test_glance_image_size_counter(self): + counters = list(glance.ImageSizePollster().get_counters(self.manager, + self.context)) + self.assertEqual(len(counters), 3) + for image in IMAGE_LIST: + self.assert_(any(map(lambda counter: counter.volume == image['size'], + counters))) diff --git a/tools/pip-requires b/tools/pip-requires index b5d09e5b..cd7024cb 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -1,4 +1,5 @@ http://tarballs.openstack.org/nova/nova-master.tar.gz +http://tarballs.openstack.org/glance/glance-master.tar.gz webob kombu iso8601