From: Guillaume Abrioux Date: Thu, 17 Aug 2023 09:18:10 +0000 (+0200) Subject: node-proxy: rename directory X-Git-Tag: testing/wip-batrick-testing-20240411.154038~520^2~73 X-Git-Url: http://git.apps.os.sepia.ceph.com/?a=commitdiff_plain;h=400edcbd05305baed8d790aeefe48958a28d2b18;p=ceph-ci.git node-proxy: rename directory this renames the node-proxy directory node-proxy > node_proxy Signed-off-by: Guillaume Abrioux --- diff --git a/src/cephadm/node-proxy/baseclient.py b/src/cephadm/node-proxy/baseclient.py deleted file mode 100644 index 735dd11e96d..00000000000 --- a/src/cephadm/node-proxy/baseclient.py +++ /dev/null @@ -1,20 +0,0 @@ -from typing import Dict - - -class BaseClient: - def __init__(self, - host: str, - username: str, - password: str) -> None: - self.host = host - self.username = username - self.password = password - - def login(self) -> None: - raise NotImplementedError() - - def logout(self) -> None: - raise NotImplementedError() - - def get_path(self, path: str) -> Dict: - raise NotImplementedError() diff --git a/src/cephadm/node-proxy/basesystem.py b/src/cephadm/node-proxy/basesystem.py deleted file mode 100644 index a56ad7e8f9d..00000000000 --- a/src/cephadm/node-proxy/basesystem.py +++ /dev/null @@ -1,46 +0,0 @@ -from util import Config -from typing import Dict, Any -from baseclient import BaseClient - - -class BaseSystem: - def __init__(self, **kw: Any) -> None: - self._system: Dict = {} - self.config: Config = kw['config'] - self.client: BaseClient - - def get_system(self) -> Dict[str, Dict[str, Dict]]: - raise NotImplementedError() - - def get_status(self) -> Dict[str, Dict[str, Dict]]: - raise NotImplementedError() - - def get_metadata(self) -> Dict[str, Dict[str, Dict]]: - raise NotImplementedError() - - def get_processors(self) -> Dict[str, Dict[str, Dict]]: - raise NotImplementedError() - - def get_memory(self) -> Dict[str, Dict[str, Dict]]: - raise NotImplementedError() - - def get_power(self) -> Dict[str, Dict[str, Dict]]: - raise NotImplementedError() - - def get_network(self) -> Dict[str, Dict[str, Dict]]: - raise NotImplementedError() - - def get_storage(self) -> Dict[str, Dict[str, Dict]]: - raise NotImplementedError() - - def start_update_loop(self) -> None: - raise NotImplementedError() - - def stop_update_loop(self) -> None: - raise NotImplementedError() - - def start_client(self) -> None: - raise NotImplementedError() - - def flush(self) -> None: - raise NotImplementedError() diff --git a/src/cephadm/node-proxy/data.py b/src/cephadm/node-proxy/data.py deleted file mode 100644 index 70339011e4a..00000000000 --- a/src/cephadm/node-proxy/data.py +++ /dev/null @@ -1,99 +0,0 @@ - -system_1 = { - - 'metadata': { - 'name': 'xx', - 'manufacturer': 'Dell', - 'model': 'HP PowerEdge', - 'chassis': 'xxx', - 'xxx': '', - }, - - 'status': { - 'State': 'Enabled', - 'Health': 'OK' - }, - - 'processors': [{ - 'description': '', - 'cores': '', - 'threads': '', - 'type': '', - 'model': '', - 'temperature': '', - 'status': { - 'State': 'Enabled', - 'Health': 'OK' - } - }], - - 'memory': { - 'description': '', - 'total': 'xx', - 'status': { - 'State': 'Enabled', - 'Health': 'OK' - }, - }, - - 'network': { - 'interfaces': [ - { - 'type': 'ethernet', - 'description': 'my ethertnet interface', - 'name': 'name of the interface', - 'description': 'description of the interface', - 'speed_mbps': 'xxx', - 'status': { - 'State': 'Enabled', - 'Health': 'OK' - }, - } - ] - }, - - 'storage': { - 'drives': [ - { - 'device': 'devc', - 'description': 'Milk, Cheese, Bread, Fruit, Vegetables', - 'serial_number': 'xxxxx', - 'location': '1I:x:y', - 'interface_type': 'SATA', - 'model': 'Buy groceries', - 'type': 'ssd|rotate|nvme', - 'capacity_bytes': '', - 'usage_bytes': '', - 'status': { - 'State': 'Enabled', - 'Health': 'OK' - }, - } - ] - }, - - 'power': - [{ - 'type': 'xx', - 'manufacturer': 'xxx', - 'model': 'xx', - 'properties': {}, - 'power_control': 'xx', - 'status': { - 'State': 'Enabled', - 'Health': 'OK' - } - }], - - 'thermal': { - 'fans': [ - { - 'id': 1, - 'status': { - 'State': 'Enabled', - 'Health': 'OK' - } - } - ] - }, -} diff --git a/src/cephadm/node-proxy/fake_cephadm/cephadm_mgr_module.py b/src/cephadm/node-proxy/fake_cephadm/cephadm_mgr_module.py deleted file mode 100644 index 6d46de40dd2..00000000000 --- a/src/cephadm/node-proxy/fake_cephadm/cephadm_mgr_module.py +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env python3 -""" -License: MIT License -Copyright (c) 2023 Miel Donkers - -Very simple HTTP server in python for logging requests -Usage:: - ./server.py [] -""" -from http.server import BaseHTTPRequestHandler, HTTPServer -import logging - -class S(BaseHTTPRequestHandler): - def _set_response(self): - self.send_response(200) - self.send_header('Content-type', 'text/html') - self.end_headers() - - def do_GET(self): - logging.info("GET request,\nPath: %s\nHeaders:\n%s\n", str(self.path), str(self.headers)) - self._set_response() - self.wfile.write("GET request for {}".format(self.path).encode('utf-8')) - - def do_POST(self): - content_length = int(self.headers['Content-Length']) # <--- Gets the size of data - post_data = self.rfile.read(content_length) # <--- Gets the data itself - logging.info("POST request,\nPath: %s\nHeaders:\n%s\n\nBody:\n%s\n", - str(self.path), str(self.headers), post_data.decode('utf-8')) - - self._set_response() - self.wfile.write("POST request for {}".format(self.path).encode('utf-8')) - -def run(server_class=HTTPServer, handler_class=S, port=8000): - logging.basicConfig(level=logging.INFO) - server_address = ('', port) - httpd = server_class(server_address, handler_class) - logging.info(f'Starting httpd on port {port}...\n') - try: - httpd.serve_forever() - except KeyboardInterrupt: - pass - httpd.server_close() - logging.info('Stopping httpd...\n') - -if __name__ == '__main__': - from sys import argv - - if len(argv) == 2: - run(port=int(argv[1])) - else: - run() diff --git a/src/cephadm/node-proxy/main.py b/src/cephadm/node-proxy/main.py deleted file mode 100644 index 45cd573192a..00000000000 --- a/src/cephadm/node-proxy/main.py +++ /dev/null @@ -1,12 +0,0 @@ -from redfish_system import RedfishSystem -import time - -host = "https://x.x.x.x:8443" -username = "myuser" -password = "mypassword" - -system = RedfishSystem(host, username, password) -system.start_update_loop() -time.sleep(20) -print(system.get_status()) -system.stop_update_loop() diff --git a/src/cephadm/node-proxy/redfish-test.py b/src/cephadm/node-proxy/redfish-test.py deleted file mode 100644 index 3aaab2d2f09..00000000000 --- a/src/cephadm/node-proxy/redfish-test.py +++ /dev/null @@ -1,27 +0,0 @@ -from redfish.rest.v1 import ServerDownOrUnreachableError -import redfish -import sys - - -login_host = "https://x.x.x.x:8443" -login_account = "myuser" -login_password = "mypassword" - -REDFISH_OBJ = redfish.redfish_client(base_url=login_host, username=login_account, password=login_password, default_prefix='/redfish/v1/') - -# Login -try: - REDFISH_OBJ.login(auth="session") -except ServerDownOrUnreachableError as excp: - sys.stderr.write("Error: server not reachable or does not support RedFish.\n") - sys.exit() - -# Get the system information /redfish/v1/Systems/1/SmartStorage/ -# /redfish/v1/Systems/1/Processors/ -# /redfish/v1/Systems/1/Memory/proc1dimm1/ -response = REDFISH_OBJ.get(sys.argv[1]) -# Print the system information -print(response.dict) - -# Logout -REDFISH_OBJ.logout() diff --git a/src/cephadm/node-proxy/redfish_client.py b/src/cephadm/node-proxy/redfish_client.py deleted file mode 100644 index 77353cd4781..00000000000 --- a/src/cephadm/node-proxy/redfish_client.py +++ /dev/null @@ -1,54 +0,0 @@ -from redfish.rest.v1 import ServerDownOrUnreachableError, \ - SessionCreationError, \ - InvalidCredentialsError -import redfish -import sys -from util import Logger -from baseclient import BaseClient -from typing import Dict - -log = Logger(__name__) - - -class RedFishClient(BaseClient): - - PREFIX = '/redfish/v1' - - def __init__(self, - host: str, - username: str, - password: str) -> None: - log.logger.info("redfish client initialization...") - super().__init__(host, username, password) - self.redfish_obj: 'redfish.redfish_client' = None - - def login(self) -> 'redfish.redfish_client': - self.redfish_obj = redfish.redfish_client(base_url=self.host, - username=self.username, - password=self.password, - default_prefix=self.PREFIX) - try: - # TODO: add a retry? check for a timeout setting - self.redfish_obj.login(auth="session") - log.logger.info(f"Logging to redfish api at {self.host} with user: {self.username}") - return self.redfish_obj - except InvalidCredentialsError as e: - log.logger.error(f"Invalid credentials for {self.username} at {self.host}:\n{e}") - except (SessionCreationError, ServerDownOrUnreachableError) as e: - log.logger.error(f"Server not reachable or does not support RedFish:\n{e}") - sys.exit(1) - - def get_path(self, path: str) -> Dict: - try: - if self.PREFIX not in path: - path = f"{self.PREFIX}{path}" - log.logger.debug(f"getting: {path}") - response = self.redfish_obj.get(path) - return response.dict - except Exception as e: - log.logger.error(f"Error getting path:\n{e}") - return {} - - def logout(self) -> None: - 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 deleted file mode 100644 index 0f4467bad5f..00000000000 --- a/src/cephadm/node-proxy/redfish_dell.py +++ /dev/null @@ -1,64 +0,0 @@ -from redfish_system import RedfishSystem -from util import Logger, normalize_dict, to_snake_case -from typing import Dict, Any - -log = Logger(__name__) - - -class RedfishDell(RedfishSystem): - def __init__(self, **kw: Any) -> None: - if kw.get('system_endpoint') is None: - kw['system_endpoint'] = '/Systems/System.Embedded.1' - super().__init__(**kw) - - def _update_network(self) -> None: - fields = ['Description', 'Name', 'SpeedMbps', 'Status'] - log.logger.info("Updating network") - self._system['network'] = self.build_data(fields, 'EthernetInterfaces') - - def _update_processors(self) -> None: - fields = ['Description', - 'TotalCores', - 'TotalThreads', - 'ProcessorType', - 'Model', - 'Status', - 'Manufacturer'] - log.logger.info("Updating processors") - self._system['processors'] = self.build_data(fields, 'Processors') - - def _update_storage(self) -> None: - fields = ['Description', - 'CapacityBytes', - 'Model', 'Protocol', - 'SerialNumber', 'Status', - 'PhysicalLocation'] - entities = self.get_members('Storage') - log.logger.info("Updating storage") - result: Dict[str, Dict[str, Dict]] = dict() - for entity in entities: - for drive in entity['Drives']: - drive_path = drive['@odata.id'] - drive_info = self._get_path(drive_path) - drive_id = drive_info['Id'] - result[drive_id] = dict() - for field in fields: - result[drive_id][to_snake_case(field)] = drive_info[field] - result[drive_id]['entity'] = entity['Id'] - self._system['storage'] = normalize_dict(result) - - def _update_metadata(self) -> None: - log.logger.info("Updating metadata") - pass - - def _update_memory(self) -> None: - fields = ['Description', - 'MemoryDeviceType', - 'CapacityMiB', - 'Status'] - log.logger.info("Updating memory") - self._system['memory'] = self.build_data(fields, 'Memory') - - def _update_power(self) -> None: - log.logger.info("Updating power") - pass diff --git a/src/cephadm/node-proxy/redfish_json_samples/interface_sample b/src/cephadm/node-proxy/redfish_json_samples/interface_sample deleted file mode 100644 index 6d351cfbc61..00000000000 --- a/src/cephadm/node-proxy/redfish_json_samples/interface_sample +++ /dev/null @@ -1,19 +0,0 @@ -{ - '@odata.context': '/redfish/v1/$metadata#Systems/Members/1/EthernetInterfaces/Members/$entity', - '@odata.id': '/redfish/v1/Systems/1/EthernetInterfaces/1/', - '@odata.type': '#EthernetInterface.1.0.0.EthernetInterface', - 'Id': '1', - 'Name': 'System Ethernet Interface', - 'Oem': { - 'Hp': { - '@odata.type': '#HpiLOEthernetNetworkInterface.1.0.0.HpiLOEthernetNetworkInterface', - 'DHCPv4': None, - 'DHCPv6': None, - 'IPv4': None, - 'IPv6': None, - 'SharedNetworkPortOptions': None - } - }, - 'SettingsResult': None, - 'Status': None -} diff --git a/src/cephadm/node-proxy/redfish_json_samples/interfaces_sample b/src/cephadm/node-proxy/redfish_json_samples/interfaces_sample deleted file mode 100644 index 811a7720d79..00000000000 --- a/src/cephadm/node-proxy/redfish_json_samples/interfaces_sample +++ /dev/null @@ -1,21 +0,0 @@ -{ - '@odata.context': '/redfish/v1/$metadata#Systems/Members/1/EthernetInterfaces', - '@odata.id': '/redfish/v1/Systems/1/EthernetInterfaces/', - '@odata.type': '#EthernetInterfaceCollection.EthernetInterfaceCollection', - 'Description': 'Collection of System Network Interfaces', - 'Members': [{ - '@odata.id': '/redfish/v1/Systems/1/EthernetInterfaces/1/' - }, { - '@odata.id': '/redfish/v1/Systems/1/EthernetInterfaces/2/' - }, { - '@odata.id': '/redfish/v1/Systems/1/EthernetInterfaces/3/' - }, { - '@odata.id': '/redfish/v1/Systems/1/EthernetInterfaces/4/' - }, { - '@odata.id': '/redfish/v1/Systems/1/EthernetInterfaces/5/' - }, { - '@odata.id': '/redfish/v1/Systems/1/EthernetInterfaces/6/' - }], - 'Members@odata.count': 6, - 'Name': 'System Network Interfaces' -} diff --git a/src/cephadm/node-proxy/redfish_json_samples/memory b/src/cephadm/node-proxy/redfish_json_samples/memory deleted file mode 100644 index fba0606f750..00000000000 --- a/src/cephadm/node-proxy/redfish_json_samples/memory +++ /dev/null @@ -1,65 +0,0 @@ -{ - '@odata.context': '/redfish/v1/$metadata#Systems/Members/1/Memory', - '@odata.id': '/redfish/v1/Systems/1/Memory/', - '@odata.type': '#HpMemoryCollection.HpMemoryCollection', - 'Description': 'Memory DIMM Collection', - 'Members': [{ - '@odata.id': '/redfish/v1/Systems/1/Memory/proc1dimm1/' - }, { - '@odata.id': '/redfish/v1/Systems/1/Memory/proc1dimm2/' - }, { - '@odata.id': '/redfish/v1/Systems/1/Memory/proc1dimm3/' - }, { - '@odata.id': '/redfish/v1/Systems/1/Memory/proc1dimm4/' - }, { - '@odata.id': '/redfish/v1/Systems/1/Memory/proc1dimm5/' - }, { - '@odata.id': '/redfish/v1/Systems/1/Memory/proc1dimm6/' - }, { - '@odata.id': '/redfish/v1/Systems/1/Memory/proc1dimm7/' - }, { - '@odata.id': '/redfish/v1/Systems/1/Memory/proc1dimm8/' - }, { - '@odata.id': '/redfish/v1/Systems/1/Memory/proc1dimm9/' - }, { - '@odata.id': '/redfish/v1/Systems/1/Memory/proc1dimm10/' - }, { - '@odata.id': '/redfish/v1/Systems/1/Memory/proc1dimm11/' - }, { - '@odata.id': '/redfish/v1/Systems/1/Memory/proc1dimm12/' - }, { - '@odata.id': '/redfish/v1/Systems/1/Memory/proc2dimm1/' - }, { - '@odata.id': '/redfish/v1/Systems/1/Memory/proc2dimm2/' - }, { - '@odata.id': '/redfish/v1/Systems/1/Memory/proc2dimm3/' - }, { - '@odata.id': '/redfish/v1/Systems/1/Memory/proc2dimm4/' - }, { - '@odata.id': '/redfish/v1/Systems/1/Memory/proc2dimm5/' - }, { - '@odata.id': '/redfish/v1/Systems/1/Memory/proc2dimm6/' - }, { - '@odata.id': '/redfish/v1/Systems/1/Memory/proc2dimm7/' - }, { - '@odata.id': '/redfish/v1/Systems/1/Memory/proc2dimm8/' - }, { - '@odata.id': '/redfish/v1/Systems/1/Memory/proc2dimm9/' - }, { - '@odata.id': '/redfish/v1/Systems/1/Memory/proc2dimm10/' - }, { - '@odata.id': '/redfish/v1/Systems/1/Memory/proc2dimm11/' - }, { - '@odata.id': '/redfish/v1/Systems/1/Memory/proc2dimm12/' - }], - 'Members@odata.count': 24, - 'Name': 'Memory DIMM Collection', - 'Oem': { - 'Hp': { - '@odata.type': '#HpAdvancedMemoryProtection.1.0.0.HpAdvancedMemoryProtection', - 'AmpModeActive': 'AdvancedECC', - 'AmpModeStatus': 'AdvancedECC', - 'AmpModeSupported': ['AdvancedECC', 'OnlineSpareRank'] - } - } -} diff --git a/src/cephadm/node-proxy/redfish_json_samples/memory_socket b/src/cephadm/node-proxy/redfish_json_samples/memory_socket deleted file mode 100644 index 283c7d41e14..00000000000 --- a/src/cephadm/node-proxy/redfish_json_samples/memory_socket +++ /dev/null @@ -1,21 +0,0 @@ -{ - '@odata.context': '/redfish/v1/$metadata#Systems/Members/1/Memory/Members/$entity', - '@odata.id': '/redfish/v1/Systems/1/Memory/proc1dimm1/', - '@odata.type': '#HpMemory.1.0.0.HpMemory', - 'DIMMStatus': 'GoodInUse', - 'DIMMTechnology': 'RDIMM', - 'DIMMType': 'DDR3', - 'DataWidth': 64, - 'ErrorCorrection': 'SingleBitECC', - 'HPMemoryType': 'HPSmartMemory', - 'Id': 'proc1dimm1', - 'Manufacturer': 'HP ', - 'MaximumFrequencyMHz': 1600, - 'MinimumVoltageVoltsX10': 13, - 'Name': 'proc1dimm1', - 'PartNumber': '713756-081 ', - 'Rank': 2, - 'SizeMB': 16384, - 'SocketLocator': 'PROC 1 DIMM 1 ', - 'TotalWidth': 72 -} diff --git a/src/cephadm/node-proxy/redfish_json_samples/processor b/src/cephadm/node-proxy/redfish_json_samples/processor deleted file mode 100644 index bc381fb5185..00000000000 --- a/src/cephadm/node-proxy/redfish_json_samples/processor +++ /dev/null @@ -1,117 +0,0 @@ -{ - '@odata.context': '/redfish/v1/$metadata#Systems/Members/1/Processors/Members/$entity', - '@odata.id': '/redfish/v1/Systems/1/Processors/1/', - '@odata.type': '#Processor.1.0.0.Processor', - 'Id': '1', - 'InstructionSet': 'x86-64', - 'Manufacturer': 'Intel', - 'MaxSpeedMHz': 4800, - 'Model': ' Intel(R) Xeon(R) CPU E5-2640 v2 @ 2.00GHz ', - 'Name': 'Processors', - 'Oem': { - 'Hp': { - '@odata.type': '#HpProcessorExt.1.0.0.HpProcessorExt', - 'AssetTag': '', - 'Cache': [{ - 'Associativity': '8waySetAssociative', - 'CacheSpeedns': 0, - 'CurrentSRAMType': ['Burst'], - 'EccType': 'SingleBitECC', - 'InstalledSizeKB': 256, - 'Location': 'Internal', - 'MaximumSizeKB': 384, - 'Name': 'Processor 1 Internal L1 Cache', - 'Policy': 'WriteBack', - 'Socketed': False, - 'SupportedSRAMType': ['Burst'], - 'SystemCacheType': 'Data' - }, { - 'Associativity': '8waySetAssociative', - 'CacheSpeedns': 0, - 'CurrentSRAMType': ['Burst'], - 'EccType': 'SingleBitECC', - 'InstalledSizeKB': 2048, - 'Location': 'Internal', - 'MaximumSizeKB': 3072, - 'Name': 'Processor 1 Internal L2 Cache', - 'Policy': 'WriteBack', - 'Socketed': False, - 'SupportedSRAMType': ['Burst'], - 'SystemCacheType': None - }, { - 'Associativity': '20waySetAssociative', - 'CacheSpeedns': 0, - 'CurrentSRAMType': ['Burst'], - 'EccType': 'SingleBitECC', - 'InstalledSizeKB': 20480, - 'Location': 'Internal', - 'MaximumSizeKB': 30720, - 'Name': 'Processor 1 Internal L3 Cache', - 'Policy': 'WriteBack', - 'Socketed': False, - 'SupportedSRAMType': ['Burst'], - 'SystemCacheType': None - }], - 'Characteristics': ['64Bit'], - 'ConfigStatus': { - 'Populated': True, - 'State': 'Enabled' - }, - 'CoresEnabled': 8, - 'ExternalClockMHz': 100, - 'MicrocodePatches': [{ - 'CpuId': '0x000206D2', - 'Date': '2011-05-03T00:00:00Z', - 'PatchId': '0x8000020C' - }, { - 'CpuId': '0x000206D3', - 'Date': '2011-04-20T00:00:00Z', - 'PatchId': '0x80000304' - }, { - 'CpuId': '0x000206D5', - 'Date': '2011-10-13T00:00:00Z', - 'PatchId': '0x00000513' - }, { - 'CpuId': '0x000206D6', - 'Date': '2018-01-30T00:00:00Z', - 'PatchId': '0x0000061C' - }, { - 'CpuId': '0x000206D7', - 'Date': '2018-01-26T00:00:00Z', - 'PatchId': '0x00000713' - }, { - 'CpuId': '0x000306E2', - 'Date': '2013-03-21T00:00:00Z', - 'PatchId': '0x0000020D' - }, { - 'CpuId': '0x000306E3', - 'Date': '2013-03-21T00:00:00Z', - 'PatchId': '0x00000308' - }, { - 'CpuId': '0x000306E4', - 'Date': '2018-01-25T00:00:00Z', - 'PatchId': '0x0000042C' - }], - 'PartNumber': '', - 'RatedSpeedMHz': 2000, - 'SerialNumber': '', - 'VoltageVoltsX10': 14 - } - }, - 'ProcessorArchitecture': 'x86', - 'ProcessorId': { - 'EffectiveFamily': '179', - 'EffectiveModel': '14', - 'IdentificationRegisters': '0x06e40003fbffbfeb', - 'MicrocodeInfo': None, - 'Step': '4', - 'VendorId': 'Intel' - }, - 'ProcessorType': 'CPU', - 'Socket': 'Proc 1', - 'Status': { - 'Health': 'OK' - }, - 'TotalCores': 8, - 'TotalThreads': 16 -} diff --git a/src/cephadm/node-proxy/redfish_json_samples/processors b/src/cephadm/node-proxy/redfish_json_samples/processors deleted file mode 100644 index c2fb740a4cc..00000000000 --- a/src/cephadm/node-proxy/redfish_json_samples/processors +++ /dev/null @@ -1,13 +0,0 @@ -{ - '@odata.context': '/redfish/v1/$metadata#Systems/Members/1/Processors', - '@odata.id': '/redfish/v1/Systems/1/Processors/', - '@odata.type': '#ProcessorCollection.ProcessorCollection', - 'Description': 'Processors view', - 'Members': [{ - '@odata.id': '/redfish/v1/Systems/1/Processors/1/' - }, { - '@odata.id': '/redfish/v1/Systems/1/Processors/2/' - }], - 'Members@odata.count': 2, - 'Name': 'Processors Collection' -} diff --git a/src/cephadm/node-proxy/redfish_json_samples/storage_sample b/src/cephadm/node-proxy/redfish_json_samples/storage_sample deleted file mode 100644 index 6d351cfbc61..00000000000 --- a/src/cephadm/node-proxy/redfish_json_samples/storage_sample +++ /dev/null @@ -1,19 +0,0 @@ -{ - '@odata.context': '/redfish/v1/$metadata#Systems/Members/1/EthernetInterfaces/Members/$entity', - '@odata.id': '/redfish/v1/Systems/1/EthernetInterfaces/1/', - '@odata.type': '#EthernetInterface.1.0.0.EthernetInterface', - 'Id': '1', - 'Name': 'System Ethernet Interface', - 'Oem': { - 'Hp': { - '@odata.type': '#HpiLOEthernetNetworkInterface.1.0.0.HpiLOEthernetNetworkInterface', - 'DHCPv4': None, - 'DHCPv6': None, - 'IPv4': None, - 'IPv6': None, - 'SharedNetworkPortOptions': None - } - }, - 'SettingsResult': None, - 'Status': None -} diff --git a/src/cephadm/node-proxy/redfish_json_samples/system b/src/cephadm/node-proxy/redfish_json_samples/system deleted file mode 100644 index 5bd20170b23..00000000000 --- a/src/cephadm/node-proxy/redfish_json_samples/system +++ /dev/null @@ -1,144 +0,0 @@ -{ - '@odata.context': '/redfish/v1/$metadata#Systems/Members/$entity', - '@odata.id': '/redfish/v1/Systems/1/', - '@odata.type': '#ComputerSystem.1.0.1.ComputerSystem', - 'Actions': { - '#ComputerSystem.Reset': { - 'ResetType@Redfish.AllowableValues': ['On', 'ForceOff', 'ForceRestart', 'Nmi', 'PushPowerButton'], - 'target': '/redfish/v1/Systems/1/Actions/ComputerSystem.Reset/' - } - }, - 'AssetTag': ' ', - 'BiosVersion': 'P71 01/22/2018', - 'Boot': { - 'BootSourceOverrideEnabled': 'Disabled', - 'BootSourceOverrideSupported': ['None', 'Floppy', 'Cd', 'Hdd', 'Usb', 'Utilities', 'BiosSetup', 'Pxe'], - 'BootSourceOverrideTarget': 'None' - }, - 'Description': 'Computer System View', - 'EthernetInterfaces': { - '@odata.id': '/redfish/v1/Systems/1/EthernetInterfaces/' - }, - 'HostName': 'hive1', - 'Id': '1', - 'IndicatorLED': 'Off', - 'Links': { - 'Chassis': [{ - '@odata.id': '/redfish/v1/Chassis/1/' - }], - 'ManagedBy': [{ - '@odata.id': '/redfish/v1/Managers/1/' - }] - }, - 'LogServices': { - '@odata.id': '/redfish/v1/Systems/1/LogServices/' - }, - 'Manufacturer': 'HPE', - 'MemorySummary': { - 'Status': { - 'HealthRollup': 'OK' - }, - 'TotalSystemMemoryGiB': 384 - }, - 'Model': 'ProLiant DL360p Gen8', - 'Name': 'Computer System', - 'Oem': { - 'Hp': { - '@odata.type': '#HpComputerSystemExt.1.2.2.HpComputerSystemExt', - 'Actions': { - '#HpComputerSystemExt.PowerButton': { - 'PushType@Redfish.AllowableValues': ['Press', 'PressAndHold'], - 'target': '/redfish/v1/Systems/1/Actions/Oem/Hp/ComputerSystemExt.PowerButton/' - }, - '#HpComputerSystemExt.SystemReset': { - 'ResetType@Redfish.AllowableValues': ['ColdBoot', 'AuxCycle'], - 'target': '/redfish/v1/Systems/1/Actions/Oem/Hp/ComputerSystemExt.SystemReset/' - } - }, - 'Bios': { - 'Backup': { - 'Date': '07/01/2015', - 'Family': 'P71', - 'VersionString': 'P71 07/01/2015' - }, - 'Bootblock': { - 'Date': '03/05/2013', - 'Family': 'P71', - 'VersionString': 'P71 03/05/2013' - }, - 'Current': { - 'Date': '01/22/2018', - 'Family': 'P71', - 'VersionString': 'P71 01/22/2018' - }, - 'UefiClass': 0 - }, - 'DeviceDiscoveryComplete': { - 'AMSDeviceDiscovery': 'NoAMS', - 'DeviceDiscovery': 'vMainDeviceDiscoveryComplete', - 'SmartArrayDiscovery': 'Complete' - }, - 'IntelligentProvisioningIndex': 3, - 'IntelligentProvisioningLocation': 'System Board', - 'IntelligentProvisioningVersion': 'N/A', - 'Links': { - 'BIOS': { - '@odata.id': '/redfish/v1/Systems/1/Bios/' - }, - 'EthernetInterfaces': { - '@odata.id': '/redfish/v1/Systems/1/EthernetInterfaces/' - }, - 'FirmwareInventory': { - '@odata.id': '/redfish/v1/Systems/1/FirmwareInventory/' - }, - 'Memory': { - '@odata.id': '/redfish/v1/Systems/1/Memory/' - }, - 'NetworkAdapters': { - '@odata.id': '/redfish/v1/Systems/1/NetworkAdapters/' - }, - 'PCIDevices': { - '@odata.id': '/redfish/v1/Systems/1/PCIDevices/' - }, - 'PCISlots': { - '@odata.id': '/redfish/v1/Systems/1/PCISlots/' - }, - 'SmartStorage': { - '@odata.id': '/redfish/v1/Systems/1/SmartStorage/' - }, - 'SoftwareInventory': { - '@odata.id': '/redfish/v1/Systems/1/SoftwareInventory/' - } - }, - 'PostState': 'FinishedPost', - 'PowerAllocationLimit': 1500, - 'PowerAutoOn': 'Restore', - 'PowerOnDelay': 'Minimum', - 'PowerRegulatorMode': 'Dynamic', - 'PowerRegulatorModesSupported': ['OSControl', 'Dynamic', 'Max', 'Min'], - 'TrustedModules': [{ - 'Status': 'NotPresent' - }], - 'VirtualProfile': 'Inactive' - } - }, - 'PowerState': 'On', - 'ProcessorSummary': { - 'Count': 2, - 'Model': ' Intel(R) Xeon(R) CPU E5-2640 v2 @ 2.00GHz ', - 'Status': { - 'HealthRollup': 'OK' - } - }, - 'Processors': { - '@odata.id': '/redfish/v1/Systems/1/Processors/' - }, - 'SKU': '654081-B21 ', - 'SerialNumber': 'CZJ4320228 ', - 'Status': { - 'Health': 'Warning', - 'State': 'Enabled' - }, - 'SystemType': 'Physical', - 'UUID': '30343536-3138-5A43-4A34-333230323238' -} \ No newline at end of file diff --git a/src/cephadm/node-proxy/redfish_system.py b/src/cephadm/node-proxy/redfish_system.py deleted file mode 100644 index f23e41af79e..00000000000 --- a/src/cephadm/node-proxy/redfish_system.py +++ /dev/null @@ -1,161 +0,0 @@ -from basesystem import BaseSystem -from redfish_client import RedFishClient -from threading import Thread, Lock -from time import sleep -from util import Logger, retry, normalize_dict, to_snake_case -from typing import Dict, Any, List - -log = Logger(__name__) - - -class RedfishSystem(BaseSystem): - def __init__(self, **kw: Any) -> None: - super().__init__(**kw) - self.host: str = kw['host'] - self.username: str = kw['username'] - self.password: str = kw['password'] - self.system_endpoint = kw.get('system_endpoint', '/Systems/1') - log.logger.info(f"redfish system initialization, host: {self.host}, user: {self.username}") - self.client = RedFishClient(self.host, self.username, self.password) - - self._system: Dict[str, Dict[str, Any]] = {} - self.run: bool = False - self.thread: Thread - self.start_client() - self.data_ready: bool = False - self.previous_data: Dict = {} - self.lock: Lock = Lock() - - @retry(retries=10, delay=2) - def _get_path(self, path: str) -> Dict: - result = self.client.get_path(path) - if result is None: - log.logger.error(f"The client reported an error when getting path: {path}") - raise RuntimeError(f"Could not get path: {path}") - return result - - def get_members(self, path: str) -> List: - _path = self._system[path]['@odata.id'] - data = self._get_path(_path) - return [self._get_path(member['@odata.id']) for member in data['Members']] - - def build_data(self, - fields: List, - path: str) -> Dict[str, Dict[str, Dict]]: - result: Dict[str, Dict[str, Dict]] = dict() - for member_info in self.get_members(path): - member_id = member_info['Id'] - result[member_id] = dict() - for field in fields: - try: - result[member_id][to_snake_case(field)] = member_info[field] - except KeyError: - log.logger.warning(f"Could not find field: {field} in member_info: {member_info}") - - return normalize_dict(result) - - def start_client(self) -> None: - if not self.client: - self.client = RedFishClient(self.host, self.username, self.password) - self.client.login() - - def get_system(self) -> Dict[str, Dict[str, Dict]]: - result = { - 'storage': self.get_storage(), - 'processors': self.get_processors(), - 'network': self.get_network(), - 'memory': self.get_memory(), - } - return result - - def get_status(self) -> Dict[str, Dict[str, Dict]]: - return self._system['status'] - - def get_metadata(self) -> Dict[str, Dict[str, Dict]]: - return self._system['metadata'] - - def get_memory(self) -> Dict[str, Dict[str, Dict]]: - return self._system['memory'] - - def get_power(self) -> Dict[str, Dict[str, Dict]]: - return self._system['power'] - - def get_processors(self) -> Dict[str, Dict[str, Dict]]: - return self._system['processors'] - - def get_network(self) -> Dict[str, Dict[str, Dict]]: - return self._system['network'] - - def get_storage(self) -> Dict[str, Dict[str, Dict]]: - return self._system['storage'] - - def _update_system(self) -> None: - redfish_system = self.client.get_path(self.system_endpoint) - self._system = {**redfish_system, **self._system} - - def _update_metadata(self) -> None: - raise NotImplementedError() - - def _update_memory(self) -> None: - raise NotImplementedError() - - def _update_power(self) -> None: - raise NotImplementedError() - - def _update_network(self) -> None: - raise NotImplementedError() - - def _update_processors(self) -> None: - raise NotImplementedError() - - def _update_storage(self) -> None: - raise NotImplementedError() - - def start_update_loop(self) -> None: - self.run = True - self.thread = Thread(target=self.update) - self.thread.start() - - def stop_update_loop(self) -> None: - self.run = False - self.thread.join() - - def update(self) -> None: - # this loop can have: - # - caching logic - try: - while self.run: - log.logger.debug("waiting for a lock.") - self.lock.acquire() - log.logger.debug("lock acquired.") - try: - self._update_system() - # following calls in theory can be done in parallel - self._update_metadata() - self._update_memory() - self._update_power() - self._update_network() - self._update_processors() - self._update_storage() - self.data_ready = True - sleep(5) - finally: - self.lock.release() - log.logger.debug("lock released.") - # Catching 'Exception' is probably not a good idea (devel only) - except Exception as e: - log.logger.error(f"Error detected, logging out from redfish api.\n{e}") - self.client.logout() - raise - - def flush(self) -> None: - log.logger.info("Acquiring lock to flush data.") - self.lock.acquire() - log.logger.info("Lock acquired, flushing data.") - self._system = {} - self.previous_data = {} - log.logger.info("Data flushed.") - self.data_ready = False - log.logger.info("Data marked as not ready.") - self.lock.release() - log.logger.info("Lock released.") diff --git a/src/cephadm/node-proxy/reporter.py b/src/cephadm/node-proxy/reporter.py deleted file mode 100644 index 07ac637bf1c..00000000000 --- a/src/cephadm/node-proxy/reporter.py +++ /dev/null @@ -1,52 +0,0 @@ -from threading import Thread -import requests -import time -from util import Logger -from typing import Any - -log = Logger(__name__) - - -class Reporter: - def __init__(self, system: Any, observer_url: str) -> None: - self.system = system - self.observer_url = observer_url - self.finish = False - - def stop(self) -> None: - self.finish = True - self.thread.join() - - def run(self) -> None: - self.thread = Thread(target=self.loop) - self.thread.start() - - def loop(self) -> None: - while not self.finish: - # Any logic to avoid sending the all the system - # information every loop can go here. In a real - # scenario probably we should just send the sub-parts - # that have changed to minimize the traffic in - # dense clusters - log.logger.debug("waiting for a lock.") - self.system.lock.acquire() - log.logger.debug("lock acquired.") - if self.system.data_ready: - log.logger.info('data ready to be sent to the mgr.') - if not self.system.get_system() == self.system.previous_data: - log.logger.info('data has changed since last iteration.') - d = self.system.get_system() - try: - # TODO: add a timeout parameter to the reporter in the config file - requests.post(f"{self.observer_url}/", json=d, timeout=5) - except requests.exceptions.RequestException as 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.logger.info('no diff, not sending data to the mgr.') - self.system.lock.release() - log.logger.debug("lock released.") - time.sleep(5) diff --git a/src/cephadm/node-proxy/requirements.txt b/src/cephadm/node-proxy/requirements.txt deleted file mode 100644 index ab20bcc64b4..00000000000 --- a/src/cephadm/node-proxy/requirements.txt +++ /dev/null @@ -1,8 +0,0 @@ -pyyaml -types-PyYAML -types-requests -mypy -flake8 -redfish -CherryPy -requests diff --git a/src/cephadm/node-proxy/server.py b/src/cephadm/node-proxy/server.py deleted file mode 100644 index d05dbebd9c6..00000000000 --- a/src/cephadm/node-proxy/server.py +++ /dev/null @@ -1,244 +0,0 @@ -import cherrypy -from redfish_dell import RedfishDell -from reporter import Reporter -from util import Config, Logger -from typing import Dict -from basesystem import BaseSystem -import sys -import argparse - -# for devel purposes -import os -DEVEL_ENV_VARS = ['REDFISH_HOST', - 'REDFISH_USERNAME', - 'REDFISH_PASSWORD'] - -DEFAULT_CONFIG = { - 'reporter': { - 'check_interval': 5, - 'push_data_max_retries': 30, - 'endpoint': 'http://127.0.0.1:8150', - }, - '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) - - -class Memory: - exposed = True - - def __init__(self, backend: BaseSystem) -> None: - self.backend = backend - - @cherrypy.tools.json_out() - def GET(self) -> Dict[str, Dict[str, Dict]]: - return {'memory': self.backend.get_memory()} - - -class Network: - exposed = True - - def __init__(self, backend: BaseSystem) -> None: - self.backend = backend - - @cherrypy.tools.json_out() - def GET(self) -> Dict[str, Dict[str, Dict]]: - return {'network': self.backend.get_network()} - - -class Processors: - exposed = True - - def __init__(self, backend: BaseSystem) -> None: - self.backend = backend - - @cherrypy.tools.json_out() - def GET(self) -> Dict[str, Dict[str, Dict]]: - return {'processors': self.backend.get_processors()} - - -class Storage: - exposed = True - - def __init__(self, backend: BaseSystem) -> None: - self.backend = backend - - @cherrypy.tools.json_out() - def GET(self) -> Dict[str, Dict[str, Dict]]: - return {'storage': self.backend.get_storage()} - - -class Status: - exposed = True - - def __init__(self, backend: BaseSystem) -> None: - self.backend = backend - - @cherrypy.tools.json_out() - def GET(self) -> Dict[str, Dict[str, Dict]]: - return {'status': self.backend.get_status()} - - -class System: - exposed = True - - def __init__(self, backend: BaseSystem) -> None: - self.memory = Memory(backend) - self.network = Network(backend) - self.processors = Processors(backend) - self.storage = Storage(backend) - self.status = Status(backend) - # actions = Actions() - # control = Control() - - -class Shutdown: - exposed = True - - def __init__(self, backend: BaseSystem, reporter: Reporter) -> None: - self.backend = backend - self.reporter = reporter - - def POST(self) -> str: - _stop(self.backend, self.reporter) - cherrypy.engine.exit() - return 'Server shutdown...' - - -def _stop(backend: BaseSystem, reporter: Reporter) -> None: - backend.stop_update_loop() - backend.client.logout() - reporter.stop() - - -class Start: - exposed = True - - def __init__(self, backend: BaseSystem, reporter: Reporter) -> None: - self.backend = backend - self.reporter = reporter - - def POST(self) -> str: - self.backend.start_client() - self.backend.start_update_loop() - self.reporter.run() - return 'node-proxy daemon started' - - -class Stop: - exposed = True - - def __init__(self, backend: BaseSystem, reporter: Reporter) -> None: - self.backend = backend - self.reporter = reporter - - def POST(self) -> str: - _stop(self.backend, self.reporter) - return 'node-proxy daemon stopped' - - -class ConfigReload: - exposed = True - - def __init__(self, config: Config) -> None: - self.config = config - - def POST(self) -> str: - self.config.reload() - return 'node-proxy config reloaded' - - -class Flush: - exposed = True - - def __init__(self, backend: BaseSystem) -> None: - self.backend = backend - - def POST(self) -> str: - self.backend.flush() - return 'node-proxy data flushed' - - -class Admin: - exposed = False - - def __init__(self, backend: BaseSystem, config: Config, reporter: Reporter) -> None: - self.reload = ConfigReload(config) - self.flush = Flush(backend) - self.shutdown = Shutdown(backend, reporter) - self.start = Start(backend, reporter) - self.stop = Stop(backend, reporter) - - -class API: - exposed = True - - def __init__(self, - backend: BaseSystem, - reporter: Reporter, - config: Config) -> None: - - self.system = System(backend) - self.admin = Admin(backend, config, reporter) - - def GET(self) -> str: - return 'use /system or /admin endpoints' - - -def main() -> None: - - parser = argparse.ArgumentParser( - prog='node-proxy', - ) - parser.add_argument( - '--config', - dest='config', - type=str, - required=False, - default='/etc/ceph/node-proxy.yml' - ) - - args = parser.parse_args() - config = Config(args.config, default_config=DEFAULT_CONFIG) - - log = Logger(__name__, level=config.__dict__['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.logger.info("Server initialization...") - system = RedfishDell(host=host, - username=username, - password=password, - system_endpoint='/Systems/System.Embedded.1', - config=config) - reporter_agent = Reporter(system, config.__dict__['reporter']['endpoint']) - cherrypy.config.update({ - 'node_proxy': config, - 'server.socket_port': config.__dict__['server']['port'] - }) - c = {'/': { - 'request.methods_with_bodies': ('POST', 'PUT', 'PATCH'), - 'request.dispatch': cherrypy.dispatch.MethodDispatcher() - }} - system.start_update_loop() - reporter_agent.run() - cherrypy.quickstart(API(system, reporter_agent, config), config=c) - - -if __name__ == '__main__': - main() diff --git a/src/cephadm/node-proxy/util.py b/src/cephadm/node-proxy/util.py deleted file mode 100644 index 98c1a7d3671..00000000000 --- a/src/cephadm/node-proxy/util.py +++ /dev/null @@ -1,98 +0,0 @@ -import logging -import yaml -import os -import time -import re -from typing import Dict, List, Callable, Any - - -class Logger: - _Logger: List['Logger'] = [] - - def __init__(self, name: str, level: int = logging.INFO): - self.name = name - self.level = level - - Logger._Logger.append(self) - self.logger = self.get_logger() - - def get_logger(self) -> logging.Logger: - 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 - - -class Config: - - def __init__(self, - config_file: str = '/etc/ceph/node-proxy.yaml', - default_config: Dict[str, Any] = {}) -> None: - self.config_file = config_file - self.default_config = default_config - - self.load_config() - - def load_config(self) -> None: - 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']) # type: ignore - _l.logger.handlers[0].setLevel(self.logging['level']) # type: ignore - - def reload(self, config_file: str = '') -> None: - if config_file != '': - self.config_file = config_file - self.load_config() - - -log = Logger(__name__) - - -def to_snake_case(name: str) -> str: - name = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name) - return re.sub('([a-z0-9])([A-Z])', r'\1_\2', name).lower() - - -def normalize_dict(test_dict: Dict) -> Dict: - res = dict() - for key in test_dict.keys(): - if isinstance(test_dict[key], dict): - res[key.lower()] = normalize_dict(test_dict[key]) - else: - res[key.lower()] = test_dict[key] - return res - - -def retry(exceptions: Any = Exception, retries: int = 20, delay: int = 1) -> Callable: - def decorator(f: Callable) -> Callable: - def _retry(*args: str, **kwargs: Any) -> Callable: - _tries = retries - while _tries > 1: - try: - log.logger.debug("{} {} attempt(s) left.".format(f, _tries - 1)) - return f(*args, **kwargs) - except exceptions: - time.sleep(delay) - _tries -= 1 - log.logger.warn("{} has failed after {} tries".format(f, retries)) - return f(*args, **kwargs) - return _retry - return decorator diff --git a/src/cephadm/node_proxy/baseclient.py b/src/cephadm/node_proxy/baseclient.py new file mode 100644 index 00000000000..735dd11e96d --- /dev/null +++ b/src/cephadm/node_proxy/baseclient.py @@ -0,0 +1,20 @@ +from typing import Dict + + +class BaseClient: + def __init__(self, + host: str, + username: str, + password: str) -> None: + self.host = host + self.username = username + self.password = password + + def login(self) -> None: + raise NotImplementedError() + + def logout(self) -> None: + raise NotImplementedError() + + def get_path(self, path: str) -> Dict: + raise NotImplementedError() diff --git a/src/cephadm/node_proxy/basesystem.py b/src/cephadm/node_proxy/basesystem.py new file mode 100644 index 00000000000..a56ad7e8f9d --- /dev/null +++ b/src/cephadm/node_proxy/basesystem.py @@ -0,0 +1,46 @@ +from util import Config +from typing import Dict, Any +from baseclient import BaseClient + + +class BaseSystem: + def __init__(self, **kw: Any) -> None: + self._system: Dict = {} + self.config: Config = kw['config'] + self.client: BaseClient + + def get_system(self) -> Dict[str, Dict[str, Dict]]: + raise NotImplementedError() + + def get_status(self) -> Dict[str, Dict[str, Dict]]: + raise NotImplementedError() + + def get_metadata(self) -> Dict[str, Dict[str, Dict]]: + raise NotImplementedError() + + def get_processors(self) -> Dict[str, Dict[str, Dict]]: + raise NotImplementedError() + + def get_memory(self) -> Dict[str, Dict[str, Dict]]: + raise NotImplementedError() + + def get_power(self) -> Dict[str, Dict[str, Dict]]: + raise NotImplementedError() + + def get_network(self) -> Dict[str, Dict[str, Dict]]: + raise NotImplementedError() + + def get_storage(self) -> Dict[str, Dict[str, Dict]]: + raise NotImplementedError() + + def start_update_loop(self) -> None: + raise NotImplementedError() + + def stop_update_loop(self) -> None: + raise NotImplementedError() + + def start_client(self) -> None: + raise NotImplementedError() + + def flush(self) -> None: + raise NotImplementedError() diff --git a/src/cephadm/node_proxy/data.py b/src/cephadm/node_proxy/data.py new file mode 100644 index 00000000000..70339011e4a --- /dev/null +++ b/src/cephadm/node_proxy/data.py @@ -0,0 +1,99 @@ + +system_1 = { + + 'metadata': { + 'name': 'xx', + 'manufacturer': 'Dell', + 'model': 'HP PowerEdge', + 'chassis': 'xxx', + 'xxx': '', + }, + + 'status': { + 'State': 'Enabled', + 'Health': 'OK' + }, + + 'processors': [{ + 'description': '', + 'cores': '', + 'threads': '', + 'type': '', + 'model': '', + 'temperature': '', + 'status': { + 'State': 'Enabled', + 'Health': 'OK' + } + }], + + 'memory': { + 'description': '', + 'total': 'xx', + 'status': { + 'State': 'Enabled', + 'Health': 'OK' + }, + }, + + 'network': { + 'interfaces': [ + { + 'type': 'ethernet', + 'description': 'my ethertnet interface', + 'name': 'name of the interface', + 'description': 'description of the interface', + 'speed_mbps': 'xxx', + 'status': { + 'State': 'Enabled', + 'Health': 'OK' + }, + } + ] + }, + + 'storage': { + 'drives': [ + { + 'device': 'devc', + 'description': 'Milk, Cheese, Bread, Fruit, Vegetables', + 'serial_number': 'xxxxx', + 'location': '1I:x:y', + 'interface_type': 'SATA', + 'model': 'Buy groceries', + 'type': 'ssd|rotate|nvme', + 'capacity_bytes': '', + 'usage_bytes': '', + 'status': { + 'State': 'Enabled', + 'Health': 'OK' + }, + } + ] + }, + + 'power': + [{ + 'type': 'xx', + 'manufacturer': 'xxx', + 'model': 'xx', + 'properties': {}, + 'power_control': 'xx', + 'status': { + 'State': 'Enabled', + 'Health': 'OK' + } + }], + + 'thermal': { + 'fans': [ + { + 'id': 1, + 'status': { + 'State': 'Enabled', + 'Health': 'OK' + } + } + ] + }, +} diff --git a/src/cephadm/node_proxy/fake_cephadm/cephadm_mgr_module.py b/src/cephadm/node_proxy/fake_cephadm/cephadm_mgr_module.py new file mode 100644 index 00000000000..6d46de40dd2 --- /dev/null +++ b/src/cephadm/node_proxy/fake_cephadm/cephadm_mgr_module.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +""" +License: MIT License +Copyright (c) 2023 Miel Donkers + +Very simple HTTP server in python for logging requests +Usage:: + ./server.py [] +""" +from http.server import BaseHTTPRequestHandler, HTTPServer +import logging + +class S(BaseHTTPRequestHandler): + def _set_response(self): + self.send_response(200) + self.send_header('Content-type', 'text/html') + self.end_headers() + + def do_GET(self): + logging.info("GET request,\nPath: %s\nHeaders:\n%s\n", str(self.path), str(self.headers)) + self._set_response() + self.wfile.write("GET request for {}".format(self.path).encode('utf-8')) + + def do_POST(self): + content_length = int(self.headers['Content-Length']) # <--- Gets the size of data + post_data = self.rfile.read(content_length) # <--- Gets the data itself + logging.info("POST request,\nPath: %s\nHeaders:\n%s\n\nBody:\n%s\n", + str(self.path), str(self.headers), post_data.decode('utf-8')) + + self._set_response() + self.wfile.write("POST request for {}".format(self.path).encode('utf-8')) + +def run(server_class=HTTPServer, handler_class=S, port=8000): + logging.basicConfig(level=logging.INFO) + server_address = ('', port) + httpd = server_class(server_address, handler_class) + logging.info(f'Starting httpd on port {port}...\n') + try: + httpd.serve_forever() + except KeyboardInterrupt: + pass + httpd.server_close() + logging.info('Stopping httpd...\n') + +if __name__ == '__main__': + from sys import argv + + if len(argv) == 2: + run(port=int(argv[1])) + else: + run() diff --git a/src/cephadm/node_proxy/main.py b/src/cephadm/node_proxy/main.py new file mode 100644 index 00000000000..45cd573192a --- /dev/null +++ b/src/cephadm/node_proxy/main.py @@ -0,0 +1,12 @@ +from redfish_system import RedfishSystem +import time + +host = "https://x.x.x.x:8443" +username = "myuser" +password = "mypassword" + +system = RedfishSystem(host, username, password) +system.start_update_loop() +time.sleep(20) +print(system.get_status()) +system.stop_update_loop() diff --git a/src/cephadm/node_proxy/redfish-test.py b/src/cephadm/node_proxy/redfish-test.py new file mode 100644 index 00000000000..3aaab2d2f09 --- /dev/null +++ b/src/cephadm/node_proxy/redfish-test.py @@ -0,0 +1,27 @@ +from redfish.rest.v1 import ServerDownOrUnreachableError +import redfish +import sys + + +login_host = "https://x.x.x.x:8443" +login_account = "myuser" +login_password = "mypassword" + +REDFISH_OBJ = redfish.redfish_client(base_url=login_host, username=login_account, password=login_password, default_prefix='/redfish/v1/') + +# Login +try: + REDFISH_OBJ.login(auth="session") +except ServerDownOrUnreachableError as excp: + sys.stderr.write("Error: server not reachable or does not support RedFish.\n") + sys.exit() + +# Get the system information /redfish/v1/Systems/1/SmartStorage/ +# /redfish/v1/Systems/1/Processors/ +# /redfish/v1/Systems/1/Memory/proc1dimm1/ +response = REDFISH_OBJ.get(sys.argv[1]) +# Print the system information +print(response.dict) + +# Logout +REDFISH_OBJ.logout() diff --git a/src/cephadm/node_proxy/redfish_client.py b/src/cephadm/node_proxy/redfish_client.py new file mode 100644 index 00000000000..77353cd4781 --- /dev/null +++ b/src/cephadm/node_proxy/redfish_client.py @@ -0,0 +1,54 @@ +from redfish.rest.v1 import ServerDownOrUnreachableError, \ + SessionCreationError, \ + InvalidCredentialsError +import redfish +import sys +from util import Logger +from baseclient import BaseClient +from typing import Dict + +log = Logger(__name__) + + +class RedFishClient(BaseClient): + + PREFIX = '/redfish/v1' + + def __init__(self, + host: str, + username: str, + password: str) -> None: + log.logger.info("redfish client initialization...") + super().__init__(host, username, password) + self.redfish_obj: 'redfish.redfish_client' = None + + def login(self) -> 'redfish.redfish_client': + self.redfish_obj = redfish.redfish_client(base_url=self.host, + username=self.username, + password=self.password, + default_prefix=self.PREFIX) + try: + # TODO: add a retry? check for a timeout setting + self.redfish_obj.login(auth="session") + log.logger.info(f"Logging to redfish api at {self.host} with user: {self.username}") + return self.redfish_obj + except InvalidCredentialsError as e: + log.logger.error(f"Invalid credentials for {self.username} at {self.host}:\n{e}") + except (SessionCreationError, ServerDownOrUnreachableError) as e: + log.logger.error(f"Server not reachable or does not support RedFish:\n{e}") + sys.exit(1) + + def get_path(self, path: str) -> Dict: + try: + if self.PREFIX not in path: + path = f"{self.PREFIX}{path}" + log.logger.debug(f"getting: {path}") + response = self.redfish_obj.get(path) + return response.dict + except Exception as e: + log.logger.error(f"Error getting path:\n{e}") + return {} + + def logout(self) -> None: + 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 new file mode 100644 index 00000000000..0f4467bad5f --- /dev/null +++ b/src/cephadm/node_proxy/redfish_dell.py @@ -0,0 +1,64 @@ +from redfish_system import RedfishSystem +from util import Logger, normalize_dict, to_snake_case +from typing import Dict, Any + +log = Logger(__name__) + + +class RedfishDell(RedfishSystem): + def __init__(self, **kw: Any) -> None: + if kw.get('system_endpoint') is None: + kw['system_endpoint'] = '/Systems/System.Embedded.1' + super().__init__(**kw) + + def _update_network(self) -> None: + fields = ['Description', 'Name', 'SpeedMbps', 'Status'] + log.logger.info("Updating network") + self._system['network'] = self.build_data(fields, 'EthernetInterfaces') + + def _update_processors(self) -> None: + fields = ['Description', + 'TotalCores', + 'TotalThreads', + 'ProcessorType', + 'Model', + 'Status', + 'Manufacturer'] + log.logger.info("Updating processors") + self._system['processors'] = self.build_data(fields, 'Processors') + + def _update_storage(self) -> None: + fields = ['Description', + 'CapacityBytes', + 'Model', 'Protocol', + 'SerialNumber', 'Status', + 'PhysicalLocation'] + entities = self.get_members('Storage') + log.logger.info("Updating storage") + result: Dict[str, Dict[str, Dict]] = dict() + for entity in entities: + for drive in entity['Drives']: + drive_path = drive['@odata.id'] + drive_info = self._get_path(drive_path) + drive_id = drive_info['Id'] + result[drive_id] = dict() + for field in fields: + result[drive_id][to_snake_case(field)] = drive_info[field] + result[drive_id]['entity'] = entity['Id'] + self._system['storage'] = normalize_dict(result) + + def _update_metadata(self) -> None: + log.logger.info("Updating metadata") + pass + + def _update_memory(self) -> None: + fields = ['Description', + 'MemoryDeviceType', + 'CapacityMiB', + 'Status'] + log.logger.info("Updating memory") + self._system['memory'] = self.build_data(fields, 'Memory') + + def _update_power(self) -> None: + log.logger.info("Updating power") + pass diff --git a/src/cephadm/node_proxy/redfish_json_samples/interface_sample b/src/cephadm/node_proxy/redfish_json_samples/interface_sample new file mode 100644 index 00000000000..6d351cfbc61 --- /dev/null +++ b/src/cephadm/node_proxy/redfish_json_samples/interface_sample @@ -0,0 +1,19 @@ +{ + '@odata.context': '/redfish/v1/$metadata#Systems/Members/1/EthernetInterfaces/Members/$entity', + '@odata.id': '/redfish/v1/Systems/1/EthernetInterfaces/1/', + '@odata.type': '#EthernetInterface.1.0.0.EthernetInterface', + 'Id': '1', + 'Name': 'System Ethernet Interface', + 'Oem': { + 'Hp': { + '@odata.type': '#HpiLOEthernetNetworkInterface.1.0.0.HpiLOEthernetNetworkInterface', + 'DHCPv4': None, + 'DHCPv6': None, + 'IPv4': None, + 'IPv6': None, + 'SharedNetworkPortOptions': None + } + }, + 'SettingsResult': None, + 'Status': None +} diff --git a/src/cephadm/node_proxy/redfish_json_samples/interfaces_sample b/src/cephadm/node_proxy/redfish_json_samples/interfaces_sample new file mode 100644 index 00000000000..811a7720d79 --- /dev/null +++ b/src/cephadm/node_proxy/redfish_json_samples/interfaces_sample @@ -0,0 +1,21 @@ +{ + '@odata.context': '/redfish/v1/$metadata#Systems/Members/1/EthernetInterfaces', + '@odata.id': '/redfish/v1/Systems/1/EthernetInterfaces/', + '@odata.type': '#EthernetInterfaceCollection.EthernetInterfaceCollection', + 'Description': 'Collection of System Network Interfaces', + 'Members': [{ + '@odata.id': '/redfish/v1/Systems/1/EthernetInterfaces/1/' + }, { + '@odata.id': '/redfish/v1/Systems/1/EthernetInterfaces/2/' + }, { + '@odata.id': '/redfish/v1/Systems/1/EthernetInterfaces/3/' + }, { + '@odata.id': '/redfish/v1/Systems/1/EthernetInterfaces/4/' + }, { + '@odata.id': '/redfish/v1/Systems/1/EthernetInterfaces/5/' + }, { + '@odata.id': '/redfish/v1/Systems/1/EthernetInterfaces/6/' + }], + 'Members@odata.count': 6, + 'Name': 'System Network Interfaces' +} diff --git a/src/cephadm/node_proxy/redfish_json_samples/memory b/src/cephadm/node_proxy/redfish_json_samples/memory new file mode 100644 index 00000000000..fba0606f750 --- /dev/null +++ b/src/cephadm/node_proxy/redfish_json_samples/memory @@ -0,0 +1,65 @@ +{ + '@odata.context': '/redfish/v1/$metadata#Systems/Members/1/Memory', + '@odata.id': '/redfish/v1/Systems/1/Memory/', + '@odata.type': '#HpMemoryCollection.HpMemoryCollection', + 'Description': 'Memory DIMM Collection', + 'Members': [{ + '@odata.id': '/redfish/v1/Systems/1/Memory/proc1dimm1/' + }, { + '@odata.id': '/redfish/v1/Systems/1/Memory/proc1dimm2/' + }, { + '@odata.id': '/redfish/v1/Systems/1/Memory/proc1dimm3/' + }, { + '@odata.id': '/redfish/v1/Systems/1/Memory/proc1dimm4/' + }, { + '@odata.id': '/redfish/v1/Systems/1/Memory/proc1dimm5/' + }, { + '@odata.id': '/redfish/v1/Systems/1/Memory/proc1dimm6/' + }, { + '@odata.id': '/redfish/v1/Systems/1/Memory/proc1dimm7/' + }, { + '@odata.id': '/redfish/v1/Systems/1/Memory/proc1dimm8/' + }, { + '@odata.id': '/redfish/v1/Systems/1/Memory/proc1dimm9/' + }, { + '@odata.id': '/redfish/v1/Systems/1/Memory/proc1dimm10/' + }, { + '@odata.id': '/redfish/v1/Systems/1/Memory/proc1dimm11/' + }, { + '@odata.id': '/redfish/v1/Systems/1/Memory/proc1dimm12/' + }, { + '@odata.id': '/redfish/v1/Systems/1/Memory/proc2dimm1/' + }, { + '@odata.id': '/redfish/v1/Systems/1/Memory/proc2dimm2/' + }, { + '@odata.id': '/redfish/v1/Systems/1/Memory/proc2dimm3/' + }, { + '@odata.id': '/redfish/v1/Systems/1/Memory/proc2dimm4/' + }, { + '@odata.id': '/redfish/v1/Systems/1/Memory/proc2dimm5/' + }, { + '@odata.id': '/redfish/v1/Systems/1/Memory/proc2dimm6/' + }, { + '@odata.id': '/redfish/v1/Systems/1/Memory/proc2dimm7/' + }, { + '@odata.id': '/redfish/v1/Systems/1/Memory/proc2dimm8/' + }, { + '@odata.id': '/redfish/v1/Systems/1/Memory/proc2dimm9/' + }, { + '@odata.id': '/redfish/v1/Systems/1/Memory/proc2dimm10/' + }, { + '@odata.id': '/redfish/v1/Systems/1/Memory/proc2dimm11/' + }, { + '@odata.id': '/redfish/v1/Systems/1/Memory/proc2dimm12/' + }], + 'Members@odata.count': 24, + 'Name': 'Memory DIMM Collection', + 'Oem': { + 'Hp': { + '@odata.type': '#HpAdvancedMemoryProtection.1.0.0.HpAdvancedMemoryProtection', + 'AmpModeActive': 'AdvancedECC', + 'AmpModeStatus': 'AdvancedECC', + 'AmpModeSupported': ['AdvancedECC', 'OnlineSpareRank'] + } + } +} diff --git a/src/cephadm/node_proxy/redfish_json_samples/memory_socket b/src/cephadm/node_proxy/redfish_json_samples/memory_socket new file mode 100644 index 00000000000..283c7d41e14 --- /dev/null +++ b/src/cephadm/node_proxy/redfish_json_samples/memory_socket @@ -0,0 +1,21 @@ +{ + '@odata.context': '/redfish/v1/$metadata#Systems/Members/1/Memory/Members/$entity', + '@odata.id': '/redfish/v1/Systems/1/Memory/proc1dimm1/', + '@odata.type': '#HpMemory.1.0.0.HpMemory', + 'DIMMStatus': 'GoodInUse', + 'DIMMTechnology': 'RDIMM', + 'DIMMType': 'DDR3', + 'DataWidth': 64, + 'ErrorCorrection': 'SingleBitECC', + 'HPMemoryType': 'HPSmartMemory', + 'Id': 'proc1dimm1', + 'Manufacturer': 'HP ', + 'MaximumFrequencyMHz': 1600, + 'MinimumVoltageVoltsX10': 13, + 'Name': 'proc1dimm1', + 'PartNumber': '713756-081 ', + 'Rank': 2, + 'SizeMB': 16384, + 'SocketLocator': 'PROC 1 DIMM 1 ', + 'TotalWidth': 72 +} diff --git a/src/cephadm/node_proxy/redfish_json_samples/processor b/src/cephadm/node_proxy/redfish_json_samples/processor new file mode 100644 index 00000000000..bc381fb5185 --- /dev/null +++ b/src/cephadm/node_proxy/redfish_json_samples/processor @@ -0,0 +1,117 @@ +{ + '@odata.context': '/redfish/v1/$metadata#Systems/Members/1/Processors/Members/$entity', + '@odata.id': '/redfish/v1/Systems/1/Processors/1/', + '@odata.type': '#Processor.1.0.0.Processor', + 'Id': '1', + 'InstructionSet': 'x86-64', + 'Manufacturer': 'Intel', + 'MaxSpeedMHz': 4800, + 'Model': ' Intel(R) Xeon(R) CPU E5-2640 v2 @ 2.00GHz ', + 'Name': 'Processors', + 'Oem': { + 'Hp': { + '@odata.type': '#HpProcessorExt.1.0.0.HpProcessorExt', + 'AssetTag': '', + 'Cache': [{ + 'Associativity': '8waySetAssociative', + 'CacheSpeedns': 0, + 'CurrentSRAMType': ['Burst'], + 'EccType': 'SingleBitECC', + 'InstalledSizeKB': 256, + 'Location': 'Internal', + 'MaximumSizeKB': 384, + 'Name': 'Processor 1 Internal L1 Cache', + 'Policy': 'WriteBack', + 'Socketed': False, + 'SupportedSRAMType': ['Burst'], + 'SystemCacheType': 'Data' + }, { + 'Associativity': '8waySetAssociative', + 'CacheSpeedns': 0, + 'CurrentSRAMType': ['Burst'], + 'EccType': 'SingleBitECC', + 'InstalledSizeKB': 2048, + 'Location': 'Internal', + 'MaximumSizeKB': 3072, + 'Name': 'Processor 1 Internal L2 Cache', + 'Policy': 'WriteBack', + 'Socketed': False, + 'SupportedSRAMType': ['Burst'], + 'SystemCacheType': None + }, { + 'Associativity': '20waySetAssociative', + 'CacheSpeedns': 0, + 'CurrentSRAMType': ['Burst'], + 'EccType': 'SingleBitECC', + 'InstalledSizeKB': 20480, + 'Location': 'Internal', + 'MaximumSizeKB': 30720, + 'Name': 'Processor 1 Internal L3 Cache', + 'Policy': 'WriteBack', + 'Socketed': False, + 'SupportedSRAMType': ['Burst'], + 'SystemCacheType': None + }], + 'Characteristics': ['64Bit'], + 'ConfigStatus': { + 'Populated': True, + 'State': 'Enabled' + }, + 'CoresEnabled': 8, + 'ExternalClockMHz': 100, + 'MicrocodePatches': [{ + 'CpuId': '0x000206D2', + 'Date': '2011-05-03T00:00:00Z', + 'PatchId': '0x8000020C' + }, { + 'CpuId': '0x000206D3', + 'Date': '2011-04-20T00:00:00Z', + 'PatchId': '0x80000304' + }, { + 'CpuId': '0x000206D5', + 'Date': '2011-10-13T00:00:00Z', + 'PatchId': '0x00000513' + }, { + 'CpuId': '0x000206D6', + 'Date': '2018-01-30T00:00:00Z', + 'PatchId': '0x0000061C' + }, { + 'CpuId': '0x000206D7', + 'Date': '2018-01-26T00:00:00Z', + 'PatchId': '0x00000713' + }, { + 'CpuId': '0x000306E2', + 'Date': '2013-03-21T00:00:00Z', + 'PatchId': '0x0000020D' + }, { + 'CpuId': '0x000306E3', + 'Date': '2013-03-21T00:00:00Z', + 'PatchId': '0x00000308' + }, { + 'CpuId': '0x000306E4', + 'Date': '2018-01-25T00:00:00Z', + 'PatchId': '0x0000042C' + }], + 'PartNumber': '', + 'RatedSpeedMHz': 2000, + 'SerialNumber': '', + 'VoltageVoltsX10': 14 + } + }, + 'ProcessorArchitecture': 'x86', + 'ProcessorId': { + 'EffectiveFamily': '179', + 'EffectiveModel': '14', + 'IdentificationRegisters': '0x06e40003fbffbfeb', + 'MicrocodeInfo': None, + 'Step': '4', + 'VendorId': 'Intel' + }, + 'ProcessorType': 'CPU', + 'Socket': 'Proc 1', + 'Status': { + 'Health': 'OK' + }, + 'TotalCores': 8, + 'TotalThreads': 16 +} diff --git a/src/cephadm/node_proxy/redfish_json_samples/processors b/src/cephadm/node_proxy/redfish_json_samples/processors new file mode 100644 index 00000000000..c2fb740a4cc --- /dev/null +++ b/src/cephadm/node_proxy/redfish_json_samples/processors @@ -0,0 +1,13 @@ +{ + '@odata.context': '/redfish/v1/$metadata#Systems/Members/1/Processors', + '@odata.id': '/redfish/v1/Systems/1/Processors/', + '@odata.type': '#ProcessorCollection.ProcessorCollection', + 'Description': 'Processors view', + 'Members': [{ + '@odata.id': '/redfish/v1/Systems/1/Processors/1/' + }, { + '@odata.id': '/redfish/v1/Systems/1/Processors/2/' + }], + 'Members@odata.count': 2, + 'Name': 'Processors Collection' +} diff --git a/src/cephadm/node_proxy/redfish_json_samples/storage_sample b/src/cephadm/node_proxy/redfish_json_samples/storage_sample new file mode 100644 index 00000000000..6d351cfbc61 --- /dev/null +++ b/src/cephadm/node_proxy/redfish_json_samples/storage_sample @@ -0,0 +1,19 @@ +{ + '@odata.context': '/redfish/v1/$metadata#Systems/Members/1/EthernetInterfaces/Members/$entity', + '@odata.id': '/redfish/v1/Systems/1/EthernetInterfaces/1/', + '@odata.type': '#EthernetInterface.1.0.0.EthernetInterface', + 'Id': '1', + 'Name': 'System Ethernet Interface', + 'Oem': { + 'Hp': { + '@odata.type': '#HpiLOEthernetNetworkInterface.1.0.0.HpiLOEthernetNetworkInterface', + 'DHCPv4': None, + 'DHCPv6': None, + 'IPv4': None, + 'IPv6': None, + 'SharedNetworkPortOptions': None + } + }, + 'SettingsResult': None, + 'Status': None +} diff --git a/src/cephadm/node_proxy/redfish_json_samples/system b/src/cephadm/node_proxy/redfish_json_samples/system new file mode 100644 index 00000000000..5bd20170b23 --- /dev/null +++ b/src/cephadm/node_proxy/redfish_json_samples/system @@ -0,0 +1,144 @@ +{ + '@odata.context': '/redfish/v1/$metadata#Systems/Members/$entity', + '@odata.id': '/redfish/v1/Systems/1/', + '@odata.type': '#ComputerSystem.1.0.1.ComputerSystem', + 'Actions': { + '#ComputerSystem.Reset': { + 'ResetType@Redfish.AllowableValues': ['On', 'ForceOff', 'ForceRestart', 'Nmi', 'PushPowerButton'], + 'target': '/redfish/v1/Systems/1/Actions/ComputerSystem.Reset/' + } + }, + 'AssetTag': ' ', + 'BiosVersion': 'P71 01/22/2018', + 'Boot': { + 'BootSourceOverrideEnabled': 'Disabled', + 'BootSourceOverrideSupported': ['None', 'Floppy', 'Cd', 'Hdd', 'Usb', 'Utilities', 'BiosSetup', 'Pxe'], + 'BootSourceOverrideTarget': 'None' + }, + 'Description': 'Computer System View', + 'EthernetInterfaces': { + '@odata.id': '/redfish/v1/Systems/1/EthernetInterfaces/' + }, + 'HostName': 'hive1', + 'Id': '1', + 'IndicatorLED': 'Off', + 'Links': { + 'Chassis': [{ + '@odata.id': '/redfish/v1/Chassis/1/' + }], + 'ManagedBy': [{ + '@odata.id': '/redfish/v1/Managers/1/' + }] + }, + 'LogServices': { + '@odata.id': '/redfish/v1/Systems/1/LogServices/' + }, + 'Manufacturer': 'HPE', + 'MemorySummary': { + 'Status': { + 'HealthRollup': 'OK' + }, + 'TotalSystemMemoryGiB': 384 + }, + 'Model': 'ProLiant DL360p Gen8', + 'Name': 'Computer System', + 'Oem': { + 'Hp': { + '@odata.type': '#HpComputerSystemExt.1.2.2.HpComputerSystemExt', + 'Actions': { + '#HpComputerSystemExt.PowerButton': { + 'PushType@Redfish.AllowableValues': ['Press', 'PressAndHold'], + 'target': '/redfish/v1/Systems/1/Actions/Oem/Hp/ComputerSystemExt.PowerButton/' + }, + '#HpComputerSystemExt.SystemReset': { + 'ResetType@Redfish.AllowableValues': ['ColdBoot', 'AuxCycle'], + 'target': '/redfish/v1/Systems/1/Actions/Oem/Hp/ComputerSystemExt.SystemReset/' + } + }, + 'Bios': { + 'Backup': { + 'Date': '07/01/2015', + 'Family': 'P71', + 'VersionString': 'P71 07/01/2015' + }, + 'Bootblock': { + 'Date': '03/05/2013', + 'Family': 'P71', + 'VersionString': 'P71 03/05/2013' + }, + 'Current': { + 'Date': '01/22/2018', + 'Family': 'P71', + 'VersionString': 'P71 01/22/2018' + }, + 'UefiClass': 0 + }, + 'DeviceDiscoveryComplete': { + 'AMSDeviceDiscovery': 'NoAMS', + 'DeviceDiscovery': 'vMainDeviceDiscoveryComplete', + 'SmartArrayDiscovery': 'Complete' + }, + 'IntelligentProvisioningIndex': 3, + 'IntelligentProvisioningLocation': 'System Board', + 'IntelligentProvisioningVersion': 'N/A', + 'Links': { + 'BIOS': { + '@odata.id': '/redfish/v1/Systems/1/Bios/' + }, + 'EthernetInterfaces': { + '@odata.id': '/redfish/v1/Systems/1/EthernetInterfaces/' + }, + 'FirmwareInventory': { + '@odata.id': '/redfish/v1/Systems/1/FirmwareInventory/' + }, + 'Memory': { + '@odata.id': '/redfish/v1/Systems/1/Memory/' + }, + 'NetworkAdapters': { + '@odata.id': '/redfish/v1/Systems/1/NetworkAdapters/' + }, + 'PCIDevices': { + '@odata.id': '/redfish/v1/Systems/1/PCIDevices/' + }, + 'PCISlots': { + '@odata.id': '/redfish/v1/Systems/1/PCISlots/' + }, + 'SmartStorage': { + '@odata.id': '/redfish/v1/Systems/1/SmartStorage/' + }, + 'SoftwareInventory': { + '@odata.id': '/redfish/v1/Systems/1/SoftwareInventory/' + } + }, + 'PostState': 'FinishedPost', + 'PowerAllocationLimit': 1500, + 'PowerAutoOn': 'Restore', + 'PowerOnDelay': 'Minimum', + 'PowerRegulatorMode': 'Dynamic', + 'PowerRegulatorModesSupported': ['OSControl', 'Dynamic', 'Max', 'Min'], + 'TrustedModules': [{ + 'Status': 'NotPresent' + }], + 'VirtualProfile': 'Inactive' + } + }, + 'PowerState': 'On', + 'ProcessorSummary': { + 'Count': 2, + 'Model': ' Intel(R) Xeon(R) CPU E5-2640 v2 @ 2.00GHz ', + 'Status': { + 'HealthRollup': 'OK' + } + }, + 'Processors': { + '@odata.id': '/redfish/v1/Systems/1/Processors/' + }, + 'SKU': '654081-B21 ', + 'SerialNumber': 'CZJ4320228 ', + 'Status': { + 'Health': 'Warning', + 'State': 'Enabled' + }, + 'SystemType': 'Physical', + 'UUID': '30343536-3138-5A43-4A34-333230323238' +} \ No newline at end of file diff --git a/src/cephadm/node_proxy/redfish_system.py b/src/cephadm/node_proxy/redfish_system.py new file mode 100644 index 00000000000..f23e41af79e --- /dev/null +++ b/src/cephadm/node_proxy/redfish_system.py @@ -0,0 +1,161 @@ +from basesystem import BaseSystem +from redfish_client import RedFishClient +from threading import Thread, Lock +from time import sleep +from util import Logger, retry, normalize_dict, to_snake_case +from typing import Dict, Any, List + +log = Logger(__name__) + + +class RedfishSystem(BaseSystem): + def __init__(self, **kw: Any) -> None: + super().__init__(**kw) + self.host: str = kw['host'] + self.username: str = kw['username'] + self.password: str = kw['password'] + self.system_endpoint = kw.get('system_endpoint', '/Systems/1') + log.logger.info(f"redfish system initialization, host: {self.host}, user: {self.username}") + self.client = RedFishClient(self.host, self.username, self.password) + + self._system: Dict[str, Dict[str, Any]] = {} + self.run: bool = False + self.thread: Thread + self.start_client() + self.data_ready: bool = False + self.previous_data: Dict = {} + self.lock: Lock = Lock() + + @retry(retries=10, delay=2) + def _get_path(self, path: str) -> Dict: + result = self.client.get_path(path) + if result is None: + log.logger.error(f"The client reported an error when getting path: {path}") + raise RuntimeError(f"Could not get path: {path}") + return result + + def get_members(self, path: str) -> List: + _path = self._system[path]['@odata.id'] + data = self._get_path(_path) + return [self._get_path(member['@odata.id']) for member in data['Members']] + + def build_data(self, + fields: List, + path: str) -> Dict[str, Dict[str, Dict]]: + result: Dict[str, Dict[str, Dict]] = dict() + for member_info in self.get_members(path): + member_id = member_info['Id'] + result[member_id] = dict() + for field in fields: + try: + result[member_id][to_snake_case(field)] = member_info[field] + except KeyError: + log.logger.warning(f"Could not find field: {field} in member_info: {member_info}") + + return normalize_dict(result) + + def start_client(self) -> None: + if not self.client: + self.client = RedFishClient(self.host, self.username, self.password) + self.client.login() + + def get_system(self) -> Dict[str, Dict[str, Dict]]: + result = { + 'storage': self.get_storage(), + 'processors': self.get_processors(), + 'network': self.get_network(), + 'memory': self.get_memory(), + } + return result + + def get_status(self) -> Dict[str, Dict[str, Dict]]: + return self._system['status'] + + def get_metadata(self) -> Dict[str, Dict[str, Dict]]: + return self._system['metadata'] + + def get_memory(self) -> Dict[str, Dict[str, Dict]]: + return self._system['memory'] + + def get_power(self) -> Dict[str, Dict[str, Dict]]: + return self._system['power'] + + def get_processors(self) -> Dict[str, Dict[str, Dict]]: + return self._system['processors'] + + def get_network(self) -> Dict[str, Dict[str, Dict]]: + return self._system['network'] + + def get_storage(self) -> Dict[str, Dict[str, Dict]]: + return self._system['storage'] + + def _update_system(self) -> None: + redfish_system = self.client.get_path(self.system_endpoint) + self._system = {**redfish_system, **self._system} + + def _update_metadata(self) -> None: + raise NotImplementedError() + + def _update_memory(self) -> None: + raise NotImplementedError() + + def _update_power(self) -> None: + raise NotImplementedError() + + def _update_network(self) -> None: + raise NotImplementedError() + + def _update_processors(self) -> None: + raise NotImplementedError() + + def _update_storage(self) -> None: + raise NotImplementedError() + + def start_update_loop(self) -> None: + self.run = True + self.thread = Thread(target=self.update) + self.thread.start() + + def stop_update_loop(self) -> None: + self.run = False + self.thread.join() + + def update(self) -> None: + # this loop can have: + # - caching logic + try: + while self.run: + log.logger.debug("waiting for a lock.") + self.lock.acquire() + log.logger.debug("lock acquired.") + try: + self._update_system() + # following calls in theory can be done in parallel + self._update_metadata() + self._update_memory() + self._update_power() + self._update_network() + self._update_processors() + self._update_storage() + self.data_ready = True + sleep(5) + finally: + self.lock.release() + log.logger.debug("lock released.") + # Catching 'Exception' is probably not a good idea (devel only) + except Exception as e: + log.logger.error(f"Error detected, logging out from redfish api.\n{e}") + self.client.logout() + raise + + def flush(self) -> None: + log.logger.info("Acquiring lock to flush data.") + self.lock.acquire() + log.logger.info("Lock acquired, flushing data.") + self._system = {} + self.previous_data = {} + log.logger.info("Data flushed.") + self.data_ready = False + log.logger.info("Data marked as not ready.") + self.lock.release() + log.logger.info("Lock released.") diff --git a/src/cephadm/node_proxy/reporter.py b/src/cephadm/node_proxy/reporter.py new file mode 100644 index 00000000000..07ac637bf1c --- /dev/null +++ b/src/cephadm/node_proxy/reporter.py @@ -0,0 +1,52 @@ +from threading import Thread +import requests +import time +from util import Logger +from typing import Any + +log = Logger(__name__) + + +class Reporter: + def __init__(self, system: Any, observer_url: str) -> None: + self.system = system + self.observer_url = observer_url + self.finish = False + + def stop(self) -> None: + self.finish = True + self.thread.join() + + def run(self) -> None: + self.thread = Thread(target=self.loop) + self.thread.start() + + def loop(self) -> None: + while not self.finish: + # Any logic to avoid sending the all the system + # information every loop can go here. In a real + # scenario probably we should just send the sub-parts + # that have changed to minimize the traffic in + # dense clusters + log.logger.debug("waiting for a lock.") + self.system.lock.acquire() + log.logger.debug("lock acquired.") + if self.system.data_ready: + log.logger.info('data ready to be sent to the mgr.') + if not self.system.get_system() == self.system.previous_data: + log.logger.info('data has changed since last iteration.') + d = self.system.get_system() + try: + # TODO: add a timeout parameter to the reporter in the config file + requests.post(f"{self.observer_url}/", json=d, timeout=5) + except requests.exceptions.RequestException as 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.logger.info('no diff, not sending data to the mgr.') + self.system.lock.release() + log.logger.debug("lock released.") + time.sleep(5) diff --git a/src/cephadm/node_proxy/requirements.txt b/src/cephadm/node_proxy/requirements.txt new file mode 100644 index 00000000000..ab20bcc64b4 --- /dev/null +++ b/src/cephadm/node_proxy/requirements.txt @@ -0,0 +1,8 @@ +pyyaml +types-PyYAML +types-requests +mypy +flake8 +redfish +CherryPy +requests diff --git a/src/cephadm/node_proxy/server.py b/src/cephadm/node_proxy/server.py new file mode 100644 index 00000000000..d05dbebd9c6 --- /dev/null +++ b/src/cephadm/node_proxy/server.py @@ -0,0 +1,244 @@ +import cherrypy +from redfish_dell import RedfishDell +from reporter import Reporter +from util import Config, Logger +from typing import Dict +from basesystem import BaseSystem +import sys +import argparse + +# for devel purposes +import os +DEVEL_ENV_VARS = ['REDFISH_HOST', + 'REDFISH_USERNAME', + 'REDFISH_PASSWORD'] + +DEFAULT_CONFIG = { + 'reporter': { + 'check_interval': 5, + 'push_data_max_retries': 30, + 'endpoint': 'http://127.0.0.1:8150', + }, + '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) + + +class Memory: + exposed = True + + def __init__(self, backend: BaseSystem) -> None: + self.backend = backend + + @cherrypy.tools.json_out() + def GET(self) -> Dict[str, Dict[str, Dict]]: + return {'memory': self.backend.get_memory()} + + +class Network: + exposed = True + + def __init__(self, backend: BaseSystem) -> None: + self.backend = backend + + @cherrypy.tools.json_out() + def GET(self) -> Dict[str, Dict[str, Dict]]: + return {'network': self.backend.get_network()} + + +class Processors: + exposed = True + + def __init__(self, backend: BaseSystem) -> None: + self.backend = backend + + @cherrypy.tools.json_out() + def GET(self) -> Dict[str, Dict[str, Dict]]: + return {'processors': self.backend.get_processors()} + + +class Storage: + exposed = True + + def __init__(self, backend: BaseSystem) -> None: + self.backend = backend + + @cherrypy.tools.json_out() + def GET(self) -> Dict[str, Dict[str, Dict]]: + return {'storage': self.backend.get_storage()} + + +class Status: + exposed = True + + def __init__(self, backend: BaseSystem) -> None: + self.backend = backend + + @cherrypy.tools.json_out() + def GET(self) -> Dict[str, Dict[str, Dict]]: + return {'status': self.backend.get_status()} + + +class System: + exposed = True + + def __init__(self, backend: BaseSystem) -> None: + self.memory = Memory(backend) + self.network = Network(backend) + self.processors = Processors(backend) + self.storage = Storage(backend) + self.status = Status(backend) + # actions = Actions() + # control = Control() + + +class Shutdown: + exposed = True + + def __init__(self, backend: BaseSystem, reporter: Reporter) -> None: + self.backend = backend + self.reporter = reporter + + def POST(self) -> str: + _stop(self.backend, self.reporter) + cherrypy.engine.exit() + return 'Server shutdown...' + + +def _stop(backend: BaseSystem, reporter: Reporter) -> None: + backend.stop_update_loop() + backend.client.logout() + reporter.stop() + + +class Start: + exposed = True + + def __init__(self, backend: BaseSystem, reporter: Reporter) -> None: + self.backend = backend + self.reporter = reporter + + def POST(self) -> str: + self.backend.start_client() + self.backend.start_update_loop() + self.reporter.run() + return 'node-proxy daemon started' + + +class Stop: + exposed = True + + def __init__(self, backend: BaseSystem, reporter: Reporter) -> None: + self.backend = backend + self.reporter = reporter + + def POST(self) -> str: + _stop(self.backend, self.reporter) + return 'node-proxy daemon stopped' + + +class ConfigReload: + exposed = True + + def __init__(self, config: Config) -> None: + self.config = config + + def POST(self) -> str: + self.config.reload() + return 'node-proxy config reloaded' + + +class Flush: + exposed = True + + def __init__(self, backend: BaseSystem) -> None: + self.backend = backend + + def POST(self) -> str: + self.backend.flush() + return 'node-proxy data flushed' + + +class Admin: + exposed = False + + def __init__(self, backend: BaseSystem, config: Config, reporter: Reporter) -> None: + self.reload = ConfigReload(config) + self.flush = Flush(backend) + self.shutdown = Shutdown(backend, reporter) + self.start = Start(backend, reporter) + self.stop = Stop(backend, reporter) + + +class API: + exposed = True + + def __init__(self, + backend: BaseSystem, + reporter: Reporter, + config: Config) -> None: + + self.system = System(backend) + self.admin = Admin(backend, config, reporter) + + def GET(self) -> str: + return 'use /system or /admin endpoints' + + +def main() -> None: + + parser = argparse.ArgumentParser( + prog='node-proxy', + ) + parser.add_argument( + '--config', + dest='config', + type=str, + required=False, + default='/etc/ceph/node-proxy.yml' + ) + + args = parser.parse_args() + config = Config(args.config, default_config=DEFAULT_CONFIG) + + log = Logger(__name__, level=config.__dict__['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.logger.info("Server initialization...") + system = RedfishDell(host=host, + username=username, + password=password, + system_endpoint='/Systems/System.Embedded.1', + config=config) + reporter_agent = Reporter(system, config.__dict__['reporter']['endpoint']) + cherrypy.config.update({ + 'node_proxy': config, + 'server.socket_port': config.__dict__['server']['port'] + }) + c = {'/': { + 'request.methods_with_bodies': ('POST', 'PUT', 'PATCH'), + 'request.dispatch': cherrypy.dispatch.MethodDispatcher() + }} + system.start_update_loop() + reporter_agent.run() + cherrypy.quickstart(API(system, reporter_agent, config), config=c) + + +if __name__ == '__main__': + main() diff --git a/src/cephadm/node_proxy/util.py b/src/cephadm/node_proxy/util.py new file mode 100644 index 00000000000..98c1a7d3671 --- /dev/null +++ b/src/cephadm/node_proxy/util.py @@ -0,0 +1,98 @@ +import logging +import yaml +import os +import time +import re +from typing import Dict, List, Callable, Any + + +class Logger: + _Logger: List['Logger'] = [] + + def __init__(self, name: str, level: int = logging.INFO): + self.name = name + self.level = level + + Logger._Logger.append(self) + self.logger = self.get_logger() + + def get_logger(self) -> logging.Logger: + 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 + + +class Config: + + def __init__(self, + config_file: str = '/etc/ceph/node-proxy.yaml', + default_config: Dict[str, Any] = {}) -> None: + self.config_file = config_file + self.default_config = default_config + + self.load_config() + + def load_config(self) -> None: + 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']) # type: ignore + _l.logger.handlers[0].setLevel(self.logging['level']) # type: ignore + + def reload(self, config_file: str = '') -> None: + if config_file != '': + self.config_file = config_file + self.load_config() + + +log = Logger(__name__) + + +def to_snake_case(name: str) -> str: + name = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name) + return re.sub('([a-z0-9])([A-Z])', r'\1_\2', name).lower() + + +def normalize_dict(test_dict: Dict) -> Dict: + res = dict() + for key in test_dict.keys(): + if isinstance(test_dict[key], dict): + res[key.lower()] = normalize_dict(test_dict[key]) + else: + res[key.lower()] = test_dict[key] + return res + + +def retry(exceptions: Any = Exception, retries: int = 20, delay: int = 1) -> Callable: + def decorator(f: Callable) -> Callable: + def _retry(*args: str, **kwargs: Any) -> Callable: + _tries = retries + while _tries > 1: + try: + log.logger.debug("{} {} attempt(s) left.".format(f, _tries - 1)) + return f(*args, **kwargs) + except exceptions: + time.sleep(delay) + _tries -= 1 + log.logger.warn("{} has failed after {} tries".format(f, retries)) + return f(*args, **kwargs) + return _retry + return decorator