diff options
-rw-r--r-- | ceilometer/compute/notifications.py | 51 | ||||
-rw-r--r-- | ceilometer/compute/pollsters/util.py | 34 | ||||
-rw-r--r-- | ceilometer/compute/util.py | 49 | ||||
-rw-r--r-- | etc/ceilometer/ceilometer.conf.sample | 2 | ||||
-rw-r--r-- | tests/compute/test_notifications.py | 248 |
5 files changed, 300 insertions, 84 deletions
diff --git a/ceilometer/compute/notifications.py b/ceilometer/compute/notifications.py index 5b59da27..2e243074 100644 --- a/ceilometer/compute/notifications.py +++ b/ceilometer/compute/notifications.py @@ -20,9 +20,13 @@ """Converters for producing compute sample messages from notification events. """ +import abc +import six + from oslo.config import cfg from ceilometer import sample +from ceilometer.compute import util from ceilometer import plugin @@ -50,10 +54,36 @@ class ComputeNotificationBase(plugin.NotificationBase): ] -class InstanceScheduled(ComputeNotificationBase): - event_types = ['scheduler.run_instance.scheduled'] +@six.add_metaclass(abc.ABCMeta) +class UserMetadataAwareInstanceNotificationBase(ComputeNotificationBase): + """Consumes notifications containing instance user metadata. + """ def process_notification(self, message): + instance_properties = self.get_instance_properties(message) + if 'metadata' in instance_properties: + src_metadata = instance_properties['metadata'] + del instance_properties['metadata'] + util.add_reserved_user_metadata(src_metadata, instance_properties) + return self.get_sample(message) + + def get_instance_properties(self, message): + """Retrieve instance properties from notification payload.""" + return message['payload'] + + @abc.abstractmethod + def get_sample(self, message): + """Derive sample from notification payload.""" + + +class InstanceScheduled(UserMetadataAwareInstanceNotificationBase): + event_types = ['scheduler.run_instance.scheduled'] + + def get_instance_properties(self, message): + """Retrieve instance properties from notification payload.""" + return message['payload']['request_spec']['instance_properties'] + + def get_sample(self, message): yield sample.Sample.from_notification( name='instance.scheduled', type=sample.TYPE_DELTA, @@ -67,14 +97,15 @@ class InstanceScheduled(ComputeNotificationBase): message=message) -class ComputeInstanceNotificationBase(ComputeNotificationBase): +class ComputeInstanceNotificationBase( + UserMetadataAwareInstanceNotificationBase): """Convert compute.instance.* notifications into Samples """ event_types = ['compute.instance.*'] class Instance(ComputeInstanceNotificationBase): - def process_notification(self, message): + def get_sample(self, message): yield sample.Sample.from_notification( name='instance', type=sample.TYPE_GAUGE, @@ -87,7 +118,7 @@ class Instance(ComputeInstanceNotificationBase): class Memory(ComputeInstanceNotificationBase): - def process_notification(self, message): + def get_sample(self, message): yield sample.Sample.from_notification( name='memory', type=sample.TYPE_GAUGE, @@ -100,7 +131,7 @@ class Memory(ComputeInstanceNotificationBase): class VCpus(ComputeInstanceNotificationBase): - def process_notification(self, message): + def get_sample(self, message): yield sample.Sample.from_notification( name='vcpus', type=sample.TYPE_GAUGE, @@ -113,7 +144,7 @@ class VCpus(ComputeInstanceNotificationBase): class RootDiskSize(ComputeInstanceNotificationBase): - def process_notification(self, message): + def get_sample(self, message): yield sample.Sample.from_notification( name='disk.root.size', type=sample.TYPE_GAUGE, @@ -126,7 +157,7 @@ class RootDiskSize(ComputeInstanceNotificationBase): class EphemeralDiskSize(ComputeInstanceNotificationBase): - def process_notification(self, message): + def get_sample(self, message): yield sample.Sample.from_notification( name='disk.ephemeral.size', type=sample.TYPE_GAUGE, @@ -139,7 +170,7 @@ class EphemeralDiskSize(ComputeInstanceNotificationBase): class InstanceFlavor(ComputeInstanceNotificationBase): - def process_notification(self, message): + def get_sample(self, message): instance_type = message.get('payload', {}).get('instance_type') if instance_type: yield sample.Sample.from_notification( @@ -160,7 +191,7 @@ class InstanceDelete(ComputeInstanceNotificationBase): event_types = ['compute.instance.delete.samples'] - def process_notification(self, message): + def get_sample(self, message): for s in message['payload'].get('samples', []): yield sample.Sample.from_notification( name=s['name'], diff --git a/ceilometer/compute/pollsters/util.py b/ceilometer/compute/pollsters/util.py index b8d84977..7b7f9af8 100644 --- a/ceilometer/compute/pollsters/util.py +++ b/ceilometer/compute/pollsters/util.py @@ -18,8 +18,7 @@ # License for the specific language governing permissions and limitations # under the License. -from oslo.config import cfg - +from ceilometer.compute import util as compute_util from ceilometer import sample from ceilometer.openstack.common import timeutils @@ -35,35 +34,6 @@ INSTANCE_PROPERTIES = [ 'ramdisk_id', ] -OPTS = [ - cfg.ListOpt('reserved_metadata_namespace', - default=['metering.'], - help='list of metadata prefixes reserved for metering use'), - cfg.IntOpt('reserved_metadata_length', - default=256, - help='limit on length of reserved metadata values'), -] - -cfg.CONF.register_opts(OPTS) - - -def _add_reserved_user_metadata(instance, metadata): - limit = cfg.CONF.reserved_metadata_length - user_metadata = {} - for prefix in cfg.CONF.reserved_metadata_namespace: - md = dict( - (k[len(prefix):].replace('.', '_'), - v[:limit] if isinstance(v, basestring) else v) - for k, v in instance.metadata.items() - if (k.startswith(prefix) and - k[len(prefix):].replace('.', '_') not in metadata) - ) - user_metadata.update(md) - if user_metadata: - metadata['user_metadata'] = user_metadata - - return metadata - def _get_metadata_from_object(instance): """Return a metadata dictionary for the instance. @@ -97,7 +67,7 @@ def _get_metadata_from_object(instance): metadata['root_gb'] = int(metadata['disk_gb']) - \ int(metadata['ephemeral_gb']) - return _add_reserved_user_metadata(instance, metadata) + return compute_util.add_reserved_user_metadata(instance.metadata, metadata) def make_sample_from_instance(instance, name, type, unit, volume, diff --git a/ceilometer/compute/util.py b/ceilometer/compute/util.py new file mode 100644 index 00000000..ba142b45 --- /dev/null +++ b/ceilometer/compute/util.py @@ -0,0 +1,49 @@ +# -*- encoding: utf-8 -*- +# +# Copyright © 2014 Red Hat, Inc +# +# Author: Eoghan Glynn <eglynn@redhat.com> +# +# 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 oslo.config import cfg + + +OPTS = [ + cfg.ListOpt('reserved_metadata_namespace', + default=['metering.'], + help='List of metadata prefixes reserved for metering use.'), + cfg.IntOpt('reserved_metadata_length', + default=256, + help='Limit on length of reserved metadata values.'), +] + +cfg.CONF.register_opts(OPTS) + + +def add_reserved_user_metadata(src_metadata, dest_metadata): + limit = cfg.CONF.reserved_metadata_length + user_metadata = {} + for prefix in cfg.CONF.reserved_metadata_namespace: + md = dict( + (k[len(prefix):].replace('.', '_'), + v[:limit] if isinstance(v, basestring) else v) + for k, v in src_metadata.items() + if (k.startswith(prefix) and + k[len(prefix):].replace('.', '_') not in dest_metadata) + ) + user_metadata.update(md) + if user_metadata: + dest_metadata['user_metadata'] = user_metadata + + return dest_metadata diff --git a/etc/ceilometer/ceilometer.conf.sample b/etc/ceilometer/ceilometer.conf.sample index 6b0ee20c..c514d2d3 100644 --- a/etc/ceilometer/ceilometer.conf.sample +++ b/etc/ceilometer/ceilometer.conf.sample @@ -48,7 +48,7 @@ # -# Options defined in ceilometer.compute.pollsters.util +# Options defined in ceilometer.compute.util # # list of metadata prefixes reserved for metering use (list diff --git a/tests/compute/test_notifications.py b/tests/compute/test_notifications.py index d341b3b4..c7b4bee5 100644 --- a/tests/compute/test_notifications.py +++ b/tests/compute/test_notifications.py @@ -201,7 +201,9 @@ INSTANCE_FINISH_RESIZE_END = { u'hostname': u's1', u'state': u'resized', u'launched_at': u'2013-01-04T15:10:14.923939', - u'metadata': [], + u'metadata': {u'metering.server_group': u'Group_A', + u'AutoScalingGroupName': u'tyky-Group_Awste7', + u'metering.foo.bar': u'true'}, u'ramdisk_id': u'5f23128e-5525-46d8-bc66-9c30cd87141a', u'access_ip_v6': None, u'disk_gb': 20, @@ -258,7 +260,9 @@ INSTANCE_RESIZE_REVERT_END = { u'hostname': u's1', u'state': u'resized', u'launched_at': u'2013-01-04T15:10:14.000000', - u'metadata': [], + u'metadata': {u'metering.server_group': u'Group_A', + u'AutoScalingGroupName': u'tyky-Group_A-wste7', + u'metering.foo.bar': u'true'}, u'ramdisk_id': u'5f23128e-5525-46d8-bc66-9c30cd87141a', u'access_ip_v6': None, u'disk_gb': 0, @@ -315,7 +319,9 @@ INSTANCE_DELETE_SAMPLES = { u'hostname': u's1', u'state': u'resized', u'launched_at': u'2013-01-04T15:10:14.000000', - u'metadata': [], + u'metadata': {u'metering.server_group': u'Group_A', + u'AutoScalingGroupName': u'tyky-Group_A-wste7', + u'metering.foo.bar': u'true'}, u'ramdisk_id': u'5f23128e-5525-46d8-bc66-9c30cd87141a', u'access_ip_v6': None, u'disk_gb': 0, @@ -352,49 +358,190 @@ INSTANCE_DELETE_SAMPLES = { u'priority': u'INFO' } - INSTANCE_SCHEDULED = { - u'_context_roles': [u'admin'], - u'_context_request_id': u'req-9da1d714-dabe-42fd-8baa-583e57cd4f1a', + u'_context_request_id': u'req-f28a836a-32bf-4cc3-940a-3515878c181f', u'_context_quota_class': None, u'event_type': u'scheduler.run_instance.scheduled', - u'_context_user_name': u'admin', - u'_context_project_name': u'admin', - u'timestamp': u'2013-01-04 15:20:32.009532', - u'_context_is_admin': True, - u'message_id': u'c48deeba-d0c3-4154-b3db-47480b52267a', - u'_context_auth_token': None, - u'_context_instance_lock_checked': False, - u'_context_project_id': u'cea4b25edb484e5392727181b7721d29', - u'_context_timestamp': u'2013-01-04T15:19:51.018218', - u'_context_read_deleted': u'no', - u'_context_user_id': u'01b83a5e23f24a6fb6cd073c0aee6eed', - u'_context_remote_address': u'10.147.132.184', - u'publisher_id': u'compute.ip-10-147-132-184.ec2.internal', + u'_context_service_catalog': [{ + u'endpoints': [{ + u'adminURL': + u'http://172.16.12.21:8776/v1/2bd766a095b44486bf07cf7f666997eb', + u'region': u'RegionOne', + u'internalURL': + u'http://172.16.12.21:8776/v1/2bd766a095b44486bf07cf7f666997eb', + u'id': u'30cb904fdc294eea9b225e06b2d0d4eb', + u'publicURL': + u'http://172.16.12.21:8776/v1/2bd766a095b44486bf07cf7f666997eb'}], + u'endpoints_links': [], + u'type': u'volume', + u'name': u'cinder'}], + u'_context_auth_token': u'TOK', + u'_context_user_id': u'0a757cd896b64b65ba3784afef564116', u'payload': { 'instance_id': 'fake-uuid1-1', - 'weighted_host': { - 'host': 'host3', - 'weight': 3.0, - }, - 'request_spec': { - 'instance_properties': { - 'root_gb': 512, - 'ephemeral_gb': 0, - 'launch_index': 0, - 'memory_mb': 512, - 'vcpus': 1, - 'os_type': 'Linux', - 'project_id': 1, - 'system_metadata': {'system': 'metadata'}}, - 'instance_type': {'memory_mb': 512, - 'vcpus': 1, - 'root_gb': 512, - 'ephemeral_gb': 0}, - 'instance_uuids': ['fake-uuid1-1'], - }, - }, - u'priority': u'INFO' + u'weighted_host': {u'host': u'eglynn-f19-devstack3', u'weight': 1.0}, + u'request_spec': { + u'num_instances': 1, + u'block_device_mapping': [{ + u'instance_uuid': u'9206baae-c3b6-41bc-96f2-2c0726ff51c8', + u'guest_format': None, + u'boot_index': 0, + u'no_device': None, + u'connection_info': None, + u'volume_id': None, + u'volume_size': None, + u'device_name': None, + u'disk_bus': None, + u'image_id': u'0560ac3f-3bcd-434d-b012-8dd7a212b73b', + u'source_type': u'image', + u'device_type': u'disk', + u'snapshot_id': None, + u'destination_type': u'local', + u'delete_on_termination': True}], + u'image': { + u'status': u'active', + u'name': u'cirros-0.3.1-x86_64-uec', + u'deleted': False, + u'container_format': u'ami', + u'created_at': u'2014-02-18T13:16:26.000000', + u'disk_format': u'ami', + u'updated_at': u'2014-02-18T13:16:27.000000', + u'properties': { + u'kernel_id': u'c8794c1a-4158-42cc-9f97-d0d250c9c6a4', + u'ramdisk_id': u'4999726c-545c-4a9e-bfc0-917459784275'}, + u'min_disk': 0, + u'min_ram': 0, + u'checksum': u'f8a2eeee2dc65b3d9b6e63678955bd83', + u'owner': u'2bd766a095b44486bf07cf7f666997eb', + u'is_public': True, + u'deleted_at': None, + u'id': u'0560ac3f-3bcd-434d-b012-8dd7a212b73b', + u'size': 25165824}, + u'instance_type': { + u'root_gb': 1, + u'name': u'm1.tiny', + u'ephemeral_gb': 0, + u'memory_mb': 512, + u'vcpus': 1, + u'extra_specs': {}, + u'swap': 0, + u'rxtx_factor': 1.0, + u'flavorid': u'1', + u'vcpu_weight': None, + u'id': 2}, + u'instance_properties': { + u'vm_state': u'building', + u'availability_zone': None, + u'terminated_at': None, + u'ephemeral_gb': 0, + u'instance_type_id': 2, + u'user_data': None, + u'cleaned': False, + u'vm_mode': None, + u'deleted_at': None, + u'reservation_id': u'r-ven5q6om', + u'id': 15, + u'security_groups': [{ + u'deleted_at': None, + u'user_id': u'0a757cd896b64b65ba3784afef564116', + u'description': u'default', + u'deleted': False, + u'created_at': u'2014-02-19T11:02:31.000000', + u'updated_at': None, + u'project_id': u'2bd766a095b44486bf07cf7f666997eb', + u'id': 1, + u'name': u'default'}], + u'disable_terminate': False, + u'root_device_name': None, + u'display_name': u'new', + u'uuid': u'9206baae-c3b6-41bc-96f2-2c0726ff51c8', + u'default_swap_device': None, + u'info_cache': { + u'instance_uuid': u'9206baae-c3b6-41bc-96f2-2c0726ff51c8', + u'deleted': False, + u'created_at': u'2014-03-05T12:44:00.000000', + u'updated_at': None, + u'network_info': [], + u'deleted_at': None}, + u'hostname': u'new', + u'launched_on': None, + u'display_description': u'new', + u'key_data': None, + u'deleted': False, + u'config_drive': u'', + u'power_state': 0, + u'default_ephemeral_device': None, + u'progress': 0, + u'project_id': u'2bd766a095b44486bf07cf7f666997eb', + u'launched_at': None, + u'scheduled_at': None, + u'node': None, + u'ramdisk_id': u'4999726c-545c-4a9e-bfc0-917459784275', + u'access_ip_v6': None, + u'access_ip_v4': None, + u'kernel_id': u'c8794c1a-4158-42cc-9f97-d0d250c9c6a4', + u'key_name': None, + u'updated_at': None, + u'host': None, + u'root_gb': 1, + u'user_id': u'0a757cd896b64b65ba3784afef564116', + u'system_metadata': { + u'image_kernel_id': + u'c8794c1a-4158-42cc-9f97-d0d250c9c6a4', + u'image_min_disk': u'1', + u'instance_type_memory_mb': u'512', + u'instance_type_swap': u'0', + u'instance_type_vcpu_weight': None, + u'instance_type_root_gb': u'1', + u'instance_type_name': u'm1.tiny', + u'image_ramdisk_id': + u'4999726c-545c-4a9e-bfc0-917459784275', + u'instance_type_id': u'2', + u'instance_type_ephemeral_gb': u'0', + u'instance_type_rxtx_factor': u'1.0', + u'instance_type_flavorid': u'1', + u'instance_type_vcpus': u'1', + u'image_container_format': u'ami', + u'image_min_ram': u'0', + u'image_disk_format': u'ami', + u'image_base_image_ref': + u'0560ac3f-3bcd-434d-b012-8dd7a212b73b'}, + u'task_state': u'scheduling', + u'shutdown_terminate': False, + u'cell_name': None, + u'ephemeral_key_uuid': None, + u'locked': False, + u'name': u'instance-0000000f', + u'created_at': u'2014-03-05T12:44:00.000000', + u'locked_by': None, + u'launch_index': 0, + u'memory_mb': 512, + u'vcpus': 1, + u'image_ref': u'0560ac3f-3bcd-434d-b012-8dd7a212b73b', + u'architecture': None, + u'auto_disk_config': False, + u'os_type': None, + u'metadata': {u'metering.server_group': u'Group_A', + u'AutoScalingGroupName': u'tyky-Group_Awste7', + u'metering.foo.bar': u'true'}}, + u'security_group': [u'default'], + u'instance_uuids': [u'9206baae-c3b6-41bc-96f2-2c0726ff51c8']}}, + u'priority': u'INFO', + u'_context_is_admin': True, + u'_context_timestamp': u'2014-03-05T12:44:00.135674', + u'publisher_id': u'scheduler.eglynn-f19-devstack3', + u'message_id': u'd6c1ae63-a26b-47c7-8397-8794216e09dd', + u'_context_remote_address': u'172.16.12.21', + u'_context_roles': [u'_member_', u'admin'], + u'timestamp': u'2014-03-05 12:44:00.733758', + u'_context_user': u'0a757cd896b64b65ba3784afef564116', + u'_unique_id': u'2af47cbdde604ff794bb046f3f9db1e2', + u'_context_project_name': u'admin', + u'_context_read_deleted': u'no', + u'_context_tenant': u'2bd766a095b44486bf07cf7f666997eb', + u'_context_instance_lock_checked': False, + u'_context_project_id': u'2bd766a095b44486bf07cf7f666997eb', + u'_context_user_name': u'admin' } @@ -424,6 +571,14 @@ class TestNotifications(base.TestCase): def _find_counter(counters, name): return filter(lambda counter: counter.name == name, counters)[0] + def _verify_user_metadata(self, metadata): + self.assertIn('user_metadata', metadata) + user_meta = metadata['user_metadata'] + self.assertEqual(user_meta.get('server_group'), 'Group_A') + self.assertNotIn('AutoScalingGroupName', user_meta) + self.assertIn('foo_bar', user_meta) + self.assertNotIn('foo.bar', user_meta) + def test_instance_create_instance(self): ic = notifications.Instance() counters = list(ic.process_notification(INSTANCE_CREATE_END)) @@ -501,6 +656,7 @@ class TestNotifications(base.TestCase): c = counters[0] self.assertEqual(c.volume, 1) self.assertEqual(c.name, 'instance:m1.small') + self._verify_user_metadata(c.resource_metadata) def test_instance_finish_resize_memory(self): ic = notifications.Memory() @@ -509,6 +665,7 @@ class TestNotifications(base.TestCase): c = counters[0] self.assertEqual(c.volume, INSTANCE_FINISH_RESIZE_END['payload']['memory_mb']) + self._verify_user_metadata(c.resource_metadata) def test_instance_finish_resize_vcpus(self): ic = notifications.VCpus() @@ -524,6 +681,7 @@ class TestNotifications(base.TestCase): self.assertEqual(len(counters), 1) c = counters[0] self.assertEqual(c.volume, 1) + self._verify_user_metadata(c.resource_metadata) def test_instance_resize_finish_flavor(self): ic = notifications.InstanceFlavor() @@ -532,6 +690,7 @@ class TestNotifications(base.TestCase): c = counters[0] self.assertEqual(c.volume, 1) self.assertEqual(c.name, 'instance:m1.tiny') + self._verify_user_metadata(c.resource_metadata) def test_instance_resize_finish_memory(self): ic = notifications.Memory() @@ -540,6 +699,7 @@ class TestNotifications(base.TestCase): c = counters[0] self.assertEqual(c.volume, INSTANCE_RESIZE_REVERT_END['payload']['memory_mb']) + self._verify_user_metadata(c.resource_metadata) def test_instance_resize_finish_vcpus(self): ic = notifications.VCpus() @@ -548,6 +708,7 @@ class TestNotifications(base.TestCase): c = counters[0] self.assertEqual(c.volume, INSTANCE_RESIZE_REVERT_END['payload']['vcpus']) + self._verify_user_metadata(c.resource_metadata) def test_instance_delete_samples(self): ic = notifications.InstanceDelete() @@ -555,6 +716,8 @@ class TestNotifications(base.TestCase): self.assertEqual(len(counters), 2) names = [c.name for c in counters] self.assertEqual(names, ['sample-name1', 'sample-name2']) + c = counters[0] + self._verify_user_metadata(c.resource_metadata) def test_instance_scheduled(self): ic = notifications.InstanceScheduled() @@ -568,3 +731,6 @@ class TestNotifications(base.TestCase): self.assertEqual(names, ['instance.scheduled']) rid = [c.resource_id for c in counters] self.assertEqual(rid, ['fake-uuid1-1']) + c = counters[0] + metadata = c.resource_metadata['request_spec']['instance_properties'] + self._verify_user_metadata(metadata) |