# Copyright 2025 Red Hat, Inc.
#
# 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 observabilityclient import prometheus_client

from watcher.common import exception
from watcher.decision_engine.datasources import base as datasource_base
from watcher.decision_engine.datasources import prometheus_base
from watcher.tests.unit import base


class TestPrometheusBase(base.BaseTestCase):
    """Test class for Prometheus base datasource class.

    This class contains tests for the PrometheusBase class
    which is used by the AetosHelper and PrometheusHelper classes.
    """

    def setUp(self):
        super().setUp()

        with (
            mock.patch.object(
                prometheus_base.PrometheusBase,
                '_setup_prometheus_client',
                return_value=prometheus_client.PrometheusAPIClient(''),
            ),
            mock.patch.object(
                prometheus_base.PrometheusBase,
                '_get_fqdn_label',
                return_value='fqdn',
            ),
            mock.patch.object(
                prometheus_base.PrometheusBase,
                '_get_instance_uuid_label',
                return_value='resource',
            ),
            mock.patch.object(
                prometheus_client.PrometheusAPIClient,
                '_get',
                return_value={
                    'data': {
                        'activeTargets': [
                            {
                                'labels': {
                                    'fqdn': 'marios-env.controlplane.domain',
                                    'instance': '10.0.1.2:9100',
                                    'job': 'node',
                                }
                            },
                            {
                                'labels': {
                                    'fqdn': 'marios-env-again.controlplane.domain',  # noqa: E501
                                    'instance': 'localhost:9100',
                                    'job': 'node',
                                }
                            },
                        ]
                    }
                },
            ),
        ):
            self.helper = prometheus_base.PrometheusBase()

        self.helper._get_instance_uuid_label = mock.Mock(
            return_value='resource'
        )
        self.helper._get_fqdn_label = mock.Mock(return_value='fqdn')

        stat_agg_patcher = mock.patch.object(
            self.helper,
            'statistic_aggregation',
            spec=datasource_base.DataSourceBase.statistic_aggregation,
        )
        self.mock_aggregation = stat_agg_patcher.start()
        self.addCleanup(stat_agg_patcher.stop)
        self.mock_instance = mock.Mock(
            uuid='uuid-0', memory=512, disk=2, vcpus=2
        )

    def _create_helper(
        self, fqdn_label='fqdn', instance_uuid_label='resource'
    ):
        with (
            mock.patch.object(
                prometheus_base.PrometheusBase,
                '_setup_prometheus_client',
                return_value=prometheus_client.PrometheusAPIClient(''),
            ),
            mock.patch.object(
                prometheus_base.PrometheusBase,
                '_get_fqdn_label',
                return_value=fqdn_label,
            ),
            mock.patch.object(
                prometheus_base.PrometheusBase,
                '_get_instance_uuid_label',
                return_value=instance_uuid_label,
            ),
        ):
            helper = prometheus_base.PrometheusBase()

        helper._get_instance_uuid_label = mock.Mock(
            return_value=instance_uuid_label
        )
        helper._get_fqdn_label = mock.Mock(return_value=fqdn_label)
        return helper

    @mock.patch.object(prometheus_client.PrometheusAPIClient, 'query')
    @mock.patch.object(prometheus_client.PrometheusAPIClient, '_get')
    def test_prometheus_statistic_aggregation(
        self, mock_prometheus_get, mock_prometheus_query
    ):
        mock_node = mock.Mock(
            uuid='1234', hostname='marios-env.controlplane.domain'
        )

        expected_cpu_usage = 3.2706140350701673

        mock_prom_metric = mock.Mock(
            labels={'instance': '10.0.1.2:9100'},
            timestamp=1731065985.408,
            value=expected_cpu_usage,
        )

        mock_prometheus_query.return_value = [mock_prom_metric]
        mock_prometheus_get.return_value = {
            'data': {
                'activeTargets': [
                    {
                        'labels': {
                            'fqdn': 'marios-env.controlplane.domain',
                            'instance': '10.0.1.2:9100',
                            'job': 'node',
                        }
                    }
                ]
            }
        }
        helper = self._create_helper()
        result = helper.statistic_aggregation(
            resource=mock_node,
            resource_type='compute_node',
            meter_name='host_cpu_usage',
            period=300,
            aggregate='mean',
            granularity=300,
        )
        self.assertEqual(expected_cpu_usage, result)
        mock_prometheus_query.assert_called_once_with(
            "100 - (avg by (fqdn)(rate(node_cpu_seconds_total"
            "{mode='idle',fqdn='marios-env.controlplane.domain'}[300s]))"
            " * 100)"
        )

    @mock.patch.object(prometheus_client.PrometheusAPIClient, 'query')
    @mock.patch.object(prometheus_client.PrometheusAPIClient, '_get')
    def test_get_instance_cpu_usage(
        self, mock_prometheus_get, mock_prometheus_query
    ):
        mock_instance = self.mock_instance
        expected_cpu_usage = 13.2706140350701673

        mock_prom_metric = mock.Mock(
            labels={'resource': 'uuid-0'},
            timestamp=1731065985.408,
            value=expected_cpu_usage,
        )
        mock_prometheus_query.return_value = [mock_prom_metric]
        helper = self._create_helper()

        cpu_usage = helper.get_instance_cpu_usage(mock_instance)
        self.assertIsInstance(cpu_usage, float)
        self.assertEqual(expected_cpu_usage, cpu_usage)

    @mock.patch.object(prometheus_client.PrometheusAPIClient, 'query')
    @mock.patch.object(prometheus_client.PrometheusAPIClient, '_get')
    def test_get_instance_ram_usage(
        self, mock_prometheus_get, mock_prometheus_query
    ):
        mock_instance = self.mock_instance
        expected_ram_usage = 49.86

        mock_prom_metric = mock.Mock(
            labels={'resource': 'uuid-0'},
            timestamp=1731065985.408,
            value=expected_ram_usage,
        )
        mock_prometheus_query.return_value = [mock_prom_metric]
        helper = self._create_helper()

        ram_usage = helper.get_instance_ram_usage(
            mock_instance, period=222, aggregate="max", granularity=200
        )
        self.assertIsInstance(ram_usage, float)
        self.assertEqual(expected_ram_usage, ram_usage)

    @mock.patch.object(prometheus_client.PrometheusAPIClient, 'query')
    @mock.patch.object(prometheus_client.PrometheusAPIClient, '_get')
    def test_get_instance_ram_allocated(
        self, mock_prometheus_get, mock_prometheus_query
    ):
        mock_instance = self.mock_instance
        helper = self._create_helper()
        ram_allocated = helper.get_instance_ram_allocated(
            mock_instance, period=222, aggregate="max"
        )
        self.assertIsInstance(ram_allocated, float)
        self.assertEqual(512, ram_allocated)

    @mock.patch.object(prometheus_client.PrometheusAPIClient, 'query')
    @mock.patch.object(prometheus_client.PrometheusAPIClient, '_get')
    def test_get_instance_root_disk_size(
        self, mock_prometheus_get, mock_prometheus_query
    ):
        mock_instance = self.mock_instance
        helper = self._create_helper()
        disk_size = helper.get_instance_root_disk_size(
            mock_instance, period=331, aggregate="avg"
        )
        self.assertIsInstance(disk_size, float)
        self.assertEqual(2, disk_size)

    @mock.patch.object(prometheus_client.PrometheusAPIClient, 'query')
    @mock.patch.object(prometheus_client.PrometheusAPIClient, '_get')
    def test_prometheus_stt_agg_instance_cpu_usage(
        self, mock_prometheus_get, mock_prometheus_query
    ):
        mock_instance = self.mock_instance
        expected_cpu_usage = 13.2706140350701673

        mock_prom_metric = mock.Mock(
            labels={'resource': 'uuid-0'},
            timestamp=1731065985.408,
            value=expected_cpu_usage,
        )
        mock_prometheus_query.return_value = [mock_prom_metric]
        helper = self._create_helper()
        result_cpu = helper.statistic_aggregation(
            resource=mock_instance,
            resource_type='instance',
            meter_name='instance_cpu_usage',
            period=300,
            granularity=300,
            aggregate='mean',
        )
        self.assertEqual(expected_cpu_usage, result_cpu)
        self.assertIsInstance(result_cpu, float)
        mock_prometheus_query.assert_called_once_with(
            "clamp_max((avg by (resource)(rate("
            "ceilometer_cpu{resource='uuid-0'}[300s]))"
            "/10e+8) *(100/2), 100)"
        )

    @mock.patch.object(prometheus_client.PrometheusAPIClient, 'query')
    @mock.patch.object(prometheus_client.PrometheusAPIClient, '_get')
    def test_prometheus_stt_agg_instance_ram_usage(
        self, mock_prometheus_get, mock_prometheus_query
    ):
        mock_instance = self.mock_instance
        expected_ram_usage = 49.86

        mock_prom_metric = mock.Mock(
            labels={'resource': 'uuid-0'},
            timestamp=1731065985.408,
            value=expected_ram_usage,
        )
        mock_prometheus_query.return_value = [mock_prom_metric]
        helper = self._create_helper()
        result_ram_usage = helper.statistic_aggregation(
            resource=mock_instance,
            resource_type='instance',
            meter_name='instance_ram_usage',
            period=300,
            granularity=300,
            aggregate='mean',
        )
        self.assertEqual(expected_ram_usage, result_ram_usage)
        self.assertIsInstance(result_ram_usage, float)
        mock_prometheus_query.assert_called_with(
            "avg_over_time(ceilometer_memory_usage{resource='uuid-0'}[300s])"
        )

    @mock.patch.object(prometheus_client.PrometheusAPIClient, 'query')
    @mock.patch.object(prometheus_client.PrometheusAPIClient, '_get')
    def test_prometheus_stt_agg_instance_root_size(
        self, mock_prometheus_get, mock_prometheus_query
    ):
        mock_instance = self.mock_instance
        helper = self._create_helper()
        result_disk = helper.statistic_aggregation(
            resource=mock_instance,
            resource_type='instance',
            meter_name='instance_root_disk_size',
            period=300,
            granularity=300,
            aggregate='mean',
        )
        self.assertEqual(2, result_disk)
        self.assertIsInstance(result_disk, float)

    @mock.patch.object(prometheus_client.PrometheusAPIClient, 'query')
    @mock.patch.object(prometheus_client.PrometheusAPIClient, '_get')
    def test_prometheus_stt_agg_instance_ram_alloc(
        self, mock_prometheus_get, mock_prometheus_query
    ):
        mock_instance = self.mock_instance
        helper = self._create_helper()
        result_memory = helper.statistic_aggregation(
            resource=mock_instance,
            resource_type='instance',
            meter_name='instance_ram_allocated',
            period=300,
            granularity=300,
            aggregate='mean',
        )
        self.assertEqual(512, result_memory)
        self.assertIsInstance(result_memory, float)

    def test_statistic_aggregation_metric_unavailable(self):
        self.assertRaisesRegex(
            NotImplementedError,
            'does not support statistic_series',
            self.helper.statistic_series,
        )

    @mock.patch.object(prometheus_client.PrometheusAPIClient, '_get')
    def test_prometheus_list_metrics(self, mock_prometheus_get):
        expected_metrics = {
            'go_gc_duration_seconds',
            'go_gc_duration_seconds_count',
            'go_gc_duration_seconds_sum',
            'go_goroutines',
        }
        mock_prometheus_get.return_value = {
            'status': 'success',
            'data': [
                'go_gc_duration_seconds',
                'go_gc_duration_seconds_count',
                'go_gc_duration_seconds_sum',
                'go_goroutines',
            ],
        }
        result = self.helper.list_metrics()
        self.assertEqual(expected_metrics, result)

    @mock.patch.object(prometheus_client.PrometheusAPIClient, '_get')
    def test_prometheus_list_metrics_error(self, mock_prometheus_get):
        mock_prometheus_get.side_effect = (
            prometheus_client.PrometheusAPIClientError("nope")
        )
        result = self.helper.list_metrics()
        self.assertEqual(set(), result)

    @mock.patch.object(prometheus_client.PrometheusAPIClient, '_get')
    def test_prometheus_check_availability(self, mock_prometheus_get):
        mock_prometheus_get.return_value = {
            'status': 'success',
            'data': {
                'startTime': '2024-11-05T12:59:56.962333207Z',
                'CWD': '/prometheus',
                'reloadConfigSuccess': True,
                'lastConfigTime': '2024-11-05T12:59:56Z',
                'corruptionCount': 0,
                'goroutineCount': 30,
                'GOMAXPROCS': 8,
                'GOMEMLIMIT': 9223372036854775807,
                'GOGC': '75',
                'GODEBUG': '',
                'storageRetention': '15d',
            },
        }
        result = self.helper.check_availability()
        self.assertEqual('available', result)

    @mock.patch.object(prometheus_client.PrometheusAPIClient, '_get')
    def test_prometheus_check_availability_error(self, mock_prometheus_get):
        mock_prometheus_get.side_effect = (
            prometheus_client.PrometheusAPIClientError("nope")
        )
        result = self.helper.check_availability()
        self.assertEqual('not available', result)

    def test_get_host_cpu_usage(self):
        cpu_use = self.helper.get_host_cpu_usage('someNode', 345, 'mean', 300)
        self.assertIsInstance(cpu_use, float)
        self.mock_aggregation.assert_called_once_with(
            'someNode',
            'compute_node',
            'host_cpu_usage',
            period=345,
            granularity=300,
            aggregate='mean',
        )

    def test_get_host_cpu_usage_none(self):
        self.mock_aggregation.return_value = None
        cpu_use = self.helper.get_host_cpu_usage('someNode', 345, 'mean', 300)
        self.assertIsNone(cpu_use)

    def test_get_host_cpu_usage_max(self):
        cpu_use = self.helper.get_host_cpu_usage('theNode', 223, 'max', 100)
        self.assertIsInstance(cpu_use, float)
        self.mock_aggregation.assert_called_once_with(
            'theNode',
            'compute_node',
            'host_cpu_usage',
            period=223,
            granularity=100,
            aggregate='min',
        )

    def test_get_host_cpu_usage_min(self):
        cpu_use = self.helper.get_host_cpu_usage('theNode', 223, 'min', 100)
        self.assertIsInstance(cpu_use, float)
        self.mock_aggregation.assert_called_once_with(
            'theNode',
            'compute_node',
            'host_cpu_usage',
            period=223,
            granularity=100,
            aggregate='max',
        )

    def test_get_host_ram_usage(self):
        ram_use = self.helper.get_host_ram_usage(
            'anotherNode', 456, 'mean', 300
        )
        self.assertIsInstance(ram_use, float)
        self.mock_aggregation.assert_called_once_with(
            'anotherNode',
            'compute_node',
            'host_ram_usage',
            period=456,
            granularity=300,
            aggregate='mean',
        )

    def test_get_host_ram_usage_none(self):
        self.mock_aggregation.return_value = None
        ram_use = self.helper.get_host_ram_usage('NOPE', 234, 'mean', 567)
        self.assertIsNone(ram_use, float)
        self.mock_aggregation.assert_called()
        self.mock_aggregation.assert_called_once_with(
            'NOPE',
            'compute_node',
            'host_ram_usage',
            period=234,
            granularity=567,
            aggregate='mean',
        )

    def test_get_host_ram_usage_max(self):
        ram_use = self.helper.get_host_ram_usage('aNode', 456, 'max', 300)
        self.assertIsInstance(ram_use, float)
        self.mock_aggregation.assert_called_once_with(
            'aNode',
            'compute_node',
            'host_ram_usage',
            period=456,
            granularity=300,
            aggregate='min',
        )

    def test_get_host_ram_usage_min(self):
        ram_use = self.helper.get_host_ram_usage('aNode', 456, 'min', 300)
        self.assertIsInstance(ram_use, float)
        self.mock_aggregation.assert_called_once_with(
            'aNode',
            'compute_node',
            'host_ram_usage',
            period=456,
            granularity=300,
            aggregate='max',
        )

    @mock.patch.object(prometheus_client.PrometheusAPIClient, '_get')
    def test_build_prometheus_fqdn_host_instance_map(
        self, mock_prometheus_get
    ):
        mock_prometheus_get.return_value = {
            'data': {
                'activeTargets': [
                    {
                        'labels': {
                            'fqdn': 'foo.controlplane.domain',
                            'instance': '10.1.2.1:9100',
                            'job': 'node',
                        }
                    },
                    {
                        'labels': {
                            'fqdn': 'bar.controlplane.domain',
                            'instance': '10.1.2.2:9100',
                            'job': 'node',
                        }
                    },
                    {
                        'labels': {
                            'fqdn': 'baz.controlplane.domain',
                            'instance': '10.1.2.3:9100',
                            'job': 'node',
                        }
                    },
                ]
            }
        }
        expected_fqdn_list = {
            'foo.controlplane.domain',
            'bar.controlplane.domain',
            'baz.controlplane.domain',
        }
        expected_host_map = {
            'foo': 'foo.controlplane.domain',
            'bar': 'bar.controlplane.domain',
            'baz': 'baz.controlplane.domain',
        }
        helper = self._create_helper()
        self.assertEqual(helper.prometheus_fqdn_labels, expected_fqdn_list)
        self.assertEqual(
            helper.prometheus_host_instance_map, expected_host_map
        )

    @mock.patch.object(prometheus_client.PrometheusAPIClient, '_get')
    def test_build_prometheus_fqdn_host_instance_map_dupl_fqdn(
        self, mock_prometheus_get
    ):
        mock_prometheus_get.return_value = {
            'data': {
                'activeTargets': [
                    {
                        'labels': {
                            'fqdn': 'foo.controlplane.domain',
                            'instance': '10.1.2.1:9100',
                            'job': 'node',
                        }
                    },
                    {
                        'labels': {
                            'fqdn': 'foo.controlplane.domain',
                            'instance': '10.1.2.1:9229',
                            'job': 'podman',
                        }
                    },
                    {
                        'labels': {
                            'fqdn': 'bar.controlplane.domain',
                            'instance': '10.1.2.2:9100',
                            'job': 'node',
                        }
                    },
                    {
                        'labels': {
                            'fqdn': 'baz.controlplane.domain',
                            'instance': '10.1.2.3:9100',
                            'job': 'node',
                        }
                    },
                ]
            }
        }
        expected_fqdn_list = {
            'foo.controlplane.domain',
            'bar.controlplane.domain',
            'baz.controlplane.domain',
        }
        expected_host_map = {
            'foo': 'foo.controlplane.domain',
            'bar': 'bar.controlplane.domain',
            'baz': 'baz.controlplane.domain',
        }
        helper = self._create_helper()
        self.assertEqual(helper.prometheus_fqdn_labels, expected_fqdn_list)
        self.assertEqual(
            helper.prometheus_host_instance_map, expected_host_map
        )

    @mock.patch.object(prometheus_client.PrometheusAPIClient, '_get')
    def test_missing_prometheus_fqdn_label(self, mock_prometheus_get):
        mock_prometheus_get.return_value = {
            'data': {
                'activeTargets': [
                    {'labels': {'instance': '10.1.2.1:9100', 'job': 'node'}},
                    {'labels': {'instance': '10.1.2.2:9100', 'job': 'node'}},
                ]
            }
        }
        helper = self._create_helper()
        self.assertEqual(set(), helper.prometheus_fqdn_labels)
        self.assertEqual({}, helper.prometheus_host_instance_map)

    @mock.patch.object(prometheus_client.PrometheusAPIClient, '_get')
    def test_using_hostnames_not_fqdn(self, mock_prometheus_get):
        mock_prometheus_get.return_value = {
            'data': {
                'activeTargets': [
                    {
                        'labels': {
                            'fqdn': 'ena',
                            'instance': '10.1.2.1:9100',
                            'job': 'node',
                        }
                    },
                    {
                        'labels': {
                            'fqdn': 'dyo',
                            'instance': '10.1.2.2:9100',
                            'job': 'node',
                        }
                    },
                ]
            }
        }
        helper = self._create_helper()
        expected_fqdn_list = {'ena', 'dyo'}
        self.assertEqual(helper.prometheus_fqdn_labels, expected_fqdn_list)
        self.assertEqual({}, helper.prometheus_host_instance_map)

    @mock.patch.object(prometheus_client.PrometheusAPIClient, '_get')
    def test_using_ips_not_fqdn(self, mock_prometheus_get):
        mock_prometheus_get.return_value = {
            'data': {
                'activeTargets': [
                    {
                        'labels': {
                            'ip_label': '10.1.2.1',
                            'instance': '10.1.2.1:9100',
                            'job': 'node',
                        }
                    },
                    {
                        'labels': {
                            'ip_label': '10.1.2.2',
                            'instance': '10.1.2.2:9100',
                            'job': 'node',
                        }
                    },
                ]
            }
        }
        helper = self._create_helper(fqdn_label='ip_label')
        expected_fqdn_list = {'10.1.2.1', '10.1.2.2'}
        self.assertEqual(helper.prometheus_fqdn_labels, expected_fqdn_list)

    @mock.patch.object(prometheus_client.PrometheusAPIClient, '_get')
    def test_override_prometheus_fqdn_label(self, mock_prometheus_get):
        mock_prometheus_get.return_value = {
            'data': {
                'activeTargets': [
                    {
                        'labels': {
                            'custom_fqdn_label': 'foo.controlplane.domain',
                            'instance': '10.1.2.1:9100',
                            'job': 'node',
                        }
                    },
                    {
                        'labels': {
                            'custom_fqdn_label': 'bar.controlplane.domain',
                            'instance': '10.1.2.2:9100',
                            'job': 'node',
                        }
                    },
                ]
            }
        }
        expected_fqdn_list = {
            'foo.controlplane.domain',
            'bar.controlplane.domain',
        }
        expected_host_map = {
            'foo': 'foo.controlplane.domain',
            'bar': 'bar.controlplane.domain',
        }
        helper = self._create_helper(fqdn_label='custom_fqdn_label')
        self.assertEqual(helper.prometheus_fqdn_labels, expected_fqdn_list)
        self.assertEqual(
            helper.prometheus_host_instance_map, expected_host_map
        )

    def test_resolve_prometheus_instance_label(self):
        expected_instance_label = 'marios-env.controlplane.domain'
        result = self.helper._resolve_prometheus_instance_label(
            'marios-env.controlplane.domain'
        )
        self.assertEqual(result, expected_instance_label)

        result = self.helper._resolve_prometheus_instance_label('marios-env')
        self.assertEqual(result, expected_instance_label)

    @mock.patch.object(prometheus_client.PrometheusAPIClient, '_get')
    def test_resolve_prometheus_instance_label_none(self, mock_prometheus_get):
        mock_prometheus_get.return_value = {'data': {'activeTargets': []}}
        result = self.helper._resolve_prometheus_instance_label('nope')
        self.assertIsNone(result)
        mock_prometheus_get.assert_called_once_with("targets?state=active")

    def test_build_prometheus_query_node_cpu_avg_agg(self):
        expected_query = (
            "100 - (avg by (fqdn)(rate(node_cpu_seconds_total"
            "{mode='idle',fqdn='a_host'}[111s])) * 100)"
        )
        result = self.helper._build_prometheus_query(
            'avg', 'node_cpu_seconds_total', 'a_host', '111'
        )
        self.assertEqual(result, expected_query)

    def test_build_prometheus_query_node_cpu_max_agg(self):
        expected_query = (
            "100 - (max by (fqdn)(rate(node_cpu_seconds_total"
            "{mode='idle',fqdn='b_host'}[444s])) * 100)"
        )
        result = self.helper._build_prometheus_query(
            'max', 'node_cpu_seconds_total', 'b_host', '444'
        )
        self.assertEqual(result, expected_query)

    def test_build_prometheus_query_node_memory_avg_agg(self):
        expected_query = (
            "(node_memory_MemTotal_bytes{fqdn='c_host'} - avg_over_time"
            "(node_memory_MemAvailable_bytes{fqdn='c_host'}[555s])) "
            "/ 1024"
        )
        result = self.helper._build_prometheus_query(
            'avg', 'node_memory_MemAvailable_bytes', 'c_host', '555'
        )
        self.assertEqual(result, expected_query)

    def test_build_prometheus_query_node_memory_min_agg(self):
        expected_query = (
            "(node_memory_MemTotal_bytes{fqdn='d_host'} - min_over_time"
            "(node_memory_MemAvailable_bytes{fqdn='d_host'}[222s])) "
            "/ 1024"
        )
        result = self.helper._build_prometheus_query(
            'min', 'node_memory_MemAvailable_bytes', 'd_host', '222'
        )
        self.assertEqual(result, expected_query)

    def test_build_prometheus_query_node_cpu_avg_agg_custom_label(self):
        self.helper.prometheus_fqdn_label = 'custom_fqdn_label'
        expected_query = (
            "100 - (avg by (custom_fqdn_label)(rate(node_cpu_seconds_total"
            "{mode='idle',custom_fqdn_label='a_host'}[111s])) * 100)"
        )
        result = self.helper._build_prometheus_query(
            'avg', 'node_cpu_seconds_total', 'a_host', '111'
        )
        self.assertEqual(result, expected_query)

    def test_build_prometheus_query_node_memory_min_agg_custom_label(self):
        self.helper.prometheus_fqdn_label = 'custom_fqdn'
        expected_query = (
            "(node_memory_MemTotal_bytes{custom_fqdn='d_host'} - min_over_time"
            "(node_memory_MemAvailable_bytes{custom_fqdn='d_host'}[222s])) "
            "/ 1024"
        )
        result = self.helper._build_prometheus_query(
            'min', 'node_memory_MemAvailable_bytes', 'd_host', '222'
        )
        self.assertEqual(result, expected_query)

    def test_build_prometheus_query_instance_memory_avg_agg(self):
        expected_query = (
            "avg_over_time(ceilometer_memory_usage{resource='uuid-0'}[555s])"
        )
        result = self.helper._build_prometheus_query(
            'avg', 'ceilometer_memory_usage', 'uuid-0', '555'
        )
        self.assertEqual(result, expected_query)

    def test_build_prometheus_query_instance_memory_min_agg(self):
        expected_query = (
            "min_over_time(ceilometer_memory_usage{resource='uuid-0'}[222s])"
        )
        result = self.helper._build_prometheus_query(
            'min', 'ceilometer_memory_usage', 'uuid-0', '222'
        )
        self.assertEqual(result, expected_query)

    def test_build_prometheus_query_instance_cpu_avg_agg(self):
        expected_query = (
            "clamp_max((avg by (resource)(rate("
            "ceilometer_cpu{resource='uuid-0'}[222s]))"
            "/10e+8) *(100/2), 100)"
        )
        result = self.helper._build_prometheus_query(
            'avg',
            'ceilometer_cpu',
            'uuid-0',
            '222',
            resource=self.mock_instance,
        )
        self.assertEqual(result, expected_query)

    def test_build_prometheus_query_instance_cpu_max_agg(self):
        expected_query = (
            "clamp_max((max by (resource)(rate("
            "ceilometer_cpu{resource='uuid-0'}[555s]))"
            "/10e+8) *(100/4), 100)"
        )
        mock_instance = mock.Mock(uuid='uuid-0', memory=512, disk=2, vcpus=4)
        result = self.helper._build_prometheus_query(
            'max', 'ceilometer_cpu', 'uuid-0', '555', resource=mock_instance
        )
        self.assertEqual(result, expected_query)

    def test_build_prometheus_query_error(self):
        self.assertRaisesRegex(
            exception.InvalidParameter,
            'Cannot process prometheus meter NOPE',
            self.helper._build_prometheus_query,
            'min',
            'NOPE',
            'the_host',
            '222',
        )
        self.assertRaisesRegex(
            exception.InvalidParameter,
            'instance_label None, period 333',
            self.helper._build_prometheus_query,
            'min',
            'node_cpu_seconds_total',
            None,
            '333',
        )

    def test_resolve_prometheus_aggregate_vanilla(self):
        result = self.helper._resolve_prometheus_aggregate('mean', 'foo')
        self.assertEqual(result, 'avg')
        result = self.helper._resolve_prometheus_aggregate('count', 'foo')
        self.assertEqual(result, 'avg')
        result = self.helper._resolve_prometheus_aggregate('max', 'foometric')
        self.assertEqual(result, 'max')
        result = self.helper._resolve_prometheus_aggregate('min', 'barmetric')
        self.assertEqual(result, 'min')

    def test_resolve_prometheus_aggregate_unknown(self):
        self.assertRaisesRegex(
            exception.InvalidParameter,
            'Unknown Watcher aggregate NOPE.',
            self.helper._resolve_prometheus_aggregate,
            'NOPE',
            'some_meter',
        )

    def test_prometheus_query_custom_uuid_label(self):
        self.helper._get_instance_uuid_label.return_value = 'custom_uuid_label'

        expected_query = (
            "clamp_max((max by (custom_uuid_label)"
            "(rate(ceilometer_cpu{custom_uuid_label='uuid-0'}[555s]))"
            "/10e+8) *(100/4), 100)"
        )
        mock_instance = mock.Mock(uuid='uuid-0', memory=512, disk=2, vcpus=4)
        result = self.helper._build_prometheus_query(
            'max', 'ceilometer_cpu', 'uuid-0', '555', resource=mock_instance
        )
        self.assertEqual(result, expected_query)
