From b0ff208138bd55024783b7b90c1a966c73070f23 Mon Sep 17 00:00:00 2001 From: Bruno Cornec Date: Fri, 11 Oct 2019 18:06:09 +0200 Subject: [PATCH] Add the monitor command - monitor command role is to print on a regular base the values of variable constants (Temperature, power, Fans, ...) - new monitor_loop parameter (wait time between queries) - new monitor_info.template - get_power has been renamed to get_powerstate to avoid confusion with Power measures and moved to the Device class as well as get_description - More genericity for the Power and Thermal classes (moved into types.py) - Build process amended (0.4.3 is the next version, repo are generic and in line with pb 0.15 Change-Id: I05016b2557b2f7638e1ea22a89dcf91ebae1066f --- examples/simple-proliant.py | 2 +- pbconf/python-redfish.yml | 19 +-- redfish-client/etc/redfish-client.conf | 1 + redfish-client/redfish-client | 30 ++++- redfish-client/redfish-client_usage.txt | 10 +- .../templates/chassis_info.template | 4 +- .../templates/monitor_info.template | 47 ++++++++ redfish-client/templates/serial_info.template | 2 +- redfish-client/templates/system_info.template | 2 +- redfish/standard.py | 79 ------------ redfish/types.py | 112 ++++++++++++++++++ 11 files changed, 207 insertions(+), 101 deletions(-) create mode 100644 redfish-client/templates/monitor_info.template diff --git a/examples/simple-proliant.py b/examples/simple-proliant.py index b640e5a..03f807a 100644 --- a/examples/simple-proliant.py +++ b/examples/simple-proliant.py @@ -57,7 +57,7 @@ print("Bios version : {}\n".format( print("Serial Number : {}\n".format( remote_mgmt.Systems.systems_dict["1"].get_serial_number())) print("Power State : {}\n".format( - remote_mgmt.Systems.systems_dict["1"].get_power())) + remote_mgmt.Systems.systems_dict["1"].get_powerstate())) print("Parameter 'SystemType' : {}\n".format( remote_mgmt.Systems.systems_dict["1"].get_parameter("SystemType"))) diff --git a/pbconf/python-redfish.yml b/pbconf/python-redfish.yml index ea2fefc..3748f1c 100644 --- a/pbconf/python-redfish.yml +++ b/pbconf/python-redfish.yml @@ -115,7 +115,7 @@ vetype: # Global version/tag for the project # projver: - python-redfish: 0.4.2 + python-redfish: 0.4.3 projtag: python-redfish: 1 # @@ -125,22 +125,23 @@ projtag: # # Is it a test version or a production version testver: - python-redfish: !!str "" + #python-redfish: !!str "" + python-redfish: true # Which upper target dir for delivery delivery: - python-redfish: !!str "" + python-redfish: test + #python-redfish: !!str "" # # Additional repository to add at build time # addrepo centos-5-x86_64 = http://packages.sw.be/rpmforge-release/rpmforge-release-0.3.6-1.el5.rf.x86_64.rpm,ftp://ftp.project-builder.org/centos/5/pb.repo # addrepo centos-4-x86_64 = http://packages.sw.be/rpmforge-release/rpmforge-release-0.3.6-1.el4.rf.x86_64.rpm,ftp://ftp.project-builder.org/centos/4/pb.repo # This will allow usage of python-simplejson 1.8.1 -addrepo: - centos-7-x86_64: https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm,ftp://ftp.project-builder.org/centos/7/x86_64/pb.repo - mageia-5-x86_64: ftp://ftp.project-builder.org/mageia/5/x86_64/pb.addmedia - fedora-25-x86_64: ftp://ftp.project-builder.org/fedora/25/x86_64/pb.repo +addbuildrepo: + du: ftp://ftp.project-builder.org/$pbos->{'name'}/$pbos->{'version'}/python-tortilla.sources.list + rpm: ftp://ftp.project-builder.org/$pbos->{'name'}/$pbos->{'version'}/$pbos->{'arch'}/python-tortilla.repo + md: !!str "" + centos: https://dl.fedoraproject.org/pub/epel/epel-release-latest-$pbos->{'version'}.noarch.rpm,ftp://ftp.project-builder.org/$pbos->{'name'}/$pbos->{'version'}/$pbos->{'arch'}/python-tortilla.repo opensuse-42.2-x86_64: ftp://ftp.project-builder.org/opensuse/42.2/x86_64/pb.repo,http://download.opensuse.org/repositories/devel:languages:python3/openSUSE_Leap_42.2/devel:languages:python3.repo,http://download.opensuse.org/repositories/Virtualization:containers/openSUSE_Leap_42.2/Virtualization:containers.repo,http://download.opensuse.org/repositories/devel:languages:python/openSUSE_Leap_42.2/devel:languages:python.repo - ubuntu-12.04-x86_64: ftp://ftp.project-builder.org/ubuntu/12.04/python-tortilla.sources.list - ubuntu-18.04-x86_64: ftp://ftp.project-builder.org/ubuntu/18.04/python-tortilla.sources.list # # Adapt to your needs: # Optional if you need to overwrite the global values above diff --git a/redfish-client/etc/redfish-client.conf b/redfish-client/etc/redfish-client.conf index 3a61e1b..a2e2261 100644 --- a/redfish-client/etc/redfish-client.conf +++ b/redfish-client/etc/redfish-client.conf @@ -1,2 +1,3 @@ [redfish-client] templates_path = PBSHAREPATH/templates +monitor_loop = 120 diff --git a/redfish-client/redfish-client b/redfish-client/redfish-client index fb09bf6..334338f 100755 --- a/redfish-client/redfish-client +++ b/redfish-client/redfish-client @@ -16,6 +16,7 @@ from builtins import object import os import sys +import time import json import docopt import logging @@ -25,6 +26,8 @@ import requests.packages.urllib3 import redfish standard_library.install_aliases() +DEFMONITORLOOP = 120 # Default loop value for monitor command + class InventoryFile(object): '''redfisht-client inventory file management''' @@ -252,6 +255,11 @@ if __name__ == '__main__': # information using jinja2 template render_template("serial_info.template") + def monitor(redfish_data): + # Monitor variable values accessible by the manager + # information using jinja2 template + render_template("monitor_info.template") + def render_template(template): try: template = jinja2_env.get_template(template) @@ -355,6 +363,7 @@ if __name__ == '__main__': # Initialize Template system (jinja2) templates_path = config.get("redfish-client", "templates_path") + dml = float(config.get("redfish-client", "monitor_loop", DEFMONITORLOOP)) logger.debug("Initialize template system") jinja2_env = jinja2.Environment( loader=jinja2.FileSystemLoader(templates_path)) @@ -401,7 +410,10 @@ if __name__ == '__main__': arguments['']) logger.debug(inventory.data) inventory.save() - elif arguments['getinfo'] is True or arguments['getserial'] is True: + elif (arguments['getinfo'] is True or + arguments['getserial'] is True or + arguments['monitor'] is True): + logger.debug('get commands') # If manager is not defined set it to 'default' if not arguments['']: @@ -417,10 +429,9 @@ if __name__ == '__main__': # Could be a nice exercice for learning python. :) logger.info('Gathering data from manager') - if arguments['--insecure'] is True: - redfish_data = get_redfish_data(connection_parameters, False) - else: - redfish_data = get_redfish_data(connection_parameters, True) + redfish_data = get_redfish_data(connection_parameters, + not arguments['--insecure']) + if arguments['getinfo'] is True: if arguments['manager'] is True: logger.debug("Manager commands") @@ -434,5 +445,14 @@ if __name__ == '__main__': if arguments['getserial'] is True: logger.debug("serial & part number commands") display_part_serial_numbers(redfish_data) + if arguments['monitor'] is True: + logger.debug("monitor command") + monitor(redfish_data) + while True: + print("Sleeping for {} seconds".format(dml)) + time.sleep(dml) + redfish_data = get_redfish_data(connection_parameters, + not arguments['--insecure']) + monitor(redfish_data) logger.info("Client session terminated") sys.exit(0) diff --git a/redfish-client/redfish-client_usage.txt b/redfish-client/redfish-client_usage.txt index f709aae..4dc9420 100644 --- a/redfish-client/redfish-client_usage.txt +++ b/redfish-client/redfish-client_usage.txt @@ -10,6 +10,7 @@ Usage: redfish-client [options] chassis getinfo [] redfish-client [options] system getinfo [] redfish-client [options] getserial [] + redfish-client [options] monitor [] redfish-client (-h | --help) redfish-client --version @@ -25,6 +26,9 @@ Options: --debugfile FILE Specify the client debugfile [default: $HOME/.redfish/redfish-client.log] --libdebugfile FILE Specify python-redfish library log file [default: $HOME/.redfish/python-redfish.log] -config commands : manage the configuration file. -manager commands : manage the manager (Light out management). If - is not provided use the 'default' entry +Commands: +config: manage the configuration file (add, remove modify managers). +manager/chassis/system: manage the manager/chassis/system (Light out management). If + is not provided use the 'default' entry +getserial: display all serial numbers found through that manager to allow inventory +monitor: monitor changing variables accessible through that manager in a loop (CTRL-C to exit) diff --git a/redfish-client/templates/chassis_info.template b/redfish-client/templates/chassis_info.template index 28acd89..39fa3c0 100644 --- a/redfish-client/templates/chassis_info.template +++ b/redfish-client/templates/chassis_info.template @@ -51,7 +51,7 @@ CPU details : {%- endif %} Available memory : {{ system.get_memory() }} Status : State : {{ system.get_status().Health }} / Health : {{ system.get_status().Health }} -Power : {{ system.get_power() }} +Power : {{ system.get_powerstate() }} Description : {{ system.get_description() }} Chassis : {{ system.get_chassis() | join(', ') }} Managers : {{ system.get_managers() | join(', ') }} @@ -88,4 +88,4 @@ Simple Storage : {%- endif %} -------------------------------------------------------------------------------- #} -{% endfor %} \ No newline at end of file +{% endfor %} diff --git a/redfish-client/templates/monitor_info.template b/redfish-client/templates/monitor_info.template new file mode 100644 index 0000000..d560e20 --- /dev/null +++ b/redfish-client/templates/monitor_info.template @@ -0,0 +1,47 @@ +Redfish API version: {{ r.get_api_version() }} +{{ r.Root.get_name() }} + +Monitoring +========== +{% for chassis_index in r.Chassis.chassis_dict | sort %} +{%- set chassis = r.Chassis.chassis_dict[chassis_index] %} +Chassis #{{ chassis_index }}: +Name: {{ chassis.get_name() }} +Power: {{ chassis.get_powerstate() }} +Temperatures : +{%- if chassis.thermal.get_temperatures() == 'Not available' %} + Not available +{%- else %} +{%- for sensor, temp in chassis.thermal.get_temperatures().items() | sort %} + {{ sensor }} : {{ temp }} +{%- endfor %} +{%- endif %} +Fans : +{%- if chassis.thermal.get_fans() == 'Not available' %} + Not available +{%- else %} +{%- for fan, rpm in chassis.thermal.get_fans().items() | sort %} + {{ fan }} : {{ rpm }} +{%- endfor %} +{%- endif %} +Power control : +{%- if chassis.power.get_power() == 'Not available' %} + Not available +{%- else %} +{%- for ps, volt in chassis.power.get_power().items() | sort %} + {{ ps }} : {{ volt }} +{%- endfor %} +{%- endif %} +{%- endfor %} + +-------------------------------------------------------------------------------- +{% for system_index in r.Systems.systems_dict | sort %} +{%- set system = r.Systems.systems_dict[system_index] %} +System #{{ system_index }}: +Name: {{ system.get_name() }} +CPU number: {{ system.get_cpucount() }} +CPU model: {{ system.get_cpumodel() }} +Status: State: {{ system.get_status().Health }} / Health: {{ system.get_status().Health }} + +-------------------------------------------------------------------------------- +{% endfor %} diff --git a/redfish-client/templates/serial_info.template b/redfish-client/templates/serial_info.template index 154c7cc..fbbc00c 100644 --- a/redfish-client/templates/serial_info.template +++ b/redfish-client/templates/serial_info.template @@ -31,7 +31,7 @@ CPU number: {{ system.get_cpucount() }} CPU model: {{ system.get_cpumodel() }} Available memory: {{ system.get_memory() }} GB Status: State: {{ system.get_status().Health }} / Health: {{ system.get_status().Health }} -Power: {{ system.get_power() }} +Power: {{ system.get_powerstate() }} {%- if system.data.Oem.Hpe or system.data.Oem.Hp %} {%- if system.network_adapters_collection %} {%- for nic_index in system.network_adapters_collection.network_adapters_dict | sort %} diff --git a/redfish-client/templates/system_info.template b/redfish-client/templates/system_info.template index 4f1eb77..0f7cee1 100644 --- a/redfish-client/templates/system_info.template +++ b/redfish-client/templates/system_info.template @@ -29,7 +29,7 @@ CPU details : {%- endif %} Available memory : {{ system.get_memory() }} GB Status : State : {{ system.get_status().Health }} / Health : {{ system.get_status().Health }} -Power : {{ system.get_power() }} +Power : {{ system.get_powerstate() }} Description : {{ system.get_description() }} Chassis : {{ system.get_chassis() | join(', ') }} Managers : {{ system.get_managers() | join(', ') }} diff --git a/redfish/standard.py b/redfish/standard.py index 8fa216c..d3cb227 100644 --- a/redfish/standard.py +++ b/redfish/standard.py @@ -322,30 +322,6 @@ class Systems(Device): except AttributeError: return "Not available" - def get_power(self): - '''Get power status of the system. - - :returns: system power state or "Not available" - :rtype: string - - ''' - try: - return self.data.PowerState - except AttributeError: - return "Not available" - - def get_description(self): - '''Get description of the system. - - :returns: system description or "Not available" - :rtype: string - - ''' - try: - return self.data.Description - except AttributeError: - return "Not available" - def get_cpucount(self): '''Get the number of cpu in the system. @@ -697,21 +673,6 @@ class ChassisCollection(BaseCollection): class Chassis(Device): '''Class to manage redfish Chassis data.''' - def __init__(self, url, connection_parameters): - '''Class constructor''' - super(Chassis, self).__init__(url, connection_parameters) - - try: - self.thermal = Thermal(self.get_link_url('Thermal'), - connection_parameters) - except AttributeError: - self.thermal = None - - try: - self.power = Power(self.get_link_url('Power'), - connection_parameters) - except AttributeError: - self.Power = None def get_type(self): '''Get chassis type @@ -724,43 +685,3 @@ class Chassis(Device): return self.data.ChassisType except AttributeError: return "Not available" - - -class Thermal(Base): - '''Class to manage redfish Thermal data.''' - def get_temperatures(self): - '''Get chassis sensors name and temparature - - :returns: chassis sensor and temperature - :rtype: dict - - ''' - temperatures = {} - - try: - for sensor in self.data.Temperatures: - temperatures[sensor.Name] = sensor.ReadingCelsius - return temperatures - except AttributeError: - return "Not available" - - def get_fans(self): - '''Get chassis fan name and rpm - - :returns: chassis fan and rpm - :rtype: dict - - ''' - fans = {} - - try: - for fan in self.data.Fans: - fans[fan.FanName] = fan.ReadingRPM - return fans - except AttributeError: - return "Not available" - - -class Power(Base): - '''Class to manage redfish Power data.''' - pass diff --git a/redfish/types.py b/redfish/types.py index 3e9bfc6..f6cd7b4 100644 --- a/redfish/types.py +++ b/redfish/types.py @@ -15,6 +15,7 @@ import ssl from . import config from . import mapping from . import exception + standard_library.install_aliases() @@ -172,10 +173,97 @@ class BaseCollection(Base): config.logger.debug(self.links) +class Thermal(Base): + '''Class to manage redfish Thermal data.''' + + # Currently doesn't support case such as: + # /redfish/v1/Systems/1/SmartStorage/ArrayControllers/0/DiskDrives/1/ + # u'CurrentTemperatureCelsius': 45 + def get_temperatures(self): + '''Get chassis sensors name and temperature + + :returns: chassis sensor and temperature + :rtype: dict + + ''' + temperatures = {} + + try: + for sensor in self.data.Temperatures: + temperatures[sensor.Name] = sensor.ReadingCelsius + return temperatures + except AttributeError: + return "Not available" + + def get_fans(self): + '''Get chassis fan name and rpm + + :returns: chassis fan and rpm + :rtype: dict + + ''' + fans = {} + + try: + for fan in self.data.Fans: + # New interface + fans[fan.Name] = str(fan.Reading) + ' ' + fan.ReadingUnits + return fans + except AttributeError: + try: + # Old interface + for fan in self.data.Fans: + fans[fan.FanName] = fan.ReadingRPM + + return fans + + except AttributeError: + return "Not available" + + +class Power(Base): + '''Class to manage redfish Power data.''' + + def get_power(self): + '''Get power supply informartion + + :returns: power supply and Voltage + :rtype: dict + + ''' + pc = {} + + # config.logger.debug("PowerControl parsed") + # config.logger.debug(self.data) + try: + for p in self.data.PowerControl: + pc[p.MemberId] = str(p.PowerConsumedWatts) + ' Watts' + return pc + except AttributeError: + return "Not available" + + class Device(Base): '''Abstract class to add common methods between devices (Chassis, Servers, System). ''' + + def __init__(self, url, connection_parameters): + '''Class constructor''' + super(Device, self).__init__(url, connection_parameters) + + try: + url2 = self.get_link_url('Thermal') + self.thermal = Thermal(url2, connection_parameters) + except AttributeError: + self.thermal = Thermal(url, connection_parameters) + + try: + url2 = self.get_link_url('Power') + self.power = Power(url2, connection_parameters) + except AttributeError: + self.power = Power(url, connection_parameters) + def get_uuid(self): '''Get device uuid @@ -287,6 +375,30 @@ class Device(Base): except AttributeError: return "Not available" + def get_description(self): + '''Get description of the device. + + :returns: device description or "Not available" + :rtype: string + + ''' + try: + return self.data.Description + except AttributeError: + return "Not available" + + def get_powerstate(self): + '''Get power status of the device. + + :returns: device power state or "Not available" + :rtype: string + + ''' + try: + return self.data.PowerState + except AttributeError: + return "Not available" + def get_fw_version(self): '''Get firmware version of the device.