"""This file allows the user to use the
entire DBMind without many installations."""
import configparser
import os
import sys
import time
import yaml
from types import SimpleNamespace
from prometheus_client.parser import text_string_to_metric_families
from dbmind import constants
from dbmind.cmd import setup
from dbmind.cmd.configs.configurators import UpdateConfig
from dbmind.common.parser.others import parse_dsn
from dbmind.components.deployment.prometheus_deploy import PROMETHEUS, SSL, EXPORTERS
from dbmind.components.deployment.prometheus_deploy import edit_prometheus_yaml
from dbmind.common.http.requests_utils import create_requests_session
def notify(msg, warn=False):
if warn:
print('WARN: ' + msg, file=sys.stderr)
else:
print(msg, file=sys.stdout)
def die(msg):
print(msg, file=sys.stderr)
sys.exit(1)
def getenv(key, default=None):
variable_value = os.getenv(key)
if variable_value is None:
return default
marks = '\'" '
new_value = variable_value.strip(marks)
if len(variable_value) != len(new_value):
notify(
'The environment variable %s has been stripped.' % key,
warn=True
)
return new_value
OPENGAUSS_DSNS = getenv('OPENGAUSS_DSNS')
CMD_EXPORTERS = getenv('CMD_EXPORTERS')
NODE_EXPORTERS = getenv('NODE_EXPORTERS')
METADATABASE = getenv('METADATABASE')
SCRAPE_INTERVAL = int(getenv('SCRAPE_INTERVAL', 15))
ADMIN_USER = getenv('MASTER_USER')
ADMIN_USER_PWD = getenv('MASTER_USER_PWD')
PATHS_CONFIG = '/etc'
DBMIND_PATHS_CONFIG = '{}/dbmind'.format(PATHS_CONFIG)
PROMETHEUS_PATHS_CONFIG = '{}/prometheus/prometheus.yml'.format(PATHS_CONFIG)
GF_PATHS_CONFIG = '{}/grafana/grafana.ini'.format(PATHS_CONFIG)
PATHS_DATA = '/data'
DBMIND_PATHS_DATA = '{}/dbmind'.format(PATHS_DATA)
PROMETHEUS_PATHS_DATA = '{}/prometheus'.format(PATHS_DATA)
GF_PATHS_DATA = '{}/grafana'.format(PATHS_DATA)
PATHS_LOG = '/log'
DBMIND_PATHS_LOGS = '{}/dbmind'.format(PATHS_LOG)
DBMIND_EXPORTER_PATHS_LOGS = '{}/exporter'.format(DBMIND_PATHS_LOGS)
PROMETHEUS_PATHS_LOG = '{}/prometheus'.format(PATHS_LOG)
GF_PATHS_LOGS = '{}/grafana'.format(PATHS_LOG)
g_agent = None
def execute(cmd):
if 'opengauss_exporter' in cmd:
print('Starting opengauss_exporter...')
else:
print('Starting cmd: ' + cmd)
return os.system(cmd)
def split_with_comma(s):
if not s:
return []
segments = s.split(',')
return list(map(str.strip, segments))
def strip_scheme(urls):
rv = []
for url in urls:
if url.startswith('https://'):
die('Not supported https yet for ' + url)
if url.startswith('http://'):
url = url[len('http://'):]
url = url.strip('/ ')
if ':' not in url:
die('Require a fixed port in the url ' + url)
rv.append(url)
return rv
def look_for_master_exporter(targets, dsn_list):
"""Currently, only supports one agent."""
NODE_INFO_METRIC = 'pg_node_info_uptime'
NODE_INFO_IS_NOT_SLAVE_LABEL = 'is_slave'
NODE_INFO_NON_SLAVE_FLAG = 'N'
for i, target in enumerate(targets):
url = 'http://{}'.format(target)
session = create_requests_session(max_retry=3, timeout=5)
try:
r = session.get(url + '/metrics')
except OSError:
notify('Failed to access %s/metrics.' % url, warn=True)
continue
if r.status_code != 200:
notify('Got status code %d from %s.' % (r.status_code, url), warn=True)
continue
for family in text_string_to_metric_families(r.text):
if family.name != NODE_INFO_METRIC:
continue
for sample in family.samples:
if sample.labels[NODE_INFO_IS_NOT_SLAVE_LABEL] == NODE_INFO_NON_SLAVE_FLAG:
dsn = parse_dsn(dsn_list[i])
return SimpleNamespace(
url=url, username=dsn['user'], password=dsn['password']
)
die('Not found a master node from given DSN.')
class PortAllocator:
START_PORT = 10000
LABEL_OPENGAUSS_EXPORTER = 'opengauss_exporter'
LABEL_REPROCESSING_EXPORTER = 'reprocessing_exporter'
LABEL_CMD_EXPORTER = 'cmd_exporter'
def __init__(self):
self.current = PortAllocator.START_PORT
self.labels = {}
def allocate(self, label):
self.current += 1
if label not in self.labels:
self.labels[label] = []
self.labels[label].append(self.current)
return self.current
def ports(self, label):
return self.labels.get(label, [])
port_allocator = PortAllocator()
def start_opengauss_exporters(dsn_list):
cmd = (
'./gs_dbmind component opengauss_exporter '
'--url \'{dsn}\' '
'--web.listen-address 0.0.0.0 '
'--web.listen-port {port} '
'--disable-http --scrape-interval-seconds {scrape_interval} '
'--log.filepath {log_path}/og_exporter_{log_suffix}.log '
'--log.level error'
)
targets = []
for dsn in dsn_list:
parsed = parse_dsn(dsn)
listen_port = port_allocator.allocate(
port_allocator.LABEL_OPENGAUSS_EXPORTER
)
execute(
cmd.format(
dsn=dsn,
port=listen_port,
scrape_interval=SCRAPE_INTERVAL,
log_path=DBMIND_EXPORTER_PATHS_LOGS,
log_suffix='%s_%s_%s' % (listen_port, parsed['host'], parsed['port'])
)
)
targets.append('127.0.0.1:{}'.format(listen_port))
return targets
def start_reprocessing_exporter():
cmd = (
'./gs_dbmind component reprocessing_exporter '
'127.0.0.1 9090 '
'--web.listen-address 0.0.0.0 '
'--web.listen-port 8181 '
'--disable-https '
'--log.filepath {log_path}/reprocessing_exporter.log '
'--log.level error'
).format(
log_path=DBMIND_EXPORTER_PATHS_LOGS
)
execute(cmd)
def start_cmd_exporters():
pass
def start_prometheus(opengauss_exporter_targets):
deploy_config = configparser.ConfigParser()
deploy_config.add_section(PROMETHEUS)
deploy_config.add_section(SSL)
deploy_config.add_section(EXPORTERS)
deploy_config.set(PROMETHEUS, 'host', '127.0.0.1')
deploy_config.set(PROMETHEUS, 'prometheus_port', '9090')
deploy_config.set(PROMETHEUS, 'reprocessing_exporter_port', '8181')
deploy_config.set(SSL, 'enable_ssl', 'false')
def edit(obj):
obj['global']['scrape_interval'] = '%ds' % SCRAPE_INTERVAL
obj['global']['scrape_timeout'] = '%ds' % (SCRAPE_INTERVAL * 0.9)
return obj
with open(PROMETHEUS_PATHS_CONFIG, 'r', encoding='utf-8') as f:
yaml_obj = yaml.safe_load(f.read())
new_yaml_obj = edit_prometheus_yaml(
yaml_obj=yaml_obj,
configs=deploy_config,
opengauss_exporter_targets=opengauss_exporter_targets,
node_exporter_targets=strip_scheme(split_with_comma(NODE_EXPORTERS)),
cmd_exporter_targets=strip_scheme(split_with_comma(CMD_EXPORTERS)),
additionally_edit=edit
)
with open(PROMETHEUS_PATHS_CONFIG, 'w') as f:
notify("Initiating Prometheus config file.")
yaml.dump(new_yaml_obj, f)
if not os.path.exists(PROMETHEUS_PATHS_LOG):
os.makedirs(PROMETHEUS_PATHS_LOG, exist_ok=True)
PROMETHEUS_BIN = '/bin/prometheus'
cmd = (
'nohup {bin} '
'--config.file={etc} '
'--storage.tsdb.path={data} '
'--web.console.libraries=/usr/share/prometheus/console_libraries '
'--web.console.templates=/usr/share/prometheus/consoles '
'--storage.tsdb.retention.time=1w '
'2>&1 >{log}/prometheus.log &'
).format(
bin=PROMETHEUS_BIN,
etc=PROMETHEUS_PATHS_CONFIG,
data=PROMETHEUS_PATHS_DATA,
log=PROMETHEUS_PATHS_LOG
)
execute(cmd)
def start_grafana():
pass
def config_dbmind_service(confpath):
setup.setup_directory(confpath)
dbmind_conf = os.path.join(DBMIND_PATHS_CONFIG, constants.CONFILE_NAME)
with UpdateConfig(dbmind_conf) as config:
config.set('TSDB', 'name', 'prometheus')
config.set('TSDB', 'host', '127.0.0.1')
config.set('TSDB', 'port', '9090')
if METADATABASE:
dsn = parse_dsn(METADATABASE)
config.set('METADATABASE', 'dbtype', 'opengauss')
config.set('METADATABASE', 'host', dsn['host'])
config.set('METADATABASE', 'port', dsn['port'])
config.set('METADATABASE', 'username', dsn['user'])
config.set('METADATABASE', 'password', dsn['password'])
config.set('METADATABASE', 'database', dsn['dbname'])
else:
config.set('METADATABASE', 'dbtype', 'sqlite')
config.set('METADATABASE', 'database', '{}/metadatabase.db'.format(DBMIND_PATHS_DATA))
config.set('AGENT', 'master_url', g_agent.url)
config.set('AGENT', 'username', g_agent.username)
config.set('AGENT', 'password', g_agent.password)
config.set('WEB-SERVICE', 'host', '0.0.0.0')
config.set('WEB-SERVICE', 'port', '8080')
config.set('LOG', 'log_directory', DBMIND_PATHS_LOGS)
return setup.initialize_and_check_config(confpath, interactive=False, quiet=True)
def start_dbmind_service():
old_cwd = os.getcwd()
if not os.path.exists(DBMIND_PATHS_DATA):
os.makedirs(DBMIND_PATHS_DATA)
if (not os.path.exists(DBMIND_PATHS_CONFIG) or
len(os.listdir(DBMIND_PATHS_CONFIG)) == 0):
success = config_dbmind_service(DBMIND_PATHS_CONFIG)
if not success:
die('Failed to configure DBMind.')
os.chdir(old_cwd)
cmd = './gs_dbmind service start -c {}'.format(DBMIND_PATHS_CONFIG)
exitcode = execute(cmd)
if exitcode:
die('Failed to start DBMind service (exitcode %d). Please check for'
'configurations.' % exitcode)
def check_all_ports():
pass
def block():
while True:
time.sleep(10)
def main_procedure():
if not OPENGAUSS_DSNS:
die('Need to set environment variable OPENGAUSS_DSNS.')
if not NODE_EXPORTERS:
die('Need to set environment variable NODE_EXPORTERS.')
if not os.path.exists(PATHS_DATA):
die('Need to bind mount a volume for ' + PATHS_DATA)
if not os.path.exists(PATHS_CONFIG):
die('Need to bind mount a volume for ' + PATHS_CONFIG)
if not os.path.exists(PATHS_LOG):
die('Need to bind mount a volume for ' + PATHS_LOG)
os.putenv('DBMIND_USE_DAEMON', '1')
os.putenv('OPENBLAS_NUM_THREADS', '1')
notify('Starting openGauss exporters using OPENGAUSS_DSNS...')
dsn_list = split_with_comma(OPENGAUSS_DSNS)
opengauss_exporter_targets = start_opengauss_exporters(dsn_list)
global g_agent
g_agent = look_for_master_exporter(opengauss_exporter_targets, dsn_list)
if ADMIN_USER and ADMIN_USER_PWD:
notify('Use the given administrative account %s.' % ADMIN_USER)
g_agent.username = ADMIN_USER
g_agent.password = ADMIN_USER_PWD
notify('Starting cmd exporters...')
start_cmd_exporters()
notify('Starting configuring Prometheus...')
start_prometheus(opengauss_exporter_targets)
notify('Starting reprocessing exporter...')
start_reprocessing_exporter()
notify('Starting Grafana...')
start_grafana()
start_dbmind_service()
check_all_ports()
notify('All tasks are starting.')
try:
block()
except KeyboardInterrupt:
die('Terminated.')
if __name__ == '__main__':
main_procedure()