Add the disk.ephemeral.size and disk.root.size pollsters
The disk.ephemeral.size meter publishes the size of an instance's ephemeral disk (in GB). The disk.root.size meter publishes the size of an instance's root volume (also in GB). Both of these meters are currently available via notifications only. This information is also available in the instance's local VM metadata, so add new pollsters to publish them. This allows for publishing the these two meters with measures at regular intervals if required, rather than relying solely on Nova to send notifications correctly. If a given instance does not change very often, the compute.instance.exists notification would only publish values to these metrics just once per hour, which might not always be sufficient to reliably supply them. These pollsters can be disabled if required by adding "!disk.ephemeral.size" and "!disk.root.size" to the applicable catch-all source in polling.yaml. Change-Id: I1edce6a4f366192346e9400065f5890dde1fc588
This commit is contained in:
@@ -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(),
|
||||
)
|
||||
|
@@ -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']))
|
||||
|
130
ceilometer/tests/unit/compute/pollsters/test_disk.py
Normal file
130
ceilometer/tests/unit/compute/pollsters/test_disk.py
Normal file
@@ -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)
|
@@ -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 |
|
||||
|
@@ -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.
|
@@ -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
|
||||
|
Reference in New Issue
Block a user