From 0a895a8212cdaa71c91c7f92b38057e6a0d0a3d9 Mon Sep 17 00:00:00 2001 From: Pradeep Kilambi Date: Wed, 1 Jul 2015 11:04:18 -0400 Subject: [PATCH] Declarative meters support This is the initial support to migrate some basic meters to leverage declarative approach. Partially Implements: blueprint declarative-notifications Change-Id: I64a4b2925981cfa14dc22ae30acd1f17821c9a2c --- ceilometer/agent/plugin_base.py | 5 +- ceilometer/meter/__init__.py | 0 ceilometer/meter/notifications.py | 204 +++++++++ .../key_value_storage/test_notifications.py | 35 +- ceilometer/tests/meter/__init__.py | 0 ceilometer/tests/meter/test_notifications.py | 149 +++++++ .../tests/orchestration/test_notifications.py | 22 +- etc/ceilometer/meters.yaml | 393 ++++++++++++++++++ 8 files changed, 786 insertions(+), 22 deletions(-) create mode 100644 ceilometer/meter/__init__.py create mode 100644 ceilometer/meter/notifications.py create mode 100644 ceilometer/tests/meter/__init__.py create mode 100644 ceilometer/tests/meter/test_notifications.py create mode 100644 etc/ceilometer/meters.yaml diff --git a/ceilometer/agent/plugin_base.py b/ceilometer/agent/plugin_base.py index 814f1862..3cae66cc 100644 --- a/ceilometer/agent/plugin_base.py +++ b/ceilometer/agent/plugin_base.py @@ -93,8 +93,9 @@ class NotificationBase(PluginBase): super(NotificationBase, self).__init__() # NOTE(gordc): this is filter rule used by oslo.messaging to dispatch # messages to an endpoint. - self.filter_rule = oslo_messaging.NotificationFilter( - event_type='|'.join(self.event_types)) + if self.event_types is not None: + self.filter_rule = oslo_messaging.NotificationFilter( + event_type='|'.join(self.event_types)) self.manager = manager @abc.abstractproperty diff --git a/ceilometer/meter/__init__.py b/ceilometer/meter/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/ceilometer/meter/notifications.py b/ceilometer/meter/notifications.py new file mode 100644 index 00000000..b9584d70 --- /dev/null +++ b/ceilometer/meter/notifications.py @@ -0,0 +1,204 @@ +# +# 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 fnmatch +import os +import six +import yaml + +import jsonpath_rw +from oslo_config import cfg +from oslo_log import log +import oslo_messaging + +from ceilometer.agent import plugin_base +from ceilometer.i18n import _LE +from ceilometer import sample + +OPTS = [ + cfg.StrOpt('meter_definitions_cfg_file', + default="meters.yaml", + help="Configuration file for defining meter notifications." + ), +] + +cfg.CONF.register_opts(OPTS, group='meter') + +LOG = log.getLogger(__name__) + + +class MeterDefinitionException(Exception): + def __init__(self, message, definition_cfg): + super(MeterDefinitionException, self).__init__(message) + self.definition_cfg = definition_cfg + + def __str__(self): + return '%s %s: %s' % (self.__class__.__name__, + self.definition_cfg, self.message) + + +class MeterDefinition(object): + + def __init__(self, definition_cfg): + self.cfg = definition_cfg + self._validate_type() + + def match_type(self, meter_name): + try: + event_type = self.cfg['event_type'] + except KeyError as err: + raise MeterDefinitionException( + _LE("Required field %s not specified") % err.args[0], self.cfg) + + if isinstance(event_type, six.string_types): + event_type = [event_type] + for t in event_type: + if fnmatch.fnmatch(meter_name, t): + return True + + def parse_fields(self, field, message): + fval = self.cfg.get(field) + if not fval: + return + if isinstance(fval, six.integer_types): + return fval + try: + parts = jsonpath_rw.parse(fval) + except Exception as e: + raise MeterDefinitionException( + _LE("Parse error in JSONPath specification " + "'%(jsonpath)s': %(err)s") + % dict(jsonpath=parts, err=e), self.cfg) + values = [match.value for match in parts.find(message) + if match.value is not None] + if values: + return values[0] + + def _validate_type(self): + if self.cfg['type'] not in sample.TYPES: + raise MeterDefinitionException( + _LE("Invalid type %s specified") % self.cfg['type'], self.cfg) + + +def get_config_file(): + config_file = cfg.CONF.meter.meter_definitions_cfg_file + if not os.path.exists(config_file): + config_file = cfg.CONF.find_file(config_file) + return config_file + + +def setup_meters_config(): + """Setup the meters definitions from yaml config file.""" + config_file = get_config_file() + if config_file is not None: + LOG.debug(_LE("Meter Definitions configuration file: %s"), config_file) + + with open(config_file) as cf: + config = cf.read() + + try: + meters_config = yaml.safe_load(config) + except yaml.YAMLError as err: + if hasattr(err, 'problem_mark'): + mark = err.problem_mark + errmsg = (_LE("Invalid YAML syntax in Meter Definitions file " + "%(file)s at line: %(line)s, column: %(column)s.") + % dict(file=config_file, + line=mark.line + 1, + column=mark.column + 1)) + else: + errmsg = (_LE("YAML error reading Meter Definitions file " + "%(file)s") + % dict(file=config_file)) + LOG.error(errmsg) + raise + + else: + LOG.debug(_LE("No Meter Definitions configuration file found!" + " Using default config.")) + meters_config = [] + + LOG.info(_LE("Meter Definitions: %s"), meters_config) + + return meters_config + + +def load_definitions(config_def): + return [MeterDefinition(event_def) + for event_def in reversed(config_def['metric'])] + + +class ProcessMeterNotifications(plugin_base.NotificationBase): + + event_types = None + + def __init__(self, manager): + super(ProcessMeterNotifications, self).__init__(manager) + self.definitions = load_definitions(setup_meters_config()) + + def get_targets(self, conf): + """Return a sequence of oslo_messaging.Target + + It is defining the exchange and topics to be connected for this plugin. + :param conf: Configuration. + #TODO(prad): This should be defined in the notification agent + """ + targets = [] + exchanges = [ + conf.nova_control_exchange, + conf.cinder_control_exchange, + conf.glance_control_exchange, + conf.neutron_control_exchange, + conf.heat_control_exchange, + conf.keystone_control_exchange, + conf.sahara_control_exchange, + conf.trove_control_exchange, + conf.zaqar_control_exchange, + conf.swift_control_exchange, + conf.magnetodb_control_exchange, + conf.ceilometer_control_exchange, + ] + + for exchange in exchanges: + targets.extend(oslo_messaging.Target(topic=topic, + exchange=exchange) + for topic in conf.notification_topics) + return targets + + def process_notification(self, notification_body): + for d in self.definitions: + if d.match_type(notification_body['event_type']): + userid = self.get_user_id(d, notification_body) + projectid = self.get_project_id(d, notification_body) + resourceid = d.parse_fields('resource_id', notification_body) + yield sample.Sample.from_notification( + name=d.cfg['name'], + type=d.cfg['type'], + unit=d.cfg['unit'], + volume=d.parse_fields('volume', notification_body), + resource_id=resourceid, + user_id=userid, + project_id=projectid, + message=notification_body) + + @staticmethod + def get_user_id(d, notification_body): + return (d.parse_fields('user_id', notification_body) or + notification_body.get('_context_user_id') or + notification_body.get('_context_user', None)) + + @staticmethod + def get_project_id(d, notification_body): + return (d.parse_fields('project_id', notification_body) or + notification_body.get('_context_tenant_id') or + notification_body.get('_context_tenant', None)) diff --git a/ceilometer/tests/key_value_storage/test_notifications.py b/ceilometer/tests/key_value_storage/test_notifications.py index e385605e..1731c268 100644 --- a/ceilometer/tests/key_value_storage/test_notifications.py +++ b/ceilometer/tests/key_value_storage/test_notifications.py @@ -13,10 +13,12 @@ import datetime import mock -from oslotest import base -from ceilometer.key_value_storage import notifications +from oslo_config import fixture as fixture_config + +from ceilometer.meter import notifications from ceilometer import sample +from ceilometer.tests import base as test def fake_uuid(x): @@ -70,7 +72,15 @@ NOTIFICATION_TABLE_DELETE = { } -class TestNotification(base.BaseTestCase): +class TestNotification(test.BaseTestCase): + + def setUp(self): + super(TestNotification, self).setUp() + self.CONF = self.useFixture(fixture_config.Config()).conf + self.CONF.set_override( + 'meter_definitions_cfg_file', + self.path_get('etc/ceilometer/meters.yaml'), group='meter') + self.handler = notifications.ProcessMeterNotifications(mock.Mock()) def _verify_common_counter(self, c, name, volume): self.assertIsNotNone(c) @@ -82,19 +92,18 @@ class TestNotification(base.BaseTestCase): self.assertEqual(u'magnetodb.winterfell.com', metadata.get('host')) def test_create_table(self): - handler = notifications.Table(mock.Mock()) - counters = list(handler.process_notification( + counters = list(self.handler.process_notification( NOTIFICATION_TABLE_CREATE)) - self.assertEqual(1, len(counters)) - table = counters[0] + self.assertEqual(2, len(counters)) + table = [item for item in counters + if item.name == "magnetodb.table.create"][0] self._verify_common_counter(table, 'magnetodb.table.create', 1) self.assertEqual(fake_uuid('u'), table.user_id) self.assertEqual(fake_uuid('t'), table.project_id) self.assertEqual(sample.TYPE_GAUGE, table.type) def test_delete_table(self): - handler = notifications.Table(mock.Mock()) - counters = list(handler.process_notification( + counters = list(self.handler.process_notification( NOTIFICATION_TABLE_DELETE)) self.assertEqual(1, len(counters)) table = counters[0] @@ -104,11 +113,11 @@ class TestNotification(base.BaseTestCase): self.assertEqual(sample.TYPE_GAUGE, table.type) def test_index_count(self): - handler = notifications.Index(mock.Mock()) - counters = list(handler.process_notification( + counters = list(self.handler.process_notification( NOTIFICATION_TABLE_CREATE)) - self.assertEqual(1, len(counters)) - table = counters[0] + self.assertEqual(2, len(counters)) + table = [item for item in counters + if item.name == "magnetodb.table.index.count"][0] self._verify_common_counter(table, 'magnetodb.table.index.count', 2) self.assertEqual(fake_uuid('u'), table.user_id) self.assertEqual(fake_uuid('t'), table.project_id) diff --git a/ceilometer/tests/meter/__init__.py b/ceilometer/tests/meter/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/ceilometer/tests/meter/test_notifications.py b/ceilometer/tests/meter/test_notifications.py new file mode 100644 index 00000000..a4f516e3 --- /dev/null +++ b/ceilometer/tests/meter/test_notifications.py @@ -0,0 +1,149 @@ +# +# 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. +"""Tests for ceilometer.meter.notifications +""" +import mock +import six +import yaml + +from oslo_config import fixture as fixture_config + +from ceilometer.meter import notifications +from ceilometer.openstack.common import fileutils +from ceilometer.tests import base as test + +NOTIFICATION = { + 'event_type': u'test.create', + 'timestamp': u'2015-06-1909: 19: 35.786893', + 'payload': {u'user_id': u'e1d870e51c7340cb9d555b15cbfcaec2', + u'resource_id': u'bea70e51c7340cb9d555b15cbfcaec23', + u'timestamp': u'2015-06-19T09: 19: 35.785330', + u'message_signature': u'fake_signature1', + u'resource_metadata': {u'foo': u'bar'}, + u'source': u'30be1fc9a03c4e94ab05c403a8a377f2: openstack', + u'volume': 1.0, + u'project_id': u'30be1fc9a03c4e94ab05c403a8a377f2', + }, + u'_context_tenant': u'30be1fc9a03c4e94ab05c403a8a377f2', + u'_context_request_id': u'req-da91b4bf-d2b5-43ae-8b66-c7752e72726d', + u'_context_user': u'e1d870e51c7340cb9d555b15cbfcaec2', + 'message_id': u'939823de-c242-45a2-a399-083f4d6a8c3e', + 'publisher_id': "foo123" +} + + +class TestMeterDefinition(test.BaseTestCase): + + def test_config_definition(self): + cfg = dict(name="test", + event_type="test.create", + type="delta", + unit="B", + volume="payload.volume", + resource_id="payload.resource_id", + project_id="payload.project_id") + handler = notifications.MeterDefinition(cfg) + self.assertTrue(handler.match_type("test.create")) + self.assertEqual(1, handler.parse_fields("volume", NOTIFICATION)) + self.assertEqual("bea70e51c7340cb9d555b15cbfcaec23", + handler.parse_fields("resource_id", NOTIFICATION)) + self.assertEqual("30be1fc9a03c4e94ab05c403a8a377f2", + handler.parse_fields("project_id", NOTIFICATION)) + + def test_config_missing_fields(self): + cfg = dict(name="test", type="delta") + handler = notifications.MeterDefinition(cfg) + try: + handler.match_type("test.create") + except notifications.MeterDefinitionException as e: + self.assertEqual("Required field event_type not specified", + e.message) + + def test_bad_type_cfg_definition(self): + cfg = dict(name="test", type="foo") + try: + notifications.MeterDefinition(cfg) + except notifications.MeterDefinitionException as e: + self.assertEqual("Invalid type foo specified", e.message) + + +class TestMeterProcessing(test.BaseTestCase): + + def setUp(self): + super(TestMeterProcessing, self).setUp() + self.CONF = self.useFixture(fixture_config.Config()).conf + self.CONF.set_override( + 'meter_definitions_cfg_file', + self.path_get('etc/ceilometer/meters.yaml'), group='meter') + self.handler = notifications.ProcessMeterNotifications(mock.Mock()) + + def __setup_meter_def_file(self, cfg): + if six.PY3: + cfg = cfg.encode('utf-8') + meter_cfg_file = fileutils.write_to_tempfile(content=cfg, + prefix="meters", + suffix="yaml") + self.CONF.set_override( + 'meter_definitions_cfg_file', + meter_cfg_file, group='meter') + cfg = notifications.setup_meters_config() + return cfg + + def test_multiple_meter(self): + cfg = yaml.dump( + {'metric': [dict(name="test1", + event_type="test.create", + type="delta", + unit="B", + volume="payload.volume", + resource_id="payload.resource_id", + project_id="payload.project_id"), + dict(name="test2", + event_type="test.create", + type="delta", + unit="B", + volume="payload.volume", + resource_id="payload.resource_id", + project_id="payload.project_id")]}) + self.handler.definitions = notifications.load_definitions( + self.__setup_meter_def_file(cfg)) + c = list(self.handler.process_notification(NOTIFICATION)) + self.assertEqual(2, len(c)) + + def test_unmatched_meter(self): + cfg = yaml.dump( + {'metric': [dict(name="test1", + event_type="test.update", + type="delta", + unit="B", + volume="payload.volume", + resource_id="payload.resource_id", + project_id="payload.project_id")]}) + self.handler.definitions = notifications.load_definitions( + self.__setup_meter_def_file(cfg)) + c = list(self.handler.process_notification(NOTIFICATION)) + self.assertEqual(0, len(c)) + + def test_regex_match_meter(self): + cfg = yaml.dump( + {'metric': [dict(name="test1", + event_type="test.*", + type="delta", + unit="B", + volume="payload.volume", + resource_id="payload.resource_id", + project_id="payload.project_id")]}) + self.handler.definitions = notifications.load_definitions( + self.__setup_meter_def_file(cfg)) + c = list(self.handler.process_notification(NOTIFICATION)) + self.assertEqual(1, len(c)) diff --git a/ceilometer/tests/orchestration/test_notifications.py b/ceilometer/tests/orchestration/test_notifications.py index e72e7dc6..f2223545 100644 --- a/ceilometer/tests/orchestration/test_notifications.py +++ b/ceilometer/tests/orchestration/test_notifications.py @@ -14,11 +14,12 @@ import datetime import mock from oslo_config import cfg +from oslo_config import fixture as fixture_config from oslo_log import log -from oslotest import base -from ceilometer.orchestration import notifications +from ceilometer.meter import notifications from ceilometer import sample +from ceilometer.tests import base as test NOW = datetime.datetime.isoformat(datetime.datetime.utcnow()) @@ -51,8 +52,7 @@ def stack_notification_for(operation, use_trust=None): trustor_id = None return { - u'event_type': '%s.stack.%s.end' % (notifications.SERVICE, - operation), + u'event_type': 'orchestration.stack.%s.end' % operation, u'_context_roles': [ u'Member', ], @@ -87,7 +87,15 @@ def stack_notification_for(operation, use_trust=None): } -class TestNotification(base.BaseTestCase): +class TestNotification(test.BaseTestCase): + + def setUp(self): + super(TestNotification, self).setUp() + self.CONF = self.useFixture(fixture_config.Config()).conf + self.CONF.set_override( + 'meter_definitions_cfg_file', + self.path_get('etc/ceilometer/meters.yaml'), group='meter') + self.handler = notifications.ProcessMeterNotifications(mock.Mock()) def _verify_common_sample(self, s, name, volume): self.assertIsNotNone(s) @@ -102,8 +110,8 @@ class TestNotification(base.BaseTestCase): def _test_operation(self, operation, trust=None): notif = stack_notification_for(operation, trust) - handler = notifications.StackCRUD(mock.Mock()) - data = list(handler.process_notification(notif)) + + data = list(self.handler.process_notification(notif)) self.assertEqual(1, len(data)) if trust: self.assertEqual(TRUSTOR_ID, data[0].user_id) diff --git a/etc/ceilometer/meters.yaml b/etc/ceilometer/meters.yaml new file mode 100644 index 00000000..320cdb9c --- /dev/null +++ b/etc/ceilometer/meters.yaml @@ -0,0 +1,393 @@ +--- + +metric: + - name: "image.size" + event_type: + - "image.upload" + - "image.delete" + - "image.update" + type: "gauge" + unit: B + volume: payload.size + resource_id: payload.id + project_id: payload.owner + + - name: "image.download" + event_type: "image.send" + type: "delta" + unit: "B" + volume: payload.bytes_sent + resource_id: payload.image_id + user_id: payload.receiver_user_id + project_id: payload.receiver_tenant_id + + - name: "image.serve" + event_type: "image.send" + type: "delta" + unit: "B" + volume: payload.bytes_sent + resource_id: payload.image_id + project_id: payload.owner_id + + - name: 'bandwidth' + event_type: 'l3.meter' + type: 'delta' + unit: 'B' + volume: payload.bytes + project_id: payload.tenant_id + resource_id: payload.label_id + + - name: 'magnetodb.table.index.count' + type: 'gauge' + unit: 'index' + event_type: 'magnetodb.table.create.end' + volume: payload.index_count + resource_id: payload.table_uuid + user_id: _context_user + + - name: 'memory' + event_type: 'compute.instance.*' + type: 'gauge' + unit: 'MB' + volume: payload.memory_mb + user_id: payload.user_id + project_id: payload.tenant_id + resource_id: payload.instance_id + + - name: 'vcpus' + event_type: 'compute.instance.*' + type: 'gauge' + unit: 'vcpu' + volume: payload.vcpus + user_id: payload.user_id + project_id: payload.tenant_id + resource_id: payload.instance_id + + - name: 'disk.root.size' + event_type: 'compute.instance.*' + type: 'gauge' + unit: 'GB' + volume: payload.root_gb + user_id: payload.user_id + project_id: payload.tenant_id + resource_id: payload.instance_id + + - name: 'disk.ephemeral.size' + event_type: 'compute.instance.*' + type: 'gauge' + unit: 'GB' + volume: payload.ephemeral_gb + user_id: payload.user_id + project_id: payload.tenant_id + resource_id: payload.instance_id + + - name: 'volume.size' + event_type: + - 'volume.exists' + - 'volume.create.*' + - 'volume.delete.*' + - 'volume.resize.*' + - 'volume.attach.*' + - 'volume.detach.*' + - 'volume.update.*' + type: 'gauge' + unit: 'GB' + volume: payload.size + user_id: payload.user_id + project_id: payload.tenant_id + resource_id: payload.volume_id + + - name: 'snapshot.size' + event_type: + - 'snapshot.exists' + - 'snapshot.create.*' + - 'snapshot.delete.*' + type: 'gauge' + unit: 'GB' + volume: payload.volume_size + user_id: payload.user_id + project_id: payload.tenant_id + resource_id: payload.snapshot_id + +# NOTE: non-metric meters are generally events/existence meters +# These are expected to be DEPRECATED in future releases +# + + - name: 'stack.create' + event_type: + - 'orchestration.stack.create.end' + type: 'delta' + unit: 'stack' + volume: 1 + user_id: _context_trustor_user_id + project_id: payload.tenant_id + resource_id: payload.stack_identity + + - name: 'stack.update' + event_type: + - 'orchestration.stack.update.end' + type: 'delta' + unit: 'stack' + volume: 1 + user_id: _context_trustor_user_id + project_id: payload.tenant_id + resource_id: payload.stack_identity + + - name: 'stack.delete' + event_type: + - 'orchestration.stack.delete.end' + type: 'delta' + unit: 'stack' + volume: 1 + user_id: _context_trustor_user_id + project_id: payload.tenant_id + resource_id: payload.stack_identity + + - name: 'stack.resume' + event_type: + - 'orchestration.stack.resume.end' + type: 'delta' + unit: 'stack' + volume: 1 + user_id: _context_trustor_user_id + project_id: payload.tenant_id + resource_id: payload.stack_identity + + - name: 'stack.suspend' + event_type: + - 'orchestration.stack.suspend.end' + type: 'delta' + unit: 'stack' + volume: 1 + user_id: _context_trustor_user_id + project_id: payload.tenant_id + resource_id: payload.stack_identity + + - name: 'magnetodb.table.create' + type: 'gauge' + unit: 'table' + volume: 1 + event_type: 'magnetodb.table.create.end' + resource_id: payload.table_uuid + user_id: _context_user + project_id: _context_tenant + + - name: 'magnetodb.table.delete' + type: 'gauge' + unit: 'table' + volume: 1 + event_type: 'magnetodb.table.delete.end' + resource_id: payload.table_uuid + user_id: _context_user + project_id: _context_tenant + + - name: 'volume' + type: 'gauge' + unit: 'volume' + volume: 1 + event_type: + - 'volume.exists' + - 'volume.create.*' + - 'volume.delete.*' + - 'volume.resize.*' + - 'volume.attach.*' + - 'volume.detach.*' + - 'volume.update.*' + resource_id: payload.volume_id + user_id: payload.user_id + project_id: payload.tenant_id + + - name: 'volume.exists' + type: 'delta' + unit: 'volume' + volume: 1 + event_type: + - 'volume.exists' + resource_id: payload.volume_id + user_id: payload.user_id + project_id: payload.tenant_id + + - name: 'volume.create.start' + type: 'delta' + unit: 'volume' + volume: 1 + event_type: + - 'volume.create.*' + resource_id: payload.volume_id + user_id: payload.user_id + project_id: payload.tenant_id + + - name: 'volume.create.end' + type: 'delta' + unit: 'volume' + volume: 1 + event_type: + - 'volume.create.*' + resource_id: payload.volume_id + user_id: payload.user_id + project_id: payload.tenant_id + + - name: 'volume.delete.start' + type: 'delta' + unit: 'volume' + volume: 1 + event_type: + - 'volume.delete.*' + resource_id: payload.volume_id + user_id: payload.user_id + project_id: payload.tenant_id + + - name: 'volume.delete.end' + type: 'delta' + unit: 'volume' + volume: 1 + event_type: + - 'volume.delete.*' + resource_id: payload.volume_id + user_id: payload.user_id + project_id: payload.tenant_id + + - name: 'volume.update.end' + type: 'delta' + unit: 'volume' + volume: 1 + event_type: + - 'volume.update.*' + resource_id: payload.volume_id + user_id: payload.user_id + project_id: payload.tenant_id + + - name: 'volume.update.start' + type: 'delta' + unit: 'volume' + volume: 1 + event_type: + - 'volume.update.*' + resource_id: payload.volume_id + user_id: payload.user_id + project_id: payload.tenant_id + + - name: 'volume.resize.end' + type: 'delta' + unit: 'volume' + volume: 1 + event_type: + - 'volume.resize.*' + resource_id: payload.volume_id + user_id: payload.user_id + project_id: payload.tenant_id + + - name: 'volume.resize.start' + type: 'delta' + unit: 'volume' + volume: 1 + event_type: + - 'volume.resize.*' + resource_id: payload.volume_id + user_id: payload.user_id + project_id: payload.tenant_id + + + - name: 'volume.attach.end' + type: 'delta' + unit: 'volume' + volume: 1 + event_type: + - 'volume.attach.*' + resource_id: payload.volume_id + user_id: payload.user_id + project_id: payload.tenant_id + + - name: 'volume.attach.start' + type: 'delta' + unit: 'volume' + volume: 1 + event_type: + - 'volume.attach.*' + resource_id: payload.volume_id + user_id: payload.user_id + project_id: payload.tenant_id + + - name: 'volume.detach.end' + type: 'delta' + unit: 'volume' + volume: 1 + event_type: + - 'volume.detach.*' + resource_id: payload.volume_id + user_id: payload.user_id + project_id: payload.tenant_id + + - name: 'volume.detach.start' + type: 'delta' + unit: 'volume' + volume: 1 + event_type: + - 'volume.detach.*' + resource_id: payload.volume_id + user_id: payload.user_id + project_id: payload.tenant_id + + + - name: 'snapshot' + type: 'gauge' + unit: 'snapshot' + volume: 1 + event_type: + - 'snapshot.exists' + - 'snapshot.create.*' + - 'snapshot.delete.*' + + resource_id: payload.snapshot_id + user_id: payload.user_id + project_id: payload.tenant_id + + - name: 'snapshot.exists' + type: 'delta' + unit: 'snapshot' + volume: 1 + event_type: + - 'snapshot.exists' + resource_id: payload.snapshot_id + user_id: payload.user_id + project_id: payload.tenant_id + + - name: 'snapshot.create.start' + type: 'delta' + unit: 'snapshot' + volume: 1 + event_type: + - 'snapshot.create.*' + resource_id: payload.snapshot_id + user_id: payload.user_id + project_id: payload.tenant_id + + - name: 'snapshot.create.end' + type: 'delta' + unit: 'snapshot' + volume: 1 + event_type: + - 'snapshot.create.*' + resource_id: payload.snapshot_id + user_id: payload.user_id + project_id: payload.tenant_id + + - name: 'snapshot.delete.start' + type: 'delta' + unit: 'snapshot' + volume: 1 + event_type: + - 'snapshot.delete.*' + resource_id: payload.snapshot_id + user_id: payload.user_id + project_id: payload.tenant_id + + - name: 'snapshot.delete.end' + type: 'delta' + unit: 'snapshot' + volume: 1 + event_type: + - 'snapshot.delete.*' + resource_id: payload.snapshot_id + user_id: payload.user_id + project_id: payload.tenant_id \ No newline at end of file