From 3922db4f3d4f0df586a89894363148a6ee84b28e Mon Sep 17 00:00:00 2001 From: Jaromir Wysoglad Date: Wed, 5 Mar 2025 04:14:47 -0500 Subject: [PATCH] Add pool capacity pollsters This adds code to retrieve pool capacity metrics via the cinder API. Change-Id: Ib84f33a91b6a69f56b8cff9da431720cb58f5d33 --- .zuul.yaml | 3 +- ceilometer/tests/unit/volume/test_cinder.py | 119 ++++++++++++++++++ ceilometer/volume/cinder.py | 117 +++++++++++++++++ ceilometer/volume/discovery.py | 7 ++ doc/source/admin/telemetry-measurements.rst | 18 +-- ...dd-pool-size-metrics-cdecb979135bba85.yaml | 11 ++ setup.cfg | 6 + 7 files changed, 272 insertions(+), 9 deletions(-) create mode 100644 releasenotes/notes/add-pool-size-metrics-cdecb979135bba85.yaml diff --git a/.zuul.yaml b/.zuul.yaml index 50f90310b7..dc799eefab 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -5,7 +5,8 @@ required-projects: - opendev.org/openstack/grenade - opendev.org/openstack/ceilometer - - gnocchixyz/gnocchi + - name: gnocchixyz/gnocchi + override-checkout: stable/4.6 vars: configure_swap_size: 8192 grenade_devstack_localrc: diff --git a/ceilometer/tests/unit/volume/test_cinder.py b/ceilometer/tests/unit/volume/test_cinder.py index 94806d311a..979a51bf9f 100644 --- a/ceilometer/tests/unit/volume/test_cinder.py +++ b/ceilometer/tests/unit/volume/test_cinder.py @@ -134,6 +134,34 @@ BACKUP_LIST = [ 'size': 1}) ] +POOL_LIST = [ + type('VolumePool', (object,), + {'name': 'localhost.localdomain@lvmdriver-1#lvmdriver-1', + 'pool_name': 'lvmdriver-1', + 'total_capacity_gb': 28.5, + 'free_capacity_gb': 28.39, + 'reserved_percentage': 0, + 'location_info': + 'LVMVolumeDriver:localhost.localdomain:stack-volumes:thin:0', + 'QoS_support': False, + 'provisioned_capacity_gb': 4.0, + 'max_over_subscription_ratio': 20.0, + 'thin_provisioning_support': True, + 'thick_provisioning_support': False, + 'total_volumes': 3, + 'filter_function': None, + 'goodness_function': None, + 'multiattach': True, + 'backend_state': 'up', + 'allocated_capacity_gb': 4, + 'cacheable': True, + 'volume_backend_name': 'lvmdriver-1', + 'storage_protocol': 'iSCSI', + 'vendor_name': 'Open Source', + 'driver_version': '3.0.0', + 'timestamp': '2025-03-21T14:19:02.901750'}) +] + class TestVolumeSizePollster(base.BaseTestCase): def setUp(self): @@ -203,3 +231,94 @@ class TestVolumeBackupSizePollster(base.BaseTestCase): self.assertEqual(1, volume_backup_size_samples[0].volume) self.assertEqual('75a52125-85ff-4a8d-b2aa-580f3b22273f', volume_backup_size_samples[0].resource_id) + + +class TestVolumeProviderPoolCapacityTotalPollster(base.BaseTestCase): + def setUp(self): + super(TestVolumeProviderPoolCapacityTotalPollster, self).setUp() + conf = service.prepare_service([], []) + self.manager = manager.AgentManager(0, conf) + self.pollster = cinder.VolumeProviderPoolCapacityTotal(conf) + + def test_volume_provider_pool_capacity_total_pollster(self): + volume_pool_size_total_samples = list( + self.pollster.get_samples(self.manager, {}, resources=POOL_LIST)) + self.assertEqual(1, len(volume_pool_size_total_samples)) + self.assertEqual('volume.provider.pool.capacity.total', + volume_pool_size_total_samples[0].name) + self.assertEqual(28.5, volume_pool_size_total_samples[0].volume) + self.assertEqual('localhost.localdomain@lvmdriver-1#lvmdriver-1', + volume_pool_size_total_samples[0].resource_id) + + +class TestVolumeProviderPoolCapacityFreePollster(base.BaseTestCase): + def setUp(self): + super(TestVolumeProviderPoolCapacityFreePollster, self).setUp() + conf = service.prepare_service([], []) + self.manager = manager.AgentManager(0, conf) + self.pollster = cinder.VolumeProviderPoolCapacityFree(conf) + + def test_volume_provider_pool_capacity_free_pollster(self): + volume_pool_size_free_samples = list( + self.pollster.get_samples(self.manager, {}, resources=POOL_LIST)) + self.assertEqual(1, len(volume_pool_size_free_samples)) + self.assertEqual('volume.provider.pool.capacity.free', + volume_pool_size_free_samples[0].name) + self.assertEqual(28.39, volume_pool_size_free_samples[0].volume) + self.assertEqual('localhost.localdomain@lvmdriver-1#lvmdriver-1', + volume_pool_size_free_samples[0].resource_id) + + +class TestVolumeProviderPoolCapacityProvisionedPollster(base.BaseTestCase): + def setUp(self): + super(TestVolumeProviderPoolCapacityProvisionedPollster, self).setUp() + conf = service.prepare_service([], []) + self.manager = manager.AgentManager(0, conf) + self.pollster = cinder.VolumeProviderPoolCapacityProvisioned(conf) + + def test_volume_provider_pool_capacity_provisioned_pollster(self): + volume_pool_size_provisioned_samples = list( + self.pollster.get_samples(self.manager, {}, resources=POOL_LIST)) + self.assertEqual(1, len(volume_pool_size_provisioned_samples)) + self.assertEqual('volume.provider.pool.capacity.provisioned', + volume_pool_size_provisioned_samples[0].name) + self.assertEqual(4.0, volume_pool_size_provisioned_samples[0].volume) + self.assertEqual('localhost.localdomain@lvmdriver-1#lvmdriver-1', + volume_pool_size_provisioned_samples[0].resource_id) + + +class TestVolumeProviderPoolCapacityVirtualFreePollster(base.BaseTestCase): + def setUp(self): + super(TestVolumeProviderPoolCapacityVirtualFreePollster, self).setUp() + conf = service.prepare_service([], []) + self.manager = manager.AgentManager(0, conf) + self.pollster = cinder.VolumeProviderPoolCapacityVirtualFree(conf) + + def test_volume_provider_pool_capacity_virtual_free_pollster(self): + volume_pool_size_virtual_free_samples = list( + self.pollster.get_samples(self.manager, {}, resources=POOL_LIST)) + self.assertEqual(1, len(volume_pool_size_virtual_free_samples)) + self.assertEqual('volume.provider.pool.capacity.virtual_free', + volume_pool_size_virtual_free_samples[0].name) + self.assertEqual(566.0, + volume_pool_size_virtual_free_samples[0].volume) + self.assertEqual('localhost.localdomain@lvmdriver-1#lvmdriver-1', + volume_pool_size_virtual_free_samples[0].resource_id) + + +class TestVolumeProviderPoolCapacityAllocatedPollster(base.BaseTestCase): + def setUp(self): + super(TestVolumeProviderPoolCapacityAllocatedPollster, self).setUp() + conf = service.prepare_service([], []) + self.manager = manager.AgentManager(0, conf) + self.pollster = cinder.VolumeProviderPoolCapacityAllocated(conf) + + def test_volume_provider_pool_capacity_allocated_pollster(self): + volume_pool_size_allocated_samples = list( + self.pollster.get_samples(self.manager, {}, resources=POOL_LIST)) + self.assertEqual(1, len(volume_pool_size_allocated_samples)) + self.assertEqual('volume.provider.pool.capacity.allocated', + volume_pool_size_allocated_samples[0].name) + self.assertEqual(4, volume_pool_size_allocated_samples[0].volume) + self.assertEqual('localhost.localdomain@lvmdriver-1#lvmdriver-1', + volume_pool_size_allocated_samples[0].resource_id) diff --git a/ceilometer/volume/cinder.py b/ceilometer/volume/cinder.py index 3037694716..b8585ce93d 100644 --- a/ceilometer/volume/cinder.py +++ b/ceilometer/volume/cinder.py @@ -12,6 +12,7 @@ # under the License. """Common code for working with volumes """ +import math from ceilometer.polling import plugin_base from ceilometer import sample @@ -117,3 +118,119 @@ class VolumeBackupSize(_Base): resource_id=backup.id, resource_metadata=self.extract_metadata(backup), ) + + +class VolumeProviderPoolCapacityTotal(_Base): + @property + def default_discovery(self): + return 'volume_pools' + + FIELDS = ['pool_name'] + + def get_samples(self, manager, cache, resources): + for pool in resources: + yield sample.Sample( + name='volume.provider.pool.capacity.total', + type=sample.TYPE_GAUGE, + unit='GB', + volume=pool.total_capacity_gb, + user_id=None, + project_id=None, + resource_id=pool.name, + resource_metadata=self.extract_metadata(pool), + ) + + +class VolumeProviderPoolCapacityFree(_Base): + @property + def default_discovery(self): + return 'volume_pools' + + FIELDS = ['pool_name'] + + def get_samples(self, manager, cache, resources): + for pool in resources: + yield sample.Sample( + name='volume.provider.pool.capacity.free', + type=sample.TYPE_GAUGE, + unit='GB', + volume=pool.free_capacity_gb, + user_id=None, + project_id=None, + resource_id=pool.name, + resource_metadata=self.extract_metadata(pool), + ) + + +class VolumeProviderPoolCapacityProvisioned(_Base): + @property + def default_discovery(self): + return 'volume_pools' + + FIELDS = ['pool_name'] + + def get_samples(self, manager, cache, resources): + for pool in resources: + yield sample.Sample( + name='volume.provider.pool.capacity.provisioned', + type=sample.TYPE_GAUGE, + unit='GB', + volume=pool.provisioned_capacity_gb, + user_id=None, + project_id=None, + resource_id=pool.name, + resource_metadata=self.extract_metadata(pool), + ) + + +class VolumeProviderPoolCapacityVirtualFree(_Base): + @property + def default_discovery(self): + return 'volume_pools' + + FIELDS = ['pool_name'] + + def get_samples(self, manager, cache, resources): + for pool in resources: + reserved_size = math.floor( + (pool.reserved_percentage / 100) * pool.total_capacity_gb + ) + max_over_subscription_ratio = 1.0 + if pool.thin_provisioning_support: + max_over_subscription_ratio = pool.max_over_subscription_ratio + value = ( + max_over_subscription_ratio * + (pool.total_capacity_gb - reserved_size) - + pool.provisioned_capacity_gb + ) + yield sample.Sample( + name='volume.provider.pool.capacity.virtual_free', + type=sample.TYPE_GAUGE, + unit='GB', + volume=value, + user_id=None, + project_id=None, + resource_id=pool.name, + resource_metadata=self.extract_metadata(pool), + ) + + +class VolumeProviderPoolCapacityAllocated(_Base): + @property + def default_discovery(self): + return 'volume_pools' + + FIELDS = ['pool_name'] + + def get_samples(self, manager, cache, resources): + for pool in resources: + yield sample.Sample( + name='volume.provider.pool.capacity.allocated', + type=sample.TYPE_GAUGE, + unit='GB', + volume=pool.allocated_capacity_gb, + user_id=None, + project_id=None, + resource_id=pool.name, + resource_metadata=self.extract_metadata(pool), + ) diff --git a/ceilometer/volume/discovery.py b/ceilometer/volume/discovery.py index c80160d928..d0bf4e166f 100644 --- a/ceilometer/volume/discovery.py +++ b/ceilometer/volume/discovery.py @@ -61,3 +61,10 @@ class VolumeBackupsDiscovery(_BaseDiscovery): """Discover volume resources to monitor.""" return self.client.backups.list(search_opts={'all_tenants': True}) + + +class VolumePoolsDiscovery(_BaseDiscovery): + def discover(self, manager, param=None): + """Discover volume resources to monitor.""" + + return self.client.pools.list(detailed=True) diff --git a/doc/source/admin/telemetry-measurements.rst b/doc/source/admin/telemetry-measurements.rst index e116a35949..3bda071092 100644 --- a/doc/source/admin/telemetry-measurements.rst +++ b/doc/source/admin/telemetry-measurements.rst @@ -443,22 +443,24 @@ The following meters are collected for OpenStack Block Storage: | | | | | | on host | +--------------------+-------+--------+----------+----------+-----------------+ | volume.provider.po\| Gauge | GB | hostname\| Notifica\| Total volume | -| ol.capacity.total | | | #pool | tion | capacity in pool| +| ol.capacity.total | | | #pool | tion, Po\| capacity in pool| +| | | | | llster | | +--------------------+-------+--------+----------+----------+-----------------+ | volume.provider.po\| Gauge | GB | hostname\| Notifica\| Free volume | -| ol.capacity.free | | | #pool | tion | capacity in pool| +| ol.capacity.free | | | #pool | tion, Po\| capacity in pool| +| | | | | llster | | +--------------------+-------+--------+----------+----------+-----------------+ | volume.provider.po\| Gauge | GB | hostname\| Notifica\| Assigned volume | -| ol.capacity.alloca\| | | #pool | tion | capacity in pool| -| ted | | | | | by Cinder | +| ol.capacity.alloca\| | | #pool | tion, Po\| capacity in pool| +| ted | | | | llster | by Cinder | +--------------------+-------+--------+----------+----------+-----------------+ | volume.provider.po\| Gauge | GB | hostname\| Notifica\| Assigned volume | -| ol.capacity.provis\| | | #pool | tion | capacity in pool| -| ioned | | | | | | +| ol.capacity.provis\| | | #pool | tion, Po\| capacity in pool| +| ioned | | | | llster | | +--------------------+-------+--------+----------+----------+-----------------+ | volume.provider.po\| Gauge | GB | hostname\| Notifica\| Virtual free | -| ol.capacity.virtua\| | | #pool | tion | volume capacity | -| l_free | | | | | in pool | +| ol.capacity.virtua\| | | #pool | tion, Po\| volume capacity | +| l_free | | | | llster | in pool | +--------------------+-------+--------+----------+----------+-----------------+ OpenStack File Share diff --git a/releasenotes/notes/add-pool-size-metrics-cdecb979135bba85.yaml b/releasenotes/notes/add-pool-size-metrics-cdecb979135bba85.yaml new file mode 100644 index 0000000000..c6f388b461 --- /dev/null +++ b/releasenotes/notes/add-pool-size-metrics-cdecb979135bba85.yaml @@ -0,0 +1,11 @@ +--- +features: + - | + Added the following meters to the central agent to capture these metrics + for each storage pool by API. + + - `volume.provider.pool.capacity.total` + - `volume.provider.pool.capacity.free` + - `volume.provider.pool.capacity.provisioned` + - `volume.provider.pool.capacity.virtual_free` + - `volume.provider.pool.capacity.allocated` diff --git a/setup.cfg b/setup.cfg index ea4995995d..4a5d095cee 100644 --- a/setup.cfg +++ b/setup.cfg @@ -60,6 +60,7 @@ ceilometer.discover.central = fip_services = ceilometer.network.services.discovery:FloatingIPDiscovery images = ceilometer.image.discovery:ImagesDiscovery volumes = ceilometer.volume.discovery:VolumeDiscovery + volume_pools = ceilometer.volume.discovery:VolumePoolsDiscovery volume_snapshots = ceilometer.volume.discovery:VolumeSnapshotsDiscovery volume_backups = ceilometer.volume.discovery:VolumeBackupsDiscovery @@ -137,6 +138,11 @@ ceilometer.poll.central = volume.size = ceilometer.volume.cinder:VolumeSizePollster volume.snapshot.size = ceilometer.volume.cinder:VolumeSnapshotSize volume.backup.size = ceilometer.volume.cinder:VolumeBackupSize + volume.provider.pool.capacity.total = ceilometer.volume.cinder:VolumeProviderPoolCapacityTotal + volume.provider.pool.capacity.free = ceilometer.volume.cinder:VolumeProviderPoolCapacityFree + volume.provider.pool.capacity.provisioned = ceilometer.volume.cinder:VolumeProviderPoolCapacityProvisioned + volume.provider.pool.capacity.virtual_free = ceilometer.volume.cinder:VolumeProviderPoolCapacityVirtualFree + volume.provider.pool.capacity.allocated = ceilometer.volume.cinder:VolumeProviderPoolCapacityAllocated ceilometer.compute.virt = libvirt = ceilometer.compute.virt.libvirt.inspector:LibvirtInspector