From 9b63e625210321076044584669e706cec8f4510f Mon Sep 17 00:00:00 2001 From: Jaromir Wysoglad Date: Thu, 17 Apr 2025 08:41:17 -0400 Subject: [PATCH] Add root_path configuration option This allows to specify a root path to prepend each api call. Change-Id: I033553809c72608388250d3ac66e17b5a2784e61 --- observabilityclient/prometheus_client.py | 9 +++++++-- .../tests/unit/test_prometheus_client.py | 8 +++++--- observabilityclient/tests/unit/test_utils.py | 18 +++++++++++++++--- observabilityclient/utils/metric_utils.py | 7 ++++++- .../notes/add-root-path-192ac0e6ea99b84f.yaml | 9 +++++++++ 5 files changed, 42 insertions(+), 9 deletions(-) create mode 100644 releasenotes/notes/add-root-path-192ac0e6ea99b84f.yaml diff --git a/observabilityclient/prometheus_client.py b/observabilityclient/prometheus_client.py index b76b288..875c247 100644 --- a/observabilityclient/prometheus_client.py +++ b/observabilityclient/prometheus_client.py @@ -53,13 +53,18 @@ class PrometheusMetric(object): class PrometheusAPIClient(object): - def __init__(self, host, session=None): + def __init__(self, host, session=None, root_path=""): self._host = host + if not self._host.endswith('/'): + self._host += '/' if session is None: self._session = requests.Session() else: self._session = session self._session.verify = False + self._root_path = root_path + if root_path != "" and not self._root_path.endswith('/'): + self._root_path += '/' def set_ca_cert(self, ca_cert): self._session.verify = ca_cert @@ -73,7 +78,7 @@ class PrometheusAPIClient(object): def _get_url(self, endpoint): scheme = 'https' if self._session.verify else 'http' - return f"{scheme}://{self._host}/api/v1/{endpoint}" # noqa: E231 + return f"{scheme}://{self._host}{self._root_path}api/v1/{endpoint}" def _get(self, endpoint, params=None): url = self._get_url(endpoint) diff --git a/observabilityclient/tests/unit/test_prometheus_client.py b/observabilityclient/tests/unit/test_prometheus_client.py index 61ff8a3..39fa88f 100644 --- a/observabilityclient/tests/unit/test_prometheus_client.py +++ b/observabilityclient/tests/unit/test_prometheus_client.py @@ -85,7 +85,8 @@ class PrometheusAPIClientTestBase(testtools.TestCase): class PrometheusAPIClientTest(PrometheusAPIClientTestBase): def test_get(self): url = "test" - expected_url = "http://localhost:9090/api/v1/test" + root_path = "root_path" + expected_url = f"http://localhost:9090/{root_path}/api/v1/{url}" params = {"query": "ceilometer_image_size{publisher='localhost'}"} expected_params = params @@ -93,7 +94,8 @@ class PrometheusAPIClientTest(PrometheusAPIClientTestBase): return_value = self.GoodResponse() with mock.patch.object(requests.Session, 'get', return_value=return_value) as m: - c = client.PrometheusAPIClient("localhost:9090") + c = client.PrometheusAPIClient("localhost:9090", + root_path=root_path) c._get(url, params) m.assert_called_with(expected_url, @@ -121,7 +123,7 @@ class PrometheusAPIClientTest(PrometheusAPIClientTestBase): def test_post(self): url = "test" - expected_url = "http://localhost:9090/api/v1/test" + expected_url = f"http://localhost:9090/api/v1/{url}" params = {"query": "ceilometer_image_size{publisher='localhost'}"} expected_params = params diff --git a/observabilityclient/tests/unit/test_utils.py b/observabilityclient/tests/unit/test_utils.py index 9764e90..7aac6ae 100644 --- a/observabilityclient/tests/unit/test_utils.py +++ b/observabilityclient/tests/unit/test_utils.py @@ -55,7 +55,7 @@ class GetPrometheusClientTest(testtools.TestCase): mock.patch.object(prometheus_client.PrometheusAPIClient, "__init__", return_value=None) as m: metric_utils.get_prometheus_client() - m.assert_called_with("somehost:1234", None) + m.assert_called_with("somehost:1234", None, "") def test_get_prometheus_client_env_override(self): with mock.patch.dict(os.environ, @@ -65,7 +65,7 @@ class GetPrometheusClientTest(testtools.TestCase): mock.patch.object(prometheus_client.PrometheusAPIClient, "__init__", return_value=None) as m: metric_utils.get_prometheus_client() - m.assert_called_with("env_override:1234", None) + m.assert_called_with("env_override:1234", None, "") def test_get_prometheus_client_no_config_file(self): patched_env = {'PROMETHEUS_HOST': 'env_override', @@ -76,7 +76,19 @@ class GetPrometheusClientTest(testtools.TestCase): mock.patch.object(prometheus_client.PrometheusAPIClient, "__init__", return_value=None) as m: metric_utils.get_prometheus_client() - m.assert_called_with("env_override:env_port", None) + m.assert_called_with("env_override:env_port", None, "") + + def test_get_prometheus_client_prefix_in_env_variable(self): + patched_env = {'PROMETHEUS_HOST': 'env_override', + 'PROMETHEUS_PORT': 'env_port', + 'PROMETHEUS_ROOT_PATH': 'root_path_env'} + with mock.patch.dict(os.environ, patched_env), \ + mock.patch.object(metric_utils, 'get_config_file', + return_value=None), \ + mock.patch.object(prometheus_client.PrometheusAPIClient, + "__init__", return_value=None) as m: + metric_utils.get_prometheus_client() + m.assert_called_with("env_override:env_port", None, "root_path_env") def test_get_prometheus_client_missing_configuration(self): with mock.patch.dict(os.environ, {}), \ diff --git a/observabilityclient/utils/metric_utils.py b/observabilityclient/utils/metric_utils.py index 925f049..21be25b 100644 --- a/observabilityclient/utils/metric_utils.py +++ b/observabilityclient/utils/metric_utils.py @@ -49,6 +49,7 @@ def get_prometheus_client(session=None): host = None port = None ca_cert = None + root_path = "" conf_file = get_config_file() if conf_file is not None: conf = yaml.safe_load(conf_file) @@ -58,6 +59,8 @@ def get_prometheus_client(session=None): port = conf['port'] if 'ca_cert' in conf: ca_cert = conf['ca_cert'] + if 'root_path' in conf: + root_path = conf['root_path'] conf_file.close() # NOTE(jwysogla): We allow to overide the prometheus.yaml by @@ -68,10 +71,12 @@ def get_prometheus_client(session=None): port = os.environ['PROMETHEUS_PORT'] if 'PROMETHEUS_CA_CERT' in os.environ: ca_cert = os.environ['PROMETHEUS_CA_CERT'] + if 'PROMETHEUS_ROOT_PATH' in os.environ: + root_path = os.environ['PROMETHEUS_ROOT_PATH'] if host is None or port is None: raise ConfigurationError("Can't find prometheus host and " "port configuration.") - client = PrometheusAPIClient(f"{host}:{port}", session) # noqa: E231 + client = PrometheusAPIClient(f"{host}:{port}", session, root_path) if ca_cert is not None: client.set_ca_cert(ca_cert) return client diff --git a/releasenotes/notes/add-root-path-192ac0e6ea99b84f.yaml b/releasenotes/notes/add-root-path-192ac0e6ea99b84f.yaml new file mode 100644 index 0000000..40507e7 --- /dev/null +++ b/releasenotes/notes/add-root-path-192ac0e6ea99b84f.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + Added a "root_path" configuration option with a PROMETHEUS_ROOT_PATH + environment variable override, which allows to set a root path, which gets + prepended to the URL path section for each Prometheus API call. So for + example when Prometheus is hosted on localhost:80/prometheus, setting the + root_path to "prometheus" will enable the observabilityclient to send + requests to URLs like localhost:80/prometheus/api/v1/labels.