1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
|
# 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
from oslo_utils import netutils
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_prom_client_from_keystone(session, adapter_options=None):
if adapter_options is None:
adapter_options = {}
endpoint = adapter.Adapter(
session=session, **adapter_options
).get_endpoint()
parsed_url = parse.urlparse(endpoint)
escaped_host = netutils.escape_ipv6(parsed_url.hostname)
root_path = parsed_url.path.strip('/')
tls = parsed_url.scheme == "https"
if parsed_url.port is not None:
url = f'{escaped_host}:{parsed_url.port}'
else:
url = escaped_host
client = PrometheusAPIClient(url, session, root_path)
if tls:
client.set_ca_cert(True)
return client
def get_prom_client_from_file_or_env():
host = port = 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 '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 in config file or "
"environment variables.")
escaped_host = netutils.escape_ipv6(host)
client = PrometheusAPIClient(
f"{escaped_host}:{port}", None, root_path
)
if ca_cert is not None:
client.set_ca_cert(ca_cert)
return client
def get_prometheus_client(session=None, adapter_options=None):
if adapter_options is None:
adapter_options = {}
keystone_error = None
if session is not None:
try:
return get_prom_client_from_keystone(session, adapter_options)
except keystone_exception.EndpointNotFound as e:
# NOTE(jwysogla): Fallback to get the endpoint configuration from
# the config file and env vars. If that doesn't work, the error
# message is raised as ConfigurationError later.
keystone_error = e
LOG.debug("Aetos endpoint discovery from Keystone failed: %s", e)
# NOTE(jwysogla): Always fallback to the original method of discovery
# through config file and env variables for backwards compatibility.
try:
return get_prom_client_from_file_or_env()
except ConfigurationError as e:
if keystone_error is not None:
raise ConfigurationError(
f"Failed to configure Prometheus client. "
f"Aetos discovery from keystone failed: '{keystone_error}'. "
f"Prometheus configuration from config file and environment "
f"variables failed: '{e}'"
)
else:
raise e
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
|