]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
node-proxy: implement config & logging management
authorGuillaume Abrioux <gabrioux@ibm.com>
Thu, 8 Jun 2023 13:12:16 +0000 (15:12 +0200)
committerGuillaume Abrioux <gabrioux@ibm.com>
Thu, 25 Jan 2024 14:50:50 +0000 (14:50 +0000)
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 <gabrioux@ibm.com>
(cherry picked from commit c5acf8183c7d6d02fb8fa301b2acdec096e37059)

src/cephadm/node-proxy/redfish_client.py
src/cephadm/node-proxy/redfish_dell.py
src/cephadm/node-proxy/redfish_system.py
src/cephadm/node-proxy/reporter.py
src/cephadm/node-proxy/server-v2.py
src/cephadm/node-proxy/system.py
src/cephadm/node-proxy/util.py

index 3948b7444f37df85a16ce771b0cb5d52d993ad22..709130bd7232d60aaa8b624e1c47ce522cbfc71c 100644 (file)
@@ -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()
index c4ccd9add4a6eb86da9dad57ad4f2605c8598a1a..bebce7e2a19b13bf51663a1677215e5441420b12 100644 (file)
@@ -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
index fffb45c64dec06741b3d6adc27ca5abfe84ad6f2..358d03d5bdf8fb7938b10eb04a7f91dd717da567 100644 (file)
@@ -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
index c20195b535d4d2766dbf980836dfc97b82d1c51a..d3be2f3393bd3ebc240458e1c75b7a4efa4b6414 100644 (file)
@@ -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)
index 3f2e3df471e0db4bc4b46d630c842de4edb3ee1b..aaf7a75e1c89550b63e18c7cf5534b623c590e1f 100644 (file)
@@ -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)
index 1b70bcd2492defc4436cbfc35416d951a35dda23..4b34001dd817182b6b2cc169fad00069c51523b9 100644 (file)
@@ -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()
index f976814c0ce199b890edf5afe577c4222ee84719..27cfffdf3c294b5a8733b0692b09252af23cb670 100644 (file)
@@ -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