2019-06-10 21:24:35 +00:00
|
|
|
from datetime import datetime
|
2022-02-13 17:48:26 +00:00
|
|
|
from functools import lru_cache
|
2019-07-17 15:17:27 +00:00
|
|
|
from os import getenv
|
2021-11-15 16:33:49 +00:00
|
|
|
from typing import Tuple, Optional
|
2019-06-10 21:24:35 +00:00
|
|
|
|
2019-11-09 19:29:23 +00:00
|
|
|
from boltons.iterutils import first
|
2021-11-15 16:33:49 +00:00
|
|
|
from elasticsearch import Elasticsearch
|
2019-06-10 21:24:35 +00:00
|
|
|
|
2021-01-05 14:44:31 +00:00
|
|
|
from apiserver.config_repo import config
|
2019-06-10 21:24:35 +00:00
|
|
|
|
|
|
|
log = config.logger(__file__)
|
|
|
|
|
2019-11-09 19:29:23 +00:00
|
|
|
OVERRIDE_HOST_ENV_KEY = (
|
2021-05-03 14:26:44 +00:00
|
|
|
"CLEARML_ELASTIC_SERVICE_HOST",
|
2019-11-09 19:29:23 +00:00
|
|
|
"TRAINS_ELASTIC_SERVICE_HOST",
|
|
|
|
"ELASTIC_SERVICE_HOST",
|
|
|
|
"ELASTIC_SERVICE_SERVICE_HOST",
|
|
|
|
)
|
2021-05-03 14:26:44 +00:00
|
|
|
OVERRIDE_PORT_ENV_KEY = (
|
|
|
|
"CLEARML_ELASTIC_SERVICE_PORT",
|
|
|
|
"TRAINS_ELASTIC_SERVICE_PORT",
|
|
|
|
"ELASTIC_SERVICE_PORT",
|
|
|
|
)
|
2019-07-17 15:17:27 +00:00
|
|
|
|
2021-11-15 16:33:49 +00:00
|
|
|
OVERRIDE_USERNAME_ENV_KEY = ("CLEARML_ELASTIC_SERVICE_USERNAME",)
|
2021-11-15 13:01:27 +00:00
|
|
|
|
2021-11-15 16:33:49 +00:00
|
|
|
OVERRIDE_PASSWORD_ENV_KEY = ("CLEARML_ELASTIC_SERVICE_PASSWORD",)
|
2021-11-15 13:01:27 +00:00
|
|
|
|
2019-11-09 19:29:23 +00:00
|
|
|
OVERRIDE_HOST = first(filter(None, map(getenv, OVERRIDE_HOST_ENV_KEY)))
|
2019-07-17 15:17:27 +00:00
|
|
|
if OVERRIDE_HOST:
|
|
|
|
log.info(f"Using override elastic host {OVERRIDE_HOST}")
|
|
|
|
|
2021-11-15 16:33:49 +00:00
|
|
|
OVERRIDE_PORT = first(filter(None, map(getenv, OVERRIDE_PORT_ENV_KEY)))
|
|
|
|
if OVERRIDE_PORT:
|
|
|
|
log.info(f"Using override elastic port {OVERRIDE_PORT}")
|
|
|
|
|
2021-11-15 13:01:27 +00:00
|
|
|
OVERRIDE_USERNAME = first(filter(None, map(getenv, OVERRIDE_USERNAME_ENV_KEY)))
|
|
|
|
if OVERRIDE_USERNAME:
|
|
|
|
log.info(f"Using override elastic username {OVERRIDE_USERNAME}")
|
|
|
|
|
|
|
|
OVERRIDE_PASSWORD = first(filter(None, map(getenv, OVERRIDE_PASSWORD_ENV_KEY)))
|
|
|
|
if OVERRIDE_PASSWORD:
|
|
|
|
log.info("Using override elastic password ********")
|
|
|
|
|
2019-06-10 21:24:35 +00:00
|
|
|
_instances = {}
|
|
|
|
|
|
|
|
|
|
|
|
class MissingClusterConfiguration(Exception):
|
|
|
|
"""
|
|
|
|
Exception when cluster configuration is not found in config files
|
|
|
|
"""
|
2019-11-09 19:29:23 +00:00
|
|
|
|
2019-06-10 21:24:35 +00:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class InvalidClusterConfiguration(Exception):
|
|
|
|
"""
|
|
|
|
Exception when cluster configuration does not contain required properties
|
|
|
|
"""
|
2019-11-09 19:29:23 +00:00
|
|
|
|
2019-06-10 21:24:35 +00:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
2021-11-15 16:33:49 +00:00
|
|
|
class MissingPasswordForElasticUser(Exception):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2021-01-05 14:29:25 +00:00
|
|
|
class ESFactory:
|
|
|
|
@classmethod
|
|
|
|
def connect(cls, cluster_name):
|
|
|
|
"""
|
|
|
|
Returns the es client for the cluster.
|
|
|
|
Connects to the cluster if did not connect previously
|
|
|
|
:param cluster_name: Dot separated cluster path in the configuration file
|
|
|
|
:return: es client
|
|
|
|
:raises MissingClusterConfiguration: in case no config section is found for the cluster
|
|
|
|
:raises InvalidClusterConfiguration: in case cluster config section misses needed properties
|
|
|
|
"""
|
|
|
|
if cluster_name not in _instances:
|
|
|
|
cluster_config = cls.get_cluster_config(cluster_name)
|
|
|
|
hosts = cluster_config.get("hosts", None)
|
|
|
|
if not hosts:
|
|
|
|
raise InvalidClusterConfiguration(cluster_name)
|
|
|
|
|
2022-02-13 17:48:26 +00:00
|
|
|
http_auth = cls.get_credentials(cluster_name)
|
2021-11-15 16:33:49 +00:00
|
|
|
|
2021-01-05 14:29:25 +00:00
|
|
|
args = cluster_config.get("args", {})
|
|
|
|
_instances[cluster_name] = Elasticsearch(
|
2021-11-15 16:33:49 +00:00
|
|
|
hosts=hosts, http_auth=http_auth, **args
|
2021-01-05 14:29:25 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
return _instances[cluster_name]
|
|
|
|
|
2021-11-15 16:33:49 +00:00
|
|
|
@classmethod
|
2022-02-13 17:48:26 +00:00
|
|
|
def get_credentials(cls, cluster_name: str, cluster_config: dict = None) -> Optional[Tuple[str, str]]:
|
|
|
|
cluster_config = cluster_config or cls.get_cluster_config(cluster_name)
|
|
|
|
if not cluster_config.get("secure", True):
|
|
|
|
return None
|
|
|
|
|
2021-11-15 16:33:49 +00:00
|
|
|
elastic_user = OVERRIDE_USERNAME or config.get("secure.elastic.user", None)
|
|
|
|
if not elastic_user:
|
|
|
|
return None
|
|
|
|
|
|
|
|
elastic_password = OVERRIDE_PASSWORD or config.get(
|
|
|
|
"secure.elastic.password", None
|
|
|
|
)
|
|
|
|
if not elastic_password:
|
|
|
|
raise MissingPasswordForElasticUser(
|
|
|
|
f"cluster={cluster_name}, username={elastic_user}"
|
|
|
|
)
|
|
|
|
|
|
|
|
return elastic_user, elastic_password
|
|
|
|
|
2021-01-05 14:29:25 +00:00
|
|
|
@classmethod
|
|
|
|
def get_all_cluster_names(cls):
|
|
|
|
return list(config.get("hosts.elastic"))
|
|
|
|
|
2021-05-03 14:48:41 +00:00
|
|
|
@classmethod
|
2021-11-15 16:33:49 +00:00
|
|
|
def get_override_host(cls, cluster_name: str) -> Tuple[str, str]:
|
|
|
|
return OVERRIDE_HOST, OVERRIDE_PORT
|
2021-05-03 14:48:41 +00:00
|
|
|
|
2021-01-05 14:29:25 +00:00
|
|
|
@classmethod
|
2022-02-13 17:48:26 +00:00
|
|
|
@lru_cache()
|
2021-01-05 14:29:25 +00:00
|
|
|
def get_cluster_config(cls, cluster_name):
|
|
|
|
"""
|
|
|
|
Returns cluster config for the specified cluster path
|
|
|
|
:param cluster_name: Dot separated cluster path in the configuration file
|
|
|
|
:return: config section for the cluster
|
|
|
|
:raises MissingClusterConfiguration: in case no config section is found for the cluster
|
|
|
|
"""
|
|
|
|
cluster_key = ".".join(("hosts.elastic", cluster_name))
|
|
|
|
cluster_config = config.get(cluster_key, None)
|
|
|
|
if not cluster_config:
|
|
|
|
raise MissingClusterConfiguration(cluster_name)
|
|
|
|
|
|
|
|
def set_host_prop(key, value):
|
2021-05-03 14:48:41 +00:00
|
|
|
for entry in cluster_config.get("hosts", []):
|
|
|
|
entry[key] = value
|
|
|
|
|
2021-11-15 16:33:49 +00:00
|
|
|
host, port = cls.get_override_host(cluster_name)
|
2021-01-05 14:29:25 +00:00
|
|
|
|
2021-05-03 14:48:41 +00:00
|
|
|
if host:
|
|
|
|
set_host_prop("host", host)
|
2021-01-05 14:29:25 +00:00
|
|
|
|
2021-05-03 14:48:41 +00:00
|
|
|
if port:
|
|
|
|
set_host_prop("port", port)
|
2021-01-05 14:29:25 +00:00
|
|
|
|
|
|
|
return cluster_config
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def connect_all(cls):
|
|
|
|
clusters = config.get("hosts.elastic").as_plain_ordered_dict()
|
|
|
|
for name in clusters:
|
|
|
|
cls.connect(name)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def instances(cls):
|
|
|
|
return _instances
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def timestamp_str_to_millis(cls, ts_str):
|
|
|
|
epoch = datetime.utcfromtimestamp(0)
|
|
|
|
current_date = datetime.strptime(ts_str, "%Y-%m-%dT%H:%M:%S.%fZ")
|
|
|
|
return int((current_date - epoch).total_seconds() * 1000.0)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get_timestamp_millis(cls):
|
|
|
|
now = datetime.utcnow()
|
|
|
|
epoch = datetime.utcfromtimestamp(0)
|
|
|
|
return int((now - epoch).total_seconds() * 1000.0)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get_es_timestamp_str(cls):
|
|
|
|
now = datetime.utcnow()
|
2021-05-03 14:26:44 +00:00
|
|
|
return (
|
|
|
|
now.strftime("%Y-%m-%dT%H:%M:%S") + ".%03d" % (now.microsecond / 1000) + "Z"
|
|
|
|
)
|
2021-01-05 14:29:25 +00:00
|
|
|
|
|
|
|
|
|
|
|
es_factory = ESFactory()
|