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
This commit is contained in:
Bruno Cornec 2019-10-11 18:06:09 +02:00 committed by Bruno Cornec
parent 929a10a85b
commit b0ff208138
11 changed files with 207 additions and 101 deletions

View File

@ -57,7 +57,7 @@ print("Bios version : {}\n".format(
print("Serial Number : {}\n".format( print("Serial Number : {}\n".format(
remote_mgmt.Systems.systems_dict["1"].get_serial_number())) remote_mgmt.Systems.systems_dict["1"].get_serial_number()))
print("Power State : {}\n".format( 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( print("Parameter 'SystemType' : {}\n".format(
remote_mgmt.Systems.systems_dict["1"].get_parameter("SystemType"))) remote_mgmt.Systems.systems_dict["1"].get_parameter("SystemType")))

View File

@ -115,7 +115,7 @@ vetype:
# Global version/tag for the project # Global version/tag for the project
# #
projver: projver:
python-redfish: 0.4.2 python-redfish: 0.4.3
projtag: projtag:
python-redfish: 1 python-redfish: 1
# #
@ -125,22 +125,23 @@ projtag:
# #
# Is it a test version or a production version # Is it a test version or a production version
testver: testver:
python-redfish: !!str "" #python-redfish: !!str ""
python-redfish: true
# Which upper target dir for delivery # Which upper target dir for delivery
delivery: delivery:
python-redfish: !!str "" python-redfish: test
#python-redfish: !!str ""
# #
# Additional repository to add at build time # 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-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 # 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 # This will allow usage of python-simplejson 1.8.1
addrepo: addbuildrepo:
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 du: ftp://ftp.project-builder.org/$pbos->{'name'}/$pbos->{'version'}/python-tortilla.sources.list
mageia-5-x86_64: ftp://ftp.project-builder.org/mageia/5/x86_64/pb.addmedia rpm: ftp://ftp.project-builder.org/$pbos->{'name'}/$pbos->{'version'}/$pbos->{'arch'}/python-tortilla.repo
fedora-25-x86_64: ftp://ftp.project-builder.org/fedora/25/x86_64/pb.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 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: # Adapt to your needs:
# Optional if you need to overwrite the global values above # Optional if you need to overwrite the global values above

View File

@ -1,2 +1,3 @@
[redfish-client] [redfish-client]
templates_path = PBSHAREPATH/templates templates_path = PBSHAREPATH/templates
monitor_loop = 120

View File

@ -16,6 +16,7 @@ from builtins import object
import os import os
import sys import sys
import time
import json import json
import docopt import docopt
import logging import logging
@ -25,6 +26,8 @@ import requests.packages.urllib3
import redfish import redfish
standard_library.install_aliases() standard_library.install_aliases()
DEFMONITORLOOP = 120 # Default loop value for monitor command
class InventoryFile(object): class InventoryFile(object):
'''redfisht-client inventory file management''' '''redfisht-client inventory file management'''
@ -252,6 +255,11 @@ if __name__ == '__main__':
# information using jinja2 template # information using jinja2 template
render_template("serial_info.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): def render_template(template):
try: try:
template = jinja2_env.get_template(template) template = jinja2_env.get_template(template)
@ -355,6 +363,7 @@ if __name__ == '__main__':
# Initialize Template system (jinja2) # Initialize Template system (jinja2)
templates_path = config.get("redfish-client", "templates_path") templates_path = config.get("redfish-client", "templates_path")
dml = float(config.get("redfish-client", "monitor_loop", DEFMONITORLOOP))
logger.debug("Initialize template system") logger.debug("Initialize template system")
jinja2_env = jinja2.Environment( jinja2_env = jinja2.Environment(
loader=jinja2.FileSystemLoader(templates_path)) loader=jinja2.FileSystemLoader(templates_path))
@ -401,7 +410,10 @@ if __name__ == '__main__':
arguments['<changed_value>']) arguments['<changed_value>'])
logger.debug(inventory.data) logger.debug(inventory.data)
inventory.save() 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') logger.debug('get commands')
# If manager is not defined set it to 'default' # If manager is not defined set it to 'default'
if not arguments['<manager_name>']: if not arguments['<manager_name>']:
@ -417,10 +429,9 @@ if __name__ == '__main__':
# Could be a nice exercice for learning python. :) # Could be a nice exercice for learning python. :)
logger.info('Gathering data from manager') logger.info('Gathering data from manager')
if arguments['--insecure'] is True: redfish_data = get_redfish_data(connection_parameters,
redfish_data = get_redfish_data(connection_parameters, False) not arguments['--insecure'])
else:
redfish_data = get_redfish_data(connection_parameters, True)
if arguments['getinfo'] is True: if arguments['getinfo'] is True:
if arguments['manager'] is True: if arguments['manager'] is True:
logger.debug("Manager commands") logger.debug("Manager commands")
@ -434,5 +445,14 @@ if __name__ == '__main__':
if arguments['getserial'] is True: if arguments['getserial'] is True:
logger.debug("serial & part number commands") logger.debug("serial & part number commands")
display_part_serial_numbers(redfish_data) 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") logger.info("Client session terminated")
sys.exit(0) sys.exit(0)

View File

@ -10,6 +10,7 @@ Usage:
redfish-client [options] chassis getinfo [<manager_name>] redfish-client [options] chassis getinfo [<manager_name>]
redfish-client [options] system getinfo [<manager_name>] redfish-client [options] system getinfo [<manager_name>]
redfish-client [options] getserial [<manager_name>] redfish-client [options] getserial [<manager_name>]
redfish-client [options] monitor [<manager_name>]
redfish-client (-h | --help) redfish-client (-h | --help)
redfish-client --version redfish-client --version
@ -25,6 +26,9 @@ Options:
--debugfile FILE Specify the client debugfile [default: $HOME/.redfish/redfish-client.log] --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] --libdebugfile FILE Specify python-redfish library log file [default: $HOME/.redfish/python-redfish.log]
config commands : manage the configuration file. Commands:
manager commands : manage the manager (Light out management). If <manager_name> config: manage the configuration file (add, remove modify managers).
is not provided use the 'default' entry manager/chassis/system: manage the manager/chassis/system (Light out management). If <manager_name>
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)

View File

@ -51,7 +51,7 @@ CPU details :
{%- endif %} {%- endif %}
Available memory : {{ system.get_memory() }} Available memory : {{ system.get_memory() }}
Status : State : {{ system.get_status().Health }} / Health : {{ system.get_status().Health }} Status : State : {{ system.get_status().Health }} / Health : {{ system.get_status().Health }}
Power : {{ system.get_power() }} Power : {{ system.get_powerstate() }}
Description : {{ system.get_description() }} Description : {{ system.get_description() }}
Chassis : {{ system.get_chassis() | join(', ') }} Chassis : {{ system.get_chassis() | join(', ') }}
Managers : {{ system.get_managers() | join(', ') }} Managers : {{ system.get_managers() | join(', ') }}

View File

@ -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 %}

View File

@ -31,7 +31,7 @@ CPU number: {{ system.get_cpucount() }}
CPU model: {{ system.get_cpumodel() }} CPU model: {{ system.get_cpumodel() }}
Available memory: {{ system.get_memory() }} GB Available memory: {{ system.get_memory() }} GB
Status: State: {{ system.get_status().Health }} / Health: {{ system.get_status().Health }} 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.data.Oem.Hpe or system.data.Oem.Hp %}
{%- if system.network_adapters_collection %} {%- if system.network_adapters_collection %}
{%- for nic_index in system.network_adapters_collection.network_adapters_dict | sort %} {%- for nic_index in system.network_adapters_collection.network_adapters_dict | sort %}

View File

@ -29,7 +29,7 @@ CPU details :
{%- endif %} {%- endif %}
Available memory : {{ system.get_memory() }} GB Available memory : {{ system.get_memory() }} GB
Status : State : {{ system.get_status().Health }} / Health : {{ system.get_status().Health }} Status : State : {{ system.get_status().Health }} / Health : {{ system.get_status().Health }}
Power : {{ system.get_power() }} Power : {{ system.get_powerstate() }}
Description : {{ system.get_description() }} Description : {{ system.get_description() }}
Chassis : {{ system.get_chassis() | join(', ') }} Chassis : {{ system.get_chassis() | join(', ') }}
Managers : {{ system.get_managers() | join(', ') }} Managers : {{ system.get_managers() | join(', ') }}

View File

@ -322,30 +322,6 @@ class Systems(Device):
except AttributeError: except AttributeError:
return "Not available" 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): def get_cpucount(self):
'''Get the number of cpu in the system. '''Get the number of cpu in the system.
@ -697,21 +673,6 @@ class ChassisCollection(BaseCollection):
class Chassis(Device): class Chassis(Device):
'''Class to manage redfish Chassis data.''' '''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): def get_type(self):
'''Get chassis type '''Get chassis type
@ -724,43 +685,3 @@ class Chassis(Device):
return self.data.ChassisType return self.data.ChassisType
except AttributeError: except AttributeError:
return "Not available" 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

View File

@ -15,6 +15,7 @@ import ssl
from . import config from . import config
from . import mapping from . import mapping
from . import exception from . import exception
standard_library.install_aliases() standard_library.install_aliases()
@ -172,10 +173,97 @@ class BaseCollection(Base):
config.logger.debug(self.links) 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): class Device(Base):
'''Abstract class to add common methods between devices '''Abstract class to add common methods between devices
(Chassis, Servers, System). (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): def get_uuid(self):
'''Get device uuid '''Get device uuid
@ -287,6 +375,30 @@ class Device(Base):
except AttributeError: except AttributeError:
return "Not available" 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): def get_fw_version(self):
'''Get firmware version of the device. '''Get firmware version of the device.