diff --git a/ceilometer/compute/pollsters/__init__.py b/ceilometer/compute/pollsters/__init__.py index 000a0222fb..9450802a6a 100644 --- a/ceilometer/compute/pollsters/__init__.py +++ b/ceilometer/compute/pollsters/__init__.py @@ -170,3 +170,44 @@ class GenericComputePollster(plugin_base.PollsterBase): 'Could not get %(name)s events for %(id)s: %(e)s', { 'name': self.sample_name, 'id': instance.id, 'e': err}, exc_info=True) + + +class InstanceMetadataPollster(plugin_base.PollsterBase): + """A base class for implementing a pollster using instance metadata. + + This metadata is originally supplied by Nova, but if + instance_discovery_method is set to libvirt_metadata, + metadata is fetched from the local libvirt socket, + just like with the standard compute pollsters. + """ + + sample_name = None + sample_unit = '' + sample_type = sample.TYPE_GAUGE + + @property + def default_discovery(self): + return 'local_instances' + + def get_resource_id(self, instance): + return instance.id + + def get_volume(self, instance): + raise ceilometer.NotImplementedError + + def get_additional_metadata(self, instance): + return {} + + def get_samples(self, manager, cache, resources): + for instance in resources: + yield util.make_sample_from_instance( + self.conf, + instance, + name=self.sample_name, + unit=self.sample_unit, + type=self.sample_type, + resource_id=self.get_resource_id(instance), + volume=self.get_volume(instance), + additional_metadata=self.get_additional_metadata(instance), + monotonic_time=now(), + ) diff --git a/ceilometer/compute/pollsters/disk.py b/ceilometer/compute/pollsters/disk.py index 90e8fcfca1..0ae9adc8e0 100644 --- a/ceilometer/compute/pollsters/disk.py +++ b/ceilometer/compute/pollsters/disk.py @@ -91,3 +91,20 @@ class PerDeviceDiskWriteLatencyPollster(PerDeviceDiskPollster): sample_type = sample.TYPE_CUMULATIVE sample_unit = 'ns' sample_stats_key = 'wr_total_times' + + +class EphemeralSizePollster(pollsters.InstanceMetadataPollster): + sample_name = 'disk.ephemeral.size' + sample_unit = 'GB' + + def get_volume(self, instance): + return int(instance.flavor['ephemeral']) + + +class RootSizePollster(pollsters.InstanceMetadataPollster): + sample_name = 'disk.root.size' + sample_unit = 'GB' + + def get_volume(self, instance): + return (int(instance.flavor['disk']) + - int(instance.flavor['ephemeral'])) diff --git a/ceilometer/tests/unit/compute/pollsters/test_disk.py b/ceilometer/tests/unit/compute/pollsters/test_disk.py new file mode 100644 index 0000000000..831a35bfc5 --- /dev/null +++ b/ceilometer/tests/unit/compute/pollsters/test_disk.py @@ -0,0 +1,130 @@ +# Copyright 2025 Catalyst Cloud Limited +# +# 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 unittest import mock + +from ceilometer.compute.pollsters import disk +from ceilometer.polling import manager +from ceilometer.tests.unit.compute.pollsters import base + + +class TestDiskPollsterBase(base.TestPollsterBase): + + TYPE = 'gauge' + + def setUp(self): + super().setUp() + self.instances = self._get_fake_instances() + + def _get_fake_instances(self, ephemeral=0): + instances = [] + for i in [1, 2]: + instance = mock.MagicMock() + instance.name = f'instance-{i}' + setattr(instance, 'OS-EXT-SRV-ATTR:instance_name', + instance.name) + instance.id = i + instance.flavor = {'name': 'm1.small', 'id': 2, 'vcpus': 1, + 'ram': 512, 'disk': 20, 'ephemeral': ephemeral} + instance.status = 'active' + instances.append(instance) + return instances + + def _check_get_samples(self, + factory, + name, + instances=None, + expected_count=2): + pollster = factory(self.CONF) + mgr = manager.AgentManager(0, self.CONF) + samples = list(pollster.get_samples(mgr, + {}, + instances or self.instances)) + self.assertGreater(len(samples), 0) + self.assertEqual({name}, set(s.name for s in samples), + (f"Only samples for meter {name} " + "should be published")) + self.assertEqual(expected_count, len(samples)) + return samples + + +class TestDiskSizePollsters(TestDiskPollsterBase): + + TYPE = 'gauge' + + def test_ephemeral_disk_zero(self): + samples = { + sample.resource_id: sample + for sample in self._check_get_samples( + disk.EphemeralSizePollster, + 'disk.ephemeral.size', + expected_count=len(self.instances))} + for instance in self.instances: + with self.subTest(instance.name): + self.assertIn(instance.id, samples) + sample = samples[instance.id] + self.assertEqual(instance.flavor['ephemeral'], + sample.volume) + self.assertEqual(self.TYPE, sample.type) + + def test_ephemeral_disk_nonzero(self): + instances = self._get_fake_instances(ephemeral=10) + samples = { + sample.resource_id: sample + for sample in self._check_get_samples( + disk.EphemeralSizePollster, + 'disk.ephemeral.size', + instances=instances, + expected_count=len(instances))} + for instance in instances: + with self.subTest(instance.name): + self.assertIn(instance.id, samples) + sample = samples[instance.id] + self.assertEqual(instance.flavor['ephemeral'], + sample.volume) + self.assertEqual(self.TYPE, sample.type) + + def test_root_disk(self): + samples = { + sample.resource_id: sample + for sample in self._check_get_samples( + disk.RootSizePollster, + 'disk.root.size', + expected_count=len(self.instances))} + for instance in self.instances: + with self.subTest(instance.name): + self.assertIn(instance.id, samples) + sample = samples[instance.id] + self.assertEqual((instance.flavor['disk'] + - instance.flavor['ephemeral']), + sample.volume) + self.assertEqual(self.TYPE, sample.type) + + def test_root_disk_ephemeral_nonzero(self): + instances = self._get_fake_instances(ephemeral=10) + samples = { + sample.resource_id: sample + for sample in self._check_get_samples( + disk.RootSizePollster, + 'disk.root.size', + instances=instances, + expected_count=len(instances))} + for instance in instances: + with self.subTest(instance.name): + self.assertIn(instance.id, samples) + sample = samples[instance.id] + self.assertEqual((instance.flavor['disk'] + - instance.flavor['ephemeral']), + sample.volume) + self.assertEqual(self.TYPE, sample.type) diff --git a/doc/source/admin/telemetry-measurements.rst b/doc/source/admin/telemetry-measurements.rst index 661cf1fe1d..e116a35949 100644 --- a/doc/source/admin/telemetry-measurements.rst +++ b/doc/source/admin/telemetry-measurements.rst @@ -124,10 +124,12 @@ The following meters are collected for OpenStack Compute. | .bytes | | | | | | | +-----------+-------+------+----------+----------+---------+------------------+ | disk.root\| Gauge | GB | instance | Notific\ | Libvirt | Size of root disk| -| .size | | | ID | ation | | | +| .size | | | ID | ation, \ | | | +| | | | | Pollster | | | +-----------+-------+------+----------+----------+---------+------------------+ | disk.ephe\| Gauge | GB | instance | Notific\ | Libvirt | Size of ephemeral| -| meral.size| | | ID | ation | | disk | +| meral.size| | | ID | ation, \ | | disk | +| | | | | Pollster | | | +-----------+-------+------+----------+----------+---------+------------------+ | disk.dev\ | Gauge | B | disk ID | Pollster | Libvirt | The amount of d\ | | ice.capa\ | | | | | | isk per device | diff --git a/releasenotes/notes/add-disk-size-pollsters-6b819d067f9cf736.yaml b/releasenotes/notes/add-disk-size-pollsters-6b819d067f9cf736.yaml new file mode 100644 index 0000000000..f3490a1772 --- /dev/null +++ b/releasenotes/notes/add-disk-size-pollsters-6b819d067f9cf736.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + The ``disk.ephemeral.size`` meter is now published as a compute pollster, + in addition to the existing notification meter. + - | + The ``disk.root.size`` meter is now published as a compute pollster, + in addition to the existing notification meter. diff --git a/setup.cfg b/setup.cfg index 1f84d13c65..ea4995995d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -94,6 +94,8 @@ ceilometer.poll.compute = disk.device.capacity = ceilometer.compute.pollsters.disk:PerDeviceCapacityPollster disk.device.allocation = ceilometer.compute.pollsters.disk:PerDeviceAllocationPollster disk.device.usage = ceilometer.compute.pollsters.disk:PerDevicePhysicalPollster + disk.ephemeral.size = ceilometer.compute.pollsters.disk:EphemeralSizePollster + disk.root.size = ceilometer.compute.pollsters.disk:RootSizePollster perf.cpu.cycles = ceilometer.compute.pollsters.instance_stats:PerfCPUCyclesPollster perf.instructions = ceilometer.compute.pollsters.instance_stats:PerfInstructionsPollster perf.cache.references = ceilometer.compute.pollsters.instance_stats:PerfCacheReferencesPollster