Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions openstack_exporter/collectors/cinderbackend.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ def describe(self):
'Cinder Reserved Space Percentage')
yield GaugeMetricFamily('cinder_percent_free',
'Cinder Percentage of available space is free.')
yield GaugeMetricFamily(
'cinder_pool_aggregate_id_missing',
'Whether the Cinder pool has no aggregate ID.'
)

def _debug_gauge(self, gauge, name, value, shard, backend, pool):
LOG.debug(f"({shard}/{backend}/{pool})-{name} = {value}")
Expand Down Expand Up @@ -265,6 +269,13 @@ def _report_stats(self, shard_name, backend, data, caps, quota_obj):
shard_name, backend, pool_name, az
)

yield self.add_gauge_metric_gauge(
'cinder_pool_aggregate_id_missing',
'Whether the Cinder pool has no aggregate ID.',
int(not aggregate_id),
shard_name, backend, pool_name, az
)

if can_overcommit:
yield self.add_gauge_metric_gauge(
'cinder_max_oversubscription_ratio',
Expand Down
1 change: 1 addition & 0 deletions openstack_exporter/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Tests for the OpenStack exporter."""
1 change: 1 addition & 0 deletions openstack_exporter/tests/unit/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Unit tests for the OpenStack exporter."""
1 change: 1 addition & 0 deletions openstack_exporter/tests/unit/collectors/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Unit tests for exporter collectors."""
107 changes: 107 additions & 0 deletions openstack_exporter/tests/unit/collectors/test_cinderbackend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# All Rights Reserved.
#
# 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 the Cinder backend collector."""

import unittest

from openstack_exporter.collectors.cinderbackend import CinderBackendCollector


class FakeQuota:
"""Minimal quota object required by the collector."""

per_volume_gigabytes = 100


class CinderBackendCollectorTest(unittest.TestCase):
"""Test Cinder backend metric reporting."""

def setUp(self):
"""Create a collector without connecting to OpenStack."""
self.collector = CinderBackendCollector.__new__(CinderBackendCollector)
self.collector.labels = [
'backend', 'pool', 'shard', 'availability_zone'
]
self.collector.aggregate_labels = ['backend', 'pool', 'shard']

def _report_pool(self, aggregate_id_marker=None, include_aggregate_id=False):
data = {
'pool': 'pool-a',
'can_overcommit': False,
'total_capacity_gb': 100,
'available_capacity_gb': 90,
'free_capacity_gb': 80,
'virtual_free_capacity_gb': 80,
'allocated_capacity_gb': 20,
'max_over_subscription_ratio': 1.0,
'overcommit_ratio': 0.2,
'reserved_percentage': 0,
}
caps = {
'backend_state': 'up',
'pool_state': 'up',
'volume_backend_name': 'backend-a',
'driver_version': '1',
'custom_attributes': {},
}
if include_aggregate_id:
data['aggregate_id'] = aggregate_id_marker
return list(self.collector._report_stats(
'shard-a', 'backend-a', data, caps, FakeQuota()))

@staticmethod
def _metric(metrics, name):
return next(metric for metric in metrics if metric.name == name)

def test_describe_registers_aggregate_id_missing_metric(self):
"""Register the aggregate ID missing metric in describe output."""
names = [metric.name for metric in self.collector.describe()]

self.assertIn('cinder_pool_aggregate_id_missing', names)

def test_real_aggregate_id_is_not_missing(self):
"""Report zero when a pool has a real aggregate ID."""
metrics = self._report_pool('aggregate-a', include_aggregate_id=True)

aggregate_id = self._metric(metrics, 'cinder_aggregate_id')
missing = self._metric(metrics, 'cinder_pool_aggregate_id_missing')
self.assertEqual('aggregate-a', aggregate_id.samples[0].labels['aggregate_id'])
self.assertEqual(0, missing.samples[0].value)
self.assertEqual({
'backend': 'backend-a',
'pool': 'pool-a',
'shard': 'shard-a',
'availability_zone': 'unknown',
}, missing.samples[0].labels)

def test_missing_aggregate_id_is_reported(self):
"""Report one for null and empty aggregate IDs."""
for marker in (None, ''):
with self.subTest(marker=marker):
metrics = self._report_pool(marker, include_aggregate_id=True)
names = [metric.name for metric in metrics]

self.assertNotIn('cinder_aggregate_id', names)
missing = self._metric(metrics, 'cinder_pool_aggregate_id_missing')
self.assertEqual(1, missing.samples[0].value)

def test_absent_aggregate_id_is_reported(self):
"""Report one when aggregate ID is absent."""
metrics = self._report_pool()

names = [metric.name for metric in metrics]
self.assertNotIn('cinder_aggregate_id', names)
missing = self._metric(metrics, 'cinder_pool_aggregate_id_missing')
self.assertEqual(1, missing.samples[0].value)