From: Guillaume Abrioux Date: Thu, 8 Jun 2023 13:12:16 +0000 (+0200) Subject: node-proxy: implement config & logging management X-Git-Tag: v18.2.4~314^2~97 X-Git-Url: http://git.apps.os.sepia.ceph.com/?a=commitdiff_plain;h=7208396ef72d60f81284058a3acebff4d8ad51a3;p=ceph.git node-proxy: implement config & logging management This adds the classes 'Config' and 'Logger' in order to manage the logging and the configuration within the node-proxy daemon. Signed-off-by: Guillaume Abrioux (cherry picked from commit c5acf8183c7d6d02fb8fa301b2acdec096e37059) --- diff --git a/src/cephadm/node-proxy/redfish_client.py b/src/cephadm/node-proxy/redfish_client.py index 3948b7444f37d..709130bd7232d 100644 --- a/src/cephadm/node-proxy/redfish_client.py +++ b/src/cephadm/node-proxy/redfish_client.py @@ -3,16 +3,16 @@ from redfish.rest.v1 import ServerDownOrUnreachableError, \ InvalidCredentialsError import redfish import sys -from util import logger +from util import Logger -log = logger(__name__, level=10) +log = Logger(__name__) class RedFishClient: PREFIX = '/redfish/v1' def __init__(self, host, username, password): - log.info(f"redfish client initialization...") + log.logger.info("redfish client initialization...") self.host = host self.username = username self.password = password @@ -24,27 +24,28 @@ class RedFishClient: password=self.password, default_prefix=self.PREFIX) try: + # TODO: add a retry? check for a timeout setting self.redfish_obj.login(auth="session") - log.info(f"Logging to redfish api at {self.host} with user: {self.username}") + log.logger.info(f"Logging to redfish api at {self.host} with user: {self.username}") return self.redfish_obj except InvalidCredentialsError as e: - log.error(f"Invalid credentials for {self.username} at {self.host}:\n{e}") + log.logger.error(f"Invalid credentials for {self.username} at {self.host}:\n{e}") except (SessionCreationError, ServerDownOrUnreachableError) as e: - log.error(f"Server not reachable or does not support RedFish:\n{e}") + log.logger.error(f"Server not reachable or does not support RedFish:\n{e}") sys.exit(1) def get_path(self, path): try: if self.PREFIX not in path: path = f"{self.PREFIX}{path}" - log.debug(f"getting: {path}") + log.logger.debug(f"getting: {path}") response = self.redfish_obj.get(path) return response.dict except Exception as e: #TODO - log.error(f"Error detected.\n{e}") + log.logger.error(f"Error detected.\n{e}") pass def logout(self): - log.info('logging out...') + log.logger.info('logging out...') self.redfish_obj.logout() diff --git a/src/cephadm/node-proxy/redfish_dell.py b/src/cephadm/node-proxy/redfish_dell.py index c4ccd9add4a6e..bebce7e2a19b1 100644 --- a/src/cephadm/node-proxy/redfish_dell.py +++ b/src/cephadm/node-proxy/redfish_dell.py @@ -1,7 +1,7 @@ from redfish_system import RedfishSystem -from util import logger, normalize_dict +from util import Logger, normalize_dict -log = logger(__name__) +log = Logger(__name__) class RedfishDell(RedfishSystem): @@ -12,7 +12,7 @@ class RedfishDell(RedfishSystem): def _update_network(self): net_path = self._system['EthernetInterfaces']['@odata.id'] - log.info("Updating network") + log.logger.info("Updating network") network_info = self.client.get_path(net_path) self._system['network'] = {} result = dict() @@ -29,7 +29,7 @@ class RedfishDell(RedfishSystem): def _update_processors(self): cpus_path = self._system['Processors']['@odata.id'] - log.info("Updating processors") + log.logger.info("Updating processors") cpus_info = self.client.get_path(cpus_path) self._system['processors'] = {} result = dict() @@ -49,7 +49,7 @@ class RedfishDell(RedfishSystem): def _update_storage(self): storage_path = self._system['Storage']['@odata.id'] - log.info("Updating storage") + log.logger.info("Updating storage") storage_info = self.client.get_path(storage_path) result = dict() for storage in storage_info['Members']: @@ -70,13 +70,13 @@ class RedfishDell(RedfishSystem): self._system['storage'] = normalize_dict(result) def _update_metadata(self): - log.info("Updating metadata") + log.logger.info("Updating metadata") pass def _update_memory(self): - log.info("Updating memory") + log.logger.info("Updating memory") pass def _update_power(self): - log.info("Updating power") + log.logger.info("Updating power") pass diff --git a/src/cephadm/node-proxy/redfish_system.py b/src/cephadm/node-proxy/redfish_system.py index fffb45c64dec0..358d03d5bdf8f 100644 --- a/src/cephadm/node-proxy/redfish_system.py +++ b/src/cephadm/node-proxy/redfish_system.py @@ -2,19 +2,21 @@ from system import System from redfish_client import RedFishClient from threading import Thread, Lock from time import sleep -from util import logger +from util import Logger -log = logger(__name__) +log = Logger(__name__) class RedfishSystem(System): def __init__(self, **kw): + super().__init__(**kw) self.host = kw.get('host') self.username = kw.get('username') self.password = kw.get('password') self.system_endpoint = kw.get('system_endpoint', '/Systems/1') - log.info(f"redfish system initialization, host: {self.host}, user: {self.username}") + log.logger.info(f"redfish system initialization, host: {self.host}, user: {self.username}") self.client = RedFishClient(self.host, self.username, self.password) + self._system = {} self.run = False self.thread = None @@ -24,7 +26,7 @@ class RedfishSystem(System): self.lock = Lock() def start_client(self): - log.info(f"redfish system initialization, host: {self.host}, user: {self.username}") + log.logger.info(f"redfish system initialization, host: {self.host}, user: {self.username}") self.client = RedFishClient(self.host, self.username, self.password) self.client.login() @@ -93,9 +95,9 @@ class RedfishSystem(System): # - caching logic try: while self.run: - log.debug("waiting for a lock.") + log.logger.debug("waiting for a lock.") self.lock.acquire() - log.debug("lock acquired.") + log.logger.debug("lock acquired.") try: self._update_system() # following calls in theory can be done in parallel @@ -109,9 +111,9 @@ class RedfishSystem(System): sleep(5) finally: self.lock.release() - log.debug("lock released.") + log.logger.debug("lock released.") # Catching 'Exception' is probably not a good idea (devel only) except Exception as e: - log.error(f"Error detected, logging out from redfish api.\n{e}") + log.logger.error(f"Error detected, logging out from redfish api.\n{e}") self.client.logout() raise diff --git a/src/cephadm/node-proxy/reporter.py b/src/cephadm/node-proxy/reporter.py index c20195b535d4d..d3be2f3393bd3 100644 --- a/src/cephadm/node-proxy/reporter.py +++ b/src/cephadm/node-proxy/reporter.py @@ -1,9 +1,9 @@ from threading import Thread import requests import time -from util import logger +from util import Logger -log = logger(__name__, level=10) +log = Logger(__name__) class Reporter: def __init__(self, system, observer_url): @@ -27,22 +27,22 @@ class Reporter: # that have changed to minimize the traffic in # dense clusters if self.system.data_ready: - log.debug("waiting for a lock.") + log.logger.debug("waiting for a lock.") self.system.lock.acquire() - log.debug("lock acquired.") + log.logger.debug("lock acquired.") if not self.system.get_system() == self.system.previous_data: - log.info('data has changed since last iteration.') + log.logger.info('data has changed since last iteration.') d = self.system.get_system() try: requests.post(f"{self.observer_url}/fake_endpoint", json=d) except requests.exceptions.RequestException as e: - log.error(f"The reporter couldn't send data to the mgr: {e}") + log.logger.error(f"The reporter couldn't send data to the mgr: {e}") # Need to add a new parameter 'max_retries' to the reporter if it can't # send the data for more than x times, maybe the daemon should stop altogether else: self.system.previous_data = self.system.get_system() else: - log.info('no diff, not sending data to the mgr.') + log.logger.info('no diff, not sending data to the mgr.') self.system.lock.release() - log.debug("lock released.") + log.logger.debug("lock released.") time.sleep(5) diff --git a/src/cephadm/node-proxy/server-v2.py b/src/cephadm/node-proxy/server-v2.py index 3f2e3df471e0d..aaf7a75e1c895 100644 --- a/src/cephadm/node-proxy/server-v2.py +++ b/src/cephadm/node-proxy/server-v2.py @@ -1,7 +1,7 @@ import cherrypy from redfish_dell import RedfishDell from reporter import Reporter -from util import logger +from util import Config, Logger import sys # for devel purposes @@ -9,21 +9,43 @@ import os DEVEL_ENV_VARS = ['REDFISH_HOST', 'REDFISH_USERNAME', 'REDFISH_PASSWORD'] + +DEFAULT_CONFIG = { + 'reporter': { + 'check_interval': 5, + 'push_data_max_retries': 30, + }, + 'system': { + 'refresh_interval': 5 + }, + 'server': { + 'port': 8080, + }, + 'logging': { + 'level': 20, + } +} + for env_var in DEVEL_ENV_VARS: if os.environ.get(env_var) is None: print(f"{env_var} environment variable must be set.") sys.exit(1) -log = logger(__name__) +config = Config(default_config=DEFAULT_CONFIG) +log = Logger(__name__, level=config.logging['level']) # must be passed as arguments host = os.environ.get('REDFISH_HOST') username = os.environ.get('REDFISH_USERNAME') password = os.environ.get('REDFISH_PASSWORD') # create the redfish system and the obsever -log.info("Server initialization...") -system = RedfishDell(host=host, username=username, password=password, system_endpoint='/Systems/System.Embedded.1') +log.logger.info("Server initialization...") +system = RedfishDell(host=host, + username=username, + password=password, + system_endpoint='/Systems/System.Embedded.1', + config=config) reporter_agent = Reporter(system, "http://127.0.0.1:8000") @@ -111,6 +133,17 @@ class Stop: return 'node-proxy daemon stopped' +class ConfigReload: + exposed = True + + def __init__(self, config): + self.config = config + + def POST(self): + self.config['node_proxy'].reload() + return 'node-proxy config reloaded' + + class API: exposed = True @@ -118,6 +151,7 @@ class API: shutdown = Shutdown() start = Start() stop = Stop() + config_reload = ConfigReload(cherrypy.config) def GET(self): return 'use /system' @@ -125,12 +159,13 @@ class API: if __name__ == '__main__': cherrypy.config.update({ - 'server.socket_port': 8080 + 'node_proxy': config, + 'server.socket_port': config.server['port'] }) - config = {'/': { + c = {'/': { 'request.methods_with_bodies': ('POST', 'PUT', 'PATCH'), 'request.dispatch': cherrypy.dispatch.MethodDispatcher() }} system.start_update_loop() reporter_agent.run() - cherrypy.quickstart(API(), config=config) + cherrypy.quickstart(API(), config=c) diff --git a/src/cephadm/node-proxy/system.py b/src/cephadm/node-proxy/system.py index 1b70bcd2492de..4b34001dd8171 100644 --- a/src/cephadm/node-proxy/system.py +++ b/src/cephadm/node-proxy/system.py @@ -1,9 +1,7 @@ -from util import Config - class System: def __init__(self, **kw): self._system = {} - self.config: Config = kw['config'] + self.config = kw['config'] def get_system(self): raise NotImplementedError() diff --git a/src/cephadm/node-proxy/util.py b/src/cephadm/node-proxy/util.py index f976814c0ce19..27cfffdf3c294 100644 --- a/src/cephadm/node-proxy/util.py +++ b/src/cephadm/node-proxy/util.py @@ -1,15 +1,7 @@ import logging +import yaml +import os -def logger(name, level=logging.INFO): - logger = logging.getLogger(name) - logger.setLevel(level) - handler = logging.StreamHandler() - handler.setLevel(level) - fmt = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') - handler.setFormatter(fmt) - logger.addHandler(handler) - - return logger def normalize_dict(test_dict): res = dict() @@ -19,3 +11,60 @@ def normalize_dict(test_dict): else: res[key.lower()] = test_dict[key] return res + + +class Config: + + def __init__(self, + config_file='/etc/ceph/node-proxy.yaml', + default_config={}): + self.config_file = config_file + self.default_config = default_config + + self.load_config() + + def load_config(self): + if os.path.exists(self.config_file): + with open(self.config_file, 'r') as f: + self.config = yaml.safe_load(f) + else: + self.config = self.default_config + + for k, v in self.default_config.items(): + if k not in self.config.keys(): + self.config[k] = v + + for k, v in self.config.items(): + setattr(self, k, v) + + # TODO: need to be improved + for _l in Logger._Logger: + _l.logger.setLevel(self.logging['level']) + _l.logger.handlers[0].setLevel(self.logging['level']) + + def reload(self, config_file=None): + if config_file != '': + self.config_file = config_file + self.load_config() + + +class Logger: + _Logger = [] + + def __init__(self, name, level=logging.INFO): + self.name = name + self.level = level + + Logger._Logger.append(self) + self.logger = self.get_logger() + + def get_logger(self): + logger = logging.getLogger(self.name) + logger.setLevel(self.level) + handler = logging.StreamHandler() + handler.setLevel(self.level) + fmt = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + handler.setFormatter(fmt) + logger.addHandler(handler) + + return logger