Files
python-observabilityclient/observabilityclient/utils/metric_utils.py
Jaromir Wysoglad e81c54e497 Retrieve prometheus information from keystone
This adds the possibility to retrieve prometheus or aetos endpoint
information from keystone. Aetos is expected to have the endpoint
registered (Aetos's devstack plugin already does this) and admins
can register plain prometheus in Keystone with a simple command.

Previous functionality with using /etc/openstack/prometheus.yaml or
env variables is kept unchanged.

Change-Id: I20eb5858244f1202ab8bc1fa26bb46b41d927ac0
2025-05-30 06:54:50 -04:00

147 lines
4.9 KiB
Python

# Copyright 2023 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 logging
import os
from urllib import parse
from keystoneauth1 import adapter
from keystoneauth1.exceptions import catalog as keystone_exception
import yaml
from observabilityclient.prometheus_client import PrometheusAPIClient
DEFAULT_CONFIG_LOCATIONS = (
[os.path.join(os.environ["HOME"], ".config/openstack/"), "/etc/openstack/"]
if "HOME" in os.environ
else ["/etc/openstack/"]
)
CONFIG_FILE_NAME = "prometheus.yaml"
LOG = logging.getLogger(__name__)
class ConfigurationError(Exception):
pass
def get_config_file():
if os.path.exists(CONFIG_FILE_NAME):
LOG.debug("Using %s as prometheus configuration", CONFIG_FILE_NAME)
return open(CONFIG_FILE_NAME, "r")
for path in DEFAULT_CONFIG_LOCATIONS:
full_filename = path + CONFIG_FILE_NAME
if os.path.exists(full_filename):
LOG.debug("Using %s as prometheus configuration", full_filename)
return open(full_filename, "r")
return None
def get_prometheus_client(session=None, adapter_options={}):
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)
if 'host' in conf:
host = conf['host']
if 'port' in conf:
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()
if session is not None and (host is None or port is None):
try:
endpoint = adapter.Adapter(
session=session, **adapter_options
).get_endpoint()
parsed_url = parse.urlparse(endpoint)
host = parsed_url.hostname
port = parsed_url.port if parsed_url.port is not None else 80
root_path = parsed_url.path.strip('/')
if parsed_url.scheme == "https" and ca_cert is None:
# NOTE(jwysogla): Use the default CA certs if the scheme
# is https, but keep the original value if already set,
# so that a custom certificate can be set in the config
# file, while the endpoint is retrieved from keystone.
ca_cert = True
except keystone_exception.EndpointNotFound:
# NOTE(jwysogla): Don't do anything here. It's still possible
# to get the correct endpoint configuration from the env vars.
# If that doesn't work, the same error message is part of the
# exception raised below.
pass
# NOTE(jwysogla): We allow to overide the prometheus.yaml by
# the environment variables
if 'PROMETHEUS_HOST' in os.environ:
host = os.environ['PROMETHEUS_HOST']
if 'PROMETHEUS_PORT' in os.environ:
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 and endpoint for service"
"prometheus not found.")
client = PrometheusAPIClient(f"{host}:{port}", session, root_path)
if ca_cert is not None:
client.set_ca_cert(ca_cert)
return client
def get_client(obj):
return obj.app.client_manager.observabilityclient
def format_labels(d: dict) -> str:
def replace_doubled_quotes(string):
if "''" in string:
string = string.replace("''", "'")
if '""' in string:
string = string.replace('""', '"')
return string
ret = ""
for key, value in d.items():
ret += "{}='{}', ".format(key, value)
ret = ret[0:-2]
old = ""
while ret != old:
old = ret
ret = replace_doubled_quotes(ret)
return ret
def metrics2cols(m):
# get all label keys
cols = list(set().union(*(d.labels.keys() for d in m)))
cols.sort()
cols.append("value")
fields = []
for metric in m:
row = [""] * len(cols)
for key, value in metric.labels.items():
row[cols.index(key)] = value
row[cols.index("value")] = metric.value
fields.append(row)
return cols, fields