from urllib.error import HTTPError, URLError
from urllib.request import urlopen, Request
from pathlib import Path
+import cephadmlib.node_proxy.server
FuncT = TypeVar('FuncT', bound=Callable)
def run(self) -> None:
self.pull_conf_settings()
+ t_node_proxy = Thread(target=cephadmlib.node_proxy.server.main)
+ t_node_proxy.start()
+
try:
for _ in range(1001):
if not port_in_use(self.ctx, EndPoint('0.0.0.0', self.starting_port)):
--- /dev/null
+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()
--- /dev/null
+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()
--- /dev/null
+
+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'
+ }
+ }
+ ]
+ },
+}
--- /dev/null
+#!/usr/bin/env python3
+"""
+License: MIT License
+Copyright (c) 2023 Miel Donkers
+
+Very simple HTTP server in python for logging requests
+Usage::
+ ./server.py [<port>]
+"""
+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()
--- /dev/null
+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()
--- /dev/null
+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()
--- /dev/null
+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()
--- /dev/null
+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
--- /dev/null
+{
+ '@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
+}
--- /dev/null
+{
+ '@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'
+}
--- /dev/null
+{
+ '@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']
+ }
+ }
+}
--- /dev/null
+{
+ '@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
+}
--- /dev/null
+{
+ '@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
+}
--- /dev/null
+{
+ '@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'
+}
--- /dev/null
+{
+ '@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
+}
--- /dev/null
+{
+ '@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
--- /dev/null
+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.")
--- /dev/null
+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)
--- /dev/null
+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('/etc/ceph/node-proxy.yml', 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()
--- /dev/null
+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
+++ /dev/null
-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()
+++ /dev/null
-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()
+++ /dev/null
-
-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'
- }
- }
- ]
- },
-}
+++ /dev/null
-#!/usr/bin/env python3
-"""
-License: MIT License
-Copyright (c) 2023 Miel Donkers
-
-Very simple HTTP server in python for logging requests
-Usage::
- ./server.py [<port>]
-"""
-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()
+++ /dev/null
-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()
+++ /dev/null
-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()
+++ /dev/null
-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()
+++ /dev/null
-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
+++ /dev/null
-{
- '@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
-}
+++ /dev/null
-{
- '@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'
-}
+++ /dev/null
-{
- '@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']
- }
- }
-}
+++ /dev/null
-{
- '@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
-}
+++ /dev/null
-{
- '@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
-}
+++ /dev/null
-{
- '@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'
-}
+++ /dev/null
-{
- '@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
-}
+++ /dev/null
-{
- '@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
+++ /dev/null
-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.")
+++ /dev/null
-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)
+++ /dev/null
-pyyaml
-types-PyYAML
-types-requests
-mypy
-flake8
-redfish
-CherryPy
-requests
+++ /dev/null
-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()
+++ /dev/null
-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