Add Prometheus TLS support

This implements support for connecting to Prometheus with TLS.
Because we now have 4 prometheus related config options, I moved them
all under a new config group called 'prometheus'.

Cursor was used to generate first version of the unit tests. I
then went through them and manually adjusted them as needed.

Assisted-By: Cursor with claude-4-sonnet model
Change-Id: Ia859551e7e403eba09117e77d747652f5aa38e8f
Signed-off-by: Jaromir Wysoglad <jwysogla@redhat.com>
This commit is contained in:
Jaromir Wysoglad
2025-07-31 05:10:16 -04:00
parent 623378a3de
commit 455c21fefe
5 changed files with 130 additions and 14 deletions

View File

@@ -19,19 +19,32 @@ from wsme import exc
from observabilityclient import prometheus_client
from observabilityclient import rbac as obsc_rbac
from oslo_config import cfg
from oslo_log import log
from oslo_utils import netutils
LOG = log.getLogger(__name__)
OPTS = [
PROMETHEUS_OPTS = [
cfg.StrOpt(
'prometheus_host',
'host',
default="localhost",
help="The host of Prometheus"),
cfg.PortOpt(
'prometheus_port',
'port',
default=9090,
help="The port of Prometheus"),
# TODO(jwysogla): TLS Prometheus options
cfg.StrOpt(
'ca_file',
help="Path to a CA cert for establishing TLS connections to "
"Prometheus. This is optional, if this isn't set, then "
"default system CA certificates will be used."
),
cfg.BoolOpt(
'use_tls',
default=False,
help="Whether TLS should be used when connecting to Prometheus."
)
]
@@ -42,11 +55,28 @@ class ServerSideError(exc.ClientSideError):
class Base(rest.RestController):
def create_prometheus_client(self, conf):
# TODO(jwysogla): Handle TLS
prometheus_host = netutils.escape_ipv6(conf.prometheus_host)
prometheus_port = conf.prometheus_port
url = f"{prometheus_host}:{prometheus_port}"
host = netutils.escape_ipv6(conf.prometheus.host)
port = conf.prometheus.port
ca_file = conf.prometheus.ca_file
use_tls = conf.prometheus.use_tls
if ca_file and not use_tls:
LOG.warning("CA file specified but TLS disabled - "
"CA file will be ignored")
url = f"{host}:{port}"
self.prometheus_client = prometheus_client.PrometheusAPIClient(url)
if use_tls:
if ca_file:
LOG.debug("TLS for Prometheus connection enabled with CA "
"file: %s", ca_file)
self.prometheus_client.set_ca_cert(ca_file)
else:
LOG.debug("TLS for Prometheus connection enabled with system "
"default CA certificates")
self.prometheus_client.set_ca_cert(True)
super(object, self).__init__()
def process_matches(self, matches, privileged, project_id):

View File

@@ -36,6 +36,7 @@ OPTS = [
def list_opts():
return [
('DEFAULT',
itertools.chain(OPTS,
aetos.controllers.api.v1.base.OPTS)),
itertools.chain(OPTS)),
('prometheus',
itertools.chain(aetos.controllers.api.v1.base.PROMETHEUS_OPTS)),
]

View File

View File

@@ -0,0 +1,85 @@
#
# 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.
import os
import tempfile
from unittest import mock
from oslo_config import fixture as fixture_config
from aetos.controllers.api.v1 import base
from aetos.tests import base as test_base
class TestBaseController(test_base.TestCase):
def setUp(self):
super().setUp()
self.controller = base.Base()
self.conf = self.useFixture(fixture_config.Config()).conf
self.conf.register_opts(base.PROMETHEUS_OPTS, group='prometheus')
def test_create_prometheus_client_without_tls(self):
"""Test prometheus client creation without TLS"""
with mock.patch(
'observabilityclient.prometheus_client.PrometheusAPIClient'
) as mock_client:
self.controller.create_prometheus_client(self.conf)
mock_client.assert_called_once_with('localhost:9090')
# Verify set_ca_cert was not called
mock_client.return_value.set_ca_cert.assert_not_called()
def test_create_prometheus_client_with_tls_custom_ca(self):
"""Test prometheus client creation with TLS with custom CA"""
# Create a temporary CA file
with tempfile.NamedTemporaryFile(mode='w', suffix='.pem',
delete=False) as f:
cert_content = ("-----BEGIN CERTIFICATE-----\n"
"test_ca_content\n"
"-----END CERTIFICATE-----")
f.write(cert_content)
ca_file_path = f.name
try:
self.conf.set_override('ca_file', ca_file_path, group='prometheus')
self.conf.set_override('use_tls', True, group='prometheus')
with mock.patch(
'observabilityclient.prometheus_client.PrometheusAPIClient'
) as mock_client:
self.controller.create_prometheus_client(self.conf)
mock_client.assert_called_once_with('localhost:9090')
# Verify set_ca_cert was called with the correct file path
mock_client.return_value.set_ca_cert.assert_called_once_with(
ca_file_path)
finally:
os.unlink(ca_file_path)
def test_create_prometheus_client_with_tls_default_ca(self):
"""Test prometheus client creation with TLS with default CA"""
self.conf.set_override('use_tls', True, group='prometheus')
with mock.patch(
'observabilityclient.prometheus_client.PrometheusAPIClient'
) as mock_client:
self.controller.create_prometheus_client(self.conf)
mock_client.assert_called_once_with('localhost:9090')
# Verify set_ca_cert was called with True
mock_client.return_value.set_ca_cert.assert_called_once_with(True)

View File

@@ -1,13 +1,13 @@
2. Edit the ``/etc/aetos/aetos.conf`` file and complete the following
actions:
* In the ``[DEFAULT]`` section, configure prometheus access:
* In the ``[prometheus]`` section, configure prometheus access:
.. code-block:: ini
[DEFAULT]
prometheus_host=localhost
prometheus_port=9090
[prometheus]
host=localhost
port=9090
* In the ``[DEFAULT]`` and ``[keystone_authtoken]`` sections,
configure Identity service access: